From 70208ba2cd0756b218333d8484de7c3208394614 Mon Sep 17 00:00:00 2001
From: Sebastian Eibl <sebastian.eibl@fau.de>
Date: Tue, 28 May 2019 13:01:06 +0200
Subject: [PATCH] introduce mesa_pd module to official waLBerla

---
 .gitlab-ci.yml                                |   33 +-
 apps/benchmarks/CMakeLists.txt                |    3 +-
 apps/benchmarks/GranularGas/CMakeLists.txt    |   14 +
 apps/benchmarks/GranularGas/GenerateModule.py |   89 +
 apps/benchmarks/GranularGas/GranularGas.cfg   |   21 +
 .../GranularGas/MESA_PD_GranularGas.cpp       |  487 ++
 .../GranularGas/MESA_PD_KernelBenchmark.cpp   |  510 ++
 .../PE_Benchmark.cfg}                         |    2 +-
 .../PE_GranularGas.cpp}                       |  186 +-
 apps/benchmarks/GranularGas/upload.py         |   56 +
 .../CMakeLists.txt                            |    4 +-
 apps/benchmarks/LennardJones/LennardJones.cpp |  207 +
 apps/benchmarks/PeriodicGranularGas/upload.py |  187 -
 .../ProbeVsExtraMessage.cpp                   |   18 +
 apps/tutorials/CMakeLists.txt                 |    1 +
 apps/tutorials/mesa_pd/01_LennardJones.cpp    |  105 +
 apps/tutorials/mesa_pd/CMakeLists.txt         |    5 +
 apps/tutorials/mesa_pd/GranularGas.cfg        |   14 +
 python/mesa_pd.py                             |  101 +
 python/mesa_pd/Container.py                   |   32 +
 python/mesa_pd/Property.py                    |   91 +
 python/mesa_pd/__init__.py                    |    0
 python/mesa_pd/accessor/Accessor.py           |   53 +
 python/mesa_pd/accessor/__init__.py           |    5 +
 python/mesa_pd/data/ContactHistory.py         |   28 +
 python/mesa_pd/data/LinkedCells.py            |    8 +
 python/mesa_pd/data/ParticleStorage.py        |   46 +
 python/mesa_pd/data/ShapeStorage.py           |   19 +
 python/mesa_pd/data/__init__.py               |   11 +
 python/mesa_pd/kernel/DoubleCast.py           |   19 +
 python/mesa_pd/kernel/ExplicitEuler.py        |   26 +
 .../mesa_pd/kernel/ExplicitEulerWithShape.py  |   30 +
 python/mesa_pd/kernel/ForceLJ.py              |   26 +
 python/mesa_pd/kernel/HeatConduction.py       |   26 +
 .../kernel/InsertParticleIntoLinkedCells.py   |   19 +
 python/mesa_pd/kernel/LinearSpringDashpot.py  |   26 +
 .../mesa_pd/kernel/NonLinearSpringDashpot.py  |   26 +
 python/mesa_pd/kernel/SingleCast.py           |   19 +
 python/mesa_pd/kernel/SpringDashpot.py        |   30 +
 .../mesa_pd/kernel/TemperatureIntegration.py  |   25 +
 python/mesa_pd/kernel/VelocityVerlet.py       |   29 +
 .../mesa_pd/kernel/VelocityVerletWithShape.py |   36 +
 python/mesa_pd/kernel/__init__.py             |   29 +
 python/mesa_pd/mpi/BroadcastProperty.py       |    7 +
 python/mesa_pd/mpi/ClearNextNeighborSync.py   |   18 +
 python/mesa_pd/mpi/ReduceContactHistory.py    |    7 +
 python/mesa_pd/mpi/ReduceProperty.py          |    7 +
 python/mesa_pd/mpi/SyncNextNeighbors.py       |   15 +
 python/mesa_pd/mpi/__init__.py                |   14 +
 .../templates/data/ContactHistory.templ.h     |  109 +
 .../templates/data/LinkedCells.templ.h        |  266 +
 .../templates/data/ParticleAccessor.templ.h   |  126 +
 .../templates/data/ParticleStorage.templ.h    |  514 ++
 .../templates/data/ShapeStorage.templ.h       |  114 +
 .../templates/kernel/DoubleCast.templ.h       |   97 +
 .../templates/kernel/ExplicitEuler.templ.h    |   87 +
 .../kernel/ExplicitEulerWithShape.templ.h     |  102 +
 .../mesa_pd/templates/kernel/ForceLJ.templ.h  |  162 +
 .../templates/kernel/HeatConduction.templ.h   |  138 +
 .../InsertParticleIntoLinkedCells.templ.h     |   94 +
 .../kernel/LinearSpringDashpot.templ.h        |  253 +
 .../kernel/NonLinearSpringDashpot.templ.h     |  273 +
 .../templates/kernel/SingleCast.templ.h       |   85 +
 .../templates/kernel/SpringDashpot.templ.h    |  195 +
 .../kernel/TemperatureIntegration.templ.h     |  127 +
 .../templates/kernel/VelocityVerlet.templ.h   |  114 +
 .../kernel/VelocityVerletWithShape.templ.h    |  137 +
 .../templates/mpi/BroadcastProperty.templ.h   |  135 +
 .../mpi/ClearNextNeighborSync.templ.h         |   87 +
 .../mpi/ReduceContactHistory.templ.h          |  113 +
 .../templates/mpi/ReduceProperty.templ.h      |  144 +
 .../templates/mpi/SyncNextNeighbors.templ.cpp |  267 +
 .../templates/mpi/SyncNextNeighbors.templ.h   |   79 +
 .../ContactHistoryNotification.templ.h        |  101 +
 .../ForceTorqueNotification.templ.h           |  113 +
 .../HeatFluxNotification.templ.h              |  107 +
 .../mpi/notifications/ParseMessage.templ.h    |  194 +
 .../ParticleCopyNotification.templ.h          |  118 +
 .../ParticleMigrationNotification.templ.h     |  110 +
 ...articleRemoteMigrationNotification.templ.h |   99 +
 .../ParticleRemovalNotification.templ.h       |   94 +
 .../ParticleUpdateNotification.templ.h        |  103 +
 .../templates/tests/CheckInterface.templ.cpp  |   72 +
 python/mesa_pd/utility.py                     |   44 +
 src/core/math/Quaternion.h                    |   24 +-
 src/core/math/Rot3.h                          |   59 +-
 src/core/math/Vector3.h                       |    2 +-
 src/core/timing/TimingPool.cpp                |    2 +-
 src/mesa_pd/CMakeLists.txt                    |    8 +
 .../AnalyticCollisionFunctions.h              |   92 +
 .../AnalyticContactDetection.h                |  178 +
 src/mesa_pd/collision_detection/BroadPhase.h  |   45 +
 src/mesa_pd/common/AABBConversion.h           |   60 +
 src/mesa_pd/common/Contains.h                 |   78 +
 src/mesa_pd/common/ParticleFunctions.h        |   96 +
 src/mesa_pd/common/RayParticleIntersection.h  |  152 +
 src/mesa_pd/data/ContactHistory.h             |  113 +
 src/mesa_pd/data/DataTypes.h                  |   40 +
 src/mesa_pd/data/Flags.h                      |  116 +
 src/mesa_pd/data/IAccessor.h                  |   48 +
 src/mesa_pd/data/LinkedCells.h                |  359 ++
 src/mesa_pd/data/ParticleAccessor.h           |  300 ++
 src/mesa_pd/data/ParticleStorage.h            | 1071 ++++
 src/mesa_pd/data/STLOverloads.h               |   60 +
 src/mesa_pd/data/ShapeStorage.h               |  126 +
 src/mesa_pd/data/shape/BaseShape.h            |   69 +
 src/mesa_pd/data/shape/HalfSpace.h            |   65 +
 src/mesa_pd/data/shape/Sphere.h               |   60 +
 src/mesa_pd/data/shape/shape_group.dox        |   11 +
 .../domain/BlockForestDataHandling.cpp        |  208 +
 src/mesa_pd/domain/BlockForestDataHandling.h  |   98 +
 src/mesa_pd/domain/BlockForestDomain.cpp      |  246 +
 src/mesa_pd/domain/BlockForestDomain.h        |  139 +
 src/mesa_pd/domain/IDomain.h                  |   74 +
 src/mesa_pd/domain/InfiniteDomain.h           |   43 +
 src/mesa_pd/domain/InfoCollection.h           |  119 +
 src/mesa_pd/kernel/DoubleCast.h               |  102 +
 src/mesa_pd/kernel/ExplicitEuler.h            |   88 +
 src/mesa_pd/kernel/ExplicitEulerWithShape.h   |  114 +
 src/mesa_pd/kernel/ForceLJ.h                  |  171 +
 src/mesa_pd/kernel/HeatConduction.h           |  128 +
 .../kernel/InsertParticleIntoLinkedCells.h    |   91 +
 src/mesa_pd/kernel/LinearSpringDashpot.h      |  357 ++
 src/mesa_pd/kernel/NonLinearSpringDashpot.h   |  377 ++
 src/mesa_pd/kernel/ParticleSelector.h         |   78 +
 src/mesa_pd/kernel/SingleCast.h               |   73 +
 src/mesa_pd/kernel/SpringDashpot.h            |  255 +
 src/mesa_pd/kernel/TemperatureIntegration.h   |  117 +
 src/mesa_pd/kernel/VelocityVerlet.h           |  118 +
 src/mesa_pd/kernel/VelocityVerletWithShape.h  |  155 +
 src/mesa_pd/kernel/mesa_pd_kernel.dox         |   11 +
 src/mesa_pd/mesa_pd_module.dox                |   10 +
 src/mesa_pd/mpi/BroadcastProperty.h           |  135 +
 src/mesa_pd/mpi/ClearNextNeighborSync.h       |   79 +
 src/mesa_pd/mpi/ContactFilter.h               |  158 +
 src/mesa_pd/mpi/ReduceContactHistory.h        |  113 +
 src/mesa_pd/mpi/ReduceProperty.h              |  144 +
 src/mesa_pd/mpi/SyncNextNeighbors.cpp         |  267 +
 src/mesa_pd/mpi/SyncNextNeighbors.h           |   79 +
 src/mesa_pd/mpi/mesa_pd_mpi.dox               |   11 +
 .../ContactHistoryNotification.h              |  101 +
 .../notifications/ForceTorqueNotification.h   |  113 +
 .../mpi/notifications/HeatFluxNotification.h  |  107 +
 .../mpi/notifications/NotificationType.h      |   74 +
 .../mpi/notifications/PackNotification.h      |   38 +
 src/mesa_pd/mpi/notifications/ParseMessage.h  |  193 +
 .../notifications/ParticleCopyNotification.h  |  150 +
 .../ParticleMigrationNotification.h           |  104 +
 .../ParticleRemoteMigrationNotification.h     |   99 +
 .../ParticleRemovalNotification.h             |   94 +
 .../ParticleUpdateNotification.h              |  109 +
 src/mesa_pd/vtk/OutputSelector.h              |   72 +
 src/mesa_pd/vtk/ParticleVtkOutput.cpp         |   89 +
 src/mesa_pd/vtk/ParticleVtkOutput.h           |   76 +
 src/mesa_pd/vtk/WriteOutput.h                 |   73 +
 src/pe/ccd/HashGrids.h                        |    2 +-
 src/vtk/BlockCellDataWriter.h                 |    8 +-
 src/vtk/UtilityFunctions.h                    |   48 +-
 src/vtk/VTKTrait.h                            |  121 +
 tests/CMakeLists.txt                          |    1 +
 tests/mesa_pd/CMakeLists.txt                  |  124 +
 tests/mesa_pd/ContactDetection.cpp            |  226 +
 .../IntegratorWithShapeEvaluation.ipynb       | 4544 +++++++++++++++++
 .../AnalyticContactDetection.cpp              |  192 +
 tests/mesa_pd/common/IntersectionRatio.cpp    |  201 +
 tests/mesa_pd/data/Flags.cpp                  |   57 +
 tests/mesa_pd/data/ParticleStorage.cpp        |   87 +
 tests/mesa_pd/domain/BlockForestDomain.cpp    |  112 +
 tests/mesa_pd/domain/BlockForestSync.cpp      |  118 +
 .../domain/BlockForestSyncPeriodic.cpp        |  116 +
 tests/mesa_pd/domain/DistanceCalculation.cpp  |   81 +
 tests/mesa_pd/domain/DynamicRefinement.cpp    |  164 +
 tests/mesa_pd/domain/SerializeDeserialize.cpp |  169 +
 .../mesa_pd/kernel/ClearNextNeighborSync.cpp  |  111 +
 .../kernel/CoefficientOfRestitutionLSD.cpp    |  197 +
 .../kernel/CoefficientOfRestitutionNLSD.cpp   |  201 +
 .../kernel/CoefficientOfRestitutionSD.cpp     |  197 +
 tests/mesa_pd/kernel/DoubleCast.cpp           |  102 +
 tests/mesa_pd/kernel/ExplicitEuler.cpp        |  116 +
 .../mesa_pd/kernel/ExplicitEulerWithShape.cpp |  125 +
 tests/mesa_pd/kernel/ForceLJ.cpp              |  128 +
 .../kernel/GenerateAnalyticContacts.cpp       |  176 +
 tests/mesa_pd/kernel/GenerateLinkedCells.cpp  |  111 +
 tests/mesa_pd/kernel/HeatConduction.cpp       |   79 +
 tests/mesa_pd/kernel/IntegratorAccuracy.cpp   |  180 +
 tests/mesa_pd/kernel/Interfaces.cpp           |   26 +
 tests/mesa_pd/kernel/LinearSpringDashpot.cpp  |  224 +
 .../kernel/LinkedCellsVsBruteForce.cpp        |  192 +
 tests/mesa_pd/kernel/SingleCast.cpp           |   86 +
 tests/mesa_pd/kernel/SpringDashpot.cpp        |  139 +
 tests/mesa_pd/kernel/SyncNextNeighbors.cpp    |  145 +
 .../mesa_pd/kernel/TemperatureIntegration.cpp |   70 +
 tests/mesa_pd/kernel/VelocityVerlet.cpp       |  118 +
 .../kernel/VelocityVerletWithShape.cpp        |  201 +
 .../ExplicitEulerInterfaceCheck.cpp           |   75 +
 .../ExplicitEulerWithShapeInterfaceCheck.cpp  |   90 +
 .../interfaces/ForceLJInterfaceCheck.cpp      |   66 +
 .../HeatConductionInterfaceCheck.cpp          |   68 +
 .../SpringDashpotInterfaceCheck.cpp           |   79 +
 .../TemperatureIntegrationInterfaceCheck.cpp  |   68 +
 .../VelocityVerletInterfaceCheck.cpp          |   80 +
 .../VelocityVerletWithShapeInterfaceCheck.cpp |   99 +
 tests/mesa_pd/mpi/BroadcastProperty.cpp       |  110 +
 tests/mesa_pd/mpi/Notifications.cpp           |   68 +
 tests/mesa_pd/mpi/ReduceContactHistory.cpp    |  182 +
 tests/mesa_pd/mpi/ReduceProperty.cpp          |  109 +
 tests/mesa_pd/vtk/VTKOutputs.cpp              |   86 +
 207 files changed, 26779 insertions(+), 323 deletions(-)
 create mode 100644 apps/benchmarks/GranularGas/CMakeLists.txt
 create mode 100755 apps/benchmarks/GranularGas/GenerateModule.py
 create mode 100644 apps/benchmarks/GranularGas/GranularGas.cfg
 create mode 100644 apps/benchmarks/GranularGas/MESA_PD_GranularGas.cpp
 create mode 100644 apps/benchmarks/GranularGas/MESA_PD_KernelBenchmark.cpp
 rename apps/benchmarks/{PeriodicGranularGas/PeriodicGranularGas.cfg => GranularGas/PE_Benchmark.cfg} (95%)
 rename apps/benchmarks/{PeriodicGranularGas/PeriodicGranularGas.cpp => GranularGas/PE_GranularGas.cpp} (57%)
 create mode 100644 apps/benchmarks/GranularGas/upload.py
 rename apps/benchmarks/{PeriodicGranularGas => LennardJones}/CMakeLists.txt (56%)
 create mode 100644 apps/benchmarks/LennardJones/LennardJones.cpp
 delete mode 100644 apps/benchmarks/PeriodicGranularGas/upload.py
 create mode 100644 apps/tutorials/mesa_pd/01_LennardJones.cpp
 create mode 100644 apps/tutorials/mesa_pd/CMakeLists.txt
 create mode 100644 apps/tutorials/mesa_pd/GranularGas.cfg
 create mode 100755 python/mesa_pd.py
 create mode 100644 python/mesa_pd/Container.py
 create mode 100644 python/mesa_pd/Property.py
 create mode 100644 python/mesa_pd/__init__.py
 create mode 100644 python/mesa_pd/accessor/Accessor.py
 create mode 100644 python/mesa_pd/accessor/__init__.py
 create mode 100644 python/mesa_pd/data/ContactHistory.py
 create mode 100644 python/mesa_pd/data/LinkedCells.py
 create mode 100644 python/mesa_pd/data/ParticleStorage.py
 create mode 100644 python/mesa_pd/data/ShapeStorage.py
 create mode 100644 python/mesa_pd/data/__init__.py
 create mode 100644 python/mesa_pd/kernel/DoubleCast.py
 create mode 100644 python/mesa_pd/kernel/ExplicitEuler.py
 create mode 100644 python/mesa_pd/kernel/ExplicitEulerWithShape.py
 create mode 100644 python/mesa_pd/kernel/ForceLJ.py
 create mode 100644 python/mesa_pd/kernel/HeatConduction.py
 create mode 100644 python/mesa_pd/kernel/InsertParticleIntoLinkedCells.py
 create mode 100644 python/mesa_pd/kernel/LinearSpringDashpot.py
 create mode 100644 python/mesa_pd/kernel/NonLinearSpringDashpot.py
 create mode 100644 python/mesa_pd/kernel/SingleCast.py
 create mode 100644 python/mesa_pd/kernel/SpringDashpot.py
 create mode 100644 python/mesa_pd/kernel/TemperatureIntegration.py
 create mode 100644 python/mesa_pd/kernel/VelocityVerlet.py
 create mode 100644 python/mesa_pd/kernel/VelocityVerletWithShape.py
 create mode 100644 python/mesa_pd/kernel/__init__.py
 create mode 100644 python/mesa_pd/mpi/BroadcastProperty.py
 create mode 100644 python/mesa_pd/mpi/ClearNextNeighborSync.py
 create mode 100644 python/mesa_pd/mpi/ReduceContactHistory.py
 create mode 100644 python/mesa_pd/mpi/ReduceProperty.py
 create mode 100644 python/mesa_pd/mpi/SyncNextNeighbors.py
 create mode 100644 python/mesa_pd/mpi/__init__.py
 create mode 100644 python/mesa_pd/templates/data/ContactHistory.templ.h
 create mode 100644 python/mesa_pd/templates/data/LinkedCells.templ.h
 create mode 100644 python/mesa_pd/templates/data/ParticleAccessor.templ.h
 create mode 100644 python/mesa_pd/templates/data/ParticleStorage.templ.h
 create mode 100644 python/mesa_pd/templates/data/ShapeStorage.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/DoubleCast.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/ExplicitEuler.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/ExplicitEulerWithShape.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/ForceLJ.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/HeatConduction.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/InsertParticleIntoLinkedCells.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/LinearSpringDashpot.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/NonLinearSpringDashpot.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/SingleCast.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/SpringDashpot.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/TemperatureIntegration.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/VelocityVerlet.templ.h
 create mode 100644 python/mesa_pd/templates/kernel/VelocityVerletWithShape.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/BroadcastProperty.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/ClearNextNeighborSync.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/ReduceContactHistory.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/ReduceProperty.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/SyncNextNeighbors.templ.cpp
 create mode 100644 python/mesa_pd/templates/mpi/SyncNextNeighbors.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/ContactHistoryNotification.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/ForceTorqueNotification.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/ParseMessage.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/ParticleCopyNotification.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/ParticleMigrationNotification.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/ParticleRemoteMigrationNotification.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/ParticleRemovalNotification.templ.h
 create mode 100644 python/mesa_pd/templates/mpi/notifications/ParticleUpdateNotification.templ.h
 create mode 100644 python/mesa_pd/templates/tests/CheckInterface.templ.cpp
 create mode 100644 python/mesa_pd/utility.py
 create mode 100644 src/mesa_pd/CMakeLists.txt
 create mode 100644 src/mesa_pd/collision_detection/AnalyticCollisionFunctions.h
 create mode 100644 src/mesa_pd/collision_detection/AnalyticContactDetection.h
 create mode 100644 src/mesa_pd/collision_detection/BroadPhase.h
 create mode 100644 src/mesa_pd/common/AABBConversion.h
 create mode 100644 src/mesa_pd/common/Contains.h
 create mode 100644 src/mesa_pd/common/ParticleFunctions.h
 create mode 100644 src/mesa_pd/common/RayParticleIntersection.h
 create mode 100644 src/mesa_pd/data/ContactHistory.h
 create mode 100644 src/mesa_pd/data/DataTypes.h
 create mode 100644 src/mesa_pd/data/Flags.h
 create mode 100644 src/mesa_pd/data/IAccessor.h
 create mode 100644 src/mesa_pd/data/LinkedCells.h
 create mode 100644 src/mesa_pd/data/ParticleAccessor.h
 create mode 100644 src/mesa_pd/data/ParticleStorage.h
 create mode 100644 src/mesa_pd/data/STLOverloads.h
 create mode 100644 src/mesa_pd/data/ShapeStorage.h
 create mode 100644 src/mesa_pd/data/shape/BaseShape.h
 create mode 100644 src/mesa_pd/data/shape/HalfSpace.h
 create mode 100644 src/mesa_pd/data/shape/Sphere.h
 create mode 100644 src/mesa_pd/data/shape/shape_group.dox
 create mode 100644 src/mesa_pd/domain/BlockForestDataHandling.cpp
 create mode 100644 src/mesa_pd/domain/BlockForestDataHandling.h
 create mode 100644 src/mesa_pd/domain/BlockForestDomain.cpp
 create mode 100644 src/mesa_pd/domain/BlockForestDomain.h
 create mode 100644 src/mesa_pd/domain/IDomain.h
 create mode 100644 src/mesa_pd/domain/InfiniteDomain.h
 create mode 100644 src/mesa_pd/domain/InfoCollection.h
 create mode 100644 src/mesa_pd/kernel/DoubleCast.h
 create mode 100644 src/mesa_pd/kernel/ExplicitEuler.h
 create mode 100644 src/mesa_pd/kernel/ExplicitEulerWithShape.h
 create mode 100644 src/mesa_pd/kernel/ForceLJ.h
 create mode 100644 src/mesa_pd/kernel/HeatConduction.h
 create mode 100644 src/mesa_pd/kernel/InsertParticleIntoLinkedCells.h
 create mode 100644 src/mesa_pd/kernel/LinearSpringDashpot.h
 create mode 100644 src/mesa_pd/kernel/NonLinearSpringDashpot.h
 create mode 100644 src/mesa_pd/kernel/ParticleSelector.h
 create mode 100644 src/mesa_pd/kernel/SingleCast.h
 create mode 100644 src/mesa_pd/kernel/SpringDashpot.h
 create mode 100644 src/mesa_pd/kernel/TemperatureIntegration.h
 create mode 100644 src/mesa_pd/kernel/VelocityVerlet.h
 create mode 100644 src/mesa_pd/kernel/VelocityVerletWithShape.h
 create mode 100644 src/mesa_pd/kernel/mesa_pd_kernel.dox
 create mode 100644 src/mesa_pd/mesa_pd_module.dox
 create mode 100644 src/mesa_pd/mpi/BroadcastProperty.h
 create mode 100644 src/mesa_pd/mpi/ClearNextNeighborSync.h
 create mode 100644 src/mesa_pd/mpi/ContactFilter.h
 create mode 100644 src/mesa_pd/mpi/ReduceContactHistory.h
 create mode 100644 src/mesa_pd/mpi/ReduceProperty.h
 create mode 100644 src/mesa_pd/mpi/SyncNextNeighbors.cpp
 create mode 100644 src/mesa_pd/mpi/SyncNextNeighbors.h
 create mode 100644 src/mesa_pd/mpi/mesa_pd_mpi.dox
 create mode 100644 src/mesa_pd/mpi/notifications/ContactHistoryNotification.h
 create mode 100644 src/mesa_pd/mpi/notifications/ForceTorqueNotification.h
 create mode 100644 src/mesa_pd/mpi/notifications/HeatFluxNotification.h
 create mode 100644 src/mesa_pd/mpi/notifications/NotificationType.h
 create mode 100644 src/mesa_pd/mpi/notifications/PackNotification.h
 create mode 100644 src/mesa_pd/mpi/notifications/ParseMessage.h
 create mode 100644 src/mesa_pd/mpi/notifications/ParticleCopyNotification.h
 create mode 100644 src/mesa_pd/mpi/notifications/ParticleMigrationNotification.h
 create mode 100644 src/mesa_pd/mpi/notifications/ParticleRemoteMigrationNotification.h
 create mode 100644 src/mesa_pd/mpi/notifications/ParticleRemovalNotification.h
 create mode 100644 src/mesa_pd/mpi/notifications/ParticleUpdateNotification.h
 create mode 100644 src/mesa_pd/vtk/OutputSelector.h
 create mode 100644 src/mesa_pd/vtk/ParticleVtkOutput.cpp
 create mode 100644 src/mesa_pd/vtk/ParticleVtkOutput.h
 create mode 100644 src/mesa_pd/vtk/WriteOutput.h
 create mode 100644 src/vtk/VTKTrait.h
 create mode 100644 tests/mesa_pd/CMakeLists.txt
 create mode 100644 tests/mesa_pd/ContactDetection.cpp
 create mode 100644 tests/mesa_pd/IntegratorWithShapeEvaluation.ipynb
 create mode 100644 tests/mesa_pd/collision_detection/AnalyticContactDetection.cpp
 create mode 100644 tests/mesa_pd/common/IntersectionRatio.cpp
 create mode 100644 tests/mesa_pd/data/Flags.cpp
 create mode 100644 tests/mesa_pd/data/ParticleStorage.cpp
 create mode 100644 tests/mesa_pd/domain/BlockForestDomain.cpp
 create mode 100644 tests/mesa_pd/domain/BlockForestSync.cpp
 create mode 100644 tests/mesa_pd/domain/BlockForestSyncPeriodic.cpp
 create mode 100644 tests/mesa_pd/domain/DistanceCalculation.cpp
 create mode 100644 tests/mesa_pd/domain/DynamicRefinement.cpp
 create mode 100644 tests/mesa_pd/domain/SerializeDeserialize.cpp
 create mode 100644 tests/mesa_pd/kernel/ClearNextNeighborSync.cpp
 create mode 100644 tests/mesa_pd/kernel/CoefficientOfRestitutionLSD.cpp
 create mode 100644 tests/mesa_pd/kernel/CoefficientOfRestitutionNLSD.cpp
 create mode 100644 tests/mesa_pd/kernel/CoefficientOfRestitutionSD.cpp
 create mode 100644 tests/mesa_pd/kernel/DoubleCast.cpp
 create mode 100644 tests/mesa_pd/kernel/ExplicitEuler.cpp
 create mode 100644 tests/mesa_pd/kernel/ExplicitEulerWithShape.cpp
 create mode 100644 tests/mesa_pd/kernel/ForceLJ.cpp
 create mode 100644 tests/mesa_pd/kernel/GenerateAnalyticContacts.cpp
 create mode 100644 tests/mesa_pd/kernel/GenerateLinkedCells.cpp
 create mode 100644 tests/mesa_pd/kernel/HeatConduction.cpp
 create mode 100644 tests/mesa_pd/kernel/IntegratorAccuracy.cpp
 create mode 100644 tests/mesa_pd/kernel/Interfaces.cpp
 create mode 100644 tests/mesa_pd/kernel/LinearSpringDashpot.cpp
 create mode 100644 tests/mesa_pd/kernel/LinkedCellsVsBruteForce.cpp
 create mode 100644 tests/mesa_pd/kernel/SingleCast.cpp
 create mode 100644 tests/mesa_pd/kernel/SpringDashpot.cpp
 create mode 100644 tests/mesa_pd/kernel/SyncNextNeighbors.cpp
 create mode 100644 tests/mesa_pd/kernel/TemperatureIntegration.cpp
 create mode 100644 tests/mesa_pd/kernel/VelocityVerlet.cpp
 create mode 100644 tests/mesa_pd/kernel/VelocityVerletWithShape.cpp
 create mode 100644 tests/mesa_pd/kernel/interfaces/ExplicitEulerInterfaceCheck.cpp
 create mode 100644 tests/mesa_pd/kernel/interfaces/ExplicitEulerWithShapeInterfaceCheck.cpp
 create mode 100644 tests/mesa_pd/kernel/interfaces/ForceLJInterfaceCheck.cpp
 create mode 100644 tests/mesa_pd/kernel/interfaces/HeatConductionInterfaceCheck.cpp
 create mode 100644 tests/mesa_pd/kernel/interfaces/SpringDashpotInterfaceCheck.cpp
 create mode 100644 tests/mesa_pd/kernel/interfaces/TemperatureIntegrationInterfaceCheck.cpp
 create mode 100644 tests/mesa_pd/kernel/interfaces/VelocityVerletInterfaceCheck.cpp
 create mode 100644 tests/mesa_pd/kernel/interfaces/VelocityVerletWithShapeInterfaceCheck.cpp
 create mode 100644 tests/mesa_pd/mpi/BroadcastProperty.cpp
 create mode 100644 tests/mesa_pd/mpi/Notifications.cpp
 create mode 100644 tests/mesa_pd/mpi/ReduceContactHistory.cpp
 create mode 100644 tests/mesa_pd/mpi/ReduceProperty.cpp
 create mode 100644 tests/mesa_pd/vtk/VTKOutputs.cpp

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 347732841..1668b816f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1752,19 +1752,19 @@ conda-py36-linux:
       - cd $CI_PROJECT_DIR/build
       - cmake .. -DWALBERLA_BUFFER_DEBUG=OFF -DWALBERLA_BUILD_TESTS=OFF -DWALBERLA_BUILD_BENCHMARKS=ON -DWALBERLA_BUILD_TUTORIALS=OFF -DWALBERLA_BUILD_TOOLS=OFF -DWALBERLA_BUILD_WITH_MPI=ON -DWALBERLA_BUILD_WITH_CUDA=OFF -DWALBERLA_BUILD_WITH_PYTHON=OFF -DWALBERLA_BUILD_WITH_OPENMP=OFF -DCMAKE_BUILD_TYPE=RELEASE -DMPIEXEC_PREFLAGS=$MPIEXEC_PREFLAGS -DWALBERLA_DOUBLE_ACCURACY=ON -DWARNING_ERROR=ON -DWALBERLA_BUILD_WITH_METIS=OFF -DWALBERLA_BUILD_WITH_PARMETIS=OFF -DWALBERLA_OPTIMIZE_FOR_LOCALHOST=ON -DWALBERLA_BUILD_WITH_FASTMATH=ON -DWALBERLA_BUILD_WITH_LTO=ON
       - cmake . -LAH
-      - cd apps/benchmarks/PeriodicGranularGas
+      - cd apps/benchmarks/GranularGas
       - make -j 20
       - export PATH=$PATH:/usr/local/likwid/bin
       - likwid-setFrequencies -t 0
       - likwid-setFrequencies -g performance
       - likwid-setFrequencies -f 3.3 # set frequency to 3.3
-      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PeriodicGranularGas PeriodicGranularGas.cfg --DEM --syncNextNeighbor | tee PeriodicGranularGas_DEM_NN.txt
-      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PeriodicGranularGas PeriodicGranularGas.cfg --DEM --syncShadowOwners | tee PeriodicGranularGas_DEM_SO.txt
-      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PeriodicGranularGas PeriodicGranularGas.cfg --HCSITS --syncNextNeighbor --InelasticFrictionlessContact | tee PeriodicGranularGas_HCSITS_NN_IFC.txt
-      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PeriodicGranularGas PeriodicGranularGas.cfg --HCSITS --syncNextNeighbor --ApproximateInelasticCoulombContactByDecoupling | tee PeriodicGranularGas_HCSITS_NN_AICCBD.txt
-      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PeriodicGranularGas PeriodicGranularGas.cfg --HCSITS --syncNextNeighbor --InelasticCoulombContactByDecoupling | tee PeriodicGranularGas_HCSITS_NN_ICCBD.txt
-      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PeriodicGranularGas PeriodicGranularGas.cfg --HCSITS --syncNextNeighbor --InelasticGeneralizedMaximumDissipationContact | tee PeriodicGranularGas_HCSITS_NN_IGMDC.txt
-      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PeriodicGranularGas PeriodicGranularGas.cfg --HCSITS --syncShadowOwners --InelasticFrictionlessContact | tee PeriodicGranularGas_HCSITS_SO_IFC.txt
+      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PE_GranularGas PE_Benchmark.cfg --DEM --syncNextNeighbor | tee GranularGas_DEM_NN.txt
+      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PE_GranularGas PE_Benchmark.cfg --DEM --syncShadowOwners | tee GranularGas_DEM_SO.txt
+      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PE_GranularGas PE_Benchmark.cfg --HCSITS --syncNextNeighbor --InelasticFrictionlessContact | tee GranularGas_HCSITS_NN_IFC.txt
+      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PE_GranularGas PE_Benchmark.cfg --HCSITS --syncNextNeighbor --ApproximateInelasticCoulombContactByDecoupling | tee GranularGas_HCSITS_NN_AICCBD.txt
+      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PE_GranularGas PE_Benchmark.cfg --HCSITS --syncNextNeighbor --InelasticCoulombContactByDecoupling | tee GranularGas_HCSITS_NN_ICCBD.txt
+      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PE_GranularGas PE_Benchmark.cfg --HCSITS --syncNextNeighbor --InelasticGeneralizedMaximumDissipationContact | tee GranularGas_HCSITS_NN_IGMDC.txt
+      - mpirun --allow-run-as-root -np 8 --map-by core --bind-to core --report-bindings ./PE_GranularGas PE_Benchmark.cfg --HCSITS --syncShadowOwners --InelasticFrictionlessContact | tee GranularGas_HCSITS_SO_IFC.txt
       - python3 upload.py
    only:
       variables:
@@ -1773,22 +1773,17 @@ conda-py36-linux:
       - docker-benchmark
    artifacts:
       paths:
-         - $CI_PROJECT_DIR/build/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas_DEM_NN.txt
-         - $CI_PROJECT_DIR/build/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas_DEM_SO.txt
-         - $CI_PROJECT_DIR/build/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas_HCSITS_NN_IFC.txt
-         - $CI_PROJECT_DIR/build/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas_HCSITS_NN_AICCBD.txt
-         - $CI_PROJECT_DIR/build/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas_HCSITS_NN_ICCBD.txt
-         - $CI_PROJECT_DIR/build/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas_HCSITS_NN_IGMDC.txt
-         - $CI_PROJECT_DIR/build/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas_HCSITS_SO_IFC.txt
+         - $CI_PROJECT_DIR/build/apps/benchmarks/GranularGas/*.txt
+         - $CI_PROJECT_DIR/build/apps/benchmarks/GranularGas/benchmark.sqlite
 
 benchmark_intel19:
    <<: *benchmark_definition
    image: i10git.cs.fau.de:5005/walberla/buildenvs/intel:19
 
-benchmark_gcc7:
+benchmark_gcc8:
    <<: *benchmark_definition
-   image: i10git.cs.fau.de:5005/walberla/buildenvs/gcc:7
+   image: i10git.cs.fau.de:5005/walberla/buildenvs/gcc:8
 
-benchmark_clang7:
+benchmark_clang8:
    <<: *benchmark_definition
-   image: i10git.cs.fau.de:5005/walberla/buildenvs/clang:7.0
\ No newline at end of file
+   image: i10git.cs.fau.de:5005/walberla/buildenvs/clang:8.0
diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt
index d5612a35b..0d3a87aa7 100644
--- a/apps/benchmarks/CMakeLists.txt
+++ b/apps/benchmarks/CMakeLists.txt
@@ -4,9 +4,10 @@ add_subdirectory( DEM )
 add_subdirectory( MeshDistance )
 add_subdirectory( CouetteFlow )
 add_subdirectory( ForcesOnSphereNearPlaneInShearFlow )
+add_subdirectory( GranularGas )
+add_subdirectory( LennardJones )
 add_subdirectory( NonUniformGrid )
 add_subdirectory( MotionSingleHeavySphere )
-add_subdirectory( PeriodicGranularGas )
 add_subdirectory( PoiseuilleChannel )
 add_subdirectory( ProbeVsExtraMessage )
 add_subdirectory( SchaeferTurek )
diff --git a/apps/benchmarks/GranularGas/CMakeLists.txt b/apps/benchmarks/GranularGas/CMakeLists.txt
new file mode 100644
index 000000000..50ec1c768
--- /dev/null
+++ b/apps/benchmarks/GranularGas/CMakeLists.txt
@@ -0,0 +1,14 @@
+waLBerla_link_files_to_builddir( *.cfg )
+waLBerla_link_files_to_builddir( *.py )
+
+waLBerla_add_executable ( NAME PE_GranularGas
+                          FILES PE_GranularGas.cpp
+                          DEPENDS blockforest core pe postprocessing )
+
+waLBerla_add_executable ( NAME MESA_PD_GranularGas
+                          FILES MESA_PD_GranularGas.cpp
+                          DEPENDS blockforest core pe mesa_pd postprocessing vtk )
+
+waLBerla_add_executable ( NAME MESA_PD_KernelBenchmark
+                          FILES MESA_PD_KernelBenchmark.cpp
+                          DEPENDS blockforest core pe mesa_pd postprocessing vtk )
diff --git a/apps/benchmarks/GranularGas/GenerateModule.py b/apps/benchmarks/GranularGas/GenerateModule.py
new file mode 100755
index 000000000..f068a6d44
--- /dev/null
+++ b/apps/benchmarks/GranularGas/GenerateModule.py
@@ -0,0 +1,89 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+import mesa_pd.data as data
+import mesa_pd.kernel as kernel
+import mesa_pd.mpi as mpi
+
+import argparse
+import numpy as np
+import os
+
+if __name__ == '__main__':
+   parser = argparse.ArgumentParser(description='Generate all necessary files for the waLBerla mesa_pd module.')
+   parser.add_argument('path', help='Where should the files be created?')
+   parser.add_argument("-f", "--force", help="Generate the files even if not inside a waLBerla directory.",
+                       action="store_true")
+   args = parser.parse_args()
+
+   if ((not os.path.isfile(args.path + "/src/walberla.h")) and (not args.force)):
+      raise RuntimeError(args.path + " is not the path to a waLBerla root directory! Specify -f to generate the files anyway.")
+
+   os.makedirs(args.path + "/src/mesa_pd/common", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/data", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/domain", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/kernel", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/mpi/notifications", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/vtk", exist_ok = True)
+
+   shapes = ["Sphere", "HalfSpace"]
+
+   ps    = data.ParticleStorage()
+   ch    = data.ContactHistory()
+   lc    = data.LinkedCells()
+   ss    = data.ShapeStorage(ps, shapes)
+
+   ps.addProperty("position",         "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ALWAYS")
+   ps.addProperty("linearVelocity",   "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ALWAYS")
+   ps.addProperty("invMass",          "walberla::real_t",        defValue="real_t(1)", syncMode="COPY")
+   ps.addProperty("force",            "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+
+   ps.addProperty("shapeID",          "size_t",                  defValue="",          syncMode="COPY")
+   ps.addProperty("rotation",         "walberla::mesa_pd::Rot3", defValue="",          syncMode="ALWAYS")
+   ps.addProperty("angularVelocity",  "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ALWAYS")
+   ps.addProperty("torque",           "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+
+   ps.addProperty("type",             "uint_t",                  defValue="0",         syncMode="COPY")
+
+   ps.addProperty("flags",            "walberla::mesa_pd::data::particle_flags::FlagT", defValue="", syncMode="COPY")
+   ps.addProperty("nextParticle",     "int",                     defValue="-1",        syncMode="NEVER")
+
+   kernels = []
+   kernels.append( kernel.DoubleCast(shapes) )
+   kernels.append( kernel.ExplicitEuler() )
+   kernels.append( kernel.ExplicitEulerWithShape() )
+   kernels.append( kernel.ForceLJ() )
+   kernels.append( kernel.HeatConduction() )
+   kernels.append( kernel.InsertParticleIntoLinkedCells() )
+   kernels.append( kernel.LinearSpringDashpot() )
+   kernels.append( kernel.NonLinearSpringDashpot() )
+   kernels.append( kernel.SingleCast(shapes) )
+   kernels.append( kernel.SpringDashpot() )
+   kernels.append( kernel.TemperatureIntegration() )
+   kernels.append( kernel.VelocityVerlet() )
+   kernels.append( kernel.VelocityVerletWithShape() )
+
+   ac = Accessor()
+   for k in kernels:
+      ac.mergeRequirements(k.getRequirements())
+   ac.printSummary()
+
+   comm = []
+   comm.append(mpi.BroadcastProperty())
+   comm.append(mpi.ClearNextNeighborSync())
+   comm.append(mpi.ReduceContactHistory())
+   comm.append(mpi.ReduceProperty())
+   comm.append(mpi.SyncNextNeighbors(ps))
+
+
+   ps.generate(args.path + "/src/mesa_pd/")
+   ch.generate(args.path + "/src/mesa_pd/")
+   lc.generate(args.path + "/src/mesa_pd/")
+   ss.generate(args.path + "/src/mesa_pd/")
+
+   for k in kernels:
+      k.generate(args.path + "/src/mesa_pd/")
+
+   for c in comm:
+      c.generate(args.path + "/src/mesa_pd/")
diff --git a/apps/benchmarks/GranularGas/GranularGas.cfg b/apps/benchmarks/GranularGas/GranularGas.cfg
new file mode 100644
index 000000000..71f9c2cc2
--- /dev/null
+++ b/apps/benchmarks/GranularGas/GranularGas.cfg
@@ -0,0 +1,21 @@
+GranularGas
+{
+   simulationCorner < 0, 0, 0 >;
+   simulationDomain < 6, 6, 6 >;
+   blocks < 2,2,2 >;
+   isPeriodic < 1, 1, 1 >;
+
+   radius  0.6;
+   spacing 1.0;
+   vMax    0.0;
+
+   dt                0.0001;
+   simulationSteps    500;
+   visSpacing         100;
+
+   HCSITSmaxIterations 10;
+   HCSITSRelaxationParameter 0.7;
+   HCSITSErrorReductionParameter 0.8;
+   HCSITSRelaxationModelStr ApproximateInelasticCoulombContactByDecoupling;
+   globalLinearAcceleration < 0, 0, 0 >;
+}
diff --git a/apps/benchmarks/GranularGas/MESA_PD_GranularGas.cpp b/apps/benchmarks/GranularGas/MESA_PD_GranularGas.cpp
new file mode 100644
index 000000000..09c6d7549
--- /dev/null
+++ b/apps/benchmarks/GranularGas/MESA_PD_GranularGas.cpp
@@ -0,0 +1,487 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   MESA_PD_GranularGas.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/vtk/ParticleVtkOutput.h>
+
+#include <mesa_pd/collision_detection/AnalyticContactDetection.h>
+#include <mesa_pd/data/LinkedCells.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/kernel/DoubleCast.h>
+#include <mesa_pd/kernel/ExplicitEulerWithShape.h>
+#include <mesa_pd/kernel/InsertParticleIntoLinkedCells.h>
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/kernel/SpringDashpot.h>
+#include <mesa_pd/mpi/ContactFilter.h>
+#include <mesa_pd/mpi/ReduceProperty.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <mesa_pd/mpi/notifications/ForceTorqueNotification.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Abort.h>
+#include <core/Environment.h>
+#include <core/math/Random.h>
+#include <core/mpi/Reduce.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/OpenMP.h>
+#include <core/timing/Timer.h>
+#include <core/timing/TimingPool.h>
+#include <core/waLBerlaBuildInfo.h>
+#include <postprocessing/sqlite/SQLite.h>
+#include <vtk/VTKOutput.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+namespace walberla {
+namespace mesa_pd {
+
+class SelectRank
+{
+public:
+   using return_type = int;
+   int operator()(const data::Particle& /*p*/) const { return rank_; }
+   int operator()(const data::Particle&& /*p*/) const { return rank_; }
+private:
+   int rank_ = walberla::mpi::MPIManager::instance()->rank();
+};
+
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+      : ParticleAccessor(ps)
+      , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+void createPlane( data::ParticleStorage& ps,
+                  data::ShapeStorage& ss,
+                  const Vec3& pos,
+                  const Vec3& normal )
+{
+   auto p0              = ps.create(true);
+   p0->getPositionRef() = pos;
+   p0->getShapeIDRef()  = ss.create<data::HalfSpace>( normal );
+   p0->getOwnerRef()    = walberla::mpi::MPIManager::instance()->rank();
+   p0->getTypeRef()     = 0;
+   data::particle_flags::set(p0->getFlagsRef(), data::particle_flags::INFINITE);
+   data::particle_flags::set(p0->getFlagsRef(), data::particle_flags::FIXED);
+   data::particle_flags::set(p0->getFlagsRef(), data::particle_flags::NON_COMMUNICATING);
+}
+
+std::string envToString(const char* env)
+{
+   return env != nullptr ? std::string(env) : "";
+}
+
+int main( int argc, char ** argv )
+{
+   using namespace walberla::timing;
+
+   Environment env(argc, argv);
+   auto mpiManager = walberla::mpi::MPIManager::instance();
+   mpiManager->useWorldComm();
+
+   logging::Logging::instance()->setStreamLogLevel(logging::Logging::INFO);
+   logging::Logging::instance()->setFileLogLevel(logging::Logging::INFO);
+
+   WALBERLA_LOG_INFO_ON_ROOT( "config file: " << argv[1] );
+   WALBERLA_LOG_INFO_ON_ROOT( "waLBerla Revision: " << WALBERLA_GIT_SHA1 );
+
+   math::seedRandomGenerator( static_cast<unsigned int>(1337 * mpiManager->worldRank()) );
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** READING CONFIG FILE ***");
+   auto cfg = env.config();
+   if (cfg == nullptr) WALBERLA_ABORT("No config specified!");
+   const Config::BlockHandle mainConf  = cfg->getBlock( "GranularGas" );
+
+   const std::string host = mainConf.getParameter<std::string>("host", "none" );
+   WALBERLA_LOG_INFO_ON_ROOT("host: " << host);
+
+   const int jobid = mainConf.getParameter<int>("jobid", 0 );
+   WALBERLA_LOG_INFO_ON_ROOT("jobid: " << jobid);
+
+   const real_t spacing = mainConf.getParameter<real_t>("spacing", real_t(1.0) );
+   WALBERLA_LOG_INFO_ON_ROOT("spacing: " << spacing);
+
+   const real_t radius = mainConf.getParameter<real_t>("radius", real_t(0.5) );
+   WALBERLA_LOG_INFO_ON_ROOT("radius: " << radius);
+
+   bool bBarrier = mainConf.getParameter<bool>("bBarrier", false );
+   WALBERLA_LOG_INFO_ON_ROOT("bBarrier: " << bBarrier);
+
+   int64_t numOuterIterations = mainConf.getParameter<int64_t>("numOuterIterations", 10 );
+   WALBERLA_LOG_INFO_ON_ROOT("numOuterIterations: " << numOuterIterations);
+
+   int64_t simulationSteps = mainConf.getParameter<int64_t>("simulationSteps", 10 );
+   WALBERLA_LOG_INFO_ON_ROOT("simulationSteps: " << simulationSteps);
+
+   real_t dt = mainConf.getParameter<real_t>("dt", real_c(0.01) );
+   WALBERLA_LOG_INFO_ON_ROOT("dt: " << dt);
+
+   const int visSpacing = mainConf.getParameter<int>("visSpacing",  1000 );
+   WALBERLA_LOG_INFO_ON_ROOT("visSpacing: " << visSpacing);
+   const std::string path = mainConf.getParameter<std::string>("path",  "vtk_out" );
+   WALBERLA_LOG_INFO_ON_ROOT("path: " << path);
+
+   const std::string sqlFile = mainConf.getParameter<std::string>("sqlFile",  "benchmark.sqlite" );
+   WALBERLA_LOG_INFO_ON_ROOT("sqlFile: " << sqlFile);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** BLOCKFOREST ***");
+   // create forest
+   auto forest = blockforest::createBlockForestFromConfig( mainConf );
+   if (!forest)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT( "No BlockForest created ... exiting!");
+      return EXIT_SUCCESS;
+   }
+   domain::BlockForestDomain domain(forest);
+
+   auto simulationDomain = forest->getDomain();
+   auto localDomain = forest->begin()->getAABB();
+   for (auto& blk : *forest)
+   {
+      localDomain.merge(blk.getAABB());
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - START ***");
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+   ParticleAccessorWithShape accessor(ps, ss);
+   data::LinkedCells     lc(localDomain.getExtended(spacing), spacing );
+
+   auto  smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+   for (auto& iBlk : *forest)
+   {
+      for (auto pt : grid_generator::SCGrid(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing))
+      {
+         WALBERLA_CHECK(iBlk.getAABB().contains(pt));
+
+         auto p                       = ps->create();
+         p->getPositionRef()          = pt;
+         p->getInteractionRadiusRef() = radius;
+         p->getShapeIDRef()           = smallSphere;
+         p->getOwnerRef()             = mpiManager->rank();
+         p->getTypeRef()              = 0;
+      }
+   }
+   int64_t numParticles = int64_c(ps->size());
+   walberla::mpi::reduceInplace(numParticles, walberla::mpi::SUM);
+   WALBERLA_LOG_INFO_ON_ROOT("#particles created: " << numParticles);
+
+   auto shift = (spacing - radius - radius) * real_t(0.5);
+   auto confiningDomain = simulationDomain.getExtended(shift);
+
+   if (!forest->isPeriodic(0))
+   {
+      createPlane(*ps, *ss, confiningDomain.minCorner(), Vec3(+1,0,0));
+      createPlane(*ps, *ss, confiningDomain.maxCorner(), Vec3(-1,0,0));
+   }
+
+   if (!forest->isPeriodic(1))
+   {
+      createPlane(*ps, *ss, confiningDomain.minCorner(), Vec3(0,+1,0));
+      createPlane(*ps, *ss, confiningDomain.maxCorner(), Vec3(0,-1,0));
+   }
+
+   if (!forest->isPeriodic(2))
+   {
+      createPlane(*ps, *ss, confiningDomain.minCorner(), Vec3(0,0,+1));
+      createPlane(*ps, *ss, confiningDomain.maxCorner(), Vec3(0,0,-1));
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - END ***");
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** VTK ***");
+   auto vtkDomainOutput = walberla::vtk::createVTKOutput_DomainDecomposition( forest, "domain_decomposition", 1, "vtk_out", "simulation_step" );
+   auto vtkOutput       = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps) ;
+   auto vtkWriter       = walberla::vtk::createVTKOutput_PointData(vtkOutput, "Bodies", 1, "vtk", "simulation_step", false, false);
+   vtkOutput->addOutput<SelectRank>("rank");
+   vtkOutput->addOutput<data::SelectParticleOwner>("owner");
+   //   vtkDomainOutput->write();
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - START ***");
+   // Init kernels
+   kernel::ExplicitEulerWithShape        explicitEulerWithShape( dt );
+   kernel::InsertParticleIntoLinkedCells ipilc;
+   kernel::SpringDashpot                 dem(1);
+   dem.setStiffness(0, 0, real_t(0));
+   dem.setDampingN (0, 0, real_t(0));
+   dem.setDampingT (0, 0, real_t(0));
+   dem.setFriction (0, 0, real_t(0));
+   collision_detection::AnalyticContactDetection              acd;
+   kernel::DoubleCast                    double_cast;
+   mpi::ContactFilter                    contact_filter;
+   mpi::ReduceProperty                   RP;
+   mpi::SyncNextNeighbors                SNN;
+
+   // initial sync
+   SNN(*ps, domain);
+
+   for (int64_t outerIteration = 0; outerIteration < numOuterIterations; ++outerIteration)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT("*** RUNNING OUTER ITERATION " << outerIteration << " ***");
+
+      WcTimer      timer;
+      WcTimingPool tp;
+      auto    SNNBytesSent     = SNN.getBytesSent();
+      auto    SNNBytesReceived = SNN.getBytesReceived();
+      auto    SNNSends         = SNN.getNumberOfSends();
+      auto    SNNReceives      = SNN.getNumberOfReceives();
+      auto    RPBytesSent      = RP.getBytesSent();
+      auto    RPBytesReceived  = RP.getBytesReceived();
+      auto    RPSends          = RP.getNumberOfSends();
+      auto    RPReceives       = RP.getNumberOfReceives();
+      int64_t contactsChecked  = 0;
+      int64_t contactsDetected = 0;
+      int64_t contactsTreated  = 0;
+      if (bBarrier) WALBERLA_MPI_BARRIER();
+      for (int64_t i=0; i < simulationSteps; ++i)
+      {
+         //      if (i % visSpacing == 0)
+         //      {
+         //         vtkWriter->write();
+         //      }
+
+         tp["GenerateLinkedCells"].start();
+         lc.clear();
+         ps->forEachParticle(true, kernel::SelectAll(), accessor, ipilc, accessor, lc);
+         if (bBarrier) WALBERLA_MPI_BARRIER();
+         tp["GenerateLinkedCells"].end();
+
+         tp["DEM"].start();
+         contactsChecked  = 0;
+         contactsDetected = 0;
+         contactsTreated  = 0;
+         lc.forEachParticlePairHalf(true,
+                                    kernel::SelectAll(),
+                                    accessor,
+                                    [&](const size_t idx1, const size_t idx2, auto& ac)
+         {
+            ++contactsChecked;
+            if (double_cast(idx1, idx2, ac, acd, ac ))
+            {
+               ++contactsDetected;
+               if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), domain))
+               {
+                  ++contactsTreated;
+                  dem(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth());
+               }
+            }
+         },
+         accessor );
+         if (bBarrier) WALBERLA_MPI_BARRIER();
+         tp["DEM"].end();
+
+         tp["ReduceForce"].start();
+         RP.operator()<ForceTorqueNotification>(*ps);
+         if (bBarrier) WALBERLA_MPI_BARRIER();
+         tp["ReduceForce"].end();
+
+         tp["Euler"].start();
+         //ps->forEachParticle(false, [&](const size_t idx){WALBERLA_CHECK_EQUAL(ps->getForce(idx), Vec3(0,0,0), *(*ps)[idx] << "\n" << idx);});
+         ps->forEachParticle(true, kernel::SelectLocal(), accessor, explicitEulerWithShape, accessor);
+         if (bBarrier) WALBERLA_MPI_BARRIER();
+         tp["Euler"].end();
+
+         tp["SNN"].start();
+         SNN(*ps, domain);
+         if (bBarrier) WALBERLA_MPI_BARRIER();
+         tp["SNN"].end();
+
+         if( i % 100 == 0 )
+         {
+            WALBERLA_LOG_DEVEL_ON_ROOT( "Timestep " << i << " / " << simulationSteps );
+            SNNBytesSent     = SNN.getBytesSent();
+            SNNBytesReceived = SNN.getBytesReceived();
+            SNNSends         = SNN.getNumberOfSends();
+            SNNReceives      = SNN.getNumberOfReceives();
+            RPBytesSent      = RP.getBytesSent();
+            RPBytesReceived  = RP.getBytesReceived();
+            RPSends          = RP.getNumberOfSends();
+            RPReceives       = RP.getNumberOfReceives();
+            walberla::mpi::reduceInplace(SNNBytesSent, walberla::mpi::SUM);
+            walberla::mpi::reduceInplace(SNNBytesReceived, walberla::mpi::SUM);
+            walberla::mpi::reduceInplace(SNNSends, walberla::mpi::SUM);
+            walberla::mpi::reduceInplace(SNNReceives, walberla::mpi::SUM);
+            walberla::mpi::reduceInplace(RPBytesSent, walberla::mpi::SUM);
+            walberla::mpi::reduceInplace(RPBytesReceived, walberla::mpi::SUM);
+            walberla::mpi::reduceInplace(RPSends, walberla::mpi::SUM);
+            walberla::mpi::reduceInplace(RPReceives, walberla::mpi::SUM);
+            auto cC = walberla::mpi::reduce(contactsChecked, walberla::mpi::SUM);
+            auto cD = walberla::mpi::reduce(contactsDetected, walberla::mpi::SUM);
+            auto cT = walberla::mpi::reduce(contactsTreated, walberla::mpi::SUM);
+            WALBERLA_LOG_DEVEL_ON_ROOT( "SNN bytes communicated:   " << SNNBytesSent << " / " << SNNBytesReceived );
+            WALBERLA_LOG_DEVEL_ON_ROOT( "SNN communication partners: " << SNNSends << " / " << SNNReceives );
+            WALBERLA_LOG_DEVEL_ON_ROOT( "RP bytes communicated:  " << RPBytesSent << " / " << RPBytesReceived );
+            WALBERLA_LOG_DEVEL_ON_ROOT( "RP communication partners: " << RPSends << " / " << RPReceives );
+            WALBERLA_LOG_DEVEL_ON_ROOT( "contacts checked/detected/treated: " << cC << " / " << cD << " / " << cT );
+            if (bBarrier) WALBERLA_MPI_BARRIER();
+         }
+      }
+      timer.end();
+      auto tp_reduced = tp.getReduced();
+      WALBERLA_LOG_INFO_ON_ROOT(*tp_reduced);
+      WALBERLA_LOG_INFO_ON_ROOT("runtime: " << timer.average());
+      auto PUpS = real_c(numParticles) * real_c(simulationSteps) / timer.average();
+      WALBERLA_LOG_INFO_ON_ROOT("PUpS: " << PUpS);
+      WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - END ***");
+
+      WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING RESULT - START ***");
+      auto pIt = ps->begin();
+      for (auto& iBlk : *forest)
+      {
+         for (auto it = grid_generator::SCIterator(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing);
+              it != grid_generator::SCIterator();
+              ++it, ++pIt)
+         {
+            WALBERLA_CHECK_UNEQUAL(pIt, ps->end());
+            WALBERLA_CHECK_FLOAT_EQUAL((*pIt).getPositionRef(), *it);
+         }
+      }
+      WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING RESULT - END ***");
+
+      WALBERLA_LOG_INFO_ON_ROOT("*** SQL OUTPUT - START ***");
+      numParticles = 0;
+      int64_t numGhostParticles = 0;
+      ps->forEachParticle(false,
+                          kernel::SelectAll(),
+                          accessor,
+                          [&numParticles, &numGhostParticles](const size_t idx, auto& ac)
+      {
+         if (data::particle_flags::isSet( ac.getFlagsRef(idx), data::particle_flags::GHOST))
+         {
+            ++numGhostParticles;
+         } else
+         {
+            ++numParticles;
+         }
+      },
+      accessor);
+      walberla::mpi::reduceInplace(numParticles, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(numGhostParticles, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(contactsChecked, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(contactsDetected, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(contactsTreated, walberla::mpi::SUM);
+      double linkedCellsVolume = lc.domain_.volume();
+      walberla::mpi::reduceInplace(linkedCellsVolume, walberla::mpi::SUM);
+      size_t numLinkedCells = lc.cells_.size();
+      walberla::mpi::reduceInplace(numLinkedCells, walberla::mpi::SUM);
+      size_t local_aabbs         = domain.getNumLocalAABBs();
+      size_t neighbor_subdomains = domain.getNumNeighborSubdomains();
+      size_t neighbor_processes  = domain.getNumNeighborProcesses();
+      walberla::mpi::reduceInplace(local_aabbs, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(neighbor_subdomains, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(neighbor_processes, walberla::mpi::SUM);
+      WALBERLA_ROOT_SECTION()
+      {
+         std::map< std::string, walberla::int64_t > integerProperties;
+         std::map< std::string, double >            realProperties;
+         std::map< std::string, std::string >       stringProperties;
+
+         stringProperties["walberla_git"]         = WALBERLA_GIT_SHA1;
+         stringProperties["tag"]                  = "mesa_pd";
+         stringProperties["host"]                 = host;
+         integerProperties["jobid"]               = jobid;
+         integerProperties["mpi_num_processes"]   = mpiManager->numProcesses();
+         integerProperties["omp_max_threads"]     = omp_get_max_threads();
+         integerProperties["numOuterIterations"]  = numOuterIterations;
+         integerProperties["simulationSteps"]     = simulationSteps;
+         integerProperties["bBarrier"]            = int64_c(bBarrier);
+         realProperties["PUpS"]                   = double_c(PUpS);
+         integerProperties["num_particles"]       = numParticles;
+         integerProperties["num_ghost_particles"] = numGhostParticles;
+         integerProperties["contacts_checked"]    = contactsChecked;
+         integerProperties["contacts_detected"]   = contactsDetected;
+         integerProperties["contacts_treated"]    = contactsTreated;
+         integerProperties["blocks_x"]            = int64_c(forest->getXSize());
+         integerProperties["blocks_y"]            = int64_c(forest->getXSize());
+         integerProperties["blocks_z"]            = int64_c(forest->getXSize());
+         realProperties["domain_x"]               = double_c(forest->getDomain().xSize());
+         realProperties["domain_y"]               = double_c(forest->getDomain().ySize());
+         realProperties["domain_z"]               = double_c(forest->getDomain().zSize());
+         integerProperties["local_aabbs"]         = int64_c(local_aabbs);
+         integerProperties["neighbor_subdomains"] = int64_c(neighbor_subdomains);
+         integerProperties["neighbor_processes"]  = int64_c(neighbor_processes);
+         integerProperties["SNNBytesSent"]        = SNNBytesSent;
+         integerProperties["SNNBytesReceived"]    = SNNBytesReceived;
+         integerProperties["SNNSends"]            = SNNSends;
+         integerProperties["SNNReceives"]         = SNNReceives;
+         integerProperties["RPBytesSent"]         = RPBytesSent;
+         integerProperties["RPBytesReceived"]     = RPBytesReceived;
+         integerProperties["RPSends"]             = RPSends;
+         integerProperties["RPReceives"]          = RPReceives;
+         realProperties["linkedCellsVolume"]      = linkedCellsVolume;
+         integerProperties["numLinkedCells"]      = int64_c(numLinkedCells);
+         stringProperties["SLURM_CLUSTER_NAME"]       = envToString(std::getenv( "SLURM_CLUSTER_NAME" ));
+         stringProperties["SLURM_CPUS_ON_NODE"]       = envToString(std::getenv( "SLURM_CPUS_ON_NODE" ));
+         stringProperties["SLURM_CPUS_PER_TASK"]      = envToString(std::getenv( "SLURM_CPUS_PER_TASK" ));
+         stringProperties["SLURM_JOB_ACCOUNT"]        = envToString(std::getenv( "SLURM_JOB_ACCOUNT" ));
+         stringProperties["SLURM_JOB_ID"]             = envToString(std::getenv( "SLURM_JOB_ID" ));
+         stringProperties["SLURM_JOB_CPUS_PER_NODE"]  = envToString(std::getenv( "SLURM_JOB_CPUS_PER_NODE" ));
+         stringProperties["SLURM_JOB_NAME"]           = envToString(std::getenv( "SLURM_JOB_NAME" ));
+         stringProperties["SLURM_JOB_NUM_NODES"]      = envToString(std::getenv( "SLURM_JOB_NUM_NODES" ));
+         stringProperties["SLURM_NTASKS"]             = envToString(std::getenv( "SLURM_NTASKS" ));
+         stringProperties["SLURM_NTASKS_PER_CORE"]    = envToString(std::getenv( "SLURM_NTASKS_PER_CORE" ));
+         stringProperties["SLURM_NTASKS_PER_NODE"]    = envToString(std::getenv( "SLURM_NTASKS_PER_NODE" ));
+         stringProperties["SLURM_NTASKS_PER_SOCKET"]  = envToString(std::getenv( "SLURM_NTASKS_PER_SOCKET" ));
+         stringProperties["SLURM_TASKS_PER_NODE"]     = envToString(std::getenv( "SLURM_TASKS_PER_NODE" ));
+
+
+         auto runId = postprocessing::storeRunInSqliteDB( sqlFile, integerProperties, stringProperties, realProperties );
+         postprocessing::storeTimingPoolInSqliteDB( sqlFile, runId, *tp_reduced, "Timeloop" );
+      }
+      WALBERLA_LOG_INFO_ON_ROOT("*** SQL OUTPUT - END ***");
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace mesa_pd
+} // namespace walberla
+
+int main( int argc, char* argv[] )
+{
+   return walberla::mesa_pd::main( argc, argv );
+}
diff --git a/apps/benchmarks/GranularGas/MESA_PD_KernelBenchmark.cpp b/apps/benchmarks/GranularGas/MESA_PD_KernelBenchmark.cpp
new file mode 100644
index 000000000..ea71d2890
--- /dev/null
+++ b/apps/benchmarks/GranularGas/MESA_PD_KernelBenchmark.cpp
@@ -0,0 +1,510 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   MESA_PD_KernelBenchmark.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/vtk/ParticleVtkOutput.h>
+
+#include <mesa_pd/collision_detection/AnalyticContactDetection.h>
+#include <mesa_pd/data/LinkedCells.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/kernel/DoubleCast.h>
+#include <mesa_pd/kernel/ExplicitEulerWithShape.h>
+#include <mesa_pd/kernel/InsertParticleIntoLinkedCells.h>
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/kernel/SpringDashpot.h>
+#include <mesa_pd/mpi/ContactFilter.h>
+#include <mesa_pd/mpi/ReduceProperty.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <mesa_pd/mpi/notifications/ForceTorqueNotification.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Abort.h>
+#include <core/Environment.h>
+#include <core/math/Random.h>
+#include <core/mpi/Reduce.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/OpenMP.h>
+#include <core/timing/Timer.h>
+#include <core/timing/TimingPool.h>
+#include <core/waLBerlaBuildInfo.h>
+#include <postprocessing/sqlite/SQLite.h>
+#include <vtk/VTKOutput.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+namespace walberla {
+namespace mesa_pd {
+
+class SelectRank
+{
+public:
+   using return_type = int;
+   int operator()(const data::Particle& /*p*/) const { return rank_; }
+   int operator()(const data::Particle&& /*p*/) const { return rank_; }
+private:
+   int rank_ = walberla::mpi::MPIManager::instance()->rank();
+};
+
+struct Contact
+{
+   Contact(const size_t idx1,
+           const size_t idx2,
+           const Vec3   contactPoint,
+           const Vec3   contactNormal,
+           const real_t penetrationDepth)
+      : idx1_(idx1)
+      , idx2_(idx2)
+      , contactPoint_(contactPoint)
+      , contactNormal_(contactNormal)
+      , penetrationDepth_(penetrationDepth) {}
+
+   size_t idx1_;
+   size_t idx2_;
+   Vec3   contactPoint_;
+   Vec3   contactNormal_;
+   real_t penetrationDepth_;
+};
+
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+      : ParticleAccessor(ps)
+      , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+void createPlane( data::ParticleStorage& ps,
+                  data::ShapeStorage& ss,
+                  const Vec3& pos,
+                  const Vec3& normal )
+{
+   auto p0              = ps.create(true);
+   p0->getPositionRef() = pos;
+   p0->getShapeIDRef()  = ss.create<data::HalfSpace>( normal );
+   p0->getOwnerRef()    = walberla::mpi::MPIManager::instance()->rank();
+   p0->getTypeRef()     = 0;
+   data::particle_flags::set(p0->getFlagsRef(), data::particle_flags::INFINITE);
+   data::particle_flags::set(p0->getFlagsRef(), data::particle_flags::FIXED);
+   data::particle_flags::set(p0->getFlagsRef(), data::particle_flags::NON_COMMUNICATING);
+}
+
+std::string envToString(const char* env)
+{
+   return env != nullptr ? std::string(env) : "";
+}
+
+int main( int argc, char ** argv )
+{
+   using namespace walberla::timing;
+
+   Environment env(argc, argv);
+   auto mpiManager = walberla::mpi::MPIManager::instance();
+   mpiManager->useWorldComm();
+
+   logging::Logging::instance()->setStreamLogLevel(logging::Logging::INFO);
+   logging::Logging::instance()->setFileLogLevel(logging::Logging::INFO);
+
+   WALBERLA_LOG_INFO_ON_ROOT( "config file: " << argv[1] );
+   WALBERLA_LOG_INFO_ON_ROOT( "waLBerla Revision: " << WALBERLA_GIT_SHA1 );
+
+   math::seedRandomGenerator( static_cast<unsigned int>(1337 * mpiManager->worldRank()) );
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** READING CONFIG FILE ***");
+   auto cfg = env.config();
+   if (cfg == nullptr) WALBERLA_ABORT("No config specified!");
+   const Config::BlockHandle mainConf  = cfg->getBlock( "GranularGas" );
+
+   const std::string host = mainConf.getParameter<std::string>("host", "none" );
+   WALBERLA_LOG_INFO_ON_ROOT("host: " << host);
+
+   const int jobid = mainConf.getParameter<int>("jobid", 0 );
+   WALBERLA_LOG_INFO_ON_ROOT("jobid: " << jobid);
+
+   const real_t spacing = mainConf.getParameter<real_t>("spacing", real_t(1.0) );
+   WALBERLA_LOG_INFO_ON_ROOT("spacing: " << spacing);
+
+   const real_t radius = mainConf.getParameter<real_t>("radius", real_t(0.5) );
+   WALBERLA_LOG_INFO_ON_ROOT("radius: " << radius);
+
+   bool bBarrier = mainConf.getParameter<bool>("bBarrier", false );
+   WALBERLA_LOG_INFO_ON_ROOT("bBarrier: " << bBarrier);
+
+   int64_t numOuterIterations = mainConf.getParameter<int64_t>("numOuterIterations", 10 );
+   WALBERLA_LOG_INFO_ON_ROOT("numOuterIterations: " << numOuterIterations);
+
+   int64_t simulationSteps = mainConf.getParameter<int64_t>("simulationSteps", 10 );
+   WALBERLA_LOG_INFO_ON_ROOT("simulationSteps: " << simulationSteps);
+
+   real_t dt = mainConf.getParameter<real_t>("dt", real_c(0.01) );
+   WALBERLA_LOG_INFO_ON_ROOT("dt: " << dt);
+
+   const int visSpacing = mainConf.getParameter<int>("visSpacing",  1000 );
+   WALBERLA_LOG_INFO_ON_ROOT("visSpacing: " << visSpacing);
+   const std::string path = mainConf.getParameter<std::string>("path",  "vtk_out" );
+   WALBERLA_LOG_INFO_ON_ROOT("path: " << path);
+
+   const std::string sqlFile = mainConf.getParameter<std::string>("sqlFile",  "benchmark.sqlite" );
+   WALBERLA_LOG_INFO_ON_ROOT("sqlFile: " << sqlFile);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** BLOCKFOREST ***");
+   // create forest
+   auto forest = blockforest::createBlockForestFromConfig( mainConf );
+   if (!forest)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT( "No BlockForest created ... exiting!");
+      return EXIT_SUCCESS;
+   }
+   domain::BlockForestDomain domain(forest);
+
+   auto simulationDomain = forest->getDomain();
+   auto localDomain = forest->begin()->getAABB();
+   for (auto& blk : *forest)
+   {
+      localDomain.merge(blk.getAABB());
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - START ***");
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+   ParticleAccessorWithShape accessor(ps, ss);
+   data::LinkedCells     lc(localDomain.getExtended(spacing), spacing );
+
+   auto  smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+   for (auto& iBlk : *forest)
+   {
+      for (auto pt : grid_generator::SCGrid(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing))
+      {
+         WALBERLA_CHECK(iBlk.getAABB().contains(pt));
+
+         auto p                       = ps->create();
+         p->getPositionRef()          = pt;
+         p->getInteractionRadiusRef() = radius;
+         p->getShapeIDRef()           = smallSphere;
+         p->getOwnerRef()             = mpiManager->rank();
+         p->getTypeRef()              = 0;
+      }
+   }
+   int64_t numParticles = int64_c(ps->size());
+   walberla::mpi::reduceInplace(numParticles, walberla::mpi::SUM);
+   WALBERLA_LOG_INFO_ON_ROOT("#particles created: " << numParticles);
+
+   auto shift = (spacing - radius - radius) * real_t(0.5);
+   auto confiningDomain = simulationDomain.getExtended(shift);
+
+   if (!forest->isPeriodic(0))
+   {
+      createPlane(*ps, *ss, confiningDomain.minCorner(), Vec3(+1,0,0));
+      createPlane(*ps, *ss, confiningDomain.maxCorner(), Vec3(-1,0,0));
+   }
+
+   if (!forest->isPeriodic(1))
+   {
+      createPlane(*ps, *ss, confiningDomain.minCorner(), Vec3(0,+1,0));
+      createPlane(*ps, *ss, confiningDomain.maxCorner(), Vec3(0,-1,0));
+   }
+
+   if (!forest->isPeriodic(2))
+   {
+      createPlane(*ps, *ss, confiningDomain.minCorner(), Vec3(0,0,+1));
+      createPlane(*ps, *ss, confiningDomain.maxCorner(), Vec3(0,0,-1));
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - END ***");
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** VTK ***");
+   auto vtkDomainOutput = walberla::vtk::createVTKOutput_DomainDecomposition( forest, "domain_decomposition", 1, "vtk_out", "simulation_step" );
+   auto vtkOutput       = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps) ;
+   auto vtkWriter       = walberla::vtk::createVTKOutput_PointData(vtkOutput, "Bodies", 1, "vtk", "simulation_step", false, false);
+   vtkOutput->addOutput<SelectRank>("rank");
+   vtkOutput->addOutput<data::SelectParticleOwner>("owner");
+   //   vtkDomainOutput->write();
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - START ***");
+   // Init kernels
+   kernel::ExplicitEulerWithShape        explicitEulerWithShape( dt );
+   kernel::InsertParticleIntoLinkedCells ipilc;
+   kernel::SpringDashpot                 dem(1);
+   dem.setStiffness(0, 0, real_t(0));
+   dem.setDampingN (0, 0, real_t(0));
+   dem.setDampingT (0, 0, real_t(0));
+   dem.setFriction (0, 0, real_t(0));
+   collision_detection::AnalyticContactDetection              acd;
+   kernel::DoubleCast                    double_cast;
+   mpi::ContactFilter                    contact_filter;
+   mpi::ReduceProperty                   RP;
+   mpi::SyncNextNeighbors                SNN;
+   std::vector<Contact>                  contacts;
+   contacts.reserve(4000000);
+
+   // initial sync
+   SNN(*ps, domain);
+
+   for (int64_t outerIteration = 0; outerIteration < numOuterIterations; ++outerIteration)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT("*** RUNNING OUTER ITERATION " << outerIteration << " ***");
+
+      WcTimingPool tp;
+
+      WALBERLA_MPI_BARRIER();
+      tp["GenerateLinkedCells"].start();
+      for (int64_t i=0; i < simulationSteps; ++i)
+      {
+         lc.clear();
+         ps->forEachParticle(true, kernel::SelectAll(), accessor, ipilc, accessor, lc);
+      }
+      tp["GenerateLinkedCells"].end();
+
+      int64_t contactsChecked  = 0;
+      int64_t contactsDetected = 0;
+      int64_t contactsTreated  = 0;
+      WALBERLA_MPI_BARRIER();
+      tp["ContactDetection"].start();
+      for (int64_t i=0; i < simulationSteps; ++i)
+      {
+         contacts.clear();
+         contactsChecked  = 0;
+         contactsDetected = 0;
+         contactsTreated  = 0;
+         lc.forEachParticlePairHalf(true,
+                                    kernel::SelectAll(),
+                                    accessor,
+                                    [&](const size_t idx1, const size_t idx2, auto& ac)
+         {
+            ++contactsChecked;
+            if (double_cast(idx1, idx2, ac, acd, ac ))
+            {
+               ++contactsDetected;
+               if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), domain))
+               {
+                  ++contactsTreated;
+                  contacts.emplace_back(acd.getIdx1(), acd.getIdx2(), acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth());
+               }
+            }
+         },
+         accessor );
+      }
+      tp["ContactDetection"].end();
+
+      WALBERLA_MPI_BARRIER();
+      tp["DEM"].start();
+      for (int64_t i=0; i < simulationSteps; ++i)
+      {
+         for (auto& c : contacts)
+         {
+            dem(c.idx1_, c.idx2_, accessor, c.contactPoint_, c.contactNormal_, c.penetrationDepth_);
+         }
+      }
+      tp["DEM"].end();
+
+      WALBERLA_MPI_BARRIER();
+      tp["ReduceForce"].start();
+      for (int64_t i=0; i < simulationSteps; ++i)
+      {
+         RP.operator()<ForceTorqueNotification>(*ps);
+      }
+      tp["ReduceForce"].end();
+
+      WALBERLA_MPI_BARRIER();
+      tp["Euler"].start();
+      for (int64_t i=0; i < simulationSteps; ++i)
+      {
+         ps->forEachParticle(true, kernel::SelectLocal(), accessor, explicitEulerWithShape, accessor);
+      }
+      tp["Euler"].end();
+
+      WALBERLA_MPI_BARRIER();
+      tp["SNN"].start();
+      for (int64_t i=0; i < simulationSteps; ++i)
+      {
+         SNN(*ps, domain);
+      }
+      tp["SNN"].end();
+
+      WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - END ***");
+
+      WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING RESULT - START ***");
+      auto pIt = ps->begin();
+      for (auto& iBlk : *forest)
+      {
+         for (auto it = grid_generator::SCIterator(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing);
+              it != grid_generator::SCIterator();
+              ++it, ++pIt)
+         {
+            WALBERLA_CHECK_UNEQUAL(pIt, ps->end());
+            WALBERLA_CHECK_FLOAT_EQUAL((*pIt).getPositionRef(), *it);
+         }
+      }
+      WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING RESULT - END ***");
+
+      WALBERLA_LOG_INFO_ON_ROOT("*** SQL OUTPUT - START ***");
+      auto SNNBytesSent     = SNN.getBytesSent();
+      auto SNNBytesReceived = SNN.getBytesReceived();
+      auto SNNSends         = SNN.getNumberOfSends();
+      auto SNNReceives      = SNN.getNumberOfReceives();
+      auto RPBytesSent      = RP.getBytesSent();
+      auto RPBytesReceived  = RP.getBytesReceived();
+      auto RPSends          = RP.getNumberOfSends();
+      auto RPReceives       = RP.getNumberOfReceives();
+      walberla::mpi::reduceInplace(SNNBytesSent, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(SNNBytesReceived, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(SNNSends, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(SNNReceives, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(RPBytesSent, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(RPBytesReceived, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(RPSends, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(RPReceives, walberla::mpi::SUM);
+      auto cC = walberla::mpi::reduce(contactsChecked, walberla::mpi::SUM);
+      auto cD = walberla::mpi::reduce(contactsDetected, walberla::mpi::SUM);
+      auto cT = walberla::mpi::reduce(contactsTreated, walberla::mpi::SUM);
+      WALBERLA_LOG_DEVEL_ON_ROOT( "SNN bytes communicated:   " << SNNBytesSent << " / " << SNNBytesReceived );
+      WALBERLA_LOG_DEVEL_ON_ROOT( "SNN communication partners: " << SNNSends << " / " << SNNReceives );
+      WALBERLA_LOG_DEVEL_ON_ROOT( "RP bytes communicated:  " << RPBytesSent << " / " << RPBytesReceived );
+      WALBERLA_LOG_DEVEL_ON_ROOT( "RP communication partners: " << RPSends << " / " << RPReceives );
+      WALBERLA_LOG_DEVEL_ON_ROOT( "contacts checked/detected/treated: " << cC << " / " << cD << " / " << cT );
+
+      auto tp_reduced = tp.getReduced();
+      WALBERLA_LOG_INFO_ON_ROOT(*tp_reduced);
+
+      numParticles = 0;
+      int64_t numGhostParticles = 0;
+      ps->forEachParticle(false,
+                          kernel::SelectAll(),
+                          accessor,
+                          [&numParticles, &numGhostParticles](const size_t idx, auto& ac)
+      {
+         if (data::particle_flags::isSet( ac.getFlagsRef(idx), data::particle_flags::GHOST))
+         {
+            ++numGhostParticles;
+         } else
+         {
+            ++numParticles;
+         }
+      },
+      accessor);
+      walberla::mpi::reduceInplace(numParticles, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(numGhostParticles, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(contactsChecked, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(contactsDetected, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(contactsTreated, walberla::mpi::SUM);
+      double linkedCellsVolume = lc.domain_.volume();
+      walberla::mpi::reduceInplace(linkedCellsVolume, walberla::mpi::SUM);
+      size_t numLinkedCells = lc.cells_.size();
+      walberla::mpi::reduceInplace(numLinkedCells, walberla::mpi::SUM);
+      size_t local_aabbs         = domain.getNumLocalAABBs();
+      size_t neighbor_subdomains = domain.getNumNeighborSubdomains();
+      size_t neighbor_processes  = domain.getNumNeighborProcesses();
+      walberla::mpi::reduceInplace(local_aabbs, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(neighbor_subdomains, walberla::mpi::SUM);
+      walberla::mpi::reduceInplace(neighbor_processes, walberla::mpi::SUM);
+      WALBERLA_ROOT_SECTION()
+      {
+         std::map< std::string, walberla::int64_t > integerProperties;
+         std::map< std::string, double >            realProperties;
+         std::map< std::string, std::string >       stringProperties;
+
+         stringProperties["walberla_git"]         = WALBERLA_GIT_SHA1;
+         stringProperties["tag"]                  = "mesa_pd";
+         stringProperties["host"]                 = host;
+         integerProperties["jobid"]               = jobid;
+         integerProperties["mpi_num_processes"]   = mpiManager->numProcesses();
+         integerProperties["omp_max_threads"]     = omp_get_max_threads();
+         integerProperties["numOuterIterations"]  = numOuterIterations;
+         integerProperties["simulationSteps"]     = simulationSteps;
+         integerProperties["bBarrier"]            = int64_c(bBarrier);
+         integerProperties["num_particles"]       = numParticles;
+         integerProperties["num_ghost_particles"] = numGhostParticles;
+         integerProperties["contacts_checked"]    = contactsChecked;
+         integerProperties["contacts_detected"]   = contactsDetected;
+         integerProperties["contacts_treated"]    = contactsTreated;
+         integerProperties["blocks_x"]            = int64_c(forest->getXSize());
+         integerProperties["blocks_y"]            = int64_c(forest->getXSize());
+         integerProperties["blocks_z"]            = int64_c(forest->getXSize());
+         realProperties["domain_x"]               = double_c(forest->getDomain().xSize());
+         realProperties["domain_y"]               = double_c(forest->getDomain().ySize());
+         realProperties["domain_z"]               = double_c(forest->getDomain().zSize());
+         integerProperties["local_aabbs"]         = int64_c(local_aabbs);
+         integerProperties["neighbor_subdomains"] = int64_c(neighbor_subdomains);
+         integerProperties["neighbor_processes"]  = int64_c(neighbor_processes);
+         integerProperties["SNNBytesSent"]        = SNNBytesSent;
+         integerProperties["SNNBytesReceived"]    = SNNBytesReceived;
+         integerProperties["SNNSends"]            = SNNSends;
+         integerProperties["SNNReceives"]         = SNNReceives;
+         integerProperties["RPBytesSent"]         = RPBytesSent;
+         integerProperties["RPBytesReceived"]     = RPBytesReceived;
+         integerProperties["RPSends"]             = RPSends;
+         integerProperties["RPReceives"]          = RPReceives;
+         realProperties["linkedCellsVolume"]      = linkedCellsVolume;
+         integerProperties["numLinkedCells"]      = int64_c(numLinkedCells);
+         stringProperties["SLURM_CLUSTER_NAME"]       = envToString(std::getenv( "SLURM_CLUSTER_NAME" ));
+         stringProperties["SLURM_CPUS_ON_NODE"]       = envToString(std::getenv( "SLURM_CPUS_ON_NODE" ));
+         stringProperties["SLURM_CPUS_PER_TASK"]      = envToString(std::getenv( "SLURM_CPUS_PER_TASK" ));
+         stringProperties["SLURM_JOB_ACCOUNT"]        = envToString(std::getenv( "SLURM_JOB_ACCOUNT" ));
+         stringProperties["SLURM_JOB_ID"]             = envToString(std::getenv( "SLURM_JOB_ID" ));
+         stringProperties["SLURM_JOB_CPUS_PER_NODE"]  = envToString(std::getenv( "SLURM_JOB_CPUS_PER_NODE" ));
+         stringProperties["SLURM_JOB_NAME"]           = envToString(std::getenv( "SLURM_JOB_NAME" ));
+         stringProperties["SLURM_JOB_NUM_NODES"]      = envToString(std::getenv( "SLURM_JOB_NUM_NODES" ));
+         stringProperties["SLURM_NTASKS"]             = envToString(std::getenv( "SLURM_NTASKS" ));
+         stringProperties["SLURM_NTASKS_PER_CORE"]    = envToString(std::getenv( "SLURM_NTASKS_PER_CORE" ));
+         stringProperties["SLURM_NTASKS_PER_NODE"]    = envToString(std::getenv( "SLURM_NTASKS_PER_NODE" ));
+         stringProperties["SLURM_NTASKS_PER_SOCKET"]  = envToString(std::getenv( "SLURM_NTASKS_PER_SOCKET" ));
+         stringProperties["SLURM_TASKS_PER_NODE"]     = envToString(std::getenv( "SLURM_TASKS_PER_NODE" ));
+
+
+         auto runId = postprocessing::storeRunInSqliteDB( sqlFile, integerProperties, stringProperties, realProperties );
+         postprocessing::storeTimingPoolInSqliteDB( sqlFile, runId, *tp_reduced, "Timeloop" );
+      }
+      WALBERLA_LOG_INFO_ON_ROOT("*** SQL OUTPUT - END ***");
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace mesa_pd
+} // namespace walberla
+
+int main( int argc, char* argv[] )
+{
+   return walberla::mesa_pd::main( argc, argv );
+}
diff --git a/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas.cfg b/apps/benchmarks/GranularGas/PE_Benchmark.cfg
similarity index 95%
rename from apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas.cfg
rename to apps/benchmarks/GranularGas/PE_Benchmark.cfg
index e281e9b0e..7d23638c7 100644
--- a/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas.cfg
+++ b/apps/benchmarks/GranularGas/PE_Benchmark.cfg
@@ -1,5 +1,5 @@
 
-PeriodicGranularGas
+GranularGas
 {
    simulationCorner < 0, 0, 0 >;
    simulationDomain < 30, 30, 30 >;
diff --git a/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas.cpp b/apps/benchmarks/GranularGas/PE_GranularGas.cpp
similarity index 57%
rename from apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas.cpp
rename to apps/benchmarks/GranularGas/PE_GranularGas.cpp
index 1dfc02fd2..6178e9ee9 100644
--- a/apps/benchmarks/PeriodicGranularGas/PeriodicGranularGas.cpp
+++ b/apps/benchmarks/GranularGas/PE_GranularGas.cpp
@@ -13,7 +13,7 @@
 //  You should have received a copy of the GNU General Public License along
 //  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
 //
-//! \file   PeriodicGranularGas.cpp
+//! \file   PE_GranularGas.cpp
 //! \author Sebastian Eibl <sebastian.eibl@fau.de>
 //
 //======================================================================================================================
@@ -27,8 +27,10 @@
 #include <core/math/Random.h>
 #include <core/grid_generator/SCIterator.h>
 #include <core/logging/Logging.h>
+#include <core/OpenMP.h>
 #include <core/timing/TimingTree.h>
 #include <core/waLBerlaBuildInfo.h>
+#include <postprocessing/sqlite/SQLite.h>
 #include <vtk/VTKOutput.h>
 
 #include <functional>
@@ -39,7 +41,7 @@ namespace walberla {
 using namespace walberla::pe;
 using namespace walberla::timing;
 
-using BodyTuple = std::tuple<Sphere> ;
+using BodyTuple = std::tuple<Sphere, Plane> ;
 
 int main( int argc, char ** argv )
 {
@@ -83,9 +85,27 @@ int main( int argc, char ** argv )
    WALBERLA_LOG_INFO_ON_ROOT("*** READING CONFIG FILE ***");
    auto cfg = env.config();
    if (cfg == nullptr) WALBERLA_ABORT("No config specified!");
-   const Config::BlockHandle mainConf  = cfg->getBlock( "PeriodicGranularGas" );
+   const Config::BlockHandle mainConf  = cfg->getBlock( "GranularGas" );
 
-   int simulationSteps = mainConf.getParameter<int>("simulationSteps", 10 );
+   const std::string host = mainConf.getParameter<std::string>("host", "none" );
+   WALBERLA_LOG_INFO_ON_ROOT("host: " << host);
+
+   const int jobid = mainConf.getParameter<int>("jobid", 0 );
+   WALBERLA_LOG_INFO_ON_ROOT("jobid: " << jobid);
+
+   const real_t spacing = mainConf.getParameter<real_t>("spacing", real_t(1.0) );
+   WALBERLA_LOG_INFO_ON_ROOT("spacing: " << spacing);
+
+   const real_t radius = mainConf.getParameter<real_t>("radius", real_t(0.5) );
+   WALBERLA_LOG_INFO_ON_ROOT("radius: " << radius);
+
+   bool bBarrier = false;
+   WALBERLA_LOG_INFO_ON_ROOT("bBarrier: " << bBarrier);
+
+   int64_t numOuterIterations = mainConf.getParameter<int64_t>("numOuterIterations", 10 );
+   WALBERLA_LOG_INFO_ON_ROOT("numOuterIterations: " << numOuterIterations);
+
+   int64_t simulationSteps = mainConf.getParameter<int64_t>("simulationSteps", 10 );
    WALBERLA_LOG_INFO_ON_ROOT("simulationSteps: " << simulationSteps);
 
    real_t dt = mainConf.getParameter<real_t>("dt", real_c(0.01) );
@@ -96,6 +116,9 @@ int main( int argc, char ** argv )
    const std::string path = mainConf.getParameter<std::string>("path",  "vtk_out" );
    WALBERLA_LOG_INFO_ON_ROOT("path: " << path);
 
+   const std::string sqlFile = mainConf.getParameter<std::string>("sqlFile",  "benchmark.sqlite" );
+   WALBERLA_LOG_INFO_ON_ROOT("sqlFile: " << sqlFile);
+
    WALBERLA_LOG_INFO_ON_ROOT("*** GLOBALBODYSTORAGE ***");
    shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
 
@@ -182,16 +205,13 @@ int main( int argc, char ** argv )
    auto vtkSphereOutput = vtk::createVTKOutput_PointData(vtkSphereHelper, "Bodies", 1, "vtk_out", "simulation_step", false, false);
 
    WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - START ***");
-   const real_t   static_cof  ( real_c(0.1) / 2 );   // Coefficient of static friction. Note: pe doubles the input coefficient of friction for material-material contacts.
-   const real_t   dynamic_cof ( static_cof ); // Coefficient of dynamic friction. Similar to static friction for low speed friction.
-   MaterialID     material = createMaterial( "granular", real_t( 1.0 ), 0, static_cof, dynamic_cof, real_t( 0.5 ), 1, 1, 0, 0 );
+   //const real_t   static_cof  ( real_c(0.1) / 2 );   // Coefficient of static friction. Note: pe doubles the input coefficient of friction for material-material contacts.
+   //const real_t   dynamic_cof ( static_cof ); // Coefficient of dynamic friction. Similar to static friction for low speed friction.
+   MaterialID     material = createMaterial( "granular", real_t( 1.0 ), 0, 0, 0, real_t( 0.5 ), 1, real_t(1e-6), 0, 0 );
 
    auto simulationDomain = forest->getDomain();
    const auto& generationDomain = simulationDomain; // simulationDomain.getExtended(-real_c(0.5) * spacing);
-
-   const real_t spacing(1.0);
-   const real_t radius(0.5);
-   uint_t numParticles = uint_c(0);
+   int64_t numParticles = 0;
 
    for (auto& currentBlock : *forest)
    {
@@ -204,6 +224,22 @@ int main( int argc, char ** argv )
    mpi::reduceInplace(numParticles, mpi::SUM);
    WALBERLA_LOG_INFO_ON_ROOT("#particles created: " << numParticles);
 
+   if (!forest->isPeriodic(0))
+   {
+      createPlane(*globalBodyStorage, 0, Vec3(+1,0,0), forest->getDomain().minCorner());
+      createPlane(*globalBodyStorage, 0, Vec3(-1,0,0), forest->getDomain().maxCorner());
+   }
+   if (!forest->isPeriodic(1))
+   {
+      createPlane(*globalBodyStorage, 0, Vec3(0,+1,0), forest->getDomain().minCorner());
+      createPlane(*globalBodyStorage, 0, Vec3(0,-1,0), forest->getDomain().maxCorner());
+   }
+   if (!forest->isPeriodic(2))
+   {
+      createPlane(*globalBodyStorage, 0, Vec3(0,0,+1), forest->getDomain().minCorner());
+      createPlane(*globalBodyStorage, 0, Vec3(0,0,-1), forest->getDomain().maxCorner());
+   }
+
    WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - END ***");
 
    // synchronize particles
@@ -211,51 +247,103 @@ int main( int argc, char ** argv )
    syncCallWithoutTT();
 
    WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - START ***");
-   WALBERLA_MPI_BARRIER();
-   WcTimer timer;
-   for (int i=0; i < simulationSteps; ++i)
+   for (int64_t outerIteration = 0; outerIteration < numOuterIterations; ++outerIteration)
    {
-      if( i % 200 == 0 )
+      WALBERLA_LOG_INFO_ON_ROOT("*** RUNNING OUTER ITERATION " << outerIteration << " ***");
+      WALBERLA_MPI_BARRIER();
+      WcTimer timer;
+      WcTimingPool tp;
+      for (int64_t i=0; i < simulationSteps; ++i)
       {
-         WALBERLA_LOG_DEVEL_ON_ROOT( "Timestep " << i << " / " << simulationSteps );
+         if( i % 200 == 0 )
+         {
+            WALBERLA_LOG_DEVEL_ON_ROOT( "Timestep " << i << " / " << simulationSteps );
+         }
+
+         tp["CR"].start();
+         cr->timestep( real_c(dt) );
+         tp["CR"].end();
+         tp["Sync"].start();
+         syncCallWithoutTT();
+         tp["Sync"].end();
+
+         //if( i % visSpacing == 0 )
+         //{
+         //   vtkDomainOutput->write( );
+         //   vtkSphereOutput->write( );
+         //}
+      }
+      WALBERLA_MPI_BARRIER();
+      timer.end();
+      WALBERLA_LOG_INFO_ON_ROOT("runtime: " << timer.average());
+      auto PUpS = real_c(numParticles) * real_c(simulationSteps) / timer.average();
+      WALBERLA_LOG_INFO_ON_ROOT("PUpS: " << PUpS);
+      auto tp_reduced = tp.getReduced();
+      WALBERLA_LOG_INFO_ON_ROOT(*tp_reduced);
+      WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - END ***");
+
+      auto temp = tt.getReduced( );
+      WALBERLA_ROOT_SECTION()
+      {
+         std::cout << temp;
       }
 
-      cr->timestep( real_c(dt) );
-      syncCallWithoutTT();
-
-//      if( i % visSpacing == 0 )
-//      {
-//         vtkDomainOutput->write( );
-//         vtkSphereOutput->write( );
-//      }
-   }
-   WALBERLA_MPI_BARRIER();
-   timer.end();
-   WALBERLA_LOG_INFO_ON_ROOT("runtime: " << timer.average());
-   WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - END ***");
-
-   auto temp = tt.getReduced( );
-   WALBERLA_ROOT_SECTION()
-   {
-      std::cout << temp;
-   }
-
-   WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING RESULT - START ***");
-   for (auto& currentBlock : *forest)
-   {
-      Storage * storage = currentBlock.getData< Storage >( storageID );
-      BodyStorage& localStorage = (*storage)[0];
+      WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING RESULT - START ***");
+      numParticles = 0;
+      int64_t numGhostParticles = 0;
+      for (auto& currentBlock : *forest)
+      {
+         Storage * storage = currentBlock.getData< Storage >( storageID );
+         BodyStorage& localStorage = (*storage)[0];
+         BodyStorage& shadowStorage = (*storage)[1];
+         numParticles += localStorage.size();
+         numGhostParticles += shadowStorage.size();
+
+         auto bodyIt = localStorage.begin();
+         for (auto it = grid_generator::SCIterator(currentBlock.getAABB().getIntersection(generationDomain), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing);
+              it != grid_generator::SCIterator();
+              ++it, ++bodyIt)
+         {
+            WALBERLA_CHECK_UNEQUAL(bodyIt, localStorage.end());
+            WALBERLA_CHECK_FLOAT_EQUAL(bodyIt->getPosition(), *it);
+         }
+      }
+      mpi::reduceInplace(numParticles, mpi::SUM);
+      mpi::reduceInplace(numGhostParticles, mpi::SUM);
+      WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING RESULT - END ***");
 
-      auto bodyIt = localStorage.begin();
-      for (auto it = grid_generator::SCIterator(currentBlock.getAABB().getIntersection(generationDomain), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing);
-           it != grid_generator::SCIterator();
-           ++it, ++bodyIt)
+      WALBERLA_ROOT_SECTION()
       {
-         WALBERLA_CHECK_UNEQUAL(bodyIt, localStorage.end());
-         WALBERLA_CHECK_FLOAT_EQUAL(bodyIt->getPosition(), *it);
+         std::map< std::string, walberla::int64_t > integerProperties;
+         std::map< std::string, double >            realProperties;
+         std::map< std::string, std::string >       stringProperties;
+
+         stringProperties["walberla_git"]         = WALBERLA_GIT_SHA1;
+         stringProperties["tag"]                  = "pe";
+         stringProperties["host"]                 = host;
+         integerProperties["jobid"]               = jobid;
+         integerProperties["bDEM"]                = bDEM;
+         integerProperties["bNN"]                 = bNN;
+         integerProperties["mpi_num_processes"]   = mpi::MPIManager::instance()->numProcesses();
+         integerProperties["omp_max_threads"]     = omp_get_max_threads();
+         integerProperties["numOuterIterations"]  = numOuterIterations;
+         integerProperties["simulationSteps"]     = simulationSteps;
+         integerProperties["bBarrier"]            = int64_c(bBarrier);
+         realProperties["PUpS"]                   = double_c(PUpS);
+         integerProperties["num_particles"]       = numParticles;
+         integerProperties["num_ghost_particles"] = numGhostParticles;
+         integerProperties["blocks_x"]            = int64_c(forest->getXSize());
+         integerProperties["blocks_y"]            = int64_c(forest->getXSize());
+         integerProperties["blocks_z"]            = int64_c(forest->getXSize());
+         realProperties["domain_x"]               = double_c(forest->getDomain().xSize());
+         realProperties["domain_y"]               = double_c(forest->getDomain().ySize());
+         realProperties["domain_z"]               = double_c(forest->getDomain().zSize());
+
+         auto runId = postprocessing::storeRunInSqliteDB( sqlFile, integerProperties, stringProperties, realProperties );
+         postprocessing::storeTimingPoolInSqliteDB( sqlFile, runId, *tp_reduced, "Timeloop" );
       }
+      WALBERLA_LOG_INFO_ON_ROOT("*** SQL OUTPUT - END ***");
    }
-   WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING RESULT - END ***");
 
    return EXIT_SUCCESS;
 }
@@ -263,5 +351,5 @@ int main( int argc, char ** argv )
 
 int main( int argc, char* argv[] )
 {
-  return walberla::main( argc, argv );
+   return walberla::main( argc, argv );
 }
diff --git a/apps/benchmarks/GranularGas/upload.py b/apps/benchmarks/GranularGas/upload.py
new file mode 100644
index 000000000..7d1b1badd
--- /dev/null
+++ b/apps/benchmarks/GranularGas/upload.py
@@ -0,0 +1,56 @@
+import os
+import time
+import math
+import random
+import re
+from influxdb import InfluxDBClient
+from git import Repo
+
+
+class Upload:
+   def __init__(self):
+      try:
+         self.write_user_pw = os.environ["INFLUXDB_WRITE_USER"]
+      except KeyError:
+         import sys
+         print('Password for the InfluxDB write_user was not set.\n',
+               'See https://docs.gitlab.com/ee/ci/variables/#secret-variables', file=sys.stderr)
+         exc_info = sys.exc_info()
+         raise exc_info[0].with_traceback(exc_info[1], exc_info[2])
+
+      self.client = InfluxDBClient('i10grafana.informatik.uni-erlangen.de', 8086,
+                                   'pe', self.write_user_pw, 'pe')
+
+   def process(self, filename, model, friction, sync, parallelization):
+      with open(filename) as f:
+          s = f.read()
+      m = re.search('PUpS: (\S*)', s)
+
+      json_body = [
+          {
+              'measurement': 'pe_benchmark',
+              'tags': {
+                  'host'            : os.uname()[1],
+                  'image'           : os.environ["DOCKER_IMAGE_NAME"],
+                  'model'           : model,
+                  'friction'        : friction,
+                  'sync'            : sync,
+                  'parallelization' : parallelization
+              },
+              'time': int(time.time()),
+              'fields': {'PUpS': float(m.group(1))}
+          }
+      ]
+      print(float(m.group(1)))
+      self.client.write_points(json_body, time_precision='s')
+
+if __name__ == "__main__":
+   up = Upload()
+   up.process("GranularGas_DEM_NN.txt", "DEM", "Coulomb", "next neighbors", "8P1T")
+   up.process("GranularGas_DEM_SO.txt", "DEM", "Coulomb", "shadow owners", "8P1T")
+   up.process("GranularGas_HCSITS_NN_IFC.txt", "HCSITS", "InelasticFrictionlessContact", "next neighbors", "8P1T")
+   up.process("GranularGas_HCSITS_NN_AICCBD.txt", "HCSITS", "ApproximateInelasticCoulombContactByDecoupling", "next neighbors", "8P1T")
+   up.process("GranularGas_HCSITS_NN_ICCBD.txt", "HCSITS", "InelasticCoulombContactByDecoupling", "next neighbors", "8P1T")
+   up.process("GranularGas_HCSITS_NN_IGMDC.txt", "HCSITS", "InelasticGeneralizedMaximumDissipationContact", "next neighbors", "8P1T")
+   up.process("GranularGas_HCSITS_SO_IFC.txt", "HCSITS", "InelasticFrictionlessContact", "shadow owners", "8P1T")
+
diff --git a/apps/benchmarks/PeriodicGranularGas/CMakeLists.txt b/apps/benchmarks/LennardJones/CMakeLists.txt
similarity index 56%
rename from apps/benchmarks/PeriodicGranularGas/CMakeLists.txt
rename to apps/benchmarks/LennardJones/CMakeLists.txt
index d26f6314f..f0438a663 100644
--- a/apps/benchmarks/PeriodicGranularGas/CMakeLists.txt
+++ b/apps/benchmarks/LennardJones/CMakeLists.txt
@@ -1,6 +1,6 @@
 waLBerla_link_files_to_builddir( *.cfg )
 waLBerla_link_files_to_builddir( *.py )
 
-waLBerla_add_executable ( NAME PeriodicGranularGas
-                          FILES PeriodicGranularGas.cpp
+waLBerla_add_executable ( NAME LennardJones
+                          FILES LennardJones.cpp
                           DEPENDS blockforest core pe )
diff --git a/apps/benchmarks/LennardJones/LennardJones.cpp b/apps/benchmarks/LennardJones/LennardJones.cpp
new file mode 100644
index 000000000..756a5cbc0
--- /dev/null
+++ b/apps/benchmarks/LennardJones/LennardJones.cpp
@@ -0,0 +1,207 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   LennardJones.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <pe/basic.h>
+#include <pe/vtk/SphereVtkOutput.h>
+
+#include <blockforest/Initialization.h>
+#include <core/Abort.h>
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/math/Random.h>
+#include <core/waLBerlaBuildInfo.h>
+#include <vtk/VTKOutput.h>
+
+#include <functional>
+#include <memory>
+#include <tuple>
+
+namespace walberla {
+using namespace walberla::pe;
+using namespace walberla::timing;
+
+using BodyTuple = std::tuple<Sphere> ;
+
+inline
+void LJ(RigidBody& bd1, RigidBody& bd2)
+{
+   if (bd1.getSystemID() != bd2.getSystemID())
+   {
+      Vec3 dir = bd1.getPosition() - bd2.getPosition();
+      const real_t inv_d = real_t(1) / length(dir);
+      dir *= inv_d;
+      WALBERLA_ASSERT_FLOAT_EQUAL(length(dir),
+                                  real_t(1),
+                                  "direction not normalized: " << dir);
+      const real_t sr = real_t(1.21) * inv_d;
+      const real_t sr6 = (sr*sr*sr)*(sr*sr*sr);
+      const real_t force = real_t(4) * real_t(2.12) * sr6 * ( sr6 - 1 );
+      bd1.addForce(force * dir);
+   }
+}
+
+inline
+void integrate(RigidBody& bd)
+{
+      bd.setLinearVel( bd.getForce() * real_t(0.01) + bd.getLinearVel() );
+      bd.setPosition( bd.getLinearVel() * real_t(0.01) + bd.getPosition() );
+      bd.setForce( Vec3( real_t(0) ) );
+}
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+
+   logging::Logging::instance()->setStreamLogLevel(logging::Logging::INFO);
+   logging::Logging::instance()->setFileLogLevel(logging::Logging::INFO);
+
+   WALBERLA_LOG_INFO_ON_ROOT( "config file: " << argv[1] )
+         WALBERLA_LOG_INFO_ON_ROOT( "waLBerla Revision: " << WALBERLA_GIT_SHA1 );
+
+   math::seedRandomGenerator( static_cast<unsigned int>(1337 * mpi::MPIManager::instance()->worldRank()) );
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** GLOBALBODYSTORAGE ***");
+   shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** BLOCKFOREST ***");
+   //domain setup
+   const real_t spacing = real_t(1.0);
+   math::AABB domain( Vec3(real_t(-0.5), real_t(-0.5), real_t(-0.5)),
+                      Vec3(real_t(+0.5), real_t(+0.5), real_t(+0.5)));
+   domain.scale(real_t(20));
+
+   // create forest
+   auto forest = blockforest::createBlockForest(domain,
+                                                Vector3<uint_t>(1,1,1),
+                                                Vector3<bool>(false, false, false));
+   if (!forest)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT( "No BlockForest created ... exiting!");
+      return EXIT_SUCCESS;
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("simulationDomain: " << forest->getDomain());
+
+   WALBERLA_LOG_INFO_ON_ROOT("blocks: " << Vector3<uint_t>(forest->getXSize(), forest->getYSize(), forest->getZSize()) );
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** BODYTUPLE ***");
+   // initialize body type ids
+   SetBodyTypeIDs<BodyTuple>::execute();
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** STORAGEDATAHANDLING ***");
+   // add block data
+   auto storageID           = forest->addBlockData(createStorageDataHandling<BodyTuple>(), "Storage");
+   auto ccdID               = forest->addBlockData(ccd::createHashGridsDataHandling( globalBodyStorage, storageID ), "CCD");
+   auto fcdID               = forest->addBlockData(fcd::createGenericFCDDataHandling<BodyTuple, fcd::AnalyticCollideFunctor>(), "FCD");
+   WALBERLA_UNUSED(ccdID);
+   WALBERLA_UNUSED(fcdID);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SYNCCALL ***");
+   std::function<void(void)> syncCallWithoutTT;
+   syncCallWithoutTT = std::bind( pe::syncNextNeighbors<BodyTuple>, std::ref(*forest), storageID, static_cast<WcTimingTree*>(nullptr), real_c(0.1), false );
+   WALBERLA_LOG_INFO_ON_ROOT("Using NextNeighbor sync!");
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** VTK ***");
+   auto vtkDomainOutput = vtk::createVTKOutput_DomainDecomposition( forest, "domain_decomposition", 1, "vtk_out", "simulation_step" );
+   auto vtkSphereHelper = make_shared<SphereVtkOutput>(storageID, *forest) ;
+   auto vtkSphereOutput = vtk::createVTKOutput_PointData(vtkSphereHelper, "Bodies", 1, "vtk_out", "simulation_step", false, false);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - START ***");
+   auto simulationDomain = forest->getDomain();
+   const auto& generationDomain = simulationDomain; // simulationDomain.getExtended(-real_c(0.5) * spacing);
+
+   //initialize particles
+   uint_t numParticles(0);
+   for (auto it = grid_generator::SCIterator(generationDomain, Vec3(spacing, spacing, spacing) * real_c(0.5), spacing);
+        it != grid_generator::SCIterator();
+        ++it)
+   {
+      SphereID sp = createSphere(*globalBodyStorage, *forest, storageID, 0, *it, real_t(1));
+
+      if (sp!=nullptr)
+      {
+         sp->setLinearVel( math::realRandom(real_t(-1), real_t(+1)),
+                           math::realRandom(real_t(-1), real_t(+1)),
+                           math::realRandom(real_t(-1), real_t(+1)));
+         ++numParticles;
+      }
+   }
+   mpi::reduceInplace(numParticles, mpi::SUM);
+   WALBERLA_LOG_INFO_ON_ROOT("#particles created: " << numParticles);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - END ***");
+
+   // synchronize particles
+   //syncCallWithoutTT();
+   //syncCallWithoutTT();
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - START ***");
+   WALBERLA_MPI_BARRIER();
+   WcTimer timer;
+   for (int i=0; i < 1000; ++i)
+   {
+      WALBERLA_LOG_DEVEL(i);
+
+      for (auto& blk : *forest)
+      {
+         Storage* storage = blk.getData< Storage >( storageID );
+         BodyStorage& localStorage = (*storage)[0];
+
+         for (auto& bd1 : localStorage)
+         {
+            for (auto& bd2 : localStorage)
+            {
+               LJ(bd1, bd2);
+            }
+         }
+         const real_t coeff = real_t(0.2);
+         for (auto& bd : localStorage)
+         {
+            bd.addForce( -coeff*bd.getPosition() );
+         }
+      }
+
+      for (auto& blk : *forest)
+      {
+         Storage* storage = blk.getData< Storage >( storageID );
+         BodyStorage& localStorage = (*storage)[0];
+
+         for (auto& bd : localStorage)
+         {
+            integrate(bd);
+         }
+      }
+
+      vtkSphereOutput->write( );
+      //syncCallWithoutTT();
+   }
+   WALBERLA_MPI_BARRIER();
+   timer.end();
+   WALBERLA_LOG_INFO_ON_ROOT("runtime: " << timer.average());
+   WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - END ***");
+   return EXIT_SUCCESS;
+}
+} // namespace walberla
+
+int main( int argc, char* argv[] )
+{
+   return walberla::main( argc, argv );
+}
diff --git a/apps/benchmarks/PeriodicGranularGas/upload.py b/apps/benchmarks/PeriodicGranularGas/upload.py
deleted file mode 100644
index 799476c1d..000000000
--- a/apps/benchmarks/PeriodicGranularGas/upload.py
+++ /dev/null
@@ -1,187 +0,0 @@
-import os
-import time
-import math
-import random
-import re
-from influxdb import InfluxDBClient
-from git import Repo
-
-
-def main():
-    try:
-        write_user_pw = os.environ["INFLUXDB_WRITE_USER"]
-    except KeyError:
-        import sys
-        print('Password for the InfluxDB write_user was not set.\n',
-              'See https://docs.gitlab.com/ee/ci/variables/#secret-variables', file=sys.stderr)
-        exc_info = sys.exc_info()
-        raise exc_info[0].with_traceback(exc_info[1], exc_info[2])
-
-    client = InfluxDBClient('i10grafana.informatik.uni-erlangen.de', 8086,
-                            'pe', write_user_pw, 'pe')
-
-    #repo = Repo(search_parent_directories=True)
-    #commit = repo.head.commit
-
-    with open("PeriodicGranularGas_DEM_NN.txt") as f:
-        s = f.read()
-    m = re.search('runtime: (\d*.\d*)', s)
-
-    json_body = [
-        {
-            'measurement': 'pe_benchmark',
-            'tags': {
-                'host'    : os.uname()[1],
-                'image'   : os.environ["DOCKER_IMAGE_NAME"],
-                'model'   : 'DEM',
-                'friction': 'Coulomb',
-                'sync'    : 'next neighbor'
-            },
-            'time': int(time.time()),
-            'fields': {'runtime': float(m.group(1))}
-        }
-    ]
-    print(float(m.group(1)))
-    client.write_points(json_body, time_precision='s')
-
-    #*************************************************
-
-    with open("PeriodicGranularGas_DEM_SO.txt") as f:
-        s = f.read()
-    m = re.search('runtime: (\d*.\d*)', s)
-
-    json_body = [
-        {
-            'measurement': 'pe_benchmark',
-            'tags': {
-                'host'    : os.uname()[1],
-                'image'   : os.environ["DOCKER_IMAGE_NAME"],
-                'model'   : 'DEM',
-                'friction': 'Coulomb',
-                'sync'    : 'shadow owner'
-            },
-            'time': int(time.time()),
-            'fields': {'runtime': float(m.group(1))}
-        }
-    ]
-    print(float(m.group(1)))
-    client.write_points(json_body, time_precision='s')
-
-    #*************************************************
-
-    with open("PeriodicGranularGas_HCSITS_NN_IFC.txt") as f:
-        s = f.read()
-    m = re.search('runtime: (\d*.\d*)', s)
-
-    json_body = [
-        {
-            'measurement': 'pe_benchmark',
-            'tags': {
-                'host'    : os.uname()[1],
-                'image'   : os.environ["DOCKER_IMAGE_NAME"],
-                'model'   : 'HCSITS',
-                'friction': 'InelasticFrictionlessContact',
-                'sync'    : 'next neighbor'
-            },
-            'time': int(time.time()),
-            'fields': {'runtime': float(m.group(1))}
-        }
-    ]
-    print(float(m.group(1)))
-    client.write_points(json_body, time_precision='s')
-
-    #*************************************************
-
-    with open("PeriodicGranularGas_HCSITS_NN_AICCBD.txt") as f:
-        s = f.read()
-    m = re.search('runtime: (\d*.\d*)', s)
-
-    json_body = [
-        {
-            'measurement': 'pe_benchmark',
-            'tags': {
-                'host'    : os.uname()[1],
-                'image'   : os.environ["DOCKER_IMAGE_NAME"],
-                'model'   : 'HCSITS',
-                'friction': 'ApproximateInelasticCoulombContactByDecoupling',
-                'sync'    : 'next neighbor'
-            },
-            'time': int(time.time()),
-            'fields': {'runtime': float(m.group(1))}
-        }
-    ]
-    print(float(m.group(1)))
-    client.write_points(json_body, time_precision='s')
-
-    #*************************************************
-
-    with open("PeriodicGranularGas_HCSITS_NN_ICCBD.txt") as f:
-        s = f.read()
-    m = re.search('runtime: (\d*.\d*)', s)
-
-    json_body = [
-        {
-            'measurement': 'pe_benchmark',
-            'tags': {
-                'host'    : os.uname()[1],
-                'image'   : os.environ["DOCKER_IMAGE_NAME"],
-                'model'   : 'HCSITS',
-                'friction': 'InelasticCoulombContactByDecoupling',
-                'sync'    : 'next neighbor'
-            },
-            'time': int(time.time()),
-            'fields': {'runtime': float(m.group(1))}
-        }
-    ]
-    print(float(m.group(1)))
-    client.write_points(json_body, time_precision='s')
-
-    #*************************************************
-
-    with open("PeriodicGranularGas_HCSITS_NN_IGMDC.txt") as f:
-        s = f.read()
-    m = re.search('runtime: (\d*.\d*)', s)
-
-    json_body = [
-        {
-            'measurement': 'pe_benchmark',
-            'tags': {
-                'host'    : os.uname()[1],
-                'image'   : os.environ["DOCKER_IMAGE_NAME"],
-                'model'   : 'HCSITS',
-                'friction': 'InelasticGeneralizedMaximumDissipationContact',
-                'sync'    : 'next neighbor'
-            },
-            'time': int(time.time()),
-            'fields': {'runtime': float(m.group(1))}
-        }
-    ]
-    print(float(m.group(1)))
-    client.write_points(json_body, time_precision='s')
-
-    #*************************************************
-
-    with open("PeriodicGranularGas_HCSITS_SO_IFC.txt") as f:
-        s = f.read()
-    m = re.search('runtime: (\d*.\d*)', s)
-
-    json_body = [
-        {
-            'measurement': 'pe_benchmark',
-            'tags': {
-                'host'    : os.uname()[1],
-                'image'   : os.environ["DOCKER_IMAGE_NAME"],
-                'model'   : 'HCSITS',
-                'friction': 'InelasticFrictionlessContact',
-                'sync'    : 'shadow owner'
-            },
-            'time': int(time.time()),
-            'fields': {'runtime': float(m.group(1))}
-        }
-    ]
-    print(float(m.group(1)))
-    client.write_points(json_body, time_precision='s')
-
-
-if __name__ == "__main__":
-    main()
diff --git a/apps/benchmarks/ProbeVsExtraMessage/ProbeVsExtraMessage.cpp b/apps/benchmarks/ProbeVsExtraMessage/ProbeVsExtraMessage.cpp
index 6fec0642e..7eb39be16 100644
--- a/apps/benchmarks/ProbeVsExtraMessage/ProbeVsExtraMessage.cpp
+++ b/apps/benchmarks/ProbeVsExtraMessage/ProbeVsExtraMessage.cpp
@@ -114,6 +114,11 @@ void communicate( MPIInfo& mpiInfo,
    }
 }
 
+std::string envToString(const char* env)
+{
+   return env != nullptr ? std::string(env) : "";
+}
+
 int main( int argc, char ** argv )
 {
    mpi::Environment mpiEnv( argc, argv );
@@ -196,6 +201,19 @@ int main( int argc, char ** argv )
       integerProperties["iterations"]   = int64_c(iterations);
       integerProperties["messageSize"]  = int64_c(messageSize);
       stringProperties["stencil"]       = stencil;
+      stringProperties["SLURM_CLUSTER_NAME"]       = envToString(std::getenv( "SLURM_CLUSTER_NAME" ));
+      stringProperties["SLURM_CPUS_ON_NODE"]       = envToString(std::getenv( "SLURM_CPUS_ON_NODE" ));
+      stringProperties["SLURM_CPUS_PER_TASK"]      = envToString(std::getenv( "SLURM_CPUS_PER_TASK" ));
+      stringProperties["SLURM_JOB_ACCOUNT"]        = envToString(std::getenv( "SLURM_JOB_ACCOUNT" ));
+      stringProperties["SLURM_JOB_ID"]             = envToString(std::getenv( "SLURM_JOB_ID" ));
+      stringProperties["SLURM_JOB_CPUS_PER_NODE"]  = envToString(std::getenv( "SLURM_JOB_CPUS_PER_NODE" ));
+      stringProperties["SLURM_JOB_NAME"]           = envToString(std::getenv( "SLURM_JOB_NAME" ));
+      stringProperties["SLURM_JOB_NUM_NODES"]      = envToString(std::getenv( "SLURM_JOB_NUM_NODES" ));
+      stringProperties["SLURM_NTASKS"]             = envToString(std::getenv( "SLURM_NTASKS" ));
+      stringProperties["SLURM_NTASKS_PER_CORE"]    = envToString(std::getenv( "SLURM_NTASKS_PER_CORE" ));
+      stringProperties["SLURM_NTASKS_PER_NODE"]    = envToString(std::getenv( "SLURM_NTASKS_PER_NODE" ));
+      stringProperties["SLURM_NTASKS_PER_SOCKET"]  = envToString(std::getenv( "SLURM_NTASKS_PER_SOCKET" ));
+      stringProperties["SLURM_TASKS_PER_NODE"]     = envToString(std::getenv( "SLURM_TASKS_PER_NODE" ));
 
       auto runId = postprocessing::storeRunInSqliteDB( "ProbeVsTwoMessages.sqlite", integerProperties, stringProperties, realProperties );
       postprocessing::storeTimingPoolInSqliteDB( "ProbeVsTwoMessages.sqlite", runId, tp, "Timings" );
diff --git a/apps/tutorials/CMakeLists.txt b/apps/tutorials/CMakeLists.txt
index 1a6e321a6..15689217a 100644
--- a/apps/tutorials/CMakeLists.txt
+++ b/apps/tutorials/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_subdirectory(basics)
 add_subdirectory(cuda)
 add_subdirectory(lbm)
+add_subdirectory(mesa_pd)
 add_subdirectory(pde)
 add_subdirectory(pe)           
diff --git a/apps/tutorials/mesa_pd/01_LennardJones.cpp b/apps/tutorials/mesa_pd/01_LennardJones.cpp
new file mode 100644
index 000000000..eec37960f
--- /dev/null
+++ b/apps/tutorials/mesa_pd/01_LennardJones.cpp
@@ -0,0 +1,105 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   01_LennardJones.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/vtk/ParticleVtkOutput.h>
+
+#include <mesa_pd/data/LinkedCells.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <mesa_pd/kernel/DoubleCast.h>
+#include <mesa_pd/kernel/ExplicitEuler.h>
+#include <mesa_pd/kernel/ForceLJ.h>
+#include <mesa_pd/kernel/InsertParticleIntoLinkedCells.h>
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/kernel/VelocityVerlet.h>
+
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/math/Random.h>
+#include <vtk/VTKOutput.h>
+
+#include <iostream>
+#include <memory>
+
+using namespace walberla;
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   //domain setup
+   const real_t spacing = real_t(1.0);
+   math::AABB domain( Vec3(real_t(-0.5), real_t(-0.5), real_t(-0.5)),
+                      Vec3(real_t(+0.5), real_t(+0.5), real_t(+0.5)));
+   domain.scale(real_t(20));
+
+   //init data structures
+   auto storage = std::make_shared<data::ParticleStorage> (100);
+   data::LinkedCells     linkedCells(domain.getScaled(2), real_t(1));
+   data::ParticleAccessor ac(storage);
+
+   //initialize particles
+   for (auto it = grid_generator::SCIterator(domain, Vec3(spacing, spacing, spacing) * real_c(0.5), spacing);
+        it != grid_generator::SCIterator();
+        ++it)
+   {
+      data::Particle&& p = *storage->create();
+      p.getPositionRef() = (*it);
+
+      p.getLinearVelocityRef() = Vec3(math::realRandom(real_t(-1), real_t(+1)),
+                                      math::realRandom(real_t(-1), real_t(+1)),
+                                      math::realRandom(real_t(-1), real_t(+1)));
+   }
+
+   auto vtkOutput       = make_shared<mesa_pd::vtk::ParticleVtkOutput>(storage) ;
+   auto vtkWriter       = walberla::vtk::createVTKOutput_PointData(vtkOutput, "Bodies", 1, "vtk", "simulation_step", false, false);
+
+   // Init kernels
+   kernel::ForceLJ                        lj(1);
+   lj.setSigma(0,0,real_t(1));
+   lj.setEpsilon(0,0,real_t(1));
+   kernel::InsertParticleIntoLinkedCells  ipilc;
+   kernel::VelocityVerletPreForceUpdate   vvPreForce( real_t(0.01) );
+   kernel::VelocityVerletPostForceUpdate  vvPostForce( real_t(0.01) );
+
+   for (auto timestep = 0; timestep < 1000; ++timestep)
+   {
+      WALBERLA_LOG_DEVEL(timestep);
+      linkedCells.clear();
+      storage->forEachParticle(true, kernel::SelectAll(), ac, ipilc, ac, linkedCells);
+      storage->forEachParticle(true, kernel::SelectAll(), ac, vvPreForce, ac);
+      linkedCells.forEachParticlePairHalf(true, kernel::SelectAll(), ac, lj, ac);
+      const real_t coeff = real_t(0.2);
+      storage->forEachParticle(true,
+                               kernel::SelectAll(),
+                               ac,
+                               [coeff](const size_t idx, auto& access){ access.setForce(idx, -coeff*access.getPosition(idx) + access.getForce(idx)); },
+                               ac);
+      storage->forEachParticle(true, kernel::SelectAll(), ac, vvPostForce, ac);
+      vtkWriter->write();
+   }
+
+   return EXIT_SUCCESS;
+}
diff --git a/apps/tutorials/mesa_pd/CMakeLists.txt b/apps/tutorials/mesa_pd/CMakeLists.txt
new file mode 100644
index 000000000..e74127bea
--- /dev/null
+++ b/apps/tutorials/mesa_pd/CMakeLists.txt
@@ -0,0 +1,5 @@
+waLBerla_link_files_to_builddir( *.cfg )
+
+waLBerla_add_executable ( NAME 01_LennardJones
+                          FILES 01_LennardJones.cpp
+                          DEPENDS core mesa_pd )
diff --git a/apps/tutorials/mesa_pd/GranularGas.cfg b/apps/tutorials/mesa_pd/GranularGas.cfg
new file mode 100644
index 000000000..ce31c4b5c
--- /dev/null
+++ b/apps/tutorials/mesa_pd/GranularGas.cfg
@@ -0,0 +1,14 @@
+GranularGas
+{
+   simulationCorner < 0, 0, 0 >;
+   simulationDomain < 40, 40, 40 >;
+   blocks < 1,1,1 >;
+   isPeriodic < 0, 0, 0 >;
+
+   radius  0.6;
+   spacing 1.0;
+
+   dt                0.0001;
+   simulationSteps   1000;
+   visSpacing         100;
+}
diff --git a/python/mesa_pd.py b/python/mesa_pd.py
new file mode 100755
index 000000000..3f8c8a002
--- /dev/null
+++ b/python/mesa_pd.py
@@ -0,0 +1,101 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+import mesa_pd.data as data
+import mesa_pd.kernel as kernel
+import mesa_pd.mpi as mpi
+
+import argparse
+import numpy as np
+import os
+
+if __name__ == '__main__':
+   parser = argparse.ArgumentParser(description='Generate all necessary files for the waLBerla mesa_pd module.')
+   parser.add_argument('path', help='Where should the files be created?')
+   parser.add_argument("-f", "--force", help="Generate the files even if not inside a waLBerla directory.",
+                       action="store_true")
+   args = parser.parse_args()
+
+   if ((not os.path.isfile(args.path + "/src/walberla.h")) and (not args.force)):
+      raise RuntimeError(args.path + " is not the path to a waLBerla root directory! Specify -f to generate the files anyway.")
+
+   os.makedirs(args.path + "/src/mesa_pd/common", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/data", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/domain", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/kernel", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/mpi/notifications", exist_ok = True)
+   os.makedirs(args.path + "/src/mesa_pd/vtk", exist_ok = True)
+
+   shapes = ["Sphere", "HalfSpace"]
+
+   ps    = data.ParticleStorage()
+   ch    = data.ContactHistory()
+   lc    = data.LinkedCells()
+   ss    = data.ShapeStorage(ps, shapes)
+
+   ps.addProperty("position",         "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ALWAYS")
+   ps.addProperty("linearVelocity",   "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ALWAYS")
+   ps.addProperty("invMass",          "walberla::real_t",        defValue="real_t(1)", syncMode="COPY")
+   ps.addProperty("force",            "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+   ps.addProperty("oldForce",         "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="MIGRATION")
+
+   ps.addProperty("shapeID",          "size_t",                  defValue="",          syncMode="COPY")
+   ps.addProperty("rotation",         "walberla::mesa_pd::Rot3", defValue="",          syncMode="ALWAYS")
+   ps.addProperty("angularVelocity",  "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ALWAYS")
+   ps.addProperty("torque",           "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+   ps.addProperty("oldTorque",        "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="MIGRATION")
+
+   ps.addProperty("type",             "uint_t",                  defValue="0",         syncMode="COPY")
+
+   ps.addProperty("flags",            "walberla::mesa_pd::data::particle_flags::FlagT", defValue="", syncMode="COPY")
+   ps.addProperty("nextParticle",     "int",                     defValue="-1",        syncMode="NEVER")
+
+   ps.addProperty("oldContactHistory", "std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>", defValue="", syncMode="ALWAYS")
+   ps.addProperty("newContactHistory", "std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>", defValue="", syncMode="NEVER")
+
+   ps.addProperty("temperature",      "walberla::real_t", defValue="real_t(0)", syncMode="ALWAYS")
+   ps.addProperty("heatFlux",         "walberla::real_t", defValue="real_t(0)", syncMode="NEVER")
+
+   ch.addProperty("tangentialSpringDisplacement", "walberla::mesa_pd::Vec3", defValue="real_t(0)")
+   ch.addProperty("isSticking",                   "bool",                    defValue="false")
+   ch.addProperty("impactVelocityMagnitude",      "real_t",                  defValue="real_t(0)")
+
+   kernels = []
+   kernels.append( kernel.DoubleCast(shapes) )
+   kernels.append( kernel.ExplicitEuler() )
+   kernels.append( kernel.ExplicitEulerWithShape() )
+   kernels.append( kernel.ForceLJ() )
+   kernels.append( kernel.HeatConduction() )
+   kernels.append( kernel.InsertParticleIntoLinkedCells() )
+   kernels.append( kernel.LinearSpringDashpot() )
+   kernels.append( kernel.NonLinearSpringDashpot() )
+   kernels.append( kernel.SingleCast(shapes) )
+   kernels.append( kernel.SpringDashpot() )
+   kernels.append( kernel.TemperatureIntegration() )
+   kernels.append( kernel.VelocityVerlet() )
+   kernels.append( kernel.VelocityVerletWithShape() )
+
+   ac = Accessor()
+   for k in kernels:
+      ac.mergeRequirements(k.getRequirements())
+   ac.printSummary()
+
+   comm = []
+   comm.append(mpi.BroadcastProperty())
+   comm.append(mpi.ClearNextNeighborSync())
+   comm.append(mpi.ReduceContactHistory())
+   comm.append(mpi.ReduceProperty())
+   comm.append(mpi.SyncNextNeighbors(ps))
+
+
+   ps.generate(args.path + "/src/mesa_pd/")
+   ch.generate(args.path + "/src/mesa_pd/")
+   lc.generate(args.path + "/src/mesa_pd/")
+   ss.generate(args.path + "/src/mesa_pd/")
+
+   for k in kernels:
+      k.generate(args.path + "/src/mesa_pd/")
+
+   for c in comm:
+      c.generate(args.path + "/src/mesa_pd/")
diff --git a/python/mesa_pd/Container.py b/python/mesa_pd/Container.py
new file mode 100644
index 000000000..3acd960e6
--- /dev/null
+++ b/python/mesa_pd/Container.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from .Property import Property, unrollDimension
+from .utility import find, TerminalColor
+
+class Container:
+   def __init__(self):
+      """Base class for a container which manages includes and properties
+      """
+
+      self.properties = []
+      self.includes   = []
+
+   def addProperty(self, name, type, access="grs", defValue="", syncMode = "ALWAYS", dim=1):
+      prop = find( lambda x : x.name == name, self.properties )
+      if (prop == None):
+         #print(TerminalColor.GREEN + "creating particle property: {}".format(name) + TerminalColor.DEFAULT)
+         self.properties.append( Property(name, type, access=access, defValue=defValue, syncMode=syncMode, dim=dim) )
+      else:
+         if not (prop.type == type and prop.name == name and prop.defValue == defValue and prop.dim == dim):
+            raise RuntimeError(TerminalColor.RED + "property definition differs from previous one:\nPREVIOUS {}\nNEW {}".format(prop, Property(name, type, defValue=defValue, syncMode=syncMode, dim=dim)) + TerminalColor.DEFAULT)
+         print(TerminalColor.YELLOW + "reusing particle property: {}".format(name) + TerminalColor.DEFAULT)
+
+   def addInclude(self, include):
+      if (include in self.includes):
+         print(TerminalColor.YELLOW + "reusing particle include: {}".format(include) + TerminalColor.DEFAULT)
+      else:
+         #print(TerminalColor.GREEN + "creating particle include: {}".format(include) + TerminalColor.DEFAULT)
+         self.includes.append(include)
+
+   def unrollDimension(self):
+      self.properties = unrollDimension(self.properties)
diff --git a/python/mesa_pd/Property.py b/python/mesa_pd/Property.py
new file mode 100644
index 000000000..20b3bc8d7
--- /dev/null
+++ b/python/mesa_pd/Property.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+from .utility import TerminalColor
+
+class Property:
+   def __init__(self, name, type, access="grs", defValue="", syncMode = "ALWAYS", dim=1):
+      """Propery of a data strcuture
+
+      Parameters
+      ----------
+      name : str
+         name of the property
+      type : str
+         type of the property
+      access : str
+         'g' for getter (getName)
+         'r' for reference (getNameRef)
+         's' for setter (setName)
+         any combination is possible
+      defValue : str
+         default value the property should be initialized with
+      syncMode : str
+         'NEVER', this property does not have to be synced
+         'COPY', this property must be synced on creation
+         'MIGRATION', this property must be synced when the ownership changes
+         'ALWAYS', this property has to be synced in every iteration
+      dim : int
+         dimensions of the property
+      """
+
+      #sort access specifier and remove duplicates
+      foo = "".join(sorted(access))
+      access = ''.join([foo[i] for i in range(len(foo)-1) if foo[i+1]!= foo[i]]+[foo[-1]])
+
+      for acc in access:
+         if not (acc in ["g","s","r"]):
+            raise RuntimeError("{} is not a valid access specifier in {}".format(acc, access))
+
+      if (not syncMode in ["NEVER", "COPY", "MIGRATION", "ALWAYS"]):
+         raise RuntimeError(TerminalColor.RED + "{} is no valid sync for property: {}".format(syncMode, name) + TerminalColor.DEFAULT)
+
+      if (dim < 1):
+         raise RuntimeError(TerminalColor.RED + "dimension has to be >=1: {}".format(dim) + TerminalColor.DEFAULT)
+
+      self.name     = name
+      self.type     = type
+      self.access   = access
+      self.defValue = defValue
+      self.syncMode = syncMode
+      self.dim      = dim
+
+   def __str__(self):
+      return "name: {}, type: {}, access: {}, defValue: {}, syncMode: {}, dim: {}".format(self.name, self.type, self.access, self.defValue, self.syncMode, self.dim)
+
+   def getAccessFunction(self):
+      """Returns a list of accessor function names
+      """
+
+      funcs = []
+      if 'g' in self.access:
+         funcs.append("get" + self.name.capitalize())
+      if 's' in self.access:
+         funcs.append("set" + self.name.capitalize())
+      if 'r' in self.access:
+         funcs.append("get" + self.name.capitalize() + "Ref")
+      return funcs
+
+def unrollDimension(props):
+   """Unrolls all more dimensional properties into one dimensional properties
+
+   Iterates over all elements. Copies all one dimensional properties.
+   More dimensional properties get split into one dimensional properties with added suffix.
+
+   Parameters
+   ----------
+   props : list
+      list of properties to be unrolled
+
+   Returns
+   -------
+      list of unrolled properties
+   """
+
+   unrolled = []
+   for prop in props:
+      if (prop.dim == 1):
+         unrolled.append(Property(prop.name, prop.type, prop.access, prop.defValue, prop.syncMode, prop.dim))
+      else:
+         for d in range(prop.dim):
+            unrolled.append(Property("{}{}".format(prop.name,d), prop.type, prop.access, prop.defValue, prop.syncMode, 1))
+   return unrolled
diff --git a/python/mesa_pd/__init__.py b/python/mesa_pd/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/python/mesa_pd/accessor/Accessor.py b/python/mesa_pd/accessor/Accessor.py
new file mode 100644
index 000000000..288ff22e4
--- /dev/null
+++ b/python/mesa_pd/accessor/Accessor.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+from ..Property import Property
+from ..utility import find, TerminalColor, generateFile
+
+class Accessor:
+   def __init__(self):
+      self.properties = []
+
+   def require(self, name, type, access):
+      """requires that a certain property is accessible
+
+      Parameters
+      ----------
+      name : str
+         name of the property requested
+      type : str
+         type of the requested property
+      access : str
+         'g' for getter (getName)
+         'r' for reference (getNameRef)
+         's' for setter (setName)
+         any combination is possible
+
+      Example
+      -------
+      require("position", "walberla::mesa_pd::Vec3", "sg")
+      """
+
+      prop = find( lambda x : x.name == name, self.properties )
+      if (prop == None):
+         #print(TerminalColor.GREEN + "[{}] creating particle property: {}".format(info, name) + TerminalColor.DEFAULT)
+         self.properties.append( Property(name, type, access = access) )
+      else:
+         if not (prop.type == type):
+            raise RuntimeError(TerminalColor.RED + "requirement definition differs from previous one:\n{} {}\n{} {}".format(name, type, prop.name, prop.type) + TerminalColor.DEFAULT)
+         foo = "".join(sorted(access + prop.access))
+         prop.access = ''.join([foo[i] for i in range(len(foo)-1) if foo[i+1]!= foo[i]]+[foo[-1]])
+
+
+   def mergeRequirements(self, accessor):
+      for req in accessor.properties:
+         self.require( req.name, req.type, req.access )
+
+   def printSummary(self):
+      print("="*90)
+      print("Requirements for Accessor:")
+      print("")
+      print("{0: <30}{1: <30}{2: <30}".format("Name", "Type", "Access"))
+      print("="*90)
+      for gs in self.properties:
+         print("{0: <30.29}{1: <30.29}{2: <30.29}".format(gs.name, gs.type, gs.access))
+      print("="*90)
diff --git a/python/mesa_pd/accessor/__init__.py b/python/mesa_pd/accessor/__init__.py
new file mode 100644
index 000000000..6e15afb2c
--- /dev/null
+++ b/python/mesa_pd/accessor/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from .Accessor import Accessor
+
+__all__ = ['Accessor']
diff --git a/python/mesa_pd/data/ContactHistory.py b/python/mesa_pd/data/ContactHistory.py
new file mode 100644
index 000000000..444da6bfc
--- /dev/null
+++ b/python/mesa_pd/data/ContactHistory.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import numpy as np
+from ..Container import Container
+from ..utility import generateFile
+
+class ContactHistory(Container):
+   def __init__(self):
+      super().__init__()
+
+   def generate(self, path):
+      self.unrollDimension()
+
+      print("="*90)
+      print("Creating ContactHistory Datastructure:")
+      print("")
+      print("{0: <20}{1: <30}{2: <20}{3: <10}".format("Type", "Name", "Def. Value", "SyncMode"))
+      print("="*90)
+      for prop in self.properties:
+         print("{0: <20.19}{1: <30.29}{2: <20.19}{3: <10.9}".format(prop.type, prop.name, prop.defValue, prop.syncMode))
+      print("="*90)
+
+      context = dict()
+      context["includes"]    = self.includes
+      context["properties"]  = self.properties
+
+      generateFile(path, 'data/ContactHistory.templ.h', context, filename='data/ContactHistory.h')
+      generateFile(path, 'mpi/notifications/ContactHistoryNotification.templ.h', context)
diff --git a/python/mesa_pd/data/LinkedCells.py b/python/mesa_pd/data/LinkedCells.py
new file mode 100644
index 000000000..60ea2ec83
--- /dev/null
+++ b/python/mesa_pd/data/LinkedCells.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+import numpy as np
+from ..utility import generateFile
+
+class LinkedCells:
+   def generate(self, path):
+      generateFile(path, 'data/LinkedCells.templ.h')
diff --git a/python/mesa_pd/data/ParticleStorage.py b/python/mesa_pd/data/ParticleStorage.py
new file mode 100644
index 000000000..e4efc9999
--- /dev/null
+++ b/python/mesa_pd/data/ParticleStorage.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import numpy as np
+from ..Container import Container
+from ..utility import generateFile
+
+class ParticleStorage(Container):
+   def __init__(self):
+      super().__init__()
+
+      self.addInclude("mesa_pd/data/Flags.h")
+
+      self.addProperty("uid",               "walberla::id_t",      defValue = "UniqueID<data::Particle>::invalidID()", syncMode="ALWAYS")
+      self.addProperty("position",          "walberla::mesa_pd::Vec3", defValue = "real_t(0)", syncMode="ALWAYS")
+      self.addProperty("interactionRadius", "walberla::real_t",    defValue = "real_t(0)", syncMode="COPY")
+      self.addProperty("flags",             "walberla::mesa_pd::data::particle_flags::FlagT", defValue = "", syncMode="COPY")
+      self.addProperty("owner",             "int",                 defValue = "-1", syncMode="COPY")
+      self.addProperty("ghostOwners",       "std::vector<int>",    defValue = "", syncMode="MIGRATION")
+
+   def generate(self, path):
+      self.unrollDimension()
+
+      print("="*90)
+      print("Creating Particle Datastructure:")
+      print("")
+      print("{0: <20}{1: <30}{2: <20}{3: <10}".format("Type", "Name", "Def. Value", "SyncMode"))
+      print("="*90)
+      for prop in self.properties:
+         print("{0: <20.19}{1: <30.29}{2: <20.19}{3: <10.9}".format(prop.type, prop.name, prop.defValue, prop.syncMode))
+      print("="*90)
+
+      context = dict()
+      context["includes"]    = self.includes
+      context["properties"]  = self.properties
+
+      generateFile(path, 'data/ParticleStorage.templ.h', context, filename='data/ParticleStorage.h')
+      generateFile(path, 'data/ParticleAccessor.templ.h', context, filename='data/ParticleAccessor.h')
+
+      generateFile(path, 'mpi/notifications/ForceTorqueNotification.templ.h', context)
+      generateFile(path, 'mpi/notifications/HeatFluxNotification.templ.h', context)
+      generateFile(path, 'mpi/notifications/ParseMessage.templ.h', context)
+      generateFile(path, 'mpi/notifications/ParticleCopyNotification.templ.h', context)
+      generateFile(path, 'mpi/notifications/ParticleMigrationNotification.templ.h', context)
+      generateFile(path, 'mpi/notifications/ParticleRemoteMigrationNotification.templ.h', context)
+      generateFile(path, 'mpi/notifications/ParticleRemovalNotification.templ.h', context)
+      generateFile(path, 'mpi/notifications/ParticleUpdateNotification.templ.h', context)
diff --git a/python/mesa_pd/data/ShapeStorage.py b/python/mesa_pd/data/ShapeStorage.py
new file mode 100644
index 000000000..f299b89d3
--- /dev/null
+++ b/python/mesa_pd/data/ShapeStorage.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+import numpy as np
+from ..utility import generateFile
+
+class ShapeStorage:
+   def __init__(self, p, shapes):
+      p.addProperty("shapeID",         "size_t",                  defValue="",          syncMode="COPY")
+      p.addProperty("rotation",        "walberla::mesa_pd::Rot3", defValue="",          syncMode="ALWAYS")
+      p.addProperty("angularVelocity", "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ALWAYS")
+      p.addProperty("torque",          "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+
+      self.shapes          = shapes
+
+   def generate(self, path):
+      context = dict()
+      context["shapes"]          = self.shapes
+
+      generateFile(path, 'data/ShapeStorage.templ.h', context)
diff --git a/python/mesa_pd/data/__init__.py b/python/mesa_pd/data/__init__.py
new file mode 100644
index 000000000..5ded7c99e
--- /dev/null
+++ b/python/mesa_pd/data/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+
+from .ContactHistory import ContactHistory
+from .LinkedCells import LinkedCells
+from .ParticleStorage import ParticleStorage
+from .ShapeStorage import ShapeStorage
+
+__all__ = ['ContactHistory',
+           'GeometryStorage',
+           'LinkedCells',
+           'ParticleStorage']
diff --git a/python/mesa_pd/kernel/DoubleCast.py b/python/mesa_pd/kernel/DoubleCast.py
new file mode 100644
index 000000000..4d132f79a
--- /dev/null
+++ b/python/mesa_pd/kernel/DoubleCast.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class DoubleCast:
+   def __init__(self, shapes):
+      self.shapes = shapes
+      self.accessor = Accessor()
+      self.accessor.require("shape", "BaseShape*", access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["interface"] = self.accessor.properties
+      context["shapes"]    = self.shapes
+      generateFile(path, 'kernel/DoubleCast.templ.h', context)
diff --git a/python/mesa_pd/kernel/ExplicitEuler.py b/python/mesa_pd/kernel/ExplicitEuler.py
new file mode 100644
index 000000000..a610c7c27
--- /dev/null
+++ b/python/mesa_pd/kernel/ExplicitEuler.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class ExplicitEuler:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("position",        "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("linearVelocity",  "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("invMass",         "walberla::real_t",        access="g" )
+      self.accessor.require("force",           "walberla::mesa_pd::Vec3", access="gs" )
+      self.accessor.require("flags",           "walberla::mesa_pd::data::particle_flags::FlagT", access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["interface"] = self.accessor.properties
+      generateFile(path, 'kernel/ExplicitEuler.templ.h', context)
+
+      context["InterfaceTestName"] = "ExplicitEulerInterfaceCheck"
+      context["KernelInclude"] = "kernel/ExplicitEuler.h"
+      context["ExplicitInstantiation"] = "template void kernel::ExplicitEuler::operator()(const size_t p_idx1, Accessor& ac) const;"
+      generateFile(path, 'tests/CheckInterface.templ.cpp', context, '../../tests/mesa_pd/kernel/interfaces/ExplicitEulerInterfaceCheck.cpp')
diff --git a/python/mesa_pd/kernel/ExplicitEulerWithShape.py b/python/mesa_pd/kernel/ExplicitEulerWithShape.py
new file mode 100644
index 000000000..cdedaa242
--- /dev/null
+++ b/python/mesa_pd/kernel/ExplicitEulerWithShape.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class ExplicitEulerWithShape:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("position",        "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("linearVelocity",  "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("invMass",         "walberla::real_t",        access="g" )
+      self.accessor.require("force",           "walberla::mesa_pd::Vec3", access="gs" )
+      self.accessor.require("rotation",        "walberla::mesa_pd::Rot3", access="gs")
+      self.accessor.require("angularVelocity", "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("invInertiaBF",    "walberla::mesa_pd::Mat3", access="g" )
+      self.accessor.require("torque",          "walberla::mesa_pd::Vec3", access="gs" )
+      self.accessor.require("flags",           "walberla::mesa_pd::data::particle_flags::FlagT", access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["interface"] = self.accessor.properties
+      generateFile(path, 'kernel/ExplicitEulerWithShape.templ.h', context)
+
+      context["InterfaceTestName"] = "ExplicitEulerWithShapeInterfaceCheck"
+      context["KernelInclude"] = "kernel/ExplicitEulerWithShape.h"
+      context["ExplicitInstantiation"] = "template void kernel::ExplicitEulerWithShape::operator()(const size_t p_idx1, Accessor& ac) const;"
+      generateFile(path, 'tests/CheckInterface.templ.cpp', context, '../../tests/mesa_pd/kernel/interfaces/ExplicitEulerWithShapeInterfaceCheck.cpp')
diff --git a/python/mesa_pd/kernel/ForceLJ.py b/python/mesa_pd/kernel/ForceLJ.py
new file mode 100644
index 000000000..4af22f77b
--- /dev/null
+++ b/python/mesa_pd/kernel/ForceLJ.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class ForceLJ:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("position",        "walberla::mesa_pd::Vec3", access="g")
+      self.accessor.require("force",           "walberla::mesa_pd::Vec3", access="r" )
+      self.accessor.require("type",            "uint_t",              access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["parameters"]       = ["epsilon", "sigma"]
+      context["interface"]        = self.accessor.properties
+
+      generateFile(path, 'kernel/ForceLJ.templ.h', context)
+
+      context["InterfaceTestName"] = "ForceLJInterfaceCheck"
+      context["KernelInclude"] = "kernel/ForceLJ.h"
+      context["ExplicitInstantiation"] = "template void kernel::ForceLJ::operator()(const size_t p_idx1, const size_t p_idx2, Accessor& ac) const;"
+      generateFile(path, 'tests/CheckInterface.templ.cpp', context, '../../tests/mesa_pd/kernel/interfaces/ForceLJInterfaceCheck.cpp')
diff --git a/python/mesa_pd/kernel/HeatConduction.py b/python/mesa_pd/kernel/HeatConduction.py
new file mode 100644
index 000000000..2a6f31337
--- /dev/null
+++ b/python/mesa_pd/kernel/HeatConduction.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile, checkInterface
+
+class HeatConduction:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("temperature",     "walberla::real_t", access="g")
+      self.accessor.require("heatFlux",        "walberla::real_t", access="gsr")
+      self.accessor.require("type",            "uint_t",           access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["parameters"]       = ["conductance"]
+      context["interface"]        = self.accessor.properties
+
+      generateFile(path, 'kernel/HeatConduction.templ.h', context)
+
+      context["InterfaceTestName"] = "HeatConductionInterfaceCheck"
+      context["KernelInclude"] = "kernel/HeatConduction.h"
+      context["ExplicitInstantiation"] = "template void kernel::HeatConduction::operator()(const size_t p_idx1, const size_t p_idx2, Accessor& ac) const;"
+      generateFile(path, 'tests/CheckInterface.templ.cpp', context, '../../tests/mesa_pd/kernel/interfaces/HeatConductionInterfaceCheck.cpp')
diff --git a/python/mesa_pd/kernel/InsertParticleIntoLinkedCells.py b/python/mesa_pd/kernel/InsertParticleIntoLinkedCells.py
new file mode 100644
index 000000000..794fab1df
--- /dev/null
+++ b/python/mesa_pd/kernel/InsertParticleIntoLinkedCells.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class InsertParticleIntoLinkedCells:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("position",     "walberla::mesa_pd::Vec3",                        access="g")
+      self.accessor.require("flags",        "walberla::mesa_pd::data::particle_flags::FlagT", access="g")
+      self.accessor.require("nextParticle", "size_t",                                     access="gs" )
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["interface"]        = self.accessor.properties
+      generateFile(path, 'kernel/InsertParticleIntoLinkedCells.templ.h', context)
diff --git a/python/mesa_pd/kernel/LinearSpringDashpot.py b/python/mesa_pd/kernel/LinearSpringDashpot.py
new file mode 100644
index 000000000..430a58dc6
--- /dev/null
+++ b/python/mesa_pd/kernel/LinearSpringDashpot.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class LinearSpringDashpot:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("uid",             "walberla::id_t",                                access="g")
+      self.accessor.require("position",        "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("linearVelocity",  "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("force",           "walberla::mesa_pd::Vec3",                           access="r" )
+      self.accessor.require("angularVelocity", "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("torque",          "walberla::mesa_pd::Vec3",                           access="r" )
+      self.accessor.require("type",            "uint_t",                                        access="g")
+      self.accessor.require("contactHistory",  "std::map<walberla::id_t, walberla::mesa_pd::Vec3>", access="gs")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["parameters"]       = ["stiffnessN", "stiffnessT", "dampingN", "dampingT", "frictionCoefficientStatic", "frictionCoefficientDynamic"]
+      context["interface"]        = self.accessor.properties
+
+      generateFile(path, 'kernel/LinearSpringDashpot.templ.h', context)
diff --git a/python/mesa_pd/kernel/NonLinearSpringDashpot.py b/python/mesa_pd/kernel/NonLinearSpringDashpot.py
new file mode 100644
index 000000000..490d05ff5
--- /dev/null
+++ b/python/mesa_pd/kernel/NonLinearSpringDashpot.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class NonLinearSpringDashpot:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("uid",             "walberla::id_t",                                access="g")
+      self.accessor.require("position",        "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("linearVelocity",  "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("force",           "walberla::mesa_pd::Vec3",                           access="r" )
+      self.accessor.require("angularVelocity", "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("torque",          "walberla::mesa_pd::Vec3",                           access="r" )
+      self.accessor.require("type",            "uint_t",                                        access="g")
+      self.accessor.require("contactHistory",  "std::map<walberla::id_t, walberla::mesa_pd::Vec3>", access="gs")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["parameters"]       = ["lnCORsqr", "meff", "stiffnessT", "dampingT", "frictionCoefficientStatic", "frictionCoefficientDynamic"]
+      context["interface"]        = self.accessor.properties
+
+      generateFile(path, 'kernel/NonLinearSpringDashpot.templ.h', context)
diff --git a/python/mesa_pd/kernel/SingleCast.py b/python/mesa_pd/kernel/SingleCast.py
new file mode 100644
index 000000000..ff253dfd0
--- /dev/null
+++ b/python/mesa_pd/kernel/SingleCast.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class SingleCast:
+   def __init__(self, shapes):
+      self.shapes = shapes
+      self.accessor = Accessor()
+      self.accessor.require("shape", "BaseShape*", access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["interface"] = self.accessor.properties
+      context["shapes"]    = self.shapes
+      generateFile(path, 'kernel/SingleCast.templ.h', context)
diff --git a/python/mesa_pd/kernel/SpringDashpot.py b/python/mesa_pd/kernel/SpringDashpot.py
new file mode 100644
index 000000000..e7d90a843
--- /dev/null
+++ b/python/mesa_pd/kernel/SpringDashpot.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile, checkInterface
+
+class SpringDashpot:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("position",        "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("linearVelocity",  "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("force",           "walberla::mesa_pd::Vec3",                           access="r" )
+      self.accessor.require("angularVelocity", "walberla::mesa_pd::Vec3",                           access="g")
+      self.accessor.require("torque",          "walberla::mesa_pd::Vec3",                           access="r" )
+      self.accessor.require("type",            "uint_t",                                            access="g")
+      self.accessor.require("contactHistory",  "std::map<walberla::id_t, walberla::mesa_pd::Vec3>", access="gs")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["parameters"]       = ["stiffness", "dampingN", "dampingT", "friction"]
+      context["interface"]        = self.accessor.properties
+
+      generateFile(path, 'kernel/SpringDashpot.templ.h', context)
+
+      context["InterfaceTestName"] = "SpringDashpotInterfaceCheck"
+      context["KernelInclude"] = "kernel/SpringDashpot.h"
+      context["ExplicitInstantiation"] = "template void kernel::SpringDashpot::operator()(const size_t p_idx1, const size_t p_idx2, Accessor& ac, const Vec3& contactPoint, const Vec3& contactNormal, const real_t& penetrationDepth) const;"
+      generateFile(path, 'tests/CheckInterface.templ.cpp', context, '../../tests/mesa_pd/kernel/interfaces/SpringDashpotInterfaceCheck.cpp')
diff --git a/python/mesa_pd/kernel/TemperatureIntegration.py b/python/mesa_pd/kernel/TemperatureIntegration.py
new file mode 100644
index 000000000..68cca40f2
--- /dev/null
+++ b/python/mesa_pd/kernel/TemperatureIntegration.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class TemperatureIntegration:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("temperature",     "walberla::real_t", access="gs")
+      self.accessor.require("heatFlux",        "walberla::real_t", access="gs")
+      self.accessor.require("type",            "uint_t",           access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["parameters"]       = ["invHeatCapacity"]
+      context["interface"] = self.accessor.properties
+      generateFile(path, 'kernel/TemperatureIntegration.templ.h', context)
+
+      context["InterfaceTestName"] = "TemperatureIntegrationInterfaceCheck"
+      context["KernelInclude"] = "kernel/TemperatureIntegration.h"
+      context["ExplicitInstantiation"] = "template void kernel::TemperatureIntegration::operator()(const size_t p_idx1, Accessor& ac) const;"
+      generateFile(path, 'tests/CheckInterface.templ.cpp', context, '../../tests/mesa_pd/kernel/interfaces/TemperatureIntegrationInterfaceCheck.cpp')
diff --git a/python/mesa_pd/kernel/VelocityVerlet.py b/python/mesa_pd/kernel/VelocityVerlet.py
new file mode 100644
index 000000000..391b77070
--- /dev/null
+++ b/python/mesa_pd/kernel/VelocityVerlet.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class VelocityVerlet:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("position",        "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("linearVelocity",  "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("invMass",         "walberla::real_t",        access="g" )
+      self.accessor.require("force",           "walberla::mesa_pd::Vec3", access="gs" )
+      self.accessor.require("oldForce",        "walberla::mesa_pd::Vec3", access="gs" )
+      self.accessor.require("flags",           "walberla::mesa_pd::data::particle_flags::FlagT", access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["interface"] = self.accessor.properties
+
+      generateFile(path, 'kernel/VelocityVerlet.templ.h', context)
+
+      context["InterfaceTestName"] = "VelocityVerletInterfaceCheck"
+      context["KernelInclude"] = "kernel/VelocityVerlet.h"
+      context["ExplicitInstantiation"] = "template void kernel::VelocityVerletPreForceUpdate::operator()(const size_t p_idx1, Accessor& ac) const;\n" +\
+      "template void kernel::VelocityVerletPostForceUpdate::operator()(const size_t p_idx1, Accessor& ac) const;"
+      generateFile(path, 'tests/CheckInterface.templ.cpp', context, '../../tests/mesa_pd/kernel/interfaces/VelocityVerletInterfaceCheck.cpp')
diff --git a/python/mesa_pd/kernel/VelocityVerletWithShape.py b/python/mesa_pd/kernel/VelocityVerletWithShape.py
new file mode 100644
index 000000000..8dbd50a0b
--- /dev/null
+++ b/python/mesa_pd/kernel/VelocityVerletWithShape.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class VelocityVerletWithShape:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("position",        "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("linearVelocity",  "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("invMass",         "walberla::real_t",        access="g" )
+      self.accessor.require("force",           "walberla::mesa_pd::Vec3", access="gs" )
+      self.accessor.require("oldForce",        "walberla::mesa_pd::Vec3", access="gs" )
+
+      self.accessor.require("rotation",        "walberla::mesa_pd::Rot3", access="gs")
+      self.accessor.require("angularVelocity", "walberla::mesa_pd::Vec3", access="gs")
+      self.accessor.require("invInertiaBF",    "walberla::mesa_pd::Mat3", access="g" )
+      self.accessor.require("torque",          "walberla::mesa_pd::Vec3", access="gs" )
+      self.accessor.require("oldTorque",       "walberla::mesa_pd::Vec3", access="gs" )
+
+      self.accessor.require("flags",           "walberla::mesa_pd::data::particle_flags::FlagT", access="g")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["interface"] = self.accessor.properties
+
+      generateFile(path, 'kernel/VelocityVerletWithShape.templ.h', context)
+
+      context["InterfaceTestName"] = "VelocityVerletWithShapeInterfaceCheck"
+      context["KernelInclude"] = "kernel/VelocityVerletWithShape.h"
+      context["ExplicitInstantiation"] = "template void kernel::VelocityVerletWithShapePreForceUpdate::operator()(const size_t p_idx1, Accessor& ac) const;\n" +\
+      "template void kernel::VelocityVerletWithShapePostForceUpdate::operator()(const size_t p_idx1, Accessor& ac) const;"
+      generateFile(path, 'tests/CheckInterface.templ.cpp', context, '../../tests/mesa_pd/kernel/interfaces/VelocityVerletWithShapeInterfaceCheck.cpp')
diff --git a/python/mesa_pd/kernel/__init__.py b/python/mesa_pd/kernel/__init__.py
new file mode 100644
index 000000000..5c96ec4d7
--- /dev/null
+++ b/python/mesa_pd/kernel/__init__.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from .DoubleCast import DoubleCast
+from .ExplicitEuler import ExplicitEuler
+from .ExplicitEulerWithShape import ExplicitEulerWithShape
+from .ForceLJ import ForceLJ
+from .HeatConduction import HeatConduction
+from .InsertParticleIntoLinkedCells import InsertParticleIntoLinkedCells
+from .LinearSpringDashpot import LinearSpringDashpot
+from .NonLinearSpringDashpot import NonLinearSpringDashpot
+from .SingleCast import SingleCast
+from .SpringDashpot import SpringDashpot
+from .TemperatureIntegration import TemperatureIntegration
+from .VelocityVerlet import VelocityVerlet
+from .VelocityVerletWithShape import VelocityVerletWithShape
+
+__all__ = ['DoubleCast',
+           'ExplicitEuler',
+           'ExplicitEulerWithShape',
+           'ForceLJ',
+           'HeatConduction',
+           'InsertParticleIntoLinkedCells',
+           'LinearSpringDashpot',
+           'NonLinearSpringDashpot',
+           'SingleCast',
+           'SpringDashpot',
+           'TemperatureIntegration',
+           'VelocityVerlet',
+           'VelocityVerletWithShape']
diff --git a/python/mesa_pd/mpi/BroadcastProperty.py b/python/mesa_pd/mpi/BroadcastProperty.py
new file mode 100644
index 000000000..4e6109ae4
--- /dev/null
+++ b/python/mesa_pd/mpi/BroadcastProperty.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+from ..utility import generateFile
+
+class BroadcastProperty:
+   def generate(self, path):
+      generateFile(path, 'mpi/BroadcastProperty.templ.h')
diff --git a/python/mesa_pd/mpi/ClearNextNeighborSync.py b/python/mesa_pd/mpi/ClearNextNeighborSync.py
new file mode 100644
index 000000000..e78fecc88
--- /dev/null
+++ b/python/mesa_pd/mpi/ClearNextNeighborSync.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from mesa_pd.accessor import Accessor
+from mesa_pd.utility import generateFile
+
+class ClearNextNeighborSync:
+   def __init__(self):
+      self.accessor = Accessor()
+      self.accessor.require("flags",        "walberla::mesa_pd::data::particle_flags::FlagT", access="g")
+      self.accessor.require("ghostOwners",  "std::vector<int>",                           access="r")
+
+   def getRequirements(self):
+      return self.accessor
+
+   def generate(self, path):
+      context = dict()
+      context["interface"] = self.accessor.properties
+      generateFile(path, 'mpi/ClearNextNeighborSync.templ.h', context)
diff --git a/python/mesa_pd/mpi/ReduceContactHistory.py b/python/mesa_pd/mpi/ReduceContactHistory.py
new file mode 100644
index 000000000..f2a4c797a
--- /dev/null
+++ b/python/mesa_pd/mpi/ReduceContactHistory.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+from ..utility import generateFile
+
+class ReduceContactHistory:
+   def generate(self, path):
+      generateFile(path, 'mpi/ReduceContactHistory.templ.h')
diff --git a/python/mesa_pd/mpi/ReduceProperty.py b/python/mesa_pd/mpi/ReduceProperty.py
new file mode 100644
index 000000000..b630a0dd0
--- /dev/null
+++ b/python/mesa_pd/mpi/ReduceProperty.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+from ..utility import generateFile
+
+class ReduceProperty:
+   def generate(self, path):
+      generateFile(path, 'mpi/ReduceProperty.templ.h')
diff --git a/python/mesa_pd/mpi/SyncNextNeighbors.py b/python/mesa_pd/mpi/SyncNextNeighbors.py
new file mode 100644
index 000000000..6fdc34f48
--- /dev/null
+++ b/python/mesa_pd/mpi/SyncNextNeighbors.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from ..utility import generateFile
+
+class SyncNextNeighbors:
+   def __init__(self, p):
+      p.addProperty("position",          "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ALWAYS")
+      p.addProperty("interactionRadius", "walberla::real_t",    defValue="real_t(0)", syncMode="ONCE")
+      p.addProperty("flags",             "walberla::mesa_pd::data::particle_flags::FlagT", defValue="", syncMode="ONCE")
+      p.addProperty("owner",             "int",                 defValue="-1",        syncMode="ONCE")
+      p.addProperty("ghostOwners",       "std::vector<int>",    defValue="",          syncMode="NEVER")
+
+   def generate(self, path):
+      generateFile(path, 'mpi/SyncNextNeighbors.templ.h')
+      generateFile(path, 'mpi/SyncNextNeighbors.templ.cpp')
diff --git a/python/mesa_pd/mpi/__init__.py b/python/mesa_pd/mpi/__init__.py
new file mode 100644
index 000000000..56849e336
--- /dev/null
+++ b/python/mesa_pd/mpi/__init__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+from .BroadcastProperty import BroadcastProperty
+from .ClearNextNeighborSync import ClearNextNeighborSync
+from .ReduceContactHistory import ReduceContactHistory
+from .ReduceProperty import ReduceProperty
+from .SyncNextNeighbors import SyncNextNeighbors
+
+__all__ = ['BroadcastProperty',
+           'ClearNextNeighborSync',
+           'ReduceContactHistory',
+           'ReduceProperty',
+           'SyncNextNeighbors',
+           ]
diff --git a/python/mesa_pd/templates/data/ContactHistory.templ.h b/python/mesa_pd/templates/data/ContactHistory.templ.h
new file mode 100644
index 000000000..b455a2fa9
--- /dev/null
+++ b/python/mesa_pd/templates/data/ContactHistory.templ.h
@@ -0,0 +1,109 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ContactHistory.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+{%- for include in includes %}
+#include <{{include}}>
+{%- endfor %}
+#include <mesa_pd/data/STLOverloads.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+#include <core/math/AABB.h>
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+#include <core/STLIO.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+class ContactHistory
+{
+public:
+   {%- for prop in properties %}
+   const {{prop.type}}& get{{prop.name | capFirst}}() const {return {{prop.name}}_;}
+   {{prop.type}}& get{{prop.name | capFirst}}Ref() {return {{prop.name}}_;}
+   void set{{prop.name | capFirst}}(const {{prop.type}}& v) { {{prop.name}}_ = v;}
+   {% endfor %}
+private:
+   {%- for prop in properties %}
+   {{prop.type}} {{prop.name}}_ {};
+   {%- endfor %}
+};
+
+inline
+std::ostream& operator<<( std::ostream& os, const ContactHistory& ch )
+{
+   os << "==========  Contact History  ==========" << "\n" <<
+   {%- for prop in properties %}
+         "{{'%-20s'|format(prop.name)}}: " << ch.get{{prop.name | capFirst}}() << "\n" <<
+   {%- endfor %}
+         "================================" << std::endl;
+   return os;
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::data::ContactHistory& obj )
+{
+   buf.addDebugMarker( "ch" );
+   {%- for prop in properties %}
+   buf << obj.get{{prop.name | capFirst}}();
+   {%- endfor %}
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::data::ContactHistory& objparam )
+{
+   buf.readDebugMarker( "ch" );
+   {%- for prop in properties %}
+   buf >> objparam.get{{prop.name | capFirst}}Ref();
+   {%- endfor %}
+   return buf;
+}
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/data/LinkedCells.templ.h b/python/mesa_pd/templates/data/LinkedCells.templ.h
new file mode 100644
index 000000000..c5107a6ef
--- /dev/null
+++ b/python/mesa_pd/templates/data/LinkedCells.templ.h
@@ -0,0 +1,266 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LinkedCells.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+#include <core/math/AABB.h>
+#include <stencil/D3Q27.h>
+
+#include <atomic>
+#include <cmath>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+struct LinkedCells
+{
+   LinkedCells(const math::AABB& domain, const real_t cellDiameter) : LinkedCells(domain, Vec3(cellDiameter,cellDiameter,cellDiameter)) {}
+   LinkedCells(const math::AABB& domain, const Vec3& cellDiameter);
+
+   void clear();
+
+   /**
+    * Calls the provided functor \p func for all particle pairs.
+    *
+    * Additional arguments can be provided. No pairs with twice the same particle.
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, j, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   void forEachParticlePair(const bool openmp,
+                            const Selector& selector,
+                            Accessor& acForLC,
+                            Func&& func,
+                            Args&&... args) const;
+   /**
+    * Calls the provided functor \p func for all particle pairs.
+    *
+    * Additional arguments can be provided. No pairs with twice the same particle are generated.
+    * No pair is called twice!
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, j, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   void forEachParticlePairHalf(const bool openmp,
+                                const Selector& selector,
+                                Accessor& acForLC,
+                                Func&& func,
+                                Args&&... args) const;
+
+   math::AABB   domain_ {};
+   Vector3<int> numCellsPerDim_ {};
+   Vec3         cellDiameter_ {};
+   Vec3         invCellDiameter_ {};
+   std::atomic<int> infiniteParticles_ {};
+   std::vector< std::atomic<int> > cells_ {};
+};
+
+inline
+math::AABB getCellAABB(const LinkedCells& ll, const int hash0, const int hash1, const int hash2)
+{
+   {%- for dim in range(3) %}
+   WALBERLA_ASSERT_GREATER_EQUAL(hash{{dim}}, 0);
+   WALBERLA_ASSERT_LESS(hash{{dim}}, ll.numCellsPerDim_[{{dim}}]);
+   {%- endfor %}
+   const auto& minCorner = ll.domain_.minCorner();
+   const real_t xMin = ll.cellDiameter_[0] * real_c(hash0) + minCorner[0];
+   const real_t yMin = ll.cellDiameter_[1] * real_c(hash1) + minCorner[1];
+   const real_t zMin = ll.cellDiameter_[2] * real_c(hash2) + minCorner[2];
+   const real_t xMax = ll.cellDiameter_[0] * real_c(hash0 + 1) + minCorner[0];
+   const real_t yMax = ll.cellDiameter_[1] * real_c(hash1 + 1) + minCorner[1];
+   const real_t zMax = ll.cellDiameter_[2] * real_c(hash2 + 1) + minCorner[2];
+   return math::AABB(xMin, yMin, zMin, xMax, yMax, zMax);
+}
+
+inline
+int getCellIdx(const LinkedCells& ll, const int hash0, const int hash1, const int hash2)
+{
+   {%- for dim in range(3) %}
+   WALBERLA_ASSERT_GREATER_EQUAL(hash{{dim}}, 0);
+   WALBERLA_ASSERT_LESS(hash{{dim}}, ll.numCellsPerDim_[{{dim}}]);
+   {%- endfor %}
+   return hash2 * ll.numCellsPerDim_[1] * ll.numCellsPerDim_[0] + hash1 * ll.numCellsPerDim_[0] + hash0;
+}
+
+inline
+LinkedCells::LinkedCells(const math::AABB& domain, const Vec3& cellDiameter)
+   : domain_(domain)
+   , numCellsPerDim_( static_cast<int>(std::ceil( domain.sizes()[0] / cellDiameter[0])),
+     static_cast<int>(std::ceil( domain.sizes()[1] / cellDiameter[1])),
+     static_cast<int>(std::ceil( domain.sizes()[2] / cellDiameter[2])) )
+   , cellDiameter_( domain.sizes()[0] / real_c(numCellsPerDim_[0]),
+     domain.sizes()[1] / real_c(numCellsPerDim_[1]),
+     domain.sizes()[2] / real_c(numCellsPerDim_[2]) )
+   , invCellDiameter_( real_t(1) / cellDiameter_[0], real_t(1) / cellDiameter_[1], real_t(1) / cellDiameter_[2] )
+   , cells_(uint_c(numCellsPerDim_[0]*numCellsPerDim_[1]*numCellsPerDim_[2]))
+{
+   //precondition
+   {%- for dim in range(3) %}
+   WALBERLA_CHECK_GREATER_EQUAL(cellDiameter[{{dim}}], real_t(0));
+   {%- endfor %}
+
+   //postcondition
+   {%- for dim in range(3) %}
+   WALBERLA_CHECK_GREATER_EQUAL(cellDiameter_[{{dim}}], real_t(0));
+   WALBERLA_CHECK_LESS_EQUAL(cellDiameter_[{{dim}}], cellDiameter[{{dim}}]);
+
+   WALBERLA_CHECK_GREATER_EQUAL(numCellsPerDim_[{{dim}}], 0);
+   {%- endfor %}
+}
+
+void LinkedCells::clear()
+{
+   const uint64_t cellsSize = cells_.size();
+   //clear existing linked cells
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static)
+   #endif
+   for (int64_t i = 0; i < int64_c(cellsSize); ++i)
+      cells_[uint64_c(i)] = -1;
+   infiniteParticles_ = -1;
+}
+
+{%- for half in [False, True] %}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void LinkedCells::forEachParticlePair{%- if half %}Half{%- endif %}(const bool openmp, const Selector& selector, Accessor& acForLC, Func&& func, Args&&... args) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+   WALBERLA_UNUSED(openmp);
+#ifdef _OPENMP
+#pragma omp parallel for schedule(static) if (openmp)
+#endif
+   for (int z = 0; z < numCellsPerDim_[2]; ++z)
+   {
+      for (int y = 0; y < numCellsPerDim_[1]; ++y)
+      {
+         for (int x = 0; x < numCellsPerDim_[0]; ++x)
+         {
+            const int cell_idx = getCellIdx(*this, x, y, z); ///< current cell index
+            int p_idx = cells_[uint_c(cell_idx)]; ///< current particle index
+            int np_idx = -1; ///< particle to be checked against
+
+            while (p_idx != -1)
+            {
+               WALBERLA_ASSERT_GREATER_EQUAL(p_idx, 0);
+               WALBERLA_ASSERT_LESS(p_idx, acForLC.size());
+
+               // check particles in own cell
+               np_idx = acForLC.getNextParticle(uint_c(p_idx)); ///< neighbor particle index
+               while (np_idx != -1)
+               {
+                  WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                  WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                  if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                  {
+                     func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                     {%- if not half %}
+                     func(uint_c(np_idx), uint_c(p_idx), std::forward<Args>(args)...);
+                     {%- endif %}
+                  }
+
+                  np_idx = acForLC.getNextParticle(uint_c(np_idx));
+               }
+
+               // check particles in neighboring cells (only positive ones)
+               for (auto dir : stencil::D3Q27::dir_pos)
+               {
+                  const int nx = x + stencil::cx[dir];
+                  const int ny = y + stencil::cy[dir];
+                  const int nz = z + stencil::cz[dir];
+                  if (nx < 0) continue;
+                  if (ny < 0) continue;
+                  if (nz < 0) continue;
+                  if (nx >= numCellsPerDim_[0]) continue;
+                  if (ny >= numCellsPerDim_[1]) continue;
+                  if (nz >= numCellsPerDim_[2]) continue;
+
+                  const int ncell_idx = getCellIdx(*this, nx, ny, nz); ///< neighbor cell index
+
+                  WALBERLA_ASSERT_GREATER_EQUAL(p_idx, 0);
+                  WALBERLA_ASSERT_LESS(p_idx, acForLC.size());
+                  np_idx = cells_[uint_c(ncell_idx)]; ///< neighbor particle index
+                  while (np_idx != -1)
+                  {
+                     WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                     WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                     if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                     {
+                        func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                        {%- if not half %}
+                        func(uint_c(np_idx), uint_c(p_idx), std::forward<Args>(args)...);
+                        {%- endif %}
+                     }
+
+                     np_idx = acForLC.getNextParticle(uint_c(np_idx));
+                  }
+               }
+
+               // check particles in infiniteParticles list
+               np_idx = infiniteParticles_; ///< neighbor particle index
+               while (np_idx != -1)
+               {
+                  WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                  WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                  if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                  {
+                     func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                     {%- if not half %}
+                     func(uint_c(np_idx), uint_c(p_idx), std::forward<Args>(args)...);
+                     {%- endif %}
+                  }
+
+                  np_idx = acForLC.getNextParticle(uint_c(np_idx));
+               }
+
+               // go to next particle
+               p_idx = acForLC.getNextParticle(uint_c(p_idx));
+            }
+         }
+      }
+   }
+}
+{%- endfor %}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/data/ParticleAccessor.templ.h b/python/mesa_pd/templates/data/ParticleAccessor.templ.h
new file mode 100644
index 000000000..387824fbe
--- /dev/null
+++ b/python/mesa_pd/templates/data/ParticleAccessor.templ.h
@@ -0,0 +1,126 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleAccessor.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/UniqueID.h>
+
+#include <limits>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+/**
+ * @brief Basic ParticleAccessor for the ParticleStorage
+ *
+ * Provides get, set and getRef for all members of the ParticleStorage.
+ * Can be used as a basis class for a more advanced ParticleAccessor.
+ */
+class ParticleAccessor : public IAccessor
+{
+public:
+   ParticleAccessor(const std::shared_ptr<data::ParticleStorage>& ps) : ps_(ps) {}
+   virtual ~ParticleAccessor() = default;
+
+   {%- for prop in properties %}
+   const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const {return ps_->get{{prop.name | capFirst}}(p_idx);}
+   {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx) {return ps_->get{{prop.name | capFirst}}Ref(p_idx);}
+   void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v) { ps_->set{{prop.name | capFirst}}(p_idx, v);}
+   {% endfor %}
+
+   id_t getInvalidUid() const {return UniqueID<data::Particle>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& uid) const {auto it = ps_->find(uid); return it != ps_->end() ? it.getIdx() : std::numeric_limits<size_t>::max();}
+   size_t size() const { return ps_->size(); }
+
+   inline size_t create(const id_t& uid);
+   inline size_t erase(const size_t& idx);
+   inline size_t find(const id_t& uid);
+protected:
+   std::shared_ptr<data::ParticleStorage> ps_;
+};
+
+inline size_t ParticleAccessor::create(const id_t& uid)
+{
+   auto it = ps_->create(uid);
+   return it.getIdx();
+}
+inline size_t ParticleAccessor::erase(const size_t& idx)
+{
+   data::ParticleStorage::iterator it(ps_.get(), idx);
+   it = ps_->erase(it);
+   return it.getIdx();
+}
+inline size_t ParticleAccessor::find(const id_t& uid)
+{
+   auto it = ps_->find(uid);
+   return it.getIdx();
+}
+
+/**
+ * @brief Basic ParticleAccessor which emulates a single particle in a ParticleStorage
+ * without actually needing a ParticleStorage. This class is used mainly for testing purposes.
+ *
+ * Provides get, set and getRef.
+ */
+class SingleParticleAccessor : public IAccessor
+{
+public:
+   virtual ~SingleParticleAccessor() = default;
+
+   {%- for prop in properties %}
+   const {{prop.type}}& get{{prop.name | capFirst}}(const size_t /*p_idx*/) const {return {{prop.name}}_;}
+   void set{{prop.name | capFirst}}(const size_t /*p_idx*/, const {{prop.type}}& v) { {{prop.name}}_ = v;}
+   {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t /*p_idx*/) {return {{prop.name}}_;}
+   {% endfor %}
+
+   id_t getInvalidUid() const {return UniqueID<data::Particle>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& uid) const {return uid == uid_ ? 0 : std::numeric_limits<size_t>::max();}
+   size_t size() const { return 1; }
+private:
+   {%- for prop in properties %}
+   {{prop.type}} {{prop.name}}_;
+   {%- endfor %}
+};
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/data/ParticleStorage.templ.h b/python/mesa_pd/templates/data/ParticleStorage.templ.h
new file mode 100644
index 000000000..c6a0f2011
--- /dev/null
+++ b/python/mesa_pd/templates/data/ParticleStorage.templ.h
@@ -0,0 +1,514 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleStorage.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <atomic>
+#include <limits>
+#include <map>
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+#include <mesa_pd/data/ContactHistory.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+{%- for include in includes %}
+#include <{{include}}>
+{%- endfor %}
+#include <mesa_pd/data/STLOverloads.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+#include <core/math/AABB.h>
+#include <core/OpenMP.h>
+#include <core/STLIO.h>
+#include <core/UniqueID.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+class ParticleStorage;
+
+class ParticleStorage
+{
+public:
+   class Particle
+   {
+   public:
+      constexpr Particle(ParticleStorage& storage, const size_t i) : storage_(storage), i_(i) {}
+      constexpr Particle(const Particle&)  = default;
+      constexpr Particle(Particle&&)  = default;
+
+      Particle& operator=(const Particle& rhs);
+      Particle& operator=(Particle&& rhs);
+
+      Particle* operator->(){return this;}
+
+      {% for prop in properties %}
+      const {{prop.type}}& get{{prop.name | capFirst}}() const {return storage_.get{{prop.name | capFirst}}(i_);}
+      {{prop.type}}& get{{prop.name | capFirst}}Ref() {return storage_.get{{prop.name | capFirst}}Ref(i_);}
+      void set{{prop.name | capFirst}}(const {{prop.type}}& v) { storage_.set{{prop.name | capFirst}}(i_, v);}
+      {% endfor %}
+
+      size_t getIdx() const {return i_;}
+   public:
+      ParticleStorage& storage_;
+      const size_t i_;
+   };
+
+   class iterator
+   {
+   public:
+      using iterator_category = std::random_access_iterator_tag;
+      using value_type        = Particle;
+      using pointer           = Particle*;
+      using reference         = Particle&;
+      using difference_type   = std::ptrdiff_t;
+
+      explicit iterator(ParticleStorage* storage, const size_t i) : storage_(storage), i_(i) {}
+      iterator(const iterator& it)         = default;
+      iterator(iterator&& it)              = default;
+      iterator& operator=(const iterator&) = default;
+      iterator& operator=(iterator&&)      = default;
+
+
+      Particle operator*() const {return Particle{*storage_, i_};}
+      Particle operator->() const {return Particle{*storage_, i_};}
+      iterator& operator++(){ ++i_; return *this; }
+      iterator operator++(int){ iterator tmp(*this); ++(*this); return tmp; }
+      iterator& operator--(){ --i_; return *this; }
+      iterator operator--(int){ iterator tmp(*this); --(*this); return tmp; }
+
+      iterator& operator+=(const size_t n){ i_+=n; return *this; }
+      iterator& operator-=(const size_t n){ i_-=n; return *this; }
+
+      friend iterator operator+(const iterator& it, const size_t n);
+      friend iterator operator+(const size_t n, const iterator& it);
+      friend iterator operator-(const iterator& it, const size_t n);
+      friend difference_type operator-(const iterator& lhs, const iterator& rhs);
+
+      friend bool operator==(const iterator& lhs, const iterator& rhs);
+      friend bool operator!=(const iterator& lhs, const iterator& rhs);
+      friend bool operator<(const iterator& lhs, const iterator& rhs);
+      friend bool operator>(const iterator& lhs, const iterator& rhs);
+      friend bool operator<=(const iterator& lhs, const iterator& rhs);
+      friend bool operator>=(const iterator& lhs, const iterator& rhs);
+
+      friend void swap(iterator& lhs, iterator& rhs);
+
+      size_t getIdx() const {return i_;}
+   private:
+      ParticleStorage* storage_;
+      size_t i_;
+   };
+
+   explicit ParticleStorage(const size_t size);
+
+   iterator begin() { return iterator(this, 0); }
+   iterator end()   { return iterator(this, size()); }
+   iterator operator[](const size_t n) { return iterator(this, n); }
+
+   {% for prop in properties %}
+   const {{prop.type}}& get{{prop.name | capFirst}}(const size_t idx) const {return {{prop.name}}_[idx];}
+   {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t idx) {return {{prop.name}}_[idx];}
+   void set{{prop.name | capFirst}}(const size_t idx, const {{prop.type}}& v) { {{prop.name}}_[idx] = v; }
+   {% endfor %}
+
+   /**
+    * @brief creates a new particle and returns an iterator pointing to it
+    *
+    * \attention Use this function only if you know what you are doing!
+    * Messing with the uid might break the simulation!
+    * If you are unsure use create(bool) instead.
+    * @param uid unique id of the particle to be created
+    * @return iterator to the newly created particle
+    */
+   inline iterator create(const id_t& uid);
+   inline iterator create(const bool global = false);
+   inline iterator erase(iterator& it);
+   /// Finds the entry corresponding to \p uid.
+   /// \return iterator to the object or end iterator
+   inline iterator find(const id_t& uid);
+   inline void reserve(const size_t size);
+   inline void clear();
+   inline size_t size() const;
+
+   /**
+    * Calls the provided functor \p func for all Particles.
+    *
+    * Additional arguments can be provided.
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticle(const bool openmp,
+                               const Selector& selector,
+                               Accessor& acForPS,
+                               Func&& func,
+                               Args&&... args);
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticle(const bool openmp,
+                               const Selector& selector,
+                               Accessor& acForPS,
+                               Func&& func,
+                               Args&&... args) const;
+   /**
+    * Calls the provided functor \p func for all Particle pairs.
+    *
+    * Additional arguments can be provided. No pairs with twice the same Particle.
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, j, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticlePair(const bool openmp,
+                                   const Selector& selector,
+                                   Accessor& acForPS,
+                                   Func&& func,
+                                   Args&&... args);
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticlePair(const bool openmp,
+                                   const Selector& selector,
+                                   Accessor& acForPS,
+                                   Func&& func,
+                                   Args&&... args) const;
+   /**
+    * Calls the provided functor \p func for all Particle pairs.
+    *
+    * Additional arguments can be provided. No pairs with twice the same Particle.
+    * Index of the first particle i will be always smaller than j! No pair is called twice!
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, j, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticlePairHalf(const bool openmp,
+                                       const Selector& selector,
+                                       Accessor& acForPS,
+                                       Func&& func,
+                                       Args&&... args);
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticlePairHalf(const bool openmp,
+                                       const Selector& selector,
+                                       Accessor& acForPS,
+                                       Func&& func,
+                                       Args&&... args) const;
+
+   private:
+   {%- for prop in properties %}
+   std::vector<{{prop.type}}> {{prop.name}}_ {};
+   {%- endfor %}
+   std::unordered_map<id_t, size_t> uidToIdx_;
+   static_assert(std::is_same<decltype(uid_)::value_type, id_t>::value,
+                 "Property uid of type id_t is missing. This property is required!");
+};
+using Particle = ParticleStorage::Particle;
+
+inline
+ParticleStorage::Particle& ParticleStorage::Particle::operator=(const ParticleStorage::Particle& rhs)
+{
+   {%- for prop in properties %}
+   get{{prop.name | capFirst}}Ref() = rhs.get{{prop.name | capFirst}}();
+   {%- endfor %}
+   return *this;
+}
+
+inline
+ParticleStorage::Particle& ParticleStorage::Particle::operator=(ParticleStorage::Particle&& rhs)
+{
+   {%- for prop in properties %}
+   get{{prop.name | capFirst}}Ref() = std::move(rhs.get{{prop.name | capFirst}}Ref());
+   {%- endfor %}
+   return *this;
+}
+
+inline
+std::ostream& operator<<( std::ostream& os, const ParticleStorage::Particle& p )
+{
+   os << "==========  {{StorageType | upper}}  ==========" << "\n" <<
+         "idx                 : " << p.getIdx() << "\n" <<
+   {%- for prop in properties %}
+         "{{'%-20s'|format(prop.name)}}: " << p.get{{prop.name | capFirst}}() << "\n" <<
+   {%- endfor %}
+         "================================" << std::endl;
+   return os;
+}
+
+inline
+ParticleStorage::iterator operator+(const ParticleStorage::iterator& it, const size_t n)
+{
+   return ParticleStorage::iterator(it.storage_, it.i_+n);
+}
+
+inline
+ParticleStorage::iterator operator+(const size_t n, const ParticleStorage::iterator& it)
+{
+   return it + n;
+}
+
+inline
+ParticleStorage::iterator operator-(const ParticleStorage::iterator& it, const size_t n)
+{
+   return ParticleStorage::iterator(it.storage_, it.i_-n);
+}
+
+inline
+ParticleStorage::iterator::difference_type operator-(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   return int64_c(lhs.i_) - int64_c(rhs.i_);
+}
+
+inline bool operator==(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ == rhs.i_);
+}
+inline bool operator!=(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ != rhs.i_);
+}
+inline bool operator<(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ < rhs.i_);
+}
+inline bool operator>(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ > rhs.i_);
+}
+inline bool operator<=(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ <= rhs.i_);
+}
+inline bool operator>=(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ >= rhs.i_);
+}
+
+inline void swap(ParticleStorage::iterator& lhs, ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   std::swap(lhs.i_, rhs.i_);
+}
+
+inline
+ParticleStorage::ParticleStorage(const size_t size)
+{
+   reserve(size);
+}
+
+
+inline ParticleStorage::iterator ParticleStorage::create(const id_t& uid)
+{
+   WALBERLA_ASSERT_EQUAL(uidToIdx_.find(uid),
+                         uidToIdx_.end(),
+                         "particle with the same uid(" << uid <<") already existing at index(" << uidToIdx_.find(uid)->second << ")");
+   {%- for prop in properties %}
+   {{prop.name}}_.emplace_back({{prop.defValue}});
+   {%- endfor %}
+   uid_.back() = uid;
+   uidToIdx_[uid] = uid_.size() - 1;
+   return iterator(this, size() - 1);
+}
+
+inline ParticleStorage::iterator ParticleStorage::create(const bool global)
+{
+   if (global)
+   {
+      auto it = create(UniqueID<Particle>::createGlobal());
+      data::particle_flags::set(flags_.back(), data::particle_flags::GLOBAL);
+      data::particle_flags::set(flags_.back(), data::particle_flags::NON_COMMUNICATING);
+      return it;
+   } else
+   {
+      return create(UniqueID<Particle>::create());
+   }
+}
+
+inline ParticleStorage::iterator ParticleStorage::erase(iterator& it)
+{
+   //swap with last element and pop
+   auto last = --end();
+   auto numElementsRemoved = uidToIdx_.erase(it->getUid());
+   WALBERLA_CHECK_EQUAL(numElementsRemoved,
+                        1,
+                        "Particle with uid " << it->getUid() << " cannot be removed (not existing).");
+   if (it != last) //skip swap if last element is removed
+   {
+      *it = *last;
+      uidToIdx_[it->getUid()] = it.getIdx();
+   }
+   {%- for prop in properties %}
+   {{prop.name}}_.pop_back();
+   {%- endfor %}
+   return it;
+}
+
+inline ParticleStorage::iterator ParticleStorage::find(const id_t& uid)
+{
+   //linear search through uid vector
+   //auto it = std::find(uid_.begin(), uid_.end(), uid);
+   //if (it == uid_.end()) return end();
+   //return iterator(this, uint_c(std::distance(uid_.begin(), it)));
+
+   //use unordered_map for faster lookup
+   auto it = uidToIdx_.find(uid);
+   if (it == uidToIdx_.end()) return end();
+   WALBERLA_ASSERT_EQUAL(it->first, uid, "Lookup via uidToIdx map is not up to date!!!");
+   return iterator(this, it->second);
+}
+
+inline void ParticleStorage::reserve(const size_t size)
+{
+   {%- for prop in properties %}
+   {{prop.name}}_.reserve(size);
+   {%- endfor %}
+}
+
+inline void ParticleStorage::clear()
+{
+   {%- for prop in properties %}
+   {{prop.name}}_.clear();
+   {%- endfor %}
+   uidToIdx_.clear();
+}
+
+inline size_t ParticleStorage::size() const
+{
+   {%- for prop in properties %}
+   //WALBERLA_ASSERT_EQUAL( {{properties[0].name}}_.size(), {{prop.name}}.size() );
+   {%- endfor %}
+   return {{properties[0].name}}_.size();
+}
+
+{%- for const in ["", "const"] %}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticle(const bool openmp,
+                                             const Selector& selector,
+                                             Accessor& acForPS,
+                                             Func&& func, Args&&... args) {{const}}
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      if (selector(uint64_c(i), acForPS))
+         func( uint64_c(i), std::forward<Args>(args)... );
+   }
+}
+{%- endfor %}
+
+{%- for const in ["", "const"] %}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticlePair(const bool openmp,
+                                                 const Selector& selector,
+                                                 Accessor& acForPS,
+                                                 Func&& func, Args&&... args) {{const}}
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      for (int64_t j = 0; j < int64_c(len); ++j)
+      {
+         if (i!=j)
+         {
+            if (selector(uint64_c(i), uint64_c(j), acForPS))
+            {
+               func( uint64_c(i), uint64_c(j), std::forward<Args>(args)... );
+            }
+         }
+      }
+   }
+}
+{%- endfor %}
+
+{%- for const in ["", "const"] %}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticlePairHalf(const bool openmp,
+                                                     const Selector& selector,
+                                                     Accessor& acForPS,
+                                                     Func&& func, Args&&... args) {{const}}
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      for (int64_t j = i+1; j < int64_c(len); ++j)
+      {
+         if (selector(uint64_c(i), uint64_c(j), acForPS))
+         {
+            func( uint64_c(i), uint64_c(j), std::forward<Args>(args)... );
+         }
+      }
+   }
+}
+{%- endfor %}
+
+
+{%- for prop in properties %}
+///Predicate that selects a certain property from a Particle
+class SelectParticle{{prop.name | capFirst}}
+{
+public:
+   using return_type = {{prop.type}};
+   {{prop.type}}& operator()(data::Particle& p) const {return p.get{{prop.name | capFirst}}Ref();}
+   {{prop.type}}& operator()(data::Particle&& p) const {return p.get{{prop.name | capFirst}}Ref();}
+   const {{prop.type}}& operator()(const data::Particle& p) const {return p.get{{prop.name | capFirst}}();}
+};
+{%- endfor %}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/data/ShapeStorage.templ.h b/python/mesa_pd/templates/data/ShapeStorage.templ.h
new file mode 100644
index 000000000..8cede0992
--- /dev/null
+++ b/python/mesa_pd/templates/data/ShapeStorage.templ.h
@@ -0,0 +1,114 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file GeometryStorage.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/shape/BaseShape.h>
+{%- for shape in shapes %}
+#include <mesa_pd/data/shape/{{shape}}.h>
+{%- endfor %}
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+#include <core/math/AABB.h>
+
+#include <memory>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+struct ShapeStorage
+{
+   std::vector<std::unique_ptr<BaseShape>> shapes {};
+
+   template <typename ShapeT, typename... Args>
+   size_t create(Args&&... args);
+
+   template <typename ReturnType, typename func>
+   ReturnType singleDispatch( ParticleStorage& ps, size_t idx, func& f );
+
+   template <typename ReturnType, typename func>
+   ReturnType doubleDispatch( ParticleStorage& ps, size_t idx, size_t idy, func& f );
+};
+//Make sure that no two different shapes have the same unique identifier!
+{%- for shape in shapes[1:] %}
+static_assert( {{shapes[0]}}::SHAPE_TYPE != {{shape}}::SHAPE_TYPE, "Shape types have to be different!" );
+{%- endfor %}
+
+template <typename ShapeT, typename... Args>
+size_t ShapeStorage::create(Args&&... args)
+{
+   shapes.push_back(std::make_unique<ShapeT>(std::forward<Args>(args)...));
+   return shapes.size() - 1;
+}
+
+template <typename ReturnType, typename func>
+ReturnType ShapeStorage::singleDispatch( ParticleStorage& ps, size_t idx, func& f )
+{
+   WALBERLA_ASSERT_LESS( idx, ps.size() );
+
+   switch (shapes[ps.getShapeID(idx)]->getShapeType())
+   {
+      {%- for shape in shapes %}
+      case {{shape}}::SHAPE_TYPE : return f(ps, idx, *static_cast<{{shape}}*>(shapes[ps.getShapeID(idx)].get()));
+      {%- endfor %}
+      default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idx)]->getShapeType() << ") could not be determined!");
+   }
+}
+
+template <typename ReturnType, typename func>
+ReturnType ShapeStorage::doubleDispatch( ParticleStorage& ps, size_t idx, size_t idy, func& f )
+{
+   WALBERLA_ASSERT_LESS( idx, ps.size() );
+   WALBERLA_ASSERT_LESS( idy, ps.size() );
+
+   switch (shapes[ps.getShapeID(idx)]->getShapeType())
+   {
+      {%- for shape1 in shapes %}
+      case {{shape1}}::SHAPE_TYPE :
+         switch (shapes[ps.getShapeID(idy)]->getShapeType())
+         {
+            {%- for shape2 in shapes %}
+            case {{shape2}}::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<{{shape1}}*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<{{shape2}}*>(shapes[ps.getShapeID(idy)].get()));
+            {%- endfor %}
+            default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idy)]->getShapeType() << ") could not be determined!");
+         }
+      {%- endfor %}
+      default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idx)]->getShapeType() << ") could not be determined!");
+   }
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/DoubleCast.templ.h b/python/mesa_pd/templates/kernel/DoubleCast.templ.h
new file mode 100644
index 000000000..68cc2b611
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/DoubleCast.templ.h
@@ -0,0 +1,97 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SingleCast.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/shape/BaseShape.h>
+{%- for shape in shapes %}
+#include <mesa_pd/data/shape/{{shape}}.h>
+{%- endfor %}
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class DoubleCast
+{
+public:
+   template <typename Accessor, typename func, typename... Args>
+   auto operator()( size_t idx, size_t idy, Accessor& ac, func& f, Args&&... args );
+};
+
+template <typename Accessor, typename func, typename... Args>
+auto DoubleCast::operator()( size_t idx, size_t idy, Accessor& ac, func& f, Args&&... args )
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   using namespace mesa_pd::data;
+
+   switch (ac.getShape(idx)->getShapeType())
+   {
+      {%- for shape1 in shapes %}
+      case {{shape1}}::SHAPE_TYPE :
+         switch (ac.getShape(idy)->getShapeType())
+         {
+            {%- for shape2 in shapes %}
+            case {{shape2}}::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<{{shape1}}*>(ac.getShape(idx)),
+                                                   *static_cast<{{shape2}}*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            {%- endfor %}
+            default : WALBERLA_ABORT("Shape type (" << ac.getShape(idy)->getShapeType() << ") could not be determined!");
+         }
+      {%- endfor %}
+      default : WALBERLA_ABORT("Shape type (" << ac.getShape(idx)->getShapeType() << ") could not be determined!");
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/ExplicitEuler.templ.h b/python/mesa_pd/templates/kernel/ExplicitEuler.templ.h
new file mode 100644
index 000000000..a81e274f3
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/ExplicitEuler.templ.h
@@ -0,0 +1,87 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ExplicitEuler.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Kernel which explicitly integrates all particles in time.
+ * This integrator integrates velocity and position.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ *
+ * \pre  All forces acting on the particles have to be set.
+ * \post All forces are reset to 0.
+ * \ingroup mesa_pd_kernel
+ */
+class ExplicitEuler
+{
+public:
+   explicit ExplicitEuler(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+private:
+   real_t dt_ = real_t(0.0);
+};
+
+template <typename Accessor>
+inline void ExplicitEuler::operator()(const size_t idx,
+                                      Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(idx), data::particle_flags::FIXED))
+   {
+      ac.setPosition      (idx, ac.getInvMass(idx) * ac.getForce(idx) * dt_ * dt_ + ac.getLinearVelocity(idx) * dt_ + ac.getPosition(idx));
+      ac.setLinearVelocity(idx, ac.getInvMass(idx) * ac.getForce(idx) * dt_ + ac.getLinearVelocity(idx));
+   }
+   ac.setForce         (idx, Vec3(real_t(0), real_t(0), real_t(0)));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/ExplicitEulerWithShape.templ.h b/python/mesa_pd/templates/kernel/ExplicitEulerWithShape.templ.h
new file mode 100644
index 000000000..7fb2d73bc
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/ExplicitEulerWithShape.templ.h
@@ -0,0 +1,102 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ExplicitEulerWithShape.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Kernel which explicitly integrates all particles in time.
+ * This integrator integrates velocity and position as well as angular velocity and rotation.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ *
+ * \pre  All forces and torques acting on the particles have to be set.
+ * \post All forces and torques are reset to 0.
+ * \ingroup mesa_pd_kernel
+ */
+class ExplicitEulerWithShape
+{
+public:
+   explicit ExplicitEulerWithShape(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+private:
+   real_t dt_ = real_t(0.0);
+};
+
+template <typename Accessor>
+inline void ExplicitEulerWithShape::operator()(const size_t idx,
+                                               Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(idx), data::particle_flags::FIXED))
+   {
+      ac.setPosition      (idx, ac.getInvMass(idx) * ac.getForce(idx) * dt_ * dt_ + ac.getLinearVelocity(idx) * dt_ + ac.getPosition(idx));
+      ac.setLinearVelocity(idx, ac.getInvMass(idx) * ac.getForce(idx) * dt_ + ac.getLinearVelocity(idx));
+
+      const Vec3 wdot = math::transformMatrixRART(ac.getRotation(idx).getMatrix(),
+                                                  ac.getInvInertiaBF(idx)) * ac.getTorque(idx);
+
+      // Calculating the rotation angle
+      const Vec3 phi( ac.getAngularVelocity(idx) * dt_ + wdot * dt_ * dt_);
+
+      // Calculating the new orientation
+      auto rotation = ac.getRotation(idx);
+      rotation.rotate( phi );
+      ac.setRotation(idx, rotation);
+
+      ac.setAngularVelocity(idx, wdot * dt_ + ac.getAngularVelocity(idx));
+   }
+
+   ac.setForce (idx, Vec3(real_t(0), real_t(0), real_t(0)));
+   ac.setTorque(idx, Vec3(real_t(0), real_t(0), real_t(0)));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/ForceLJ.templ.h b/python/mesa_pd/templates/kernel/ForceLJ.templ.h
new file mode 100644
index 000000000..c0b6234a3
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/ForceLJ.templ.h
@@ -0,0 +1,162 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ForceLJ.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Kernel which calculates the Lennard Jones froce between two particles.
+ *
+ * This kernel uses the type property of a particle to decide on the material parameters.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class ForceLJ
+{
+public:
+   ForceLJ(const uint_t numParticleTypes);
+   ForceLJ(const ForceLJ& other) = default;
+   ForceLJ(ForceLJ&& other) = default;
+   ForceLJ& operator=(const ForceLJ& other) = default;
+   ForceLJ& operator=(ForceLJ&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx, const size_t np_idx, Accessor& ac) const;
+
+   {% for param in parameters %}
+   /// assumes this parameter is symmetric
+   void set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val);
+   {%- endfor %}
+
+   {% for param in parameters %}
+   real_t get{{param | capFirst}}(const size_t type1, const size_t type2) const;
+   {%- endfor %}
+private:
+   uint_t numParticleTypes_;
+   {% for param in parameters %}
+   std::vector<real_t> {{param}} {};
+   {%- endfor %}
+};
+
+ForceLJ::ForceLJ(const uint_t numParticleTypes)
+{
+   numParticleTypes_ = numParticleTypes;
+   {% for param in parameters %}
+   {{param}}.resize(numParticleTypes * numParticleTypes, real_t(0));
+   {%- endfor %}
+}
+
+{% for param in parameters %}
+inline void ForceLJ::set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   {{param}}[numParticleTypes_*type1 + type2] = val;
+   {{param}}[numParticleTypes_*type2 + type1] = val;
+}
+{%- endfor %}
+
+{% for param in parameters %}
+inline real_t ForceLJ::get{{param | capFirst}}(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( {{param}}[numParticleTypes_*type1 + type2],
+                                {{param}}[numParticleTypes_*type2 + type1],
+                                "parameter matrix for {{param}} not symmetric!");
+   return {{param}}[numParticleTypes_*type1 + type2];
+}
+{%- endfor %}
+
+template <typename Accessor>
+inline void ForceLJ::operator()(const size_t p_idx, const size_t np_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx != np_idx)
+   {
+      Vec3 dir = ac.getPosition(p_idx) - ac.getPosition(np_idx);
+      const real_t rsq = sqrLength(dir);
+      const real_t sr2 = real_t(1.0) / rsq;
+      const real_t sr2sigma = sr2 * getSigma(ac.getType(p_idx), ac.getType(np_idx)) * getSigma(ac.getType(p_idx), ac.getType(np_idx));
+      const real_t sr6 = sr2sigma * sr2sigma * sr2sigma;
+      const real_t force = real_t(48) * sr6 * ( sr6 - real_t(0.5) ) * sr2 * getEpsilon(ac.getType(p_idx), ac.getType(np_idx));
+      const Vec3 f = force * dir;
+
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[0]  += f[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[1]  += f[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[2]  += f[2];
+
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(np_idx)[0]  -= f[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(np_idx)[1]  -= f[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(np_idx)[2]  -= f[2];
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/HeatConduction.templ.h b/python/mesa_pd/templates/kernel/HeatConduction.templ.h
new file mode 100644
index 000000000..8fe94e6da
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/HeatConduction.templ.h
@@ -0,0 +1,138 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file HeatConduction.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/ParticleFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <core/math/Constants.h>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Basic DEM kernel
+ *
+ * This DEM kernel supports spring&dashpot in normal direction as well as friction in tangential direction.
+ *
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class HeatConduction
+{
+public:
+   HeatConduction(const uint_t numParticleTypes);
+   HeatConduction(const HeatConduction& other) = default;
+   HeatConduction(HeatConduction&& other) = default;
+   HeatConduction& operator=(const HeatConduction& other) = default;
+   HeatConduction& operator=(HeatConduction&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac) const;
+
+   {% for param in parameters %}
+   /// assumes this parameter is symmetric
+   void set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val);
+   {%- endfor %}
+
+   {% for param in parameters %}
+   real_t get{{param | capFirst}}(const size_t type1, const size_t type2) const;
+   {%- endfor %}
+
+private:
+   uint_t numParticleTypes_;
+   {% for param in parameters %}
+   std::vector<real_t> {{param}}_ {};
+   {%- endfor %}
+};
+
+HeatConduction::HeatConduction(const uint_t numParticleTypes)
+{
+   numParticleTypes_ = numParticleTypes;
+   {% for param in parameters %}
+   {{param}}_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   {%- endfor %}
+}
+
+{% for param in parameters %}
+inline void HeatConduction::set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   {{param}}_[numParticleTypes_*type1 + type2] = val;
+   {{param}}_[numParticleTypes_*type2 + type1] = val;
+}
+{%- endfor %}
+
+{% for param in parameters %}
+inline real_t HeatConduction::get{{param | capFirst}}(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( {{param}}_[numParticleTypes_*type1 + type2],
+                                {{param}}_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for {{param}} not symmetric!");
+   return {{param}}_[numParticleTypes_*type1 + type2];
+}
+{%- endfor %}
+
+template <typename Accessor>
+inline void HeatConduction::operator()(const size_t p_idx1,
+                                       const size_t p_idx2,
+                                       Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx1 != p_idx2)
+   {
+      auto deltaT = ac.getTemperature(p_idx1) - ac.getTemperature(p_idx2);
+      ac.getHeatFluxRef(p_idx1) -= deltaT * getConductance(ac.getType(p_idx1), ac.getType(p_idx2));
+      ac.getHeatFluxRef(p_idx2) += deltaT * getConductance(ac.getType(p_idx1), ac.getType(p_idx2));
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/InsertParticleIntoLinkedCells.templ.h b/python/mesa_pd/templates/kernel/InsertParticleIntoLinkedCells.templ.h
new file mode 100644
index 000000000..ac2bcc557
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/InsertParticleIntoLinkedCells.templ.h
@@ -0,0 +1,94 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file InsertParticleIntoLinkedCells.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/LinkedCells.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Inserts a particle into the data::LinkedCells data structure
+ *
+ * \attention Make sure to data::LinkedCells::clear() the data structure before
+ * reinserting new particles.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class InsertParticleIntoLinkedCells
+{
+public:
+   template <typename Accessor>
+   void operator()(const size_t p_idx, Accessor& ac, data::LinkedCells& lc) const;
+};
+
+template <typename Accessor>
+inline void InsertParticleIntoLinkedCells::operator()(const size_t p_idx, Accessor& ac, data::LinkedCells& lc) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   const auto& minCorner = lc.domain_.minCorner();
+   if (data::particle_flags::isSet(ac.getFlags(p_idx), data::particle_flags::INFINITE))
+   {
+      ac.setNextParticle(p_idx, lc.infiniteParticles_.exchange(int_c(p_idx)));
+   } else
+   {
+      {%- for dim in range(3) %}
+      int hash{{dim}} = static_cast<int>(std::floor((ac.getPosition(p_idx)[{{dim}}] - minCorner[{{dim}}]) * lc.invCellDiameter_[{{dim}}]));
+      {%- endfor %}
+      {%- for dim in range(3) %}
+      if (hash{{dim}} < 0) hash{{dim}} = 0;
+      if (hash{{dim}} >= lc.numCellsPerDim_[{{dim}}]) hash{{dim}} = lc.numCellsPerDim_[{{dim}}] - 1;
+      {%- endfor %}
+      int cell_idx = getCellIdx(lc, hash0, hash1, hash2);
+      ac.setNextParticle(p_idx, lc.cells_[uint_c(cell_idx)].exchange(int_c(p_idx)));
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/LinearSpringDashpot.templ.h b/python/mesa_pd/templates/kernel/LinearSpringDashpot.templ.h
new file mode 100644
index 000000000..f5c1b8e22
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/LinearSpringDashpot.templ.h
@@ -0,0 +1,253 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LinearSpringDashpot.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/ParticleFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <core/logging/Logging.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Advanced DEM kernel
+ *
+ * This model is a linearized version of
+ * Edward Biegert, Bernhard Vowinckel, Eckart Meiburg
+ * A collision model for grain-resolving simulations of flows over dense, mobile, polydisperse granular sediment beds
+ * https://doi.org/10.1016/j.jcp.2017.03.035
+ *
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class LinearSpringDashpot
+{
+public:
+   LinearSpringDashpot(const uint_t numParticleTypes);
+   LinearSpringDashpot(const LinearSpringDashpot& other) = default;
+   LinearSpringDashpot(LinearSpringDashpot&& other) = default;
+   LinearSpringDashpot& operator=(const LinearSpringDashpot& other) = default;
+   LinearSpringDashpot& operator=(LinearSpringDashpot&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac,
+                   const Vec3& contactPoint,
+                   const Vec3& contactNormal,
+                   const real_t& penetrationDepth,
+                   const real_t& dt) const;
+
+   {% for param in parameters %}
+   /// assumes this parameter is symmetric
+   void set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val);
+   {%- endfor %}
+
+   {% for param in parameters %}
+   real_t get{{param | capFirst}}(const size_t type1, const size_t type2) const;
+   {%- endfor %}
+private:
+   uint_t numParticleTypes_;
+   {% for param in parameters %}
+   std::vector<real_t> {{param}}_ {};
+   {%- endfor %}
+};
+
+LinearSpringDashpot::LinearSpringDashpot(const uint_t numParticleTypes)
+{
+   numParticleTypes_ = numParticleTypes;
+   {% for param in parameters %}
+   {{param}}_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   {%- endfor %}
+}
+
+{% for param in parameters %}
+inline void LinearSpringDashpot::set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   {{param}}_[numParticleTypes_*type1 + type2] = val;
+   {{param}}_[numParticleTypes_*type2 + type1] = val;
+}
+{%- endfor %}
+
+{% for param in parameters %}
+inline real_t LinearSpringDashpot::get{{param | capFirst}}(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( {{param}}_[numParticleTypes_*type1 + type2],
+                                {{param}}_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for {{param}} not symmetric!");
+   return {{param}}_[numParticleTypes_*type1 + type2];
+}
+{%- endfor %}
+
+template <typename Accessor>
+inline void LinearSpringDashpot::operator()(const size_t p_idx1,
+                                            const size_t p_idx2,
+                                            Accessor& ac,
+                                            const Vec3& contactPoint,
+                                            const Vec3& contactNormal,
+                                            const real_t& penetrationDepth,
+                                            const real_t& dt) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx1 != p_idx2)
+   {
+
+      WALBERLA_ASSERT_FLOAT_EQUAL(math::sqrLength(contactNormal), real_t(1));
+
+      real_t delta = -penetrationDepth;
+      if (delta < real_t(0)) return;
+
+
+      const Vec3 relVel ( -(getVelocityAtWFPoint(p_idx1, ac, contactPoint) - getVelocityAtWFPoint(p_idx2, ac, contactPoint)) );
+      const Vec3 relVelN( math::dot(relVel, contactNormal) * contactNormal );
+      const Vec3 relVelT( relVel - relVelN );
+
+
+      // calculate the normal force based on a linear spring-dashpot force model
+      Vec3 fN = getStiffnessN(ac.getType(p_idx1), ac.getType(p_idx2)) * delta * contactNormal +
+                getDampingN(ac.getType(p_idx1), ac.getType(p_idx2)) * relVelN;
+
+
+      // get further collision properties from the contact history or initialize them
+      Vec3 tangentialSpringDisplacement( real_t(0) );
+      real_t impactVelocityMagnitude(real_t(0));
+      bool isSticking = false;
+      auto contactHistory = ac.getOldContactHistoryRef(p_idx1).find(ac.getUid(p_idx2)); //TODO assert symmetry
+      if(contactHistory != ac.getOldContactHistoryRef(p_idx1).end())
+      {
+         // get infos from the contact history
+         tangentialSpringDisplacement = contactHistory->second.getTangentialSpringDisplacement();
+         isSticking = contactHistory->second.getIsSticking();
+         impactVelocityMagnitude = contactHistory->second.getImpactVelocityMagnitude();
+
+      }else
+      {
+         // new contact: initialize values
+         impactVelocityMagnitude = relVel.length();
+      }
+
+      //TODO: move to own tangential integration kernel?
+      Vec3 rotatedTangentialDisplacement = tangentialSpringDisplacement - contactNormal * (contactNormal * tangentialSpringDisplacement);
+      Vec3 newTangentialSpringDisplacement = rotatedTangentialDisplacement.sqrLength() <= real_t(0) ? // avoid division by zero
+                                             Vec3(real_t(0)) :
+                                             ( rotatedTangentialDisplacement * std::sqrt((tangentialSpringDisplacement.sqrLength() / rotatedTangentialDisplacement.sqrLength())));
+      newTangentialSpringDisplacement = newTangentialSpringDisplacement + dt * relVelT;
+
+      // calculate the tangential force based on a linear spring-dashpot force model
+      const real_t stiffnessT = getStiffnessT(ac.getType(p_idx1), ac.getType(p_idx2));
+      const real_t dampingT = getDampingT(ac.getType(p_idx1), ac.getType(p_idx2));
+      Vec3 fTLS = stiffnessT * newTangentialSpringDisplacement +
+                  dampingT * relVelT;
+
+      const Vec3 t = fTLS.getNormalizedOrZero(); // tangential unit vector
+
+      // calculate friction force
+      const real_t fFrictionAbsStatic = getFrictionCoefficientStatic(ac.getType(p_idx1), ac.getType(p_idx2)) * fN.length(); // sticking, rolling
+      const real_t fFrictionAbsDynamic = getFrictionCoefficientDynamic(ac.getType(p_idx1), ac.getType(p_idx2)) * fN.length(); // sliding
+
+      const real_t tangentialVelocityThreshold = real_t(1e-8);
+
+      real_t fFrictionAbs;
+      if( isSticking && relVelT.length() < tangentialVelocityThreshold && fTLS.length() < fFrictionAbsStatic  )
+      {
+         fFrictionAbs = fFrictionAbsStatic;
+      }
+      else if( isSticking && fTLS.length() < fFrictionAbsDynamic )
+      {
+         // sticking
+         fFrictionAbs = fFrictionAbsDynamic;
+      }
+      else
+      {
+         // slipping
+         fFrictionAbs = fFrictionAbsDynamic;
+
+         isSticking = false;
+
+         // reset displacement vector
+         if(stiffnessT > real_t(0) ) newTangentialSpringDisplacement = ( fFrictionAbs * t - dampingT * relVelT ) / stiffnessT;
+
+         // if tangential force falls below coulomb limit, we are back in sticking
+         if( fTLS.length() < fFrictionAbsDynamic )
+         {
+            //TODO really?
+            isSticking = true;
+         }
+      }
+
+      const real_t fTabs( std::min( fTLS.length(), fFrictionAbs) );
+      const Vec3   fT   ( fTabs * t );
+
+      //TODO check if tangential spring displacement is same for symmetric case
+      auto& ch1 = ac.getNewContactHistoryRef(p_idx1)[ac.getUid(p_idx2)];
+      ch1.setTangentialSpringDisplacement(newTangentialSpringDisplacement);
+      ch1.setIsSticking(isSticking);
+      ch1.setImpactVelocityMagnitude(impactVelocityMagnitude);
+
+      auto& ch2 = ac.getNewContactHistoryRef(p_idx2)[ac.getUid(p_idx1)];
+      ch2.setTangentialSpringDisplacement(newTangentialSpringDisplacement);
+      ch2.setIsSticking(isSticking);
+      ch2.setImpactVelocityMagnitude(impactVelocityMagnitude);
+
+      // Add normal force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fN, contactPoint );
+      addForceAtWFPosAtomic( p_idx2, ac, -fN, contactPoint );
+
+      // Add tangential force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fT, contactPoint );
+      addForceAtWFPosAtomic( p_idx2, ac, -fT, contactPoint );
+
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/NonLinearSpringDashpot.templ.h b/python/mesa_pd/templates/kernel/NonLinearSpringDashpot.templ.h
new file mode 100644
index 000000000..45318c780
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/NonLinearSpringDashpot.templ.h
@@ -0,0 +1,273 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file NonLinearSpringDashpot.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/ParticleFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <core/logging/Logging.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Advanced DEM kernel
+ *
+ * This model is the model from
+ * Edward Biegert, Bernhard Vowinckel, Eckart Meiburg
+ * A collision model for grain-resolving simulations of flows over dense, mobile, polydisperse granular sediment beds
+ * https://doi.org/10.1016/j.jcp.2017.03.035
+ *
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class NonLinearSpringDashpot
+{
+public:
+   NonLinearSpringDashpot(const uint_t numParticleTypes, const real_t collisionTime);
+   NonLinearSpringDashpot(const NonLinearSpringDashpot& other) = default;
+   NonLinearSpringDashpot(NonLinearSpringDashpot&& other) = default;
+   NonLinearSpringDashpot& operator=(const NonLinearSpringDashpot& other) = default;
+   NonLinearSpringDashpot& operator=(NonLinearSpringDashpot&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac,
+                   const Vec3& contactPoint,
+                   const Vec3& contactNormal,
+                   const real_t& penetrationDepth,
+                   const real_t& dt) const;
+
+   void setCOR(const size_t type1, const size_t type2, const real_t& val);
+   {% for param in parameters %}
+   /// assumes this parameter is symmetric
+   void set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val);
+   {%- endfor %}
+
+   {% for param in parameters %}
+   real_t get{{param | capFirst}}(const size_t type1, const size_t type2) const;
+   {%- endfor %}
+private:
+   uint_t numParticleTypes_;
+   real_t collisionTime_;
+   {% for param in parameters %}
+   std::vector<real_t> {{param}}_ {};
+   {%- endfor %}
+};
+
+NonLinearSpringDashpot::NonLinearSpringDashpot(const uint_t numParticleTypes, const real_t collisionTime)
+{
+   numParticleTypes_ = numParticleTypes;
+   {% for param in parameters %}
+   {{param}}_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   {%- endfor %}
+   collisionTime_ = collisionTime;
+}
+
+inline void NonLinearSpringDashpot::setCOR(const size_t type1, const size_t type2, const real_t& val)
+{
+   auto lnVal = std::log(val);
+   auto lnValSqr = lnVal * lnVal;
+   setLnCORsqr(type1, type2, lnValSqr);
+}
+
+{% for param in parameters %}
+inline void NonLinearSpringDashpot::set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   {{param}}_[numParticleTypes_*type1 + type2] = val;
+   {{param}}_[numParticleTypes_*type2 + type1] = val;
+}
+{%- endfor %}
+
+{% for param in parameters %}
+inline real_t NonLinearSpringDashpot::get{{param | capFirst}}(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( {{param}}_[numParticleTypes_*type1 + type2],
+                                {{param}}_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for {{param}} not symmetric!");
+   return {{param}}_[numParticleTypes_*type1 + type2];
+}
+{%- endfor %}
+
+template <typename Accessor>
+inline void NonLinearSpringDashpot::operator()(const size_t p_idx1,
+                                            const size_t p_idx2,
+                                            Accessor& ac,
+                                            const Vec3& contactPoint,
+                                            const Vec3& contactNormal,
+                                            const real_t& penetrationDepth,
+                                            const real_t& dt) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx1 != p_idx2)
+   {
+
+      WALBERLA_ASSERT_FLOAT_EQUAL(math::sqrLength(contactNormal), real_t(1));
+
+      real_t delta = -penetrationDepth;
+      if (delta < real_t(0)) return;
+
+
+      const Vec3 relVel ( -(getVelocityAtWFPoint(p_idx1, ac, contactPoint) - getVelocityAtWFPoint(p_idx2, ac, contactPoint)) );
+      const Vec3 relVelN( math::dot(relVel, contactNormal) * contactNormal );
+      const Vec3 relVelT( relVel - relVelN );
+
+      // get further collision properties from the contact history or initialize them
+      Vec3 tangentialSpringDisplacement( real_t(0) );
+      real_t impactVelocityMagnitude(real_t(0));
+      bool isSticking = false;
+      auto contactHistory = ac.getOldContactHistoryRef(p_idx1).find(ac.getUid(p_idx2)); //TODO assert symmetry
+      if(contactHistory != ac.getOldContactHistoryRef(p_idx1).end())
+      {
+         // get infos from the contact history
+         tangentialSpringDisplacement = contactHistory->second.getTangentialSpringDisplacement();
+         isSticking = contactHistory->second.getIsSticking();
+         impactVelocityMagnitude = contactHistory->second.getImpactVelocityMagnitude();
+
+      }else
+      {
+         // new contact: initialize values
+         impactVelocityMagnitude = relVel.length();
+      }
+
+      // ACTM: adapt collision coefficients
+      const real_t A = real_t(0.716);
+      const real_t B = real_t(0.830);
+      const real_t C = real_t(0.744);
+      const real_t alpha = real_t(1.111);
+      const real_t tau_c0 = real_t(3.218);
+      const real_t nu = getLnCORsqr(ac.getType(p_idx1), ac.getType(p_idx2));
+      const real_t lambda = (-real_t(0.5) * C * nu + std::sqrt(real_t(0.25) * C * C * nu * nu + alpha * alpha * tau_c0 * tau_c0 * nu)) / (alpha * alpha * tau_c0 * tau_c0);
+      const real_t tStar = std::sqrt(real_t(1) - A * lambda - B * lambda * lambda) * collisionTime_ / tau_c0;
+      const real_t meff = getMeff(ac.getType(p_idx1), ac.getType(p_idx2));
+      const real_t dn = real_t(2) * lambda * meff / tStar;
+      const real_t kn = meff / std::sqrt(impactVelocityMagnitude * std::pow(tStar, real_t(5)));
+
+      // calculate the normal force based on a non-linear spring-dashpot force model
+      Vec3 fN = kn * std::pow(delta, real_t(3)/real_t(2)) * contactNormal + dn * relVelN;
+
+      //TODO: move to own tangential integration kernel?
+      Vec3 rotatedTangentialDisplacement = tangentialSpringDisplacement - contactNormal * (contactNormal * tangentialSpringDisplacement);
+      Vec3 newTangentialSpringDisplacement = rotatedTangentialDisplacement.sqrLength() <= real_t(0) ? // avoid division by zero
+                                             Vec3(real_t(0)) :
+                                             ( rotatedTangentialDisplacement * std::sqrt((tangentialSpringDisplacement.sqrLength() / rotatedTangentialDisplacement.sqrLength())));
+      newTangentialSpringDisplacement = newTangentialSpringDisplacement + dt * relVelT;
+
+      // calculate the tangential force based on a linear spring-dashpot force model
+      const real_t stiffnessT = getStiffnessT(ac.getType(p_idx1), ac.getType(p_idx2));
+      const real_t dampingT = getDampingT(ac.getType(p_idx1), ac.getType(p_idx2));
+      Vec3 fTLS = stiffnessT * newTangentialSpringDisplacement +
+                  dampingT * relVelT;
+
+      const Vec3 t = fTLS.getNormalizedOrZero(); // tangential unit vector
+
+      // calculate friction force
+      const real_t fFrictionAbsStatic = getFrictionCoefficientStatic(ac.getType(p_idx1), ac.getType(p_idx2)) * fN.length(); // sticking, rolling
+      const real_t fFrictionAbsDynamic = getFrictionCoefficientDynamic(ac.getType(p_idx1), ac.getType(p_idx2)) * fN.length(); // sliding
+
+      const real_t tangentialVelocityThreshold = real_t(1e-8);
+
+      real_t fFrictionAbs;
+      if( isSticking && relVelT.length() < tangentialVelocityThreshold && fTLS.length() < fFrictionAbsStatic  )
+      {
+         fFrictionAbs = fFrictionAbsStatic;
+      }
+      else if( isSticking && fTLS.length() < fFrictionAbsDynamic )
+      {
+         // sticking
+         fFrictionAbs = fFrictionAbsDynamic;
+      }
+      else
+      {
+         // slipping
+         fFrictionAbs = fFrictionAbsDynamic;
+
+         isSticking = false;
+
+         // reset displacement vector
+         if(stiffnessT > real_t(0) ) newTangentialSpringDisplacement = ( fFrictionAbs * t - dampingT * relVelT ) / stiffnessT;
+
+         // if tangential force falls below coulomb limit, we are back in sticking
+         if( fTLS.length() < fFrictionAbsDynamic )
+         {
+            //TODO really?
+            isSticking = true;
+         }
+      }
+
+      const real_t fTabs( std::min( fTLS.length(), fFrictionAbs) );
+      const Vec3   fT   ( fTabs * t );
+
+      //TODO check if tangential spring displacement is same for symmetric case
+      auto& ch1 = ac.getNewContactHistoryRef(p_idx1)[ac.getUid(p_idx2)];
+      ch1.setTangentialSpringDisplacement(newTangentialSpringDisplacement);
+      ch1.setIsSticking(isSticking);
+      ch1.setImpactVelocityMagnitude(impactVelocityMagnitude);
+
+      auto& ch2 = ac.getNewContactHistoryRef(p_idx2)[ac.getUid(p_idx1)];
+      ch2.setTangentialSpringDisplacement(newTangentialSpringDisplacement);
+      ch2.setIsSticking(isSticking);
+      ch2.setImpactVelocityMagnitude(impactVelocityMagnitude);
+
+      // Add normal force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fN, contactPoint );
+      addForceAtWFPosAtomic( p_idx2, ac, -fN, contactPoint );
+
+      // Add tangential force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fT, contactPoint );
+      addForceAtWFPosAtomic( p_idx2, ac, -fT, contactPoint );
+
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/SingleCast.templ.h b/python/mesa_pd/templates/kernel/SingleCast.templ.h
new file mode 100644
index 000000000..d9d771fd9
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/SingleCast.templ.h
@@ -0,0 +1,85 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SingleCast.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/shape/BaseShape.h>
+{%- for shape in shapes %}
+#include <mesa_pd/data/shape/{{shape}}.h>
+{%- endfor %}
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class SingleCast
+{
+public:
+   template <typename Accessor, typename func, typename... Args>
+   auto operator()( size_t idx, Accessor& ac, func& f, Args&&... args );
+};
+
+template <typename Accessor, typename func, typename... Args>
+auto SingleCast::operator()( size_t idx, Accessor& ac, func& f, Args&&... args )
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   using namespace mesa_pd::data;
+   switch (ac.getShape(idx)->getShapeType())
+   {
+      {%- for shape in shapes %}
+      case {{shape}}::SHAPE_TYPE : return f(idx, *static_cast<{{shape}}*>(ac.getShape(idx)), std::forward<Args>(args)...);
+      {%- endfor %}
+      default : WALBERLA_ABORT("Shape type (" << ac.getShape(idx)->getShapeType() << ") could not be determined!");
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/SpringDashpot.templ.h b/python/mesa_pd/templates/kernel/SpringDashpot.templ.h
new file mode 100644
index 000000000..1a6f39ca1
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/SpringDashpot.templ.h
@@ -0,0 +1,195 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SpringDashpot.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/ParticleFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <core/math/Constants.h>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Basic DEM kernel
+ *
+ * This DEM kernel supports spring&dashpot in normal direction as well as friction in tangential direction.
+ *
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class SpringDashpot
+{
+public:
+   SpringDashpot(const uint_t numParticleTypes);
+   SpringDashpot(const SpringDashpot& other) = default;
+   SpringDashpot(SpringDashpot&& other) = default;
+   SpringDashpot& operator=(const SpringDashpot& other) = default;
+   SpringDashpot& operator=(SpringDashpot&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac,
+                   const Vec3& contactPoint,
+                   const Vec3& contactNormal,
+                   const real_t& penetrationDepth) const;
+
+   {% for param in parameters %}
+   /// assumes this parameter is symmetric
+   void set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val);
+   {%- endfor %}
+
+   {% for param in parameters %}
+   real_t get{{param | capFirst}}(const size_t type1, const size_t type2) const;
+   {%- endfor %}
+
+   inline
+   real_t calcCoefficientOfRestitution(const size_t type1,
+                                       const size_t type2,
+                                       const real_t meff)
+   {
+      auto a = real_t(0.5) * getDampingN(type1, type2) / meff;
+      return std::exp(-a * math::M_PI / std::sqrt(getStiffness(type1, type2) / meff - a*a));
+   }
+
+   inline
+   real_t calcCollisionTime(const size_t type1,
+                            const size_t type2,
+                            const real_t meff)
+   {
+      auto a = real_t(0.5) * getDampingN(type1, type2) / meff;
+      return math::M_PI / std::sqrt( getStiffness(type1, type2)/meff - a*a);
+   }
+
+   inline
+   void setParametersFromCOR(const size_t type1,
+                             const size_t type2,
+                             const real_t cor,
+                             const real_t collisionTime,
+                             const real_t meff)
+   {
+      const real_t lnDryResCoeff = std::log(cor);
+      setStiffness(type1, type2, math::M_PI * math::M_PI * meff / ( collisionTime * collisionTime * ( real_t(1) - lnDryResCoeff * lnDryResCoeff / ( math::M_PI * math::M_PI + lnDryResCoeff* lnDryResCoeff ))  ));
+      setDampingN( type1, type2, - real_t(2) * std::sqrt( meff * getStiffness(type1, type2) ) * ( lnDryResCoeff / std::sqrt( math::M_PI * math::M_PI + ( lnDryResCoeff * lnDryResCoeff ) ) ));
+   }
+private:
+   uint_t numParticleTypes_;
+   {% for param in parameters %}
+   std::vector<real_t> {{param}}_ {};
+   {%- endfor %}
+};
+
+SpringDashpot::SpringDashpot(const uint_t numParticleTypes)
+{
+   numParticleTypes_ = numParticleTypes;
+   {% for param in parameters %}
+   {{param}}_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   {%- endfor %}
+}
+
+{% for param in parameters %}
+inline void SpringDashpot::set{{param | capFirst}}(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   {{param}}_[numParticleTypes_*type1 + type2] = val;
+   {{param}}_[numParticleTypes_*type2 + type1] = val;
+}
+{%- endfor %}
+
+{% for param in parameters %}
+inline real_t SpringDashpot::get{{param | capFirst}}(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( {{param}}_[numParticleTypes_*type1 + type2],
+                                {{param}}_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for {{param}} not symmetric!");
+   return {{param}}_[numParticleTypes_*type1 + type2];
+}
+{%- endfor %}
+
+template <typename Accessor>
+inline void SpringDashpot::operator()(const size_t p_idx1,
+                                      const size_t p_idx2,
+                                      Accessor& ac,
+                                      const Vec3& contactPoint,
+                                      const Vec3& contactNormal,
+                                      const real_t& penetrationDepth) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx1 != p_idx2)
+   {
+      // Global position of contact
+      const Vec3& gpos( contactPoint );
+      // The absolute value of the penetration length
+      real_t delta = -penetrationDepth;
+      if (delta < real_t(0)) return;
+
+      const Vec3   relVel ( -(getVelocityAtWFPoint(p_idx1, ac, gpos) - getVelocityAtWFPoint(p_idx2, ac, gpos)) );
+      const real_t relVelN( math::dot(relVel, contactNormal) );
+      const Vec3   relVelT( relVel - ( relVelN * contactNormal ) );
+
+      // Calculating the normal force based on a linear spring-dashpot force model
+      real_t fNabs = getStiffness(ac.getType(p_idx1), ac.getType(p_idx2)) * delta + getDampingN(ac.getType(p_idx1), ac.getType(p_idx2)) * relVelN;
+      const Vec3& fN = fNabs * contactNormal;
+
+      // Calculating the tangential force based on the model by Haff and Werner
+      const real_t fTabs( std::min( getDampingT(ac.getType(p_idx1), ac.getType(p_idx2)) * relVelT.length(), getFriction(ac.getType(p_idx1), ac.getType(p_idx2)) * fNabs ) );
+      const Vec3   fT   ( fTabs * relVelT.getNormalizedOrZero() );
+
+      // Add normal force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fN, gpos );
+      addForceAtWFPosAtomic( p_idx2, ac, -fN, gpos );
+
+      // Add tangential force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fT, gpos );
+      addForceAtWFPosAtomic( p_idx2, ac, -fT, gpos );
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/TemperatureIntegration.templ.h b/python/mesa_pd/templates/kernel/TemperatureIntegration.templ.h
new file mode 100644
index 000000000..92aa9f58d
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/TemperatureIntegration.templ.h
@@ -0,0 +1,127 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file TemperatureIntegration.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Kernel which explicitly integrates all particles in time.
+ * This integrator integrates velocity and position.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ *
+ * \pre  All forces acting on the particles have to be set.
+ * \post All forces are reset to 0.
+ * \ingroup mesa_pd_kernel
+ */
+class TemperatureIntegration
+{
+public:
+   TemperatureIntegration(const real_t dt, const uint_t numParticleTypes);
+   TemperatureIntegration(const TemperatureIntegration& other) = default;
+   TemperatureIntegration(TemperatureIntegration&& other) = default;
+   TemperatureIntegration& operator=(const TemperatureIntegration& other) = default;
+   TemperatureIntegration& operator=(TemperatureIntegration&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   {% for param in parameters %}
+   /// assumes this parameter is symmetric
+   void set{{param | capFirst}}(const size_t type, const real_t& val);
+   {%- endfor %}
+
+   {% for param in parameters %}
+   real_t get{{param | capFirst}}(const size_t type) const;
+   {%- endfor %}
+private:
+   real_t dt_ = real_t(0.0);
+
+   uint_t numParticleTypes_;
+   {% for param in parameters %}
+   std::vector<real_t> {{param}}_ {};
+   {%- endfor %}
+};
+
+TemperatureIntegration::TemperatureIntegration(const real_t dt, const uint_t numParticleTypes)
+   : dt_(dt)
+{
+   numParticleTypes_ = numParticleTypes;
+   {% for param in parameters %}
+   {{param}}_.resize(numParticleTypes, real_t(0));
+   {%- endfor %}
+}
+
+{% for param in parameters %}
+inline void TemperatureIntegration::set{{param | capFirst}}(const size_t type, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type, numParticleTypes_ );
+   {{param}}_[type] = val;
+}
+{%- endfor %}
+
+{% for param in parameters %}
+inline real_t TemperatureIntegration::get{{param | capFirst}}(const size_t type) const
+{
+   WALBERLA_ASSERT_LESS( type, numParticleTypes_ );
+   return {{param}}_[type];
+}
+{%- endfor %}
+
+template <typename Accessor>
+inline void TemperatureIntegration::operator()(const size_t idx,
+                                               Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   //formula for heat capacity
+   ac.setTemperature(idx, getInvHeatCapacity(ac.getType(idx)) * ac.getHeatFlux(idx) * dt_ + ac.getTemperature(idx));
+   ac.setHeatFlux   (idx, real_t(0));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/VelocityVerlet.templ.h b/python/mesa_pd/templates/kernel/VelocityVerlet.templ.h
new file mode 100644
index 000000000..a3df53325
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/VelocityVerlet.templ.h
@@ -0,0 +1,114 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file VelocityVerlet.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Velocity verlet integration for all particles.
+ *
+ * Velocit verlet integration is a two part kernel. preForceUpdate has to be
+ * called before the force calculation and postFroceUpdate afterwards. The
+ * integration is only complete when both functions are called. The integration
+ * is symplectic.
+ * \attention The force calculation has to be independent of velocity.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class VelocityVerletPreForceUpdate
+{
+public:
+   VelocityVerletPreForceUpdate(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   real_t dt_;
+};
+
+/// \see VelocityVerletPreForceUpdate
+class VelocityVerletPostForceUpdate
+{
+public:
+   VelocityVerletPostForceUpdate(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   real_t dt_;
+};
+
+template <typename Accessor>
+inline void VelocityVerletPreForceUpdate::operator()(const size_t p_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(p_idx), data::particle_flags::FIXED))
+   {
+      ac.setPosition(p_idx, ac.getPosition(p_idx) +
+                            ac.getLinearVelocity(p_idx) * dt_ +
+                            real_t(0.5) * ac.getInvMass(p_idx) * ac.getOldForce(p_idx) * dt_ * dt_);
+   }
+}
+
+template <typename Accessor>
+inline void VelocityVerletPostForceUpdate::operator()(const size_t p_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(p_idx), data::particle_flags::FIXED))
+   {
+      ac.setLinearVelocity(p_idx, ac.getLinearVelocity(p_idx) +
+                                  real_t(0.5) * ac.getInvMass(p_idx) * (ac.getOldForce(p_idx) + ac.getForce(p_idx)) * dt_);
+   }
+   ac.setOldForce(p_idx,       ac.getForce(p_idx));
+   ac.setForce(p_idx,          Vec3(real_t(0), real_t(0), real_t(0)));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/kernel/VelocityVerletWithShape.templ.h b/python/mesa_pd/templates/kernel/VelocityVerletWithShape.templ.h
new file mode 100644
index 000000000..c166e1be5
--- /dev/null
+++ b/python/mesa_pd/templates/kernel/VelocityVerletWithShape.templ.h
@@ -0,0 +1,137 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file VelocityVerletWithShape.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Velocity verlet integration for all particles.
+ *
+ * Velocit verlet integration is a two part kernel. preForceUpdate has to be
+ * called before the force calculation and postFroceUpdate afterwards. The
+ * integration is only complete when both functions are called. The integration
+ * is symplectic.
+ * \attention The force calculation has to be independent of velocity.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class VelocityVerletWithShapePreForceUpdate
+{
+public:
+   VelocityVerletWithShapePreForceUpdate(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   real_t dt_;
+};
+
+/// \see VelocityVerletPreForceUpdate
+class VelocityVerletWithShapePostForceUpdate
+{
+public:
+   VelocityVerletWithShapePostForceUpdate(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   real_t dt_;
+};
+
+template <typename Accessor>
+inline void VelocityVerletWithShapePreForceUpdate::operator()(const size_t p_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(p_idx), data::particle_flags::FIXED))
+   {
+      ac.setPosition(p_idx, ac.getPosition(p_idx) +
+                            ac.getLinearVelocity(p_idx) * dt_ +
+                            real_t(0.5) * ac.getInvMass(p_idx) * ac.getOldForce(p_idx) * dt_ * dt_);
+
+      const Vec3 wdot = math::transformMatrixRART(ac.getRotation(p_idx).getMatrix(),
+                                                  ac.getInvInertiaBF(p_idx)) * ac.getOldTorque(p_idx);
+
+      // Calculating the rotation angle
+      const Vec3 phi( ac.getAngularVelocity(p_idx) * dt_ + real_t(0.5) * wdot * dt_ * dt_);
+
+      // Calculating the new orientation
+      auto rotation = ac.getRotation(p_idx);
+      rotation.rotate( phi );
+      ac.setRotation(p_idx, rotation);
+   }
+}
+
+template <typename Accessor>
+inline void VelocityVerletWithShapePostForceUpdate::operator()(const size_t p_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(p_idx), data::particle_flags::FIXED))
+   {
+      ac.setLinearVelocity(p_idx, ac.getLinearVelocity(p_idx) +
+                                  real_t(0.5) * ac.getInvMass(p_idx) * (ac.getOldForce(p_idx) + ac.getForce(p_idx)) * dt_);
+
+
+      const auto torque = ac.getOldTorque(p_idx) + ac.getTorque(p_idx);
+      const Vec3 wdot = math::transformMatrixRART(ac.getRotation(p_idx).getMatrix(),
+                                                  ac.getInvInertiaBF(p_idx)) * torque;
+
+      ac.setAngularVelocity(p_idx, ac.getAngularVelocity(p_idx) +
+                                   real_t(0.5) * wdot * dt_ );
+   }
+
+   ac.setOldForce(p_idx,       ac.getForce(p_idx));
+   ac.setForce(p_idx,          Vec3(real_t(0), real_t(0), real_t(0)));
+
+   ac.setOldTorque(p_idx,      ac.getTorque(p_idx));
+   ac.setTorque(p_idx,         Vec3(real_t(0), real_t(0), real_t(0)));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/templates/mpi/BroadcastProperty.templ.h b/python/mesa_pd/templates/mpi/BroadcastProperty.templ.h
new file mode 100644
index 000000000..2279df7d1
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/BroadcastProperty.templ.h
@@ -0,0 +1,135 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BroadcastProperty.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/mpi/BufferSystem.h>
+#include <core/logging/Logging.h>
+
+#include <type_traits>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Broadcast a property from the master particle to all corresponding ghost particles.
+ *
+ * \par Usage:
+ * The property which will be broadcasted can be selected by the Notification template
+ * (see ForceTorqueNotification).
+ * void update(data::Particle&& p, const Notification::Parameters& objparam)
+ * will be called to update the ghost particles with the information transmitted.
+ *
+ * \post
+ * - all ghost particles got updated
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class BroadcastProperty
+{
+public:
+   template <typename Notification>
+   void operator()(data::ParticleStorage& ps) const;
+
+   int64_t getBytesSent() const { return bs.getBytesSent(); }
+   int64_t getBytesReceived() const { return bs.getBytesReceived(); }
+
+   int64_t getNumberOfSends() const { return bs.getNumberOfSends(); }
+   int64_t getNumberOfReceives() const { return bs.getNumberOfReceives(); }
+private:
+   mutable walberla::mpi::BufferSystem bs = walberla::mpi::BufferSystem(walberla::mpi::MPIManager::instance()->comm() );
+
+   int numProcesses_ = walberla::mpi::MPIManager::instance()->numProcesses();
+};
+
+template <typename Notification>
+void BroadcastProperty::operator()(data::ParticleStorage& ps) const
+{
+   if (numProcesses_ == 1) return;
+
+   std::set<int> recvRanks; // potential message senders
+
+   WALBERLA_LOG_DETAIL( "Assembling of property reduction message starts...");
+
+   for( auto p : ps )
+   {
+      if (data::particle_flags::isSet( p.getFlags(), data::particle_flags::GHOST))
+      {
+         // Will receive message from the particles owner
+         recvRanks.insert(p.getOwner());
+      } else
+      {
+         //local particles should send the property to all ghost particles
+         for (auto& ghostRank : p.getGhostOwners())
+         {
+            auto& sb = bs.sendBuffer(ghostRank);
+            if (sb.isEmpty())
+            {
+               // fill empty buffers with a dummy byte to force transmission
+               sb << walberla::uint8_c(0);
+            }
+            sb << Notification( p );
+         }
+      }
+   }
+
+   WALBERLA_LOG_DETAIL( "Assembling of property broadcasting message ended." );
+
+   bs.setReceiverInfo(recvRanks, true);
+   bs.sendAll();
+
+   // Receiving the updates for the remote rigid bodies from the connected processes
+   WALBERLA_LOG_DETAIL( "Parsing of property broadcasting message starts..." );
+   for( auto it = bs.begin(); it != bs.end(); ++it )
+   {
+      walberla::uint8_t tmp;
+      it.buffer() >> tmp;
+      while( !it.buffer().isEmpty() )
+      {
+         typename Notification::Parameters objparam;
+         it.buffer() >> objparam;
+
+         WALBERLA_LOG_DETAIL( "Received reduction notification from neighboring process with rank " << it.rank() );
+
+         auto pIt = ps.find( objparam.uid_ );
+         WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+         update(*pIt, objparam);
+
+         WALBERLA_LOG_DETAIL( "Processed broadcasting notification for particle " << objparam.uid_ << "."  );
+      }
+   }
+   WALBERLA_LOG_DETAIL( "Parsing of property broadcasting message ended." );
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/python/mesa_pd/templates/mpi/ClearNextNeighborSync.templ.h b/python/mesa_pd/templates/mpi/ClearNextNeighborSync.templ.h
new file mode 100644
index 000000000..df3505f47
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/ClearNextNeighborSync.templ.h
@@ -0,0 +1,87 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ClearNextNeighborSync.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/Flags.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Clear all ghost particles and reset ghost owner information
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+ * const {{prop.type}}& get{{prop.name | capFirst}}(const size_t p_idx) const;
+   {%- endif %}
+   {%- if 's' in prop.access %}
+ * void set{{prop.name | capFirst}}(const size_t p_idx, const {{prop.type}}& v);
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+ * {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t p_idx);
+   {%- endif %}
+ *
+   {%- endfor %}
+ * \endcode
+ *
+ * \post All ghost particles are deleted.
+ * \post All ghost owners are reset.
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class ClearNextNeighborSync
+{
+public:
+   template <typename Accessor>
+   void operator()(Accessor& ac) const;
+};
+
+template <typename Accessor>
+void ClearNextNeighborSync::operator()(Accessor& ac) const
+{
+   for (size_t idx = 0; idx < ac.size(); )
+   {
+      if (data::particle_flags::isSet( ac.getFlags(idx), data::particle_flags::GHOST))
+      {
+         //ghost particle
+         idx = ac.erase(idx);
+         continue;
+      } else
+      {
+         //local particle
+         ac.getGhostOwnersRef(idx).clear();
+      }
+      ++idx;
+   }
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/python/mesa_pd/templates/mpi/ReduceContactHistory.templ.h b/python/mesa_pd/templates/mpi/ReduceContactHistory.templ.h
new file mode 100644
index 000000000..1798e721f
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/ReduceContactHistory.templ.h
@@ -0,0 +1,113 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ReduceContactHistory.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ContactHistory.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <mesa_pd/kernel/ParticleSelector.h>
+
+#include <mesa_pd/mpi/notifications/ContactHistoryNotification.h>
+#include <mesa_pd/mpi/ReduceProperty.h>
+
+#include <core/logging/Logging.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Reduces all contact history from ghost particles to the master particle.
+ *
+ * \attention
+ * This kernel *only* reduces the contact history. You have to manually
+ * broadcast it to ghost particles if needed. This can be done by either
+ * using BroadcastProperty or SyncNextNeighbors.
+ *
+ * \par Usage:
+ * The contact history aggregated by a call to this kernel is available in
+ * oldContactHistory. Use this data to write the current contact history
+ * to newContactHistory. The next call to this kernel will delete all
+ * contact data which is not stored in newContactHistory!
+ *
+ * \pre
+ * - up to date contact information has to be stored in newContactHistory
+ * - only contact information of local contacts should be stored
+ * - data in oldContactHistory will be overwritten
+ *
+ * \post
+ * - oldContactHistory for the master particle contains the reduced data
+ * - oldContactHistory for ghost particles is empty
+ * - newContactHistory is empty for master as well as ghost particles
+ *
+ * \internal
+ * Two contact histories are needed to decided which contacts are still persistent and
+ * which have been separated. OldContactHistory stores all contact information from the last
+ * time step. This can be used to populate newContactHistory. Processes should only populate
+ * newContactHistory if they have a persisting contact! During the reduce operation newContactHistory
+ * will be reduced effectively eliminating all contacts that have separated. Subsequently it
+ * is swapped with oldContactHistory to create the initial state. This is oldContactHistory
+ * has all information, newContactHistory is empty.
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class ReduceContactHistory
+{
+public:
+   void operator()(data::ParticleStorage& ps) const;
+private:
+   ReduceProperty RP_;
+
+   int numProcesses_ = walberla::mpi::MPIManager::instance()->numProcesses();
+};
+
+void ReduceContactHistory::operator()(data::ParticleStorage& ps) const
+{
+   //no need to reduce if run with only one process
+   if (numProcesses_ != 1)
+   {
+      RP_.operator()<ContactHistoryNotification>(ps);
+   }
+
+   const auto size = ps.size();
+   for (size_t idx = 0; idx < size; ++idx)
+   {
+      if (!data::particle_flags::isSet( ps.getFlags(idx), data::particle_flags::GHOST) )
+      {
+         std::swap(ps.getOldContactHistoryRef(idx), ps.getNewContactHistoryRef(idx));
+         ps.getNewContactHistoryRef(idx).clear();
+      } else
+      {
+         ps.getOldContactHistoryRef(idx).clear();
+         ps.getNewContactHistoryRef(idx).clear();
+      }
+   }
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/python/mesa_pd/templates/mpi/ReduceProperty.templ.h b/python/mesa_pd/templates/mpi/ReduceProperty.templ.h
new file mode 100644
index 000000000..99eab36d7
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/ReduceProperty.templ.h
@@ -0,0 +1,144 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SyncProperty.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/mpi/BufferSystem.h>
+#include <core/logging/Logging.h>
+
+#include <type_traits>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Reduce a property from all ghost particles to the corresponding master particle.
+ *
+ * \attention
+ * This kernel does not broadcast the reduced property to all ghost particles. Use
+ * BroadcastProperty or SyncNextNeighbors for that.
+ *
+ * \par Usage:
+ * The property which will be reduced can be selected by the Notification template
+ * (see ForceTorqueNotification).
+ * void reduce(data::Particle&& p, const Notification::Parameters& objparam)
+ * will be called to reduce the all incoming properties.
+ *
+ * \pre
+ * - the property is up to date on all ghost particles
+ *
+ * \post
+ * - the property at all ghost particles stays unchanged
+ * - the property at the master particle is the reduced one
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class ReduceProperty
+{
+public:
+   template <typename Notification>
+   void operator()(data::ParticleStorage& ps) const;
+
+   int64_t getBytesSent() const { return bs.getBytesSent(); }
+   int64_t getBytesReceived() const { return bs.getBytesReceived(); }
+
+   int64_t getNumberOfSends() const { return bs.getNumberOfSends(); }
+   int64_t getNumberOfReceives() const { return bs.getNumberOfReceives(); }
+private:
+   mutable walberla::mpi::BufferSystem bs = walberla::mpi::BufferSystem(walberla::mpi::MPIManager::instance()->comm() );
+
+   int numProcesses_ = walberla::mpi::MPIManager::instance()->numProcesses();
+};
+
+template <typename Notification>
+void ReduceProperty::operator()(data::ParticleStorage& ps) const
+{
+   if (numProcesses_ == 1) return;
+
+   std::set<int> recvRanks; // potential message senders
+
+   WALBERLA_LOG_DETAIL( "Assembling of property reduction message starts...");
+
+   for( auto p : ps )
+   {
+      if (data::particle_flags::isSet( p.getFlags(), data::particle_flags::GHOST))
+      {
+         //ghost particles should send their property
+         auto& sb = bs.sendBuffer(p.getOwner());
+         if (sb.isEmpty())
+         {
+            // fill empty buffers with a dummy byte to force transmission
+            sb << walberla::uint8_c(0);
+         }
+
+         sb << Notification( p );
+      } else
+      {
+         //local particles should receive the property and sum it up
+         for (auto& ghostRank : p.getGhostOwners())
+         {
+            recvRanks.insert(ghostRank);
+         }
+      }
+   }
+
+   WALBERLA_LOG_DETAIL( "Assembling of property reduction message ended." );
+
+   bs.setReceiverInfo(recvRanks, true);
+   bs.sendAll();
+
+   // Receiving the updates for the remote rigid bodies from the connected processes
+   WALBERLA_LOG_DETAIL( "Parsing of property reduction message starts..." );
+   for( auto it = bs.begin(); it != bs.end(); ++it )
+   {
+      walberla::uint8_t tmp;
+      it.buffer() >> tmp;
+      while( !it.buffer().isEmpty() )
+      {
+         typename Notification::Parameters objparam;
+         it.buffer() >> objparam;
+
+         WALBERLA_LOG_DETAIL( "Received reduction notification from neighboring process with rank " << it.rank() );
+
+         auto pIt = ps.find( objparam.uid_ );
+         WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+         reduce(*pIt, objparam);
+
+         WALBERLA_LOG_DETAIL( "Processed reduction notification for particle " << objparam.uid_ << "."  );
+      }
+   }
+   WALBERLA_LOG_DETAIL( "Parsing of property reduction message ended." );
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/python/mesa_pd/templates/mpi/SyncNextNeighbors.templ.cpp b/python/mesa_pd/templates/mpi/SyncNextNeighbors.templ.cpp
new file mode 100644
index 000000000..4765e4f13
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/SyncNextNeighbors.templ.cpp
@@ -0,0 +1,267 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SyncNextNeighbors.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include "SyncNextNeighbors.h"
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+void SyncNextNeighbors::operator()(data::ParticleStorage& ps,
+                                   const domain::IDomain& domain,
+                                   const real_t dx) const
+{
+   if (numProcesses_ == 1) return;
+
+   neighborRanks_ = domain.getNeighborProcesses();
+   for( uint_t nbProcessRank : neighborRanks_ )
+   {
+      if (bs.sendBuffer(nbProcessRank).isEmpty())
+      {
+         // fill empty buffers with a dummy byte to force transmission
+         bs.sendBuffer(nbProcessRank) << walberla::uint8_c(0);
+      }
+   }
+   generateSynchronizationMessages(ps, domain, dx);
+
+   // size of buffer is unknown and changes with each send
+   bs.setReceiverInfoFromSendBufferState(false, true);
+   bs.sendAll();
+
+   // Receiving the updates for the remote rigid bodies from the connected processes
+   WALBERLA_LOG_DETAIL( "Parsing of particle synchronization response starts..." );
+   ParseMessage parseMessage;
+   for( auto it = bs.begin(); it != bs.end(); ++it )
+   {
+      walberla::uint8_t tmp;
+      it.buffer() >> tmp;
+      while( !it.buffer().isEmpty() )
+      {
+         parseMessage(it.rank(), it.buffer(), ps, domain);
+      }
+   }
+   WALBERLA_LOG_DETAIL( "Parsing of particle synchronization response ended." );
+}
+
+/**
+ * Removes a particle from the local storage and informs ghost particle holders.
+ *
+ * This function removes the particle from the particle storage and generates deletion notifications.
+ */
+inline
+data::ParticleStorage::iterator removeAndNotify( walberla::mpi::BufferSystem& bs,
+                                                 data::ParticleStorage& ps,
+                                                 data::ParticleStorage::iterator& pIt )
+{
+   WALBERLA_ASSERT( !data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST),
+                    "Trying to remove ghost particle from the particle storage." );
+
+   WALBERLA_ASSERT( !data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GLOBAL),
+                    "Trying to remove a global particle from the particle storage." );
+
+   if( !pIt->getGhostOwners().empty() )
+   {
+      // Notify registered processes (intersecting or interacting) of particle removal since they possess a shadow copy.
+      for( auto ghostRank : pIt->getGhostOwnersRef() )
+      {
+         WALBERLA_LOG_DETAIL( "__Notify registered process " << ghostRank << " of deletion of particle " << pIt->getUid() );
+         auto& sb = bs.sendBuffer(ghostRank);
+         if (sb.isEmpty()) sb << walberla::uint8_c(0);
+         packNotification(sb, ParticleRemovalNotification( *pIt ));
+      }
+   }
+
+   pIt->getGhostOwnersRef().clear();
+   return ps.erase( pIt );
+}
+
+void SyncNextNeighbors::generateSynchronizationMessages(data::ParticleStorage& ps,
+                                                        const domain::IDomain& domain,
+                                                        const real_t dx) const
+{
+   const uint_t ownRank = uint_c(rank_);
+
+   WALBERLA_LOG_DETAIL( "Assembling of particle synchronization message starts..." );
+
+   // position update
+   for( auto pIt = ps.begin(); pIt != ps.end(); )
+   {
+      //skip all ghost particles
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST))
+      {
+         ++pIt;
+         continue;
+      }
+
+      //skip all particles that do not communicate (create ghost particles) on other processes
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::NON_COMMUNICATING))
+      {
+         ++pIt;
+         continue;
+      }
+
+      if (domain.isContainedInLocalSubdomain(pIt->getPosition(), pIt->getInteractionRadius()))
+      {
+         //no sync needed
+         //just delete ghost particles if there are any
+
+         for (const auto& ghostOwner : pIt->getGhostOwners() )
+         {
+            auto& buffer( bs.sendBuffer(ghostOwner) );
+
+            WALBERLA_LOG_DETAIL( "Sending removal notification for particle " << pIt->getUid() << " to process " << ghostOwner );
+
+            packNotification(buffer, ParticleRemovalNotification( *pIt ));
+         }
+
+         pIt->getGhostOwnersRef().clear();
+
+         ++pIt;
+         continue;
+      }
+
+      //correct position to make sure particle is always inside the domain!
+      //everything is decided by the master particle therefore ghost particles are not touched
+      if (!data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::FIXED) &&
+          !data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST))
+      {
+         domain.periodicallyMapToDomain( pIt->getPositionRef() );
+      }
+
+      // Note: At this point we know that the particle was locally owned before the position update.
+      WALBERLA_CHECK_EQUAL(pIt->getOwner(), ownRank);
+
+      WALBERLA_LOG_DETAIL( "Processing local particle " << pIt->getUid() );
+
+      // Update nearest neighbor processes.
+      for( uint_t nbProcessRank : neighborRanks_ )
+      {
+         if( domain.intersectsWithProcessSubdomain( nbProcessRank, pIt->getPosition(), pIt->getInteractionRadius() + dx ) )
+         {
+            auto ghostOwnerIt = std::find( pIt->getGhostOwners().begin(), pIt->getGhostOwners().end(), nbProcessRank );
+            if( ghostOwnerIt != pIt->getGhostOwners().end() )
+            {
+               // already a ghost there -> update
+               auto& buffer( bs.sendBuffer(nbProcessRank) );
+               WALBERLA_LOG_DETAIL( "Sending update notification for particle " << pIt->getUid() << " to process " << (nbProcessRank) );
+               packNotification(buffer, ParticleUpdateNotification( *pIt ));
+            } else
+            {
+               // no ghost there -> create ghost
+               auto& buffer( bs.sendBuffer(nbProcessRank) );
+               WALBERLA_LOG_DETAIL( "Sending shadow copy notification for particle " << pIt->getUid() << " to process " << (nbProcessRank) );
+               packNotification(buffer, ParticleCopyNotification( *pIt ));
+               pIt->getGhostOwnersRef().emplace_back( int_c(nbProcessRank) );
+            }
+         }
+         else
+         {
+            //no overlap with neighboring process -> delete if ghost is there
+            auto ghostOwnerIt = std::find( pIt->getGhostOwners().begin(), pIt->getGhostOwners().end(), nbProcessRank );
+            if( ghostOwnerIt != pIt->getGhostOwners().end() )
+            {
+               // In case the rigid particle no longer intersects the remote process nor interacts with it but is registered,
+               // send removal notification.
+               auto& buffer( bs.sendBuffer(nbProcessRank) );
+
+               WALBERLA_LOG_DETAIL( "Sending removal notification for particle " << pIt->getUid() << " to process " << nbProcessRank );
+
+               packNotification(buffer, ParticleRemovalNotification( *pIt ));
+
+               pIt->getGhostOwnersRef().erase(ghostOwnerIt);
+            }
+         }
+      }
+
+      //particle has left subdomain?
+      const auto ownerRank = domain.findContainingProcessRank( pIt->getPosition() );
+      if( ownerRank != int_c(ownRank) )
+      {
+         WALBERLA_LOG_DETAIL( "Local particle " << pIt->getUid() << " is no longer on process " << ownRank << " but on process " << ownerRank );
+
+         if( ownerRank < 0 ) {
+            // No owner found: Outflow condition.
+            WALBERLA_LOG_DETAIL( "Sending deletion notifications for particle " << pIt->getUid() << " due to outflow." );
+
+            // Registered processes receive removal notification in the remove() routine.
+            pIt = removeAndNotify( bs, ps, pIt );
+
+            continue;
+         }
+
+         WALBERLA_LOG_DETAIL( "Sending migration notification for particle " << pIt->getUid() << " to process " << ownerRank << "." );
+         //WALBERLA_LOG_DETAIL( "Process registration list before migration: " << pIt->getGhostOwners() );
+
+         // Set new owner and transform to ghost particle
+         pIt->setOwner(ownerRank);
+         data::particle_flags::set( pIt->getFlagsRef(), data::particle_flags::GHOST );
+
+         // currently position is mapped to periodically to global domain,
+         // this might not be the correct position for a ghost particle
+         domain.correctParticlePosition( pIt->getPositionRef() );
+
+         // Correct registration list (exclude new owner and us - the old owner) and
+         // notify registered processes (except for new owner) of (remote) migration since they possess a ghost particle.
+         auto ownerIt = std::find( pIt->getGhostOwners().begin(), pIt->getGhostOwners().end(), ownerRank );
+         WALBERLA_CHECK_UNEQUAL(ownerIt, pIt->getGhostOwners().end(), "New owner has to be former ghost owner!" );
+
+         pIt->getGhostOwnersRef().erase( ownerIt );
+
+         for( auto ghostRank : pIt->getGhostOwners() )
+         {
+            auto& buffer( bs.sendBuffer(ghostRank) );
+
+            WALBERLA_LOG_DETAIL( "Sending remote migration notification for particle " << pIt->getUid() <<
+                                 " to process " << ghostRank );
+
+            packNotification(buffer, ParticleRemoteMigrationNotification( *pIt, ownerRank ));
+         }
+
+         pIt->getGhostOwnersRef().emplace_back( int_c(ownRank) );
+
+         // Send migration notification to new owner
+         auto& buffer( bs.sendBuffer(ownerRank) );
+         packNotification(buffer, ParticleMigrationNotification( *pIt ));
+
+         pIt->getGhostOwnersRef().clear();
+
+         continue;
+
+      } else
+      {
+         // particle still is locally owned after position update.
+         WALBERLA_LOG_DETAIL( "Owner of particle " << pIt->getUid() << " is still process " << pIt->getOwner() );
+      }
+
+      ++pIt;
+   }
+
+   WALBERLA_LOG_DETAIL( "Assembling of particle synchronization message ended." );
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/python/mesa_pd/templates/mpi/SyncNextNeighbors.templ.h b/python/mesa_pd/templates/mpi/SyncNextNeighbors.templ.h
new file mode 100644
index 000000000..73cf82002
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/SyncNextNeighbors.templ.h
@@ -0,0 +1,79 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SyncNextNeighbors.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/IDomain.h>
+#include <mesa_pd/mpi/notifications/PackNotification.h>
+#include <mesa_pd/mpi/notifications/ParseMessage.h>
+#include <mesa_pd/mpi/notifications/ParticleCopyNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleMigrationNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleRemoteMigrationNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleRemovalNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleUpdateNotification.h>
+
+#include <core/mpi/BufferSystem.h>
+#include <core/logging/Logging.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Kernel which updates all ghost particles.
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class SyncNextNeighbors
+{
+public:
+   void operator()(data::ParticleStorage& ps,
+                   const domain::IDomain& domain,
+                   const real_t dx = real_t(0)) const;
+
+   int64_t getBytesSent() const { return bs.getBytesSent(); }
+   int64_t getBytesReceived() const { return bs.getBytesReceived(); }
+
+   int64_t getNumberOfSends() const { return bs.getNumberOfSends(); }
+   int64_t getNumberOfReceives() const { return bs.getNumberOfReceives(); }
+private:
+   void generateSynchronizationMessages(data::ParticleStorage& ps,
+                                        const domain::IDomain& domain,
+                                        const real_t dx) const;
+   mutable std::vector<uint_t> neighborRanks_; ///cache for neighbor ranks -> will be updated in operator()
+
+   mutable walberla::mpi::BufferSystem bs = walberla::mpi::BufferSystem( walberla::mpi::MPIManager::instance()->comm() );
+
+   int numProcesses_ = walberla::mpi::MPIManager::instance()->numProcesses();
+   int rank_         = walberla::mpi::MPIManager::instance()->rank();
+};
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ContactHistoryNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/ContactHistoryNotification.templ.h
new file mode 100644
index 000000000..15ab081fd
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/ContactHistoryNotification.templ.h
@@ -0,0 +1,101 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ContactHistoryNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ContactHistory.h>
+#include <mesa_pd/data/DataTypes.h>
+
+#include <core/mpi/BufferDataTypeExtensions.h>
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Trasmits the contact history
+ */
+class ContactHistoryNotification
+{
+public:
+   struct Parameters
+   {
+      id_t uid_;
+      std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory> contactHistory_;
+   };
+
+   inline explicit ContactHistoryNotification( const data::Particle& p ) : p_(p) {}
+
+   const data::Particle& p_;
+};
+
+void reduce(data::Particle&& p, const ContactHistoryNotification::Parameters& objparam)
+{
+   auto& ch = p.getNewContactHistoryRef();
+   for (auto& entry : objparam.contactHistory_)
+   {
+      auto ret = ch.insert(entry);
+      WALBERLA_CHECK(ret.second, "entry already present: " << entry.first << " / " << entry.second);
+   }
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ContactHistoryNotification& obj )
+{
+   buf.addDebugMarker( "ft" );
+   buf << obj.p_.getUid();
+   buf << obj.p_.getNewContactHistory();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ContactHistoryNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "ft" );
+   buf >> objparam.uid_;
+   buf >> objparam.contactHistory_;
+   return buf;
+}
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ForceTorqueNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/ForceTorqueNotification.templ.h
new file mode 100644
index 000000000..f00db9e42
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/ForceTorqueNotification.templ.h
@@ -0,0 +1,113 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticlePropertyNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Trasmits force and torque information.
+ */
+class ForceTorqueNotification
+{
+public:
+   struct Parameters
+   {
+      id_t uid_;
+      Vec3 force_;
+      Vec3 torque_;
+   };
+
+   inline explicit ForceTorqueNotification( const data::Particle& p ) : p_(p) {}
+
+   const data::Particle& p_;
+};
+
+void reduce(data::Particle&& p, const ForceTorqueNotification::Parameters& objparam)
+{
+   p.getForceRef()  += objparam.force_;
+   p.getTorqueRef() += objparam.torque_;
+}
+
+void update(data::Particle&& p, const ForceTorqueNotification::Parameters& objparam)
+{
+   p.setForce(  objparam.force_  );
+   p.setTorque( objparam.torque_ );
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ForceTorqueNotification& obj )
+{
+   buf.addDebugMarker( "ft" );
+   buf << obj.p_.getUid();
+   buf << obj.p_.getForce();
+   buf << obj.p_.getTorque();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ForceTorqueNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "ft" );
+   buf >> objparam.uid_;
+   buf >> objparam.force_;
+   buf >> objparam.torque_;
+   return buf;
+}
+
+template< >
+struct BufferSizeTrait< mesa_pd::ForceTorqueNotification > {
+   static const bool constantSize = true;
+   static const uint_t size = BufferSizeTrait<id_t>::size +
+                              BufferSizeTrait<mesa_pd::Vec3>::size +
+                              BufferSizeTrait<mesa_pd::Vec3>::size +
+                              mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h
new file mode 100644
index 000000000..fa5caceda
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h
@@ -0,0 +1,107 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file HeatFluxNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Trasmits force and torque information.
+ */
+class HeatFluxNotification
+{
+public:
+   struct Parameters
+   {
+      id_t uid_;
+      real_t heatFlux_;
+   };
+
+   inline explicit HeatFluxNotification( const data::Particle& p ) : p_(p) {}
+
+   const data::Particle& p_;
+};
+
+void reduce(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
+{
+   p.getHeatFluxRef()  += objparam.heatFlux_;
+}
+
+void update(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
+{
+   p.setHeatFlux( objparam.heatFlux_ );
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::HeatFluxNotification& obj )
+{
+   buf.addDebugMarker( "hf" );
+   buf << obj.p_.getUid();
+   buf << obj.p_.getHeatFlux();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::HeatFluxNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "hf" );
+   buf >> objparam.uid_;
+   buf >> objparam.heatFlux_;
+   return buf;
+}
+
+template< >
+struct BufferSizeTrait< mesa_pd::HeatFluxNotification > {
+   static const bool constantSize = true;
+   static const uint_t size = BufferSizeTrait<id_t>::size +
+                              BufferSizeTrait<real_t>::size +
+                              mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ParseMessage.templ.h b/python/mesa_pd/templates/mpi/notifications/ParseMessage.templ.h
new file mode 100644
index 000000000..3bab14e36
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/ParseMessage.templ.h
@@ -0,0 +1,194 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParseMessage.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//! \brief Parsing of messages
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/IDomain.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+#include <mesa_pd/mpi/notifications/ParticleCopyNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleMigrationNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleRemoteMigrationNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleRemovalNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleUpdateNotification.h>
+
+#include <core/debug/Debug.h>
+#include <core/logging/Logging.h>
+#include <core/mpi/RecvBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+class ParseMessage
+{
+public:
+   void operator()(int sender,
+                   walberla::mpi::RecvBuffer& rb,
+                   data::ParticleStorage& ps,
+                   const domain::IDomain& domain);
+private:
+   int receiver_ = int_c( walberla::mpi::MPIManager::instance()->rank() );
+};
+
+inline
+void ParseMessage::operator()(int sender,
+                              walberla::mpi::RecvBuffer& rb,
+                              data::ParticleStorage& ps,
+                              const domain::IDomain& domain)
+{
+   NotificationType notificationType;
+   rb >> notificationType;
+
+   switch( notificationType ) {
+   case PARTICLE_COPY_NOTIFICATION: {
+      typename ParticleCopyNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_COPY_NOTIFICATION for particle " << objparam.uid << "from neighboring process with rank " << sender );
+
+      WALBERLA_CHECK_EQUAL( ps.find(objparam.uid), ps.end(), "Ghost particle with id " << objparam.uid << " already existend.");
+
+      auto pIt = createNewParticle(ps, objparam);
+
+      domain.correctParticlePosition(pIt->getPositionRef());
+
+      WALBERLA_CHECK(!data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST));
+      data::particle_flags::set(pIt->getFlagsRef(), data::particle_flags::GHOST);
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_COPY_NOTIFICATION for particle " << objparam.uid << "."  );
+
+      break;
+   }
+   case PARTICLE_UPDATE_NOTIFICATION: {
+      typename ParticleUpdateNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_UPDATE_NOTIFICATION for particle " << objparam.uid <<
+                           " from neighboring process with rank " << sender );
+
+      auto pIt = ps.find( objparam.uid );
+      WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+      WALBERLA_CHECK_EQUAL( pIt->getOwner(), sender, "Update notifications must be sent by owner." );
+      WALBERLA_CHECK(data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST),
+                     "Update notification must only concern shadow copies.");
+
+      {%- for prop in properties %}
+      {%- if prop.syncMode in ["ALWAYS"] %}
+      pIt->set{{prop.name | capFirst}}(objparam.{{prop.name}});
+      {%- endif %}
+      {%- endfor %}
+
+      domain.correctParticlePosition(pIt->getPositionRef());
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_UPDATE_NOTIFICATION." );
+
+      break;
+   }
+   case PARTICLE_MIGRATION_NOTIFICATION: {
+      ParticleMigrationNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_MIGRATION_NOTIFICATION for particle " << objparam.uid_ <<
+                           " from neighboring process with rank " << sender );
+
+      auto pIt = ps.find( objparam.uid_ );
+      WALBERLA_CHECK_UNEQUAL( pIt, ps.end(),
+                              "Object with id: " << objparam.uid_ << " not found! Cannot transfer ownership!" );
+      WALBERLA_CHECK_EQUAL( sender, pIt->getOwner(), "Migration notifications must be sent by previous owner.");
+      WALBERLA_CHECK(data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST),
+                     "Migration notification must only concern ghost particles");
+
+      WALBERLA_CHECK( domain.isContainedInProcessSubdomain( uint_c(receiver_), pIt->getPosition() ),
+                      "Receiving particle migration even though we do not own it." );
+
+      pIt->setOwner(receiver_);
+      data::particle_flags::unset(pIt->getFlagsRef(), data::particle_flags::GHOST);
+      {%- for prop in properties %}
+      {%- if prop.syncMode in ["MIGRATION"] %}
+      pIt->set{{prop.name | capFirst}}(objparam.{{prop.name}}_);
+      {%- endif %}
+      {%- endfor %}
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_MIGRATION_NOTIFICATION." );
+
+      break;
+   }
+   case PARTICLE_REMOTE_MIGRATION_NOTIFICATION: {
+      ParticleRemoteMigrationNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_REMOTE_MIGRATION_NOTIFICATION for particle " << objparam.uid_ <<
+                           " from neighboring process with rank " << sender <<
+                           " (previous owner):\nnew owner = " << objparam.newOwner_ );
+
+      auto pIt = ps.find( objparam.uid_ );
+      WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+      WALBERLA_CHECK_EQUAL( sender, pIt->getOwner(),
+                            "Remote migration notifications must be sent by previous owner." );
+      WALBERLA_CHECK(data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST),
+                     "Particles in remote migration notifications must be available as ghost particles in local process.");
+      WALBERLA_CHECK_UNEQUAL( objparam.newOwner_, receiver_,
+                              "Particles in remote migration notifications may not migrate to local process." );
+
+      pIt->setOwner(objparam.newOwner_);
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_REMOTE_MIGRATION_NOTIFICATION." );
+
+      break;
+   }
+   case PARTICLE_REMOVAL_NOTIFICATION: {
+      ParticleRemovalNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_REMOVAL_NOTIFICATION for particle " << objparam.uid_ <<
+                           " from neighboring process with rank " << sender << " (owner)." );
+
+      // Remove ghost particle as prompted.
+      auto pIt = ps.find( objparam.uid_ );
+      WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+      WALBERLA_CHECK(data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST),
+                     "Only ghost particles should be removed by this message.");
+
+      WALBERLA_CHECK_EQUAL( pIt->getOwner(), sender,
+                            "Only owner is allowed to send removal notifications." );
+
+      ps.erase(pIt);
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_REMOVAL_NOTIFICATION" );
+
+      break;
+   }
+   default:
+      throw std::runtime_error( "Received invalid notification type." );
+   }
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ParticleCopyNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/ParticleCopyNotification.templ.h
new file mode 100644
index 000000000..f9dbf1988
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/ParticleCopyNotification.templ.h
@@ -0,0 +1,118 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleCopyNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * A complete particle copy for a new ghost particle.
+ *
+ * Copies all properties marked COPY or ALWAYS.
+ */
+class ParticleCopyNotification
+{
+public:
+   struct Parameters
+   {
+      {%- for prop in properties %}
+      {%- if prop.syncMode in ["COPY", "ALWAYS"] %}
+      {{prop.type}} {{prop.name}} {{'{'}}{{prop.defValue}}{{'}'}};
+      {%- endif %}
+      {%- endfor %}
+   };
+
+   inline explicit ParticleCopyNotification( const data::Particle& particle ) : particle_(particle) {}
+   const data::Particle& particle_;
+};
+
+inline data::ParticleStorage::iterator createNewParticle(data::ParticleStorage& ps, const ParticleCopyNotification::Parameters& data)
+{
+   WALBERLA_ASSERT_EQUAL(ps.find(data.uid), ps.end(), "Particle with same uid already existent!");
+
+   auto pIt = ps.create(data.uid);
+   {%- for prop in properties %}
+   {%- if prop.syncMode in ["COPY", "ALWAYS"] %}
+   pIt->set{{prop.name | capFirst}}(data.{{prop.name}});
+   {%- endif %}
+   {%- endfor %}
+   return pIt;
+}
+
+template<>
+struct NotificationTrait<ParticleCopyNotification>
+{
+   static const NotificationType id = PARTICLE_COPY_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleCopyNotification& obj )
+{
+   buf.addDebugMarker( "cn" );
+   {%- for prop in properties %}
+   {%- if prop.syncMode in ["COPY", "ALWAYS"] %}
+   buf << obj.particle_.get{{prop.name | capFirst}}();
+   {%- endif %}
+   {%- endfor %}
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleCopyNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "cn" );
+   {%- for prop in properties %}
+   {%- if prop.syncMode in ["COPY", "ALWAYS"] %}
+   buf >> objparam.{{prop.name}};
+   {%- endif %}
+   {%- endfor %}
+   return buf;
+}
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ParticleMigrationNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/ParticleMigrationNotification.templ.h
new file mode 100644
index 000000000..3e78ed0b7
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/ParticleMigrationNotification.templ.h
@@ -0,0 +1,110 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleMigrationNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/BufferDataTypeExtensions.h>
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Migrate the particle to this process. Making the receiver the new owner.
+ */
+class ParticleMigrationNotification {
+public:
+   struct Parameters {
+      id_t uid_;
+      {%- for prop in properties %}
+      {%- if prop.syncMode in ["MIGRATION"] %}
+      {{prop.type}} {{prop.name}}_ {{'{'}}{{prop.defValue}}{{'}'}};
+      {%- endif %}
+      {%- endfor %}
+   };
+
+   inline explicit ParticleMigrationNotification( const data::Particle& particle ) : particle_(particle) {}
+   const data::Particle& particle_;
+};
+
+template<>
+struct NotificationTrait<ParticleMigrationNotification>
+{
+   static const NotificationType id = PARTICLE_MIGRATION_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleMigrationNotification& obj )
+{
+   buf.addDebugMarker( "mn" );
+   buf << obj.particle_.getUid();
+   {%- for prop in properties %}
+   {%- if prop.syncMode in ["MIGRATION"] %}
+   buf << obj.particle_.get{{prop.name | capFirst}}();
+   {%- endif %}
+   {%- endfor %}
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleMigrationNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "mn" );
+   buf >> objparam.uid_;
+   {%- for prop in properties %}
+   {%- if prop.syncMode in ["MIGRATION"] %}
+   buf >> objparam.{{prop.name}}_;
+   {%- endif %}
+   {%- endfor %}
+   return buf;
+}
+
+template<>
+struct BufferSizeTrait< mesa_pd::ParticleMigrationNotification > {
+   static const bool constantSize = false;
+};
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ParticleRemoteMigrationNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/ParticleRemoteMigrationNotification.templ.h
new file mode 100644
index 000000000..3a1cbceeb
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/ParticleRemoteMigrationNotification.templ.h
@@ -0,0 +1,99 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleRemoteMigrationNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * The ownership for one of the ghost particles has changed.
+ */
+class ParticleRemoteMigrationNotification {
+public:
+   struct Parameters {
+      id_t uid_;
+      int newOwner_;
+   };
+
+   inline ParticleRemoteMigrationNotification( const data::Particle& particle, const int& newOwner )
+      : particle_(particle), newOwner_(newOwner) {}
+   const data::Particle& particle_;
+   const int newOwner_;
+};
+
+template<>
+struct NotificationTrait<ParticleRemoteMigrationNotification>
+{
+   static const NotificationType id = PARTICLE_REMOTE_MIGRATION_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleRemoteMigrationNotification& obj )
+{
+   buf.addDebugMarker( "rm" );
+   buf << obj.particle_.getUid();
+   buf << obj.newOwner_;
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleRemoteMigrationNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "rm" );
+   buf >> objparam.uid_;
+   buf >> objparam.newOwner_;
+   return buf;
+}
+
+template <>
+struct BufferSizeTrait< mesa_pd::ParticleRemoteMigrationNotification > {
+   static const bool constantSize = true;
+   static const uint_t size = BufferSizeTrait<id_t>::size + BufferSizeTrait<int>::size + mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ParticleRemovalNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/ParticleRemovalNotification.templ.h
new file mode 100644
index 000000000..9b3e56b77
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/ParticleRemovalNotification.templ.h
@@ -0,0 +1,94 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleRemovalNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * The specified particle should be removed from the process.
+ */
+class ParticleRemovalNotification {
+public:
+   struct Parameters {
+      id_t uid_;
+   };
+
+   inline explicit ParticleRemovalNotification( const data::Particle& particle ) : particle_(particle) {}
+   const data::Particle& particle_;
+};
+
+template<>
+struct NotificationTrait<ParticleRemovalNotification>
+{
+   static const NotificationType id = PARTICLE_REMOVAL_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleRemovalNotification& obj )
+{
+   buf.addDebugMarker( "un" );
+   buf << obj.particle_.getUid();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleRemovalNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "un" );
+   buf >> objparam.uid_;
+   return buf;
+}
+
+template<>
+struct BufferSizeTrait< mesa_pd::ParticleRemovalNotification > {
+   static const bool constantSize = true;
+   static const uint_t size = BufferSizeTrait<id_t>::size + mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ParticleUpdateNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/ParticleUpdateNotification.templ.h
new file mode 100644
index 000000000..dd59c8217
--- /dev/null
+++ b/python/mesa_pd/templates/mpi/notifications/ParticleUpdateNotification.templ.h
@@ -0,0 +1,103 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleUpdateNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Updates a ghost particle.
+ *
+ * Sends all properties marked as ALWAYS.
+ */
+class ParticleUpdateNotification {
+public:
+   struct Parameters {
+   {%- for prop in properties %}
+   {%- if prop.syncMode in ["ALWAYS"] %}
+   {{prop.type}} {{prop.name}} {{'{'}}{{prop.defValue}}{{'}'}};
+   {%- endif %}
+   {%- endfor %}
+   };
+
+   inline explicit ParticleUpdateNotification( const data::Particle& particle ) : particle_(particle) {}
+   const data::Particle& particle_;
+};
+
+template<>
+struct NotificationTrait<ParticleUpdateNotification>
+{
+   static const NotificationType id = PARTICLE_UPDATE_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleUpdateNotification& obj )
+{
+   buf.addDebugMarker( "un" );
+   {%- for prop in properties %}
+   {%- if prop.syncMode in ["ALWAYS"] %}
+   buf << obj.particle_.get{{prop.name | capFirst}}();
+   {%- endif %}
+   {%- endfor %}
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleUpdateNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "un" );
+   {%- for prop in properties %}
+   {%- if prop.syncMode in ["ALWAYS"] %}
+   buf >> objparam.{{prop.name}};
+   {%- endif %}
+   {%- endfor %}
+   return buf;
+}
+
+} // mpi
+} // walberla
diff --git a/python/mesa_pd/templates/tests/CheckInterface.templ.cpp b/python/mesa_pd/templates/tests/CheckInterface.templ.cpp
new file mode 100644
index 000000000..6bbd8f4f3
--- /dev/null
+++ b/python/mesa_pd/templates/tests/CheckInterface.templ.cpp
@@ -0,0 +1,72 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file {{InterfaceTestName}}.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/{{KernelInclude}}>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+
+   {%- for prop in interface %}
+   {%- if 'g' in prop.access %}
+   const {{prop.type}}& get{{prop.name | capFirst}}(const size_t /*p_idx*/) const {return {{prop.name}}_;}
+   {%- endif %}
+   {%- if 's' in prop.access %}
+   void set{{prop.name | capFirst}}(const size_t /*p_idx*/, const {{prop.type}}& v) { {{prop.name}}_ = v;}
+   {%- endif %}
+   {%- if 'r' in prop.access %}
+   {{prop.type}}& get{{prop.name | capFirst}}Ref(const size_t /*p_idx*/) {return {{prop.name}}_;}
+   {%- endif %}
+   {% endfor %}
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   {%- for prop in interface %}
+   {{prop.type}} {{prop.name}}_;
+   {%- endfor %}
+};
+
+{{ExplicitInstantiation}}
+
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/python/mesa_pd/utility.py b/python/mesa_pd/utility.py
new file mode 100644
index 000000000..eff810487
--- /dev/null
+++ b/python/mesa_pd/utility.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+from jinja2 import Environment, FileSystemLoader
+import os
+
+class TerminalColor:
+   DEFAULT = '\033[0m'
+   GREEN   = '\033[92m'
+   YELLOW  = '\033[93m'
+   RED     = '\033[91m'
+
+def find(f, seq):
+   """Return first item in sequence where f(item) == True."""
+   for item in seq:
+      if f(item):
+         return item
+   return None
+
+def capFirst(s):
+   return s[0].capitalize() + s[1:]
+
+def getJinjaEnvironment():
+   dirname = os.path.dirname(__file__)
+   env = Environment(loader=FileSystemLoader(dirname + '/templates'))
+   env.filters['capFirst'] = capFirst
+   return env
+
+def checkInterface(path, template, accessor):
+   with open(path + template, 'r') as myfile:
+      data = myfile.read()
+   for prop in accessor.properties:
+      for func in prop.getFunctionNames():
+         if not (func in data):
+            raise RuntimeError("{} required but not used in kernel ({})".format(func, template))
+
+def generateFile(path, template, context = {}, filename=None):
+   if filename==None:
+      filename = template.replace(".templ", "")
+   env = getJinjaEnvironment()
+   print("generating: " + path + filename)
+   fout = open(path + filename, "wb")
+   content = env.get_template(template).render(context)
+   fout.write(content.encode('utf8'))
+   fout.close()
diff --git a/src/core/math/Quaternion.h b/src/core/math/Quaternion.h
index f61ac6f4d..dd3a4298a 100644
--- a/src/core/math/Quaternion.h
+++ b/src/core/math/Quaternion.h
@@ -235,7 +235,12 @@ template< typename Type >  // Data type of the quaternion
 inline Quaternion<Type>::Quaternion( Type r, Type i, Type j, Type k )
 {
    v_[0] = r; v_[1] = i; v_[2] = j; v_[3] = k;
-   WALBERLA_CHECK_FLOAT_EQUAL( r*r + i*i + j*j + k*k, Type(1), "Invalid quaternion parameters" );
+
+   WALBERLA_CHECK_FLOAT_EQUAL( r*r + i*i + j*j + k*k, Type(1), "Invalid quaternion parameters:\n" <<
+                               "r: " << r << "\n" <<
+                               "i: " << i << "\n" <<
+                               "j: " << j << "\n" <<
+                               "k: " << k);
 }
 //*************************************************************************************************
 
@@ -495,7 +500,9 @@ inline const Matrix3<Type> Quaternion<Type>::toRotationMatrix() const
 template< typename Type >
 inline Type Quaternion<Type>::getAngle() const
 {
-   return Type(2)*std::acos(v_[0]);
+   //due to numerical accuracy v_[0] might be slightly larger than 1
+   //which will results in nan for the acos function.
+   if (v_[0]<Type(1)) return Type(2)*std::acos(v_[0]); else return Type(0);
 }
 //*************************************************************************************************
 
@@ -510,11 +517,17 @@ inline Type Quaternion<Type>::getAngle() const
 template< typename Type >
 inline const Vector3<Type> Quaternion<Type>::getAxis() const
 {
-   Type s( std::sqrt( 1-v_[0]*v_[0] ) );
-   if (s < std::numeric_limits<Type>::epsilon())
+   // tmp might be negative due to numerical accuracy
+   // check before putting it into sqrt!!!
+   auto tmp = Type(1)-v_[0]*v_[0];
+
+   if (tmp < std::numeric_limits<Type>::epsilon())
+   {
       return Vector3<Type>( 1, 0, 0 );
-   else
+   } else {
+      Type s( std::sqrt( tmp ) );
       return Vector3<Type>( v_[1] / s, v_[2] / s, v_[3] / s );
+   }
 }
 //*************************************************************************************************
 
@@ -1054,6 +1067,7 @@ inline const Quaternion< typename MathTrait<T1,T2>::MultType >
    const MT k( lhs[0]*rhs[3] + lhs[3]*rhs[0] + lhs[1]*rhs[2] - lhs[2]*rhs[1] );
 
    const MT len2( r*r + i*i + j*j + k*k );
+   WALBERLA_ASSERT(!std::isnan(len2), lhs << "\n" << rhs);
 
    if( std::fabs( len2 - MT(1) ) < MT(1E-8) ) {
       return Quaternion<MT>( r, i, j, k );
diff --git a/src/core/math/Rot3.h b/src/core/math/Rot3.h
index 30b4952d6..71f8d629c 100644
--- a/src/core/math/Rot3.h
+++ b/src/core/math/Rot3.h
@@ -47,31 +47,72 @@ class Rot3
    /*! \endcond */
    //**********************************************************************************************
 public:
-   Rot3(const Quaternion<Type>& q);
+   Rot3();
+   Rot3(const Vector3<Type>& rot);
+   Rot3(const Vector3<Type>& axis, const real_t& angle);
+   Rot3(const Quaternion<Type> q);
 
    const Quaternion<Type>& getQuaternion() const { return quat_; }
    const Matrix3<Type>&    getMatrix() const { return mat_; }
 
-   void rotate(const Quaternion<Type>& q);
+   void rotate(const Vector3<Type>& rot);
+   void rotate(const Vector3<Type>& axis, const real_t& angle);
 private:
    Quaternion<Type> quat_;
    Matrix3<Type>    mat_;
 };
 
 template< typename Type >  // floating point type
-inline Rot3<Type>::Rot3(const Quaternion<Type>& q)
-   : quat_(q)
-   , mat_ (q.toRotationMatrix())
+inline Rot3<Type>::Rot3()
+   : quat_(Vector3<Type>(Type(1),Type(0),Type(0)), Type(0))
+   , mat_(quat_.toRotationMatrix())
 {
    WALBERLA_ASSERT_FLOAT_EQUAL( mat_.getDeterminant(), real_t(1), "Corrupted rotation matrix determinant" );
 }
 
 template< typename Type >  // floating point type
-inline void Rot3<Type>::rotate(const Quaternion<Type>& q)
+inline Rot3<Type>::Rot3(const Vector3<Type>& phi)
+   : Rot3<Type>()
 {
-   quat_ = q * quat_;
-   mat_  = quat_.toRotationMatrix();
-   WALBERLA_ASSERT_FLOAT_EQUAL( mat_.getDeterminant(), real_t(1), "Corrupted rotation matrix determinant" );
+   rotate(phi);
+}
+
+template< typename Type >  // floating point type
+inline Rot3<Type>::Rot3(const Vector3<Type>& axis, const real_t& angle)
+   : Rot3<Type>()
+{
+   rotate(axis, angle);
+}
+
+template< typename Type >  // floating point type
+inline Rot3<Type>::Rot3(const Quaternion<Type> q)
+   : quat_(q)
+   , mat_(quat_.toRotationMatrix())
+{}
+
+template< typename Type >  // floating point type
+inline void Rot3<Type>::rotate(const Vector3<Type>& phi)
+{
+   auto len = phi.length();
+   if (!floatIsEqual(len, 0))
+   {
+      auto q = Quaternion<Type>( phi, len );
+      quat_ = q * quat_;
+      mat_  = quat_.toRotationMatrix();
+      WALBERLA_ASSERT_FLOAT_EQUAL( mat_.getDeterminant(), real_t(1), "Corrupted rotation matrix determinant" );
+   }
+}
+
+template< typename Type >  // floating point type
+inline void Rot3<Type>::rotate(const Vector3<Type>& axis, const real_t& angle)
+{
+   if (!floatIsEqual(angle, 0))
+   {
+      auto q = Quaternion<Type>( axis, angle );
+      quat_ = q * quat_;
+      mat_  = quat_.toRotationMatrix();
+      WALBERLA_ASSERT_FLOAT_EQUAL( mat_.getDeterminant(), real_t(1), "Corrupted rotation matrix determinant" );
+   }
 }
 
 template< typename Type >  // floating point type
diff --git a/src/core/math/Vector3.h b/src/core/math/Vector3.h
index c322c1380..c5ec3e7b8 100644
--- a/src/core/math/Vector3.h
+++ b/src/core/math/Vector3.h
@@ -882,7 +882,7 @@ Vector3<typename Vector3<Type>::Length> Vector3<Type>::getNormalizedOrZero() con
                             static_cast<Length>( v_[1] ) * ilen,
                             static_cast<Length>( v_[2] ) * ilen );
 
-   WALBERLA_ASSERT_FLOAT_EQUAL( result.sqrLength(), 1.0 );
+   WALBERLA_ASSERT_FLOAT_EQUAL( result.sqrLength(), 1.0, "initial vector: " << result );
 
    return result;
 }
diff --git a/src/core/timing/TimingPool.cpp b/src/core/timing/TimingPool.cpp
index 372a81b6a..dff973201 100644
--- a/src/core/timing/TimingPool.cpp
+++ b/src/core/timing/TimingPool.cpp
@@ -295,7 +295,7 @@ void TimingPool<TP>::print( std::ostream & os ) const
       os << setw(firstColumn-1) << std::left  << i->first  << " "       << "|";
       os << setw(PERCENT_COLUMN-2) << std::right << std::fixed << std::setprecision(2) << percentage << "% |";
       os << setw(OTHER_COLUMNS) << std::right << ( ( count == uint_t(0) ) ? 0.0 : i->second.total() )    << "|";
-      os << setw(OTHER_COLUMNS) << std::right << ( ( count == uint_t(0) ) ? 0.0 : i->second.average() )  << "|";
+      os << setw(OTHER_COLUMNS) << std::right << std::setprecision(3) << ( ( count == uint_t(0) ) ? 0.0 : i->second.average() )  << "|";
       os << setw(OTHER_COLUMNS) << std::right << i->second.getCounter() << "|";
       os << setw(OTHER_COLUMNS) << std::right << ( ( count == uint_t(0) ) ? 0.0 : i->second.min() )      << "|";
       os << setw(OTHER_COLUMNS) << std::right << ( ( count == uint_t(0) ) ? 0.0 : i->second.max() )      << "|";
diff --git a/src/mesa_pd/CMakeLists.txt b/src/mesa_pd/CMakeLists.txt
new file mode 100644
index 000000000..744387e40
--- /dev/null
+++ b/src/mesa_pd/CMakeLists.txt
@@ -0,0 +1,8 @@
+
+###################################################################################################
+#
+# Module rigid particle dynamics (RPD)
+#
+###################################################################################################
+
+waLBerla_add_module( DEPENDS blockforest core pe stencil vtk )
diff --git a/src/mesa_pd/collision_detection/AnalyticCollisionFunctions.h b/src/mesa_pd/collision_detection/AnalyticCollisionFunctions.h
new file mode 100644
index 000000000..62f4f30e5
--- /dev/null
+++ b/src/mesa_pd/collision_detection/AnalyticCollisionFunctions.h
@@ -0,0 +1,92 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file AnalyticCollisionFunctions.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+
+#include <core/logging/Logging.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace collision_detection {
+namespace analytic {
+
+inline
+bool detectSphereSphereCollision( const Vec3&   pos1,
+                                  const real_t& radius1,
+                                  const Vec3&   pos2,
+                                  const real_t& radius2,
+                                        Vec3&   contactPoint,
+                                        Vec3&   contactNormal,
+                                        real_t& penetrationDepth,
+                                  const real_t& contactThreshold)
+{
+   contactNormal         = pos1 - pos2;
+   auto contactNormalSq  = contactNormal.sqrLength();
+   real_t separationDist = radius1 + radius2 + contactThreshold;
+
+   if( contactNormalSq < separationDist * separationDist ) {
+      penetrationDepth = std::sqrt(contactNormalSq) - radius1 - radius2;
+      math::normalize(contactNormal);
+      const real_t k( radius2 + real_c(0.5) * penetrationDepth );
+      contactPoint = ( pos2 + contactNormal * k );
+      return true;
+   }
+   return false;
+}
+
+inline
+bool detectSphereHalfSpaceCollision( const Vec3&   pos1,
+                                     const real_t& radius1,
+                                     const Vec3&   pos2,
+                                     const Vec3&   normal2,
+                                           Vec3&   contactPoint,
+                                           Vec3&   contactNormal,
+                                           real_t& penetrationDepth,
+                                     const real_t& contactThreshold)
+{
+   /**
+    * Plane displacement from the origin.
+    *
+    * The displacement can be categorized in the following way:\n
+    * - > 0: The global origin is inside the plane\n
+    * - < 0: The global origin is outside the plane\n
+    * - = 0: The global origin is on the surface of the plane
+   **/
+   const real_t d = math::dot(normal2, pos2);
+
+   const real_t k = math::dot(normal2, pos1);
+   penetrationDepth = ( k - radius1 - d );
+
+   if( penetrationDepth < contactThreshold )
+   {
+      contactPoint = ( pos1 - ( radius1 + penetrationDepth ) * normal2 );
+      contactNormal = normal2;
+      return true;
+   }
+   return false;
+}
+
+
+} //namespace collision_detection
+} //namespace analytic
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/collision_detection/AnalyticContactDetection.h b/src/mesa_pd/collision_detection/AnalyticContactDetection.h
new file mode 100644
index 000000000..dd5656bf5
--- /dev/null
+++ b/src/mesa_pd/collision_detection/AnalyticContactDetection.h
@@ -0,0 +1,178 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file AnalyticContactDetection.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/collision_detection/AnalyticCollisionFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/shape/BaseShape.h>
+#include <mesa_pd/data/shape/HalfSpace.h>
+#include <mesa_pd/data/shape/Sphere.h>
+
+#include <core/logging/Logging.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace collision_detection {
+
+/**
+ * Collision detection functor which uses analytic functions.
+ *
+ * Calculates and stores contact information between two particles.
+ * If a collision was successfully detected by the operator()
+ * contactPoint, contactNormal and penetrationDepth contain the
+ * contact information. If no collision was detected the values of
+ * these variables is undefined!
+ */
+class AnalyticContactDetection
+{
+public:
+   size_t& getIdx1() {return idx1_;}
+   size_t& getIdx2() {return idx2_;}
+   Vec3&   getContactPoint() {return contactPoint_;}
+   Vec3&   getContactNormal() {return contactNormal_;}
+   real_t& getPenetrationDepth() {return penetrationDepth_;}
+
+   const size_t& getIdx1() const {return idx1_;}
+   const size_t& getIdx2() const {return idx2_;}
+   const Vec3&   getContactPoint() const {return contactPoint_;}
+   const Vec3&   getContactNormal() const {return contactNormal_;}
+   const real_t& getPenetrationDepth() const {return penetrationDepth_;}
+
+   real_t& getContactThreshold() {return contactThreshold_;}
+
+   template <typename GEO1_T, typename GEO2_T, typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    const GEO1_T& geo1,
+                    const GEO2_T& geo2,
+                    Accessor& ac);
+
+   template <typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    const data::Sphere& geo1,
+                    const data::Sphere& geo2,
+                    Accessor& ac);
+
+   template <typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    const data::Sphere& s,
+                    const data::HalfSpace& p,
+                    Accessor& ac );
+
+   template <typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    const data::HalfSpace& p,
+                    const data::Sphere& s,
+                    Accessor& ac);
+
+private:
+   size_t idx1_;
+   size_t idx2_;
+   Vec3   contactPoint_;
+   Vec3   contactNormal_;
+   real_t penetrationDepth_;
+
+   real_t contactThreshold_ = real_t(0.0);
+};
+
+template <typename GEO1_T, typename GEO2_T, typename Accessor>
+inline bool AnalyticContactDetection::operator()( const size_t /*idx1*/,
+                                                  const size_t /*idx2*/,
+                                                  const GEO1_T& /*geo1*/,
+                                                  const GEO2_T& /*geo2*/,
+                                                  Accessor& /*ac*/)
+{
+   WALBERLA_ABORT("Collision not implemented!")
+}
+
+template <typename Accessor>
+inline bool AnalyticContactDetection::operator()( const size_t idx1,
+                                                  const size_t idx2,
+                                                  const data::Sphere& geo1,
+                                                  const data::Sphere& geo2,
+                                                  Accessor& ac)
+{
+   WALBERLA_ASSERT_UNEQUAL(idx1, idx2, "colliding with itself!");
+
+   //ensure collision order idx2 has to be larger than idx1
+   if (ac.getUid(idx2) < ac.getUid(idx1))
+      return operator()(idx2, idx1, geo2, geo1, ac);
+
+   getIdx1() = idx1;
+   getIdx2() = idx2;
+   return analytic::detectSphereSphereCollision(ac.getPosition(getIdx1()),
+                                                geo1.getRadius(),
+                                                ac.getPosition(getIdx2()),
+                                                geo2.getRadius(),
+                                                getContactPoint(),
+                                                getContactNormal(),
+                                                getPenetrationDepth(),
+                                                getContactThreshold());
+}
+
+template <typename Accessor>
+inline bool AnalyticContactDetection::operator()( const size_t idx1,
+                                                  const size_t idx2,
+                                                  const data::Sphere& s,
+                                                  const data::HalfSpace& p,
+                                                  Accessor& ac )
+{
+   WALBERLA_ASSERT_UNEQUAL(idx1, idx2, "colliding with itself!");
+
+   getIdx1() = idx1;
+   getIdx2() = idx2;
+   return analytic::detectSphereHalfSpaceCollision(ac.getPosition(getIdx1()),
+                                                   s.getRadius(),
+                                                   ac.getPosition(getIdx2()),
+                                                   p.getNormal(),
+                                                   getContactPoint(),
+                                                   getContactNormal(),
+                                                   getPenetrationDepth(),
+                                                   getContactThreshold());
+}
+
+template <typename Accessor>
+inline bool AnalyticContactDetection::operator()( const size_t idx1,
+                                                  const size_t idx2,
+                                                  const data::HalfSpace& p,
+                                                  const data::Sphere& s,
+                                                  Accessor& ac)
+{
+   return operator()(idx2, idx1, s, p, ac);
+}
+
+inline
+std::ostream& operator<<( std::ostream& os, const AnalyticContactDetection& ac )
+{
+   os << "idx1:               " << ac.getIdx1() << "\n" <<
+         "idx2:               " << ac.getIdx2() << "\n" <<
+         "contact point:      " << ac.getContactPoint() << "\n" <<
+         "contact normal:     " << ac.getContactNormal() << "\n" <<
+         "penetration depth:  " << ac.getPenetrationDepth() << std::endl;
+   return os;
+}
+
+} //namespace collision_detection
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/collision_detection/BroadPhase.h b/src/mesa_pd/collision_detection/BroadPhase.h
new file mode 100644
index 000000000..07fe49801
--- /dev/null
+++ b/src/mesa_pd/collision_detection/BroadPhase.h
@@ -0,0 +1,45 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BroadPhase.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+namespace walberla {
+namespace mesa_pd {
+namespace collision_detection {
+
+/**
+ * Checks if particle idx1 is within interaction distance with particle idx2.
+ * Collisions of infinite particles are also filtered.
+ */
+template <typename Accessor>
+bool isInInteractionDistance(const size_t idx1, const size_t idx2, Accessor& ac)
+{
+   using namespace data::particle_flags;
+   if (isSet(ac.getFlags(idx1), INFINITE)) return true;
+   if (isSet(ac.getFlags(idx2), INFINITE)) return true;
+   auto separationDist = ac.getInteractionRadius(idx1) + ac.getInteractionRadius(idx2);
+   auto realDist2 = (ac.getPosition(idx1) - ac.getPosition(idx2)).sqrLength();
+   if (realDist2 < separationDist*separationDist) return true;
+   return false;
+}
+
+} //namespace collision_detection
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/common/AABBConversion.h b/src/mesa_pd/common/AABBConversion.h
new file mode 100644
index 000000000..ade72f356
--- /dev/null
+++ b/src/mesa_pd/common/AABBConversion.h
@@ -0,0 +1,60 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file AABBConversion.h
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <core/math/AABB.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <limits>
+
+namespace walberla {
+namespace mesa_pd {
+
+math::AABB getAABBFromInteractionRadius(const Vector3<real_t> & pos, const real_t interactionRadius )
+{
+   return math::AABB( pos[0]-interactionRadius, pos[1]-interactionRadius, pos[2]-interactionRadius,
+                      pos[0]+interactionRadius, pos[1]+interactionRadius, pos[2]+interactionRadius );
+}
+
+
+// requires: interactionRadius
+// flags: infinite
+template<typename ParticleAccessor_T>
+math::AABB getParticleAABB(const size_t particleIdx, const ParticleAccessor_T& ac)
+{
+   static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+   if( mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::INFINITE) )
+   {
+      auto inf = std::numeric_limits<real_t>::infinity();
+      return math::AABB( -inf, -inf, -inf,
+                          inf,  inf,  inf);
+   }
+   else
+   {
+      return getAABBFromInteractionRadius(ac.getPosition(particleIdx), ac.getInteractionRadius(particleIdx));
+   }
+}
+
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/common/Contains.h b/src/mesa_pd/common/Contains.h
new file mode 100644
index 000000000..0e98ed9c4
--- /dev/null
+++ b/src/mesa_pd/common/Contains.h
@@ -0,0 +1,78 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file Contains.h
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/shape/HalfSpace.h>
+#include <mesa_pd/data/shape/Sphere.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/*
+ * contains functionality
+ */
+
+bool isPointInsideSphere(const Vector3<real_t>& point,
+                         const Vector3<real_t>& spherePosition, const real_t sphereRadius )
+{
+   return !((point - spherePosition).sqrLength() > sphereRadius * sphereRadius);
+}
+
+bool isPointInsideHalfSpace(const Vector3<real_t>& point,
+                            const Vector3<real_t>& halfSpacePosition, const Vector3<real_t>& halfSpaceNormal, const math::Matrix3<real_t>& halfSpaceRotationMatrix )
+{
+   return !(halfSpaceRotationMatrix.getTranspose() * (point - halfSpacePosition) * halfSpaceNormal > real_t(0));
+}
+
+//TODO add ellipsoids
+
+struct ContainsPointFunctor
+{
+   template<typename ParticleAccessor_T, typename Shape_T>
+   bool operator()(const size_t /*particleIdx*/, const Shape_T& /*shape*/, const ParticleAccessor_T& /*ac*/, const Vector3<real_t>& /*point*/ )
+   {
+      WALBERLA_ABORT("ContainsPoint not implemented!");
+   }
+
+   template<typename ParticleAccessor_T>
+   bool operator()(const size_t particleIdx, const mesa_pd::data::Sphere& sphere, const ParticleAccessor_T& ac, const Vector3<real_t>& point )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      return isPointInsideSphere(point, ac.getPosition(particleIdx), sphere.getRadius() );
+   }
+
+   template<typename ParticleAccessor_T>
+   bool operator()(const size_t particleIdx, const mesa_pd::data::HalfSpace& halfSpace, const ParticleAccessor_T& ac, const Vector3<real_t>& point )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      return isPointInsideHalfSpace(point, ac.getPosition(particleIdx), halfSpace.getNormal(), ac.getRotation(particleIdx).getMatrix() );
+   }
+
+};
+
+
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/common/ParticleFunctions.h b/src/mesa_pd/common/ParticleFunctions.h
new file mode 100644
index 000000000..195dec2c7
--- /dev/null
+++ b/src/mesa_pd/common/ParticleFunctions.h
@@ -0,0 +1,96 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleFunctions.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/domain/IDomain.h>
+
+#include <core/logging/Logging.h>
+#include <core/mpi/MPIManager.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Returns the "surface" velocity at a certain point given in world frame coordinates.
+ */
+template <typename Accessor>
+inline Vec3 getVelocityAtWFPoint(const size_t p_idx, Accessor& ac, const Vec3& wf_pt)
+{
+   return ac.getLinearVelocity(p_idx) + cross(ac.getAngularVelocity(p_idx), ( wf_pt - ac.getPosition(p_idx) ));
+}
+
+/**
+ * Force is applied at the center of mass.
+ */
+template <typename Accessor>
+inline void addForceAtomic(const size_t p_idx, Accessor& ac, const Vec3& f)
+{
+   // Increasing the force and torque on this particle
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[0]  += f[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[1]  += f[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[2]  += f[2];
+}
+
+template <typename Accessor>
+inline void addForceAtWFPosAtomic(const size_t p_idx, Accessor& ac, const Vec3& f, const Vec3& wf_pt)
+{
+   // Increasing the force and torque on this particle
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[0]  += f[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[1]  += f[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[2]  += f[2];
+
+   const auto t = cross(( wf_pt - ac.getPosition(p_idx) ), f);
+
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getTorqueRef(p_idx)[0] += t[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getTorqueRef(p_idx)[1] += t[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getTorqueRef(p_idx)[2] += t[2];
+}
+
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/common/RayParticleIntersection.h b/src/mesa_pd/common/RayParticleIntersection.h
new file mode 100644
index 000000000..ce4b273df
--- /dev/null
+++ b/src/mesa_pd/common/RayParticleIntersection.h
@@ -0,0 +1,152 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file RayParticleIntersection.h
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/Contains.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/shape/HalfSpace.h>
+#include <mesa_pd/data/shape/Sphere.h>
+#include <mesa_pd/kernel/SingleCast.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/*
+ * ray - particle intersection ratio functionality
+ */
+
+real_t raySphereIntersectionRatio( const Vector3<real_t> & rayOrigin, const Vector3<real_t> & rayDirection,
+                                   const Vector3<real_t> & spherePosition, const real_t sphereRadius )
+{
+   WALBERLA_ASSERT( !isPointInsideSphere(rayOrigin, spherePosition, sphereRadius ), "rayOrigin: " << rayOrigin );
+   WALBERLA_ASSERT(  isPointInsideSphere(rayOrigin + rayDirection, spherePosition, sphereRadius ), "rayOrigin + rayDirection: " << rayOrigin + rayDirection );
+
+   // get the physical sphere center
+
+   real_t dirLength = rayDirection.length();
+   Vector3< real_t > l = spherePosition - rayOrigin;
+   real_t s = l * rayDirection / dirLength;
+   real_t lsqr = l.sqrLength();
+   real_t msqr = lsqr - s * s;
+   real_t rsqr = real_c( sphereRadius * sphereRadius );
+   real_t q = std::sqrt( rsqr - msqr );
+   real_t delta = ( lsqr > rsqr ) ? s - q : s + q;
+   delta /= dirLength;
+
+   WALBERLA_ASSERT_GREATER_EQUAL( delta, real_t( 0 ) );
+   WALBERLA_ASSERT_LESS_EQUAL( delta, real_t( 1 ) );
+
+   return delta;
+}
+
+real_t rayHalfSpaceIntersectionRatio( const Vector3<real_t> & rayOrigin, const Vector3<real_t> & rayDirection,
+                                      const Vector3<real_t> & halfSpacePosition, const Vector3<real_t> & halfSpaceNormal, const math::Matrix3<real_t>& halfSpaceRotationMatrix)
+{
+   WALBERLA_ASSERT( !isPointInsideHalfSpace( rayOrigin, halfSpacePosition, halfSpaceNormal, halfSpaceRotationMatrix ), "rayOrigin: " << rayOrigin );
+   WALBERLA_ASSERT(  isPointInsideHalfSpace( rayOrigin + rayDirection, halfSpacePosition, halfSpaceNormal, halfSpaceRotationMatrix ), "rayOrigin + rayDirection: " << rayOrigin + rayDirection );
+
+   const Vector3<real_t> planeNormal( halfSpaceRotationMatrix * halfSpaceNormal );
+
+   real_t denom = planeNormal * rayDirection;
+
+   auto diff = halfSpacePosition - rayOrigin;
+
+   WALBERLA_ASSERT_FLOAT_UNEQUAL(denom, real_t(0));
+
+   real_t delta = diff * planeNormal / denom;
+
+   WALBERLA_ASSERT_GREATER_EQUAL( delta, real_t( 0 ) );
+   WALBERLA_ASSERT_LESS_EQUAL( delta, real_t( 1 ) );
+
+   return delta;
+}
+
+//TODO add ellipsoids from pe_coupling/geometry/PeIntersectionRatio.cpp
+
+template <typename ParticleAccessor_T>
+real_t intersectionRatioBisection( const size_t particleIdx, const ParticleAccessor_T& ac,
+                                   const Vector3<real_t>& rayOrigin, const Vector3<real_t>& rayDirection,
+                                   real_t epsilon)
+{
+   mesa_pd::kernel::SingleCast singleCast;
+   ContainsPointFunctor containsPointFctr;
+
+   WALBERLA_ASSERT( !singleCast(particleIdx, ac, containsPointFctr, ac, rayOrigin), "rayOrigin: " << rayOrigin );
+   WALBERLA_ASSERT(  singleCast(particleIdx, ac, containsPointFctr, ac, rayOrigin + rayDirection), "rayOrigin + rayDirection: " << rayOrigin + rayDirection );
+
+   const real_t sqEpsilon         = epsilon * epsilon;
+   const real_t sqDirectionLength = rayDirection.sqrLength();
+
+   real_t q( 0.5 );
+   real_t qDelta( 0.5 );
+
+   while( qDelta * qDelta * sqDirectionLength >= sqEpsilon )
+   {
+      qDelta *= real_t( 0.5 );
+      Vector3<real_t> p = rayOrigin + q * rayDirection;
+      if( singleCast(particleIdx, ac, containsPointFctr, ac, p) )
+      {
+         q -= qDelta;
+      }
+      else
+      {
+         q += qDelta;
+      }
+   }
+
+   WALBERLA_ASSERT_GREATER_EQUAL( q, real_t( 0 ) );
+   WALBERLA_ASSERT_LESS_EQUAL( q, real_t( 1 ) );
+
+   return q;
+}
+
+struct RayParticleIntersectionRatioFunctor
+{
+   template<typename ParticleAccessor_T, typename Shape_T>
+   real_t operator()(const size_t particleIdx, const Shape_T& /*shape*/, const ParticleAccessor_T& ac, const Vector3<real_t>& rayOrigin, const Vector3<real_t>& rayDirection, real_t epsilon )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      return intersectionRatioBisection(particleIdx, ac, rayOrigin, rayDirection, epsilon );
+   }
+
+   template<typename ParticleAccessor_T>
+   real_t operator()(const size_t particleIdx, const mesa_pd::data::Sphere& sphere, const ParticleAccessor_T& ac, const Vector3<real_t>& rayOrigin, const Vector3<real_t>& rayDirection, real_t /*epsilon*/ )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      return raySphereIntersectionRatio(rayOrigin, rayDirection, ac.getPosition(particleIdx), sphere.getRadius() );
+   }
+
+   template<typename ParticleAccessor_T>
+   real_t operator()(const size_t particleIdx, const mesa_pd::data::HalfSpace& halfSpace, const ParticleAccessor_T& ac, const Vector3<real_t>& rayOrigin, const Vector3<real_t>& rayDirection, real_t /*epsilon*/ )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      return rayHalfSpaceIntersectionRatio(rayOrigin, rayDirection, ac.getPosition(particleIdx), halfSpace.getNormal(), ac.getRotation(particleIdx).getMatrix() );
+   }
+
+};
+
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/data/ContactHistory.h b/src/mesa_pd/data/ContactHistory.h
new file mode 100644
index 000000000..2e8712209
--- /dev/null
+++ b/src/mesa_pd/data/ContactHistory.h
@@ -0,0 +1,113 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ContactHistory.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/STLOverloads.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+#include <core/math/AABB.h>
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+#include <core/STLIO.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+class ContactHistory
+{
+public:
+   const walberla::mesa_pd::Vec3& getTangentialSpringDisplacement() const {return tangentialSpringDisplacement_;}
+   walberla::mesa_pd::Vec3& getTangentialSpringDisplacementRef() {return tangentialSpringDisplacement_;}
+   void setTangentialSpringDisplacement(const walberla::mesa_pd::Vec3& v) { tangentialSpringDisplacement_ = v;}
+   
+   const bool& getIsSticking() const {return isSticking_;}
+   bool& getIsStickingRef() {return isSticking_;}
+   void setIsSticking(const bool& v) { isSticking_ = v;}
+   
+   const real_t& getImpactVelocityMagnitude() const {return impactVelocityMagnitude_;}
+   real_t& getImpactVelocityMagnitudeRef() {return impactVelocityMagnitude_;}
+   void setImpactVelocityMagnitude(const real_t& v) { impactVelocityMagnitude_ = v;}
+   
+private:
+   walberla::mesa_pd::Vec3 tangentialSpringDisplacement_ {};
+   bool isSticking_ {};
+   real_t impactVelocityMagnitude_ {};
+};
+
+inline
+std::ostream& operator<<( std::ostream& os, const ContactHistory& ch )
+{
+   os << "==========  Contact History  ==========" << "\n" <<
+         "tangentialSpringDisplacement: " << ch.getTangentialSpringDisplacement() << "\n" <<
+         "isSticking          : " << ch.getIsSticking() << "\n" <<
+         "impactVelocityMagnitude: " << ch.getImpactVelocityMagnitude() << "\n" <<
+         "================================" << std::endl;
+   return os;
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::data::ContactHistory& obj )
+{
+   buf.addDebugMarker( "ch" );
+   buf << obj.getTangentialSpringDisplacement();
+   buf << obj.getIsSticking();
+   buf << obj.getImpactVelocityMagnitude();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::data::ContactHistory& objparam )
+{
+   buf.readDebugMarker( "ch" );
+   buf >> objparam.getTangentialSpringDisplacementRef();
+   buf >> objparam.getIsStickingRef();
+   buf >> objparam.getImpactVelocityMagnitudeRef();
+   return buf;
+}
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/data/DataTypes.h b/src/mesa_pd/data/DataTypes.h
new file mode 100644
index 000000000..0ab8e22b3
--- /dev/null
+++ b/src/mesa_pd/data/DataTypes.h
@@ -0,0 +1,40 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file DataTypes.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <core/DataTypes.h>
+#include <core/math/Matrix3.h>
+#include <core/math/Quaternion.h>
+#include <core/math/Rot3.h>
+#include <core/math/Vector3.h>
+
+#include <mesa_pd/data/Flags.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+using Mat3 = math::Matrix3<real_t>;
+using Rot3 = math::Rot3<real_t>;
+using Quat = math::Quaternion<real_t>;
+using Vec3 = math::Vector3<real_t>;
+
+}
+}
diff --git a/src/mesa_pd/data/Flags.h b/src/mesa_pd/data/Flags.h
new file mode 100644
index 000000000..14d1593c2
--- /dev/null
+++ b/src/mesa_pd/data/Flags.h
@@ -0,0 +1,116 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file Flags.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+
+#include <ostream>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+namespace particle_flags {
+
+class FlagT
+{
+public:
+   using value_type = uint8_t;
+
+   value_type& getRawDataRef() {return flags_;}
+   const value_type& getRawDataRef() const {return flags_;}
+private:
+   value_type flags_ = 0;
+};
+
+/// default value after initialization
+static const FlagT::value_type NO_FLAGS          = 0;
+/// the particle extends into at least one direction till infinity
+static const FlagT::value_type INFINITE          = (1 << 1);
+/// spatial integrators will skip this particle, however, forces and torques are reset
+static const FlagT::value_type FIXED             = (1 << 2);
+/// all communication functions are skipped for this particle
+static const FlagT::value_type NON_COMMUNICATING = (1 << 3);
+/// this is a ghost particle
+static const FlagT::value_type GHOST             = (1 << 4);
+/// this particle exists on all processes with the same uid
+static const FlagT::value_type GLOBAL            = (1 << 5);
+
+inline void set(FlagT& flags, const FlagT::value_type flag)
+{
+   flags.getRawDataRef() |= flag;
+}
+
+inline void unset(FlagT& flags, const FlagT::value_type flag)
+{
+   flags.getRawDataRef() &= static_cast<FlagT::value_type>(~flag);
+}
+
+inline bool isSet(const FlagT& flags, const FlagT::value_type flag)
+{
+   return (flags.getRawDataRef() & flag) != 0;
+}
+
+inline std::ostream& operator<<( std::ostream& os, const FlagT& flags )
+{
+   os << flags.getRawDataRef();
+   return os;
+}
+
+} //namespace particle_flags
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const walberla::mesa_pd::data::particle_flags::FlagT& flags )
+{
+   buf.addDebugMarker( "fl" );
+   buf << flags.getRawDataRef();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, walberla::mesa_pd::data::particle_flags::FlagT& flags )
+{
+   buf.readDebugMarker( "fl" );
+   buf >> flags.getRawDataRef();
+   return buf;
+}
+
+template<> // value type
+struct BufferSizeTrait< walberla::mesa_pd::data::particle_flags::FlagT > {
+   static const bool constantSize = true;
+   static const uint_t size = sizeof(walberla::mesa_pd::data::particle_flags::FlagT::value_type) + mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
diff --git a/src/mesa_pd/data/IAccessor.h b/src/mesa_pd/data/IAccessor.h
new file mode 100644
index 000000000..2db039769
--- /dev/null
+++ b/src/mesa_pd/data/IAccessor.h
@@ -0,0 +1,48 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file IAccessor.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <core/DataTypes.h>
+
+#include <typeinfo>
+#include <type_traits>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+/**
+ * @brief Add this as a base class to identify your class as a accessor.
+ *
+ * Accessors passed via templated arguments might be checked like
+ * \code
+ * static_assert(std::is_base_of<IAccessor, X>::value, typeid(X).name() + " is not a accessor");
+ * \endcode
+ */
+class IAccessor
+{
+public:
+   virtual ~IAccessor() = default;
+};
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/data/LinkedCells.h b/src/mesa_pd/data/LinkedCells.h
new file mode 100644
index 000000000..cdcec8172
--- /dev/null
+++ b/src/mesa_pd/data/LinkedCells.h
@@ -0,0 +1,359 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LinkedCells.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+#include <core/math/AABB.h>
+#include <stencil/D3Q27.h>
+
+#include <atomic>
+#include <cmath>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+struct LinkedCells
+{
+   LinkedCells(const math::AABB& domain, const real_t cellDiameter) : LinkedCells(domain, Vec3(cellDiameter,cellDiameter,cellDiameter)) {}
+   LinkedCells(const math::AABB& domain, const Vec3& cellDiameter);
+
+   void clear();
+
+   /**
+    * Calls the provided functor \p func for all particle pairs.
+    *
+    * Additional arguments can be provided. No pairs with twice the same particle.
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, j, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   void forEachParticlePair(const bool openmp,
+                            const Selector& selector,
+                            Accessor& acForLC,
+                            Func&& func,
+                            Args&&... args) const;
+   /**
+    * Calls the provided functor \p func for all particle pairs.
+    *
+    * Additional arguments can be provided. No pairs with twice the same particle are generated.
+    * No pair is called twice!
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, j, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   void forEachParticlePairHalf(const bool openmp,
+                                const Selector& selector,
+                                Accessor& acForLC,
+                                Func&& func,
+                                Args&&... args) const;
+
+   math::AABB   domain_ {};
+   Vector3<int> numCellsPerDim_ {};
+   Vec3         cellDiameter_ {};
+   Vec3         invCellDiameter_ {};
+   std::atomic<int> infiniteParticles_ {};
+   std::vector< std::atomic<int> > cells_ {};
+};
+
+inline
+math::AABB getCellAABB(const LinkedCells& ll, const int hash0, const int hash1, const int hash2)
+{
+   WALBERLA_ASSERT_GREATER_EQUAL(hash0, 0);
+   WALBERLA_ASSERT_LESS(hash0, ll.numCellsPerDim_[0]);
+   WALBERLA_ASSERT_GREATER_EQUAL(hash1, 0);
+   WALBERLA_ASSERT_LESS(hash1, ll.numCellsPerDim_[1]);
+   WALBERLA_ASSERT_GREATER_EQUAL(hash2, 0);
+   WALBERLA_ASSERT_LESS(hash2, ll.numCellsPerDim_[2]);
+   const auto& minCorner = ll.domain_.minCorner();
+   const real_t xMin = ll.cellDiameter_[0] * real_c(hash0) + minCorner[0];
+   const real_t yMin = ll.cellDiameter_[1] * real_c(hash1) + minCorner[1];
+   const real_t zMin = ll.cellDiameter_[2] * real_c(hash2) + minCorner[2];
+   const real_t xMax = ll.cellDiameter_[0] * real_c(hash0 + 1) + minCorner[0];
+   const real_t yMax = ll.cellDiameter_[1] * real_c(hash1 + 1) + minCorner[1];
+   const real_t zMax = ll.cellDiameter_[2] * real_c(hash2 + 1) + minCorner[2];
+   return math::AABB(xMin, yMin, zMin, xMax, yMax, zMax);
+}
+
+inline
+int getCellIdx(const LinkedCells& ll, const int hash0, const int hash1, const int hash2)
+{
+   WALBERLA_ASSERT_GREATER_EQUAL(hash0, 0);
+   WALBERLA_ASSERT_LESS(hash0, ll.numCellsPerDim_[0]);
+   WALBERLA_ASSERT_GREATER_EQUAL(hash1, 0);
+   WALBERLA_ASSERT_LESS(hash1, ll.numCellsPerDim_[1]);
+   WALBERLA_ASSERT_GREATER_EQUAL(hash2, 0);
+   WALBERLA_ASSERT_LESS(hash2, ll.numCellsPerDim_[2]);
+   return hash2 * ll.numCellsPerDim_[1] * ll.numCellsPerDim_[0] + hash1 * ll.numCellsPerDim_[0] + hash0;
+}
+
+inline
+LinkedCells::LinkedCells(const math::AABB& domain, const Vec3& cellDiameter)
+   : domain_(domain)
+   , numCellsPerDim_( static_cast<int>(std::ceil( domain.sizes()[0] / cellDiameter[0])),
+     static_cast<int>(std::ceil( domain.sizes()[1] / cellDiameter[1])),
+     static_cast<int>(std::ceil( domain.sizes()[2] / cellDiameter[2])) )
+   , cellDiameter_( domain.sizes()[0] / real_c(numCellsPerDim_[0]),
+     domain.sizes()[1] / real_c(numCellsPerDim_[1]),
+     domain.sizes()[2] / real_c(numCellsPerDim_[2]) )
+   , invCellDiameter_( real_t(1) / cellDiameter_[0], real_t(1) / cellDiameter_[1], real_t(1) / cellDiameter_[2] )
+   , cells_(uint_c(numCellsPerDim_[0]*numCellsPerDim_[1]*numCellsPerDim_[2]))
+{
+   //precondition
+   WALBERLA_CHECK_GREATER_EQUAL(cellDiameter[0], real_t(0));
+   WALBERLA_CHECK_GREATER_EQUAL(cellDiameter[1], real_t(0));
+   WALBERLA_CHECK_GREATER_EQUAL(cellDiameter[2], real_t(0));
+
+   //postcondition
+   WALBERLA_CHECK_GREATER_EQUAL(cellDiameter_[0], real_t(0));
+   WALBERLA_CHECK_LESS_EQUAL(cellDiameter_[0], cellDiameter[0]);
+
+   WALBERLA_CHECK_GREATER_EQUAL(numCellsPerDim_[0], 0);
+   WALBERLA_CHECK_GREATER_EQUAL(cellDiameter_[1], real_t(0));
+   WALBERLA_CHECK_LESS_EQUAL(cellDiameter_[1], cellDiameter[1]);
+
+   WALBERLA_CHECK_GREATER_EQUAL(numCellsPerDim_[1], 0);
+   WALBERLA_CHECK_GREATER_EQUAL(cellDiameter_[2], real_t(0));
+   WALBERLA_CHECK_LESS_EQUAL(cellDiameter_[2], cellDiameter[2]);
+
+   WALBERLA_CHECK_GREATER_EQUAL(numCellsPerDim_[2], 0);
+}
+
+void LinkedCells::clear()
+{
+   const uint64_t cellsSize = cells_.size();
+   //clear existing linked cells
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static)
+   #endif
+   for (int64_t i = 0; i < int64_c(cellsSize); ++i)
+      cells_[uint64_c(i)] = -1;
+   infiniteParticles_ = -1;
+}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void LinkedCells::forEachParticlePair(const bool openmp, const Selector& selector, Accessor& acForLC, Func&& func, Args&&... args) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+   WALBERLA_UNUSED(openmp);
+#ifdef _OPENMP
+#pragma omp parallel for schedule(static) if (openmp)
+#endif
+   for (int z = 0; z < numCellsPerDim_[2]; ++z)
+   {
+      for (int y = 0; y < numCellsPerDim_[1]; ++y)
+      {
+         for (int x = 0; x < numCellsPerDim_[0]; ++x)
+         {
+            const int cell_idx = getCellIdx(*this, x, y, z); ///< current cell index
+            int p_idx = cells_[uint_c(cell_idx)]; ///< current particle index
+            int np_idx = -1; ///< particle to be checked against
+
+            while (p_idx != -1)
+            {
+               WALBERLA_ASSERT_GREATER_EQUAL(p_idx, 0);
+               WALBERLA_ASSERT_LESS(p_idx, acForLC.size());
+
+               // check particles in own cell
+               np_idx = acForLC.getNextParticle(uint_c(p_idx)); ///< neighbor particle index
+               while (np_idx != -1)
+               {
+                  WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                  WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                  if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                  {
+                     func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                     func(uint_c(np_idx), uint_c(p_idx), std::forward<Args>(args)...);
+                  }
+
+                  np_idx = acForLC.getNextParticle(uint_c(np_idx));
+               }
+
+               // check particles in neighboring cells (only positive ones)
+               for (auto dir : stencil::D3Q27::dir_pos)
+               {
+                  const int nx = x + stencil::cx[dir];
+                  const int ny = y + stencil::cy[dir];
+                  const int nz = z + stencil::cz[dir];
+                  if (nx < 0) continue;
+                  if (ny < 0) continue;
+                  if (nz < 0) continue;
+                  if (nx >= numCellsPerDim_[0]) continue;
+                  if (ny >= numCellsPerDim_[1]) continue;
+                  if (nz >= numCellsPerDim_[2]) continue;
+
+                  const int ncell_idx = getCellIdx(*this, nx, ny, nz); ///< neighbor cell index
+
+                  WALBERLA_ASSERT_GREATER_EQUAL(p_idx, 0);
+                  WALBERLA_ASSERT_LESS(p_idx, acForLC.size());
+                  np_idx = cells_[uint_c(ncell_idx)]; ///< neighbor particle index
+                  while (np_idx != -1)
+                  {
+                     WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                     WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                     if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                     {
+                        func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                        func(uint_c(np_idx), uint_c(p_idx), std::forward<Args>(args)...);
+                     }
+
+                     np_idx = acForLC.getNextParticle(uint_c(np_idx));
+                  }
+               }
+
+               // check particles in infiniteParticles list
+               np_idx = infiniteParticles_; ///< neighbor particle index
+               while (np_idx != -1)
+               {
+                  WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                  WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                  if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                  {
+                     func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                     func(uint_c(np_idx), uint_c(p_idx), std::forward<Args>(args)...);
+                  }
+
+                  np_idx = acForLC.getNextParticle(uint_c(np_idx));
+               }
+
+               // go to next particle
+               p_idx = acForLC.getNextParticle(uint_c(p_idx));
+            }
+         }
+      }
+   }
+}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void LinkedCells::forEachParticlePairHalf(const bool openmp, const Selector& selector, Accessor& acForLC, Func&& func, Args&&... args) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+   WALBERLA_UNUSED(openmp);
+#ifdef _OPENMP
+#pragma omp parallel for schedule(static) if (openmp)
+#endif
+   for (int z = 0; z < numCellsPerDim_[2]; ++z)
+   {
+      for (int y = 0; y < numCellsPerDim_[1]; ++y)
+      {
+         for (int x = 0; x < numCellsPerDim_[0]; ++x)
+         {
+            const int cell_idx = getCellIdx(*this, x, y, z); ///< current cell index
+            int p_idx = cells_[uint_c(cell_idx)]; ///< current particle index
+            int np_idx = -1; ///< particle to be checked against
+
+            while (p_idx != -1)
+            {
+               WALBERLA_ASSERT_GREATER_EQUAL(p_idx, 0);
+               WALBERLA_ASSERT_LESS(p_idx, acForLC.size());
+
+               // check particles in own cell
+               np_idx = acForLC.getNextParticle(uint_c(p_idx)); ///< neighbor particle index
+               while (np_idx != -1)
+               {
+                  WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                  WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                  if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                  {
+                     func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                  }
+
+                  np_idx = acForLC.getNextParticle(uint_c(np_idx));
+               }
+
+               // check particles in neighboring cells (only positive ones)
+               for (auto dir : stencil::D3Q27::dir_pos)
+               {
+                  const int nx = x + stencil::cx[dir];
+                  const int ny = y + stencil::cy[dir];
+                  const int nz = z + stencil::cz[dir];
+                  if (nx < 0) continue;
+                  if (ny < 0) continue;
+                  if (nz < 0) continue;
+                  if (nx >= numCellsPerDim_[0]) continue;
+                  if (ny >= numCellsPerDim_[1]) continue;
+                  if (nz >= numCellsPerDim_[2]) continue;
+
+                  const int ncell_idx = getCellIdx(*this, nx, ny, nz); ///< neighbor cell index
+
+                  WALBERLA_ASSERT_GREATER_EQUAL(p_idx, 0);
+                  WALBERLA_ASSERT_LESS(p_idx, acForLC.size());
+                  np_idx = cells_[uint_c(ncell_idx)]; ///< neighbor particle index
+                  while (np_idx != -1)
+                  {
+                     WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                     WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                     if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                     {
+                        func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                     }
+
+                     np_idx = acForLC.getNextParticle(uint_c(np_idx));
+                  }
+               }
+
+               // check particles in infiniteParticles list
+               np_idx = infiniteParticles_; ///< neighbor particle index
+               while (np_idx != -1)
+               {
+                  WALBERLA_ASSERT_GREATER_EQUAL(np_idx, 0);
+                  WALBERLA_ASSERT_LESS(np_idx, acForLC.size());
+
+                  if (selector(uint_c(p_idx), uint_c(np_idx), acForLC))
+                  {
+                     func(uint_c(p_idx), uint_c(np_idx), std::forward<Args>(args)...);
+                  }
+
+                  np_idx = acForLC.getNextParticle(uint_c(np_idx));
+               }
+
+               // go to next particle
+               p_idx = acForLC.getNextParticle(uint_c(p_idx));
+            }
+         }
+      }
+   }
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/data/ParticleAccessor.h b/src/mesa_pd/data/ParticleAccessor.h
new file mode 100644
index 000000000..978ab9609
--- /dev/null
+++ b/src/mesa_pd/data/ParticleAccessor.h
@@ -0,0 +1,300 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleAccessor.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/UniqueID.h>
+
+#include <limits>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+/**
+ * @brief Basic ParticleAccessor for the ParticleStorage
+ *
+ * Provides get, set and getRef for all members of the ParticleStorage.
+ * Can be used as a basis class for a more advanced ParticleAccessor.
+ */
+class ParticleAccessor : public IAccessor
+{
+public:
+   ParticleAccessor(const std::shared_ptr<data::ParticleStorage>& ps) : ps_(ps) {}
+   virtual ~ParticleAccessor() = default;
+   const walberla::id_t& getUid(const size_t p_idx) const {return ps_->getUid(p_idx);}
+   walberla::id_t& getUidRef(const size_t p_idx) {return ps_->getUidRef(p_idx);}
+   void setUid(const size_t p_idx, const walberla::id_t& v) { ps_->setUid(p_idx, v);}
+   
+   const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const {return ps_->getPosition(p_idx);}
+   walberla::mesa_pd::Vec3& getPositionRef(const size_t p_idx) {return ps_->getPositionRef(p_idx);}
+   void setPosition(const size_t p_idx, const walberla::mesa_pd::Vec3& v) { ps_->setPosition(p_idx, v);}
+   
+   const walberla::real_t& getInteractionRadius(const size_t p_idx) const {return ps_->getInteractionRadius(p_idx);}
+   walberla::real_t& getInteractionRadiusRef(const size_t p_idx) {return ps_->getInteractionRadiusRef(p_idx);}
+   void setInteractionRadius(const size_t p_idx, const walberla::real_t& v) { ps_->setInteractionRadius(p_idx, v);}
+   
+   const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t p_idx) const {return ps_->getFlags(p_idx);}
+   walberla::mesa_pd::data::particle_flags::FlagT& getFlagsRef(const size_t p_idx) {return ps_->getFlagsRef(p_idx);}
+   void setFlags(const size_t p_idx, const walberla::mesa_pd::data::particle_flags::FlagT& v) { ps_->setFlags(p_idx, v);}
+   
+   const int& getOwner(const size_t p_idx) const {return ps_->getOwner(p_idx);}
+   int& getOwnerRef(const size_t p_idx) {return ps_->getOwnerRef(p_idx);}
+   void setOwner(const size_t p_idx, const int& v) { ps_->setOwner(p_idx, v);}
+   
+   const std::vector<int>& getGhostOwners(const size_t p_idx) const {return ps_->getGhostOwners(p_idx);}
+   std::vector<int>& getGhostOwnersRef(const size_t p_idx) {return ps_->getGhostOwnersRef(p_idx);}
+   void setGhostOwners(const size_t p_idx, const std::vector<int>& v) { ps_->setGhostOwners(p_idx, v);}
+   
+   const size_t& getShapeID(const size_t p_idx) const {return ps_->getShapeID(p_idx);}
+   size_t& getShapeIDRef(const size_t p_idx) {return ps_->getShapeIDRef(p_idx);}
+   void setShapeID(const size_t p_idx, const size_t& v) { ps_->setShapeID(p_idx, v);}
+   
+   const walberla::mesa_pd::Rot3& getRotation(const size_t p_idx) const {return ps_->getRotation(p_idx);}
+   walberla::mesa_pd::Rot3& getRotationRef(const size_t p_idx) {return ps_->getRotationRef(p_idx);}
+   void setRotation(const size_t p_idx, const walberla::mesa_pd::Rot3& v) { ps_->setRotation(p_idx, v);}
+   
+   const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t p_idx) const {return ps_->getAngularVelocity(p_idx);}
+   walberla::mesa_pd::Vec3& getAngularVelocityRef(const size_t p_idx) {return ps_->getAngularVelocityRef(p_idx);}
+   void setAngularVelocity(const size_t p_idx, const walberla::mesa_pd::Vec3& v) { ps_->setAngularVelocity(p_idx, v);}
+   
+   const walberla::mesa_pd::Vec3& getTorque(const size_t p_idx) const {return ps_->getTorque(p_idx);}
+   walberla::mesa_pd::Vec3& getTorqueRef(const size_t p_idx) {return ps_->getTorqueRef(p_idx);}
+   void setTorque(const size_t p_idx, const walberla::mesa_pd::Vec3& v) { ps_->setTorque(p_idx, v);}
+   
+   const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t p_idx) const {return ps_->getLinearVelocity(p_idx);}
+   walberla::mesa_pd::Vec3& getLinearVelocityRef(const size_t p_idx) {return ps_->getLinearVelocityRef(p_idx);}
+   void setLinearVelocity(const size_t p_idx, const walberla::mesa_pd::Vec3& v) { ps_->setLinearVelocity(p_idx, v);}
+   
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ps_->getInvMass(p_idx);}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ps_->getInvMassRef(p_idx);}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ps_->setInvMass(p_idx, v);}
+   
+   const walberla::mesa_pd::Vec3& getForce(const size_t p_idx) const {return ps_->getForce(p_idx);}
+   walberla::mesa_pd::Vec3& getForceRef(const size_t p_idx) {return ps_->getForceRef(p_idx);}
+   void setForce(const size_t p_idx, const walberla::mesa_pd::Vec3& v) { ps_->setForce(p_idx, v);}
+   
+   const walberla::mesa_pd::Vec3& getOldForce(const size_t p_idx) const {return ps_->getOldForce(p_idx);}
+   walberla::mesa_pd::Vec3& getOldForceRef(const size_t p_idx) {return ps_->getOldForceRef(p_idx);}
+   void setOldForce(const size_t p_idx, const walberla::mesa_pd::Vec3& v) { ps_->setOldForce(p_idx, v);}
+   
+   const walberla::mesa_pd::Vec3& getOldTorque(const size_t p_idx) const {return ps_->getOldTorque(p_idx);}
+   walberla::mesa_pd::Vec3& getOldTorqueRef(const size_t p_idx) {return ps_->getOldTorqueRef(p_idx);}
+   void setOldTorque(const size_t p_idx, const walberla::mesa_pd::Vec3& v) { ps_->setOldTorque(p_idx, v);}
+   
+   const uint_t& getType(const size_t p_idx) const {return ps_->getType(p_idx);}
+   uint_t& getTypeRef(const size_t p_idx) {return ps_->getTypeRef(p_idx);}
+   void setType(const size_t p_idx, const uint_t& v) { ps_->setType(p_idx, v);}
+   
+   const int& getNextParticle(const size_t p_idx) const {return ps_->getNextParticle(p_idx);}
+   int& getNextParticleRef(const size_t p_idx) {return ps_->getNextParticleRef(p_idx);}
+   void setNextParticle(const size_t p_idx, const int& v) { ps_->setNextParticle(p_idx, v);}
+   
+   const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getOldContactHistory(const size_t p_idx) const {return ps_->getOldContactHistory(p_idx);}
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getOldContactHistoryRef(const size_t p_idx) {return ps_->getOldContactHistoryRef(p_idx);}
+   void setOldContactHistory(const size_t p_idx, const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& v) { ps_->setOldContactHistory(p_idx, v);}
+   
+   const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getNewContactHistory(const size_t p_idx) const {return ps_->getNewContactHistory(p_idx);}
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getNewContactHistoryRef(const size_t p_idx) {return ps_->getNewContactHistoryRef(p_idx);}
+   void setNewContactHistory(const size_t p_idx, const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& v) { ps_->setNewContactHistory(p_idx, v);}
+   
+   const walberla::real_t& getTemperature(const size_t p_idx) const {return ps_->getTemperature(p_idx);}
+   walberla::real_t& getTemperatureRef(const size_t p_idx) {return ps_->getTemperatureRef(p_idx);}
+   void setTemperature(const size_t p_idx, const walberla::real_t& v) { ps_->setTemperature(p_idx, v);}
+   
+   const walberla::real_t& getHeatFlux(const size_t p_idx) const {return ps_->getHeatFlux(p_idx);}
+   walberla::real_t& getHeatFluxRef(const size_t p_idx) {return ps_->getHeatFluxRef(p_idx);}
+   void setHeatFlux(const size_t p_idx, const walberla::real_t& v) { ps_->setHeatFlux(p_idx, v);}
+   
+
+   id_t getInvalidUid() const {return UniqueID<data::Particle>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& uid) const {auto it = ps_->find(uid); return it != ps_->end() ? it.getIdx() : std::numeric_limits<size_t>::max();}
+   size_t size() const { return ps_->size(); }
+
+   inline size_t create(const id_t& uid);
+   inline size_t erase(const size_t& idx);
+   inline size_t find(const id_t& uid);
+protected:
+   std::shared_ptr<data::ParticleStorage> ps_;
+};
+
+inline size_t ParticleAccessor::create(const id_t& uid)
+{
+   auto it = ps_->create(uid);
+   return it.getIdx();
+}
+inline size_t ParticleAccessor::erase(const size_t& idx)
+{
+   data::ParticleStorage::iterator it(ps_.get(), idx);
+   it = ps_->erase(it);
+   return it.getIdx();
+}
+inline size_t ParticleAccessor::find(const id_t& uid)
+{
+   auto it = ps_->find(uid);
+   return it.getIdx();
+}
+
+/**
+ * @brief Basic ParticleAccessor which emulates a single particle in a ParticleStorage
+ * without actually needing a ParticleStorage. This class is used mainly for testing purposes.
+ *
+ * Provides get, set and getRef.
+ */
+class SingleParticleAccessor : public IAccessor
+{
+public:
+   virtual ~SingleParticleAccessor() = default;
+   const walberla::id_t& getUid(const size_t /*p_idx*/) const {return uid_;}
+   void setUid(const size_t /*p_idx*/, const walberla::id_t& v) { uid_ = v;}
+   walberla::id_t& getUidRef(const size_t /*p_idx*/) {return uid_;}
+   
+   const walberla::mesa_pd::Vec3& getPosition(const size_t /*p_idx*/) const {return position_;}
+   void setPosition(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { position_ = v;}
+   walberla::mesa_pd::Vec3& getPositionRef(const size_t /*p_idx*/) {return position_;}
+   
+   const walberla::real_t& getInteractionRadius(const size_t /*p_idx*/) const {return interactionRadius_;}
+   void setInteractionRadius(const size_t /*p_idx*/, const walberla::real_t& v) { interactionRadius_ = v;}
+   walberla::real_t& getInteractionRadiusRef(const size_t /*p_idx*/) {return interactionRadius_;}
+   
+   const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t /*p_idx*/) const {return flags_;}
+   void setFlags(const size_t /*p_idx*/, const walberla::mesa_pd::data::particle_flags::FlagT& v) { flags_ = v;}
+   walberla::mesa_pd::data::particle_flags::FlagT& getFlagsRef(const size_t /*p_idx*/) {return flags_;}
+   
+   const int& getOwner(const size_t /*p_idx*/) const {return owner_;}
+   void setOwner(const size_t /*p_idx*/, const int& v) { owner_ = v;}
+   int& getOwnerRef(const size_t /*p_idx*/) {return owner_;}
+   
+   const std::vector<int>& getGhostOwners(const size_t /*p_idx*/) const {return ghostOwners_;}
+   void setGhostOwners(const size_t /*p_idx*/, const std::vector<int>& v) { ghostOwners_ = v;}
+   std::vector<int>& getGhostOwnersRef(const size_t /*p_idx*/) {return ghostOwners_;}
+   
+   const size_t& getShapeID(const size_t /*p_idx*/) const {return shapeID_;}
+   void setShapeID(const size_t /*p_idx*/, const size_t& v) { shapeID_ = v;}
+   size_t& getShapeIDRef(const size_t /*p_idx*/) {return shapeID_;}
+   
+   const walberla::mesa_pd::Rot3& getRotation(const size_t /*p_idx*/) const {return rotation_;}
+   void setRotation(const size_t /*p_idx*/, const walberla::mesa_pd::Rot3& v) { rotation_ = v;}
+   walberla::mesa_pd::Rot3& getRotationRef(const size_t /*p_idx*/) {return rotation_;}
+   
+   const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t /*p_idx*/) const {return angularVelocity_;}
+   void setAngularVelocity(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { angularVelocity_ = v;}
+   walberla::mesa_pd::Vec3& getAngularVelocityRef(const size_t /*p_idx*/) {return angularVelocity_;}
+   
+   const walberla::mesa_pd::Vec3& getTorque(const size_t /*p_idx*/) const {return torque_;}
+   void setTorque(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { torque_ = v;}
+   walberla::mesa_pd::Vec3& getTorqueRef(const size_t /*p_idx*/) {return torque_;}
+   
+   const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t /*p_idx*/) const {return linearVelocity_;}
+   void setLinearVelocity(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { linearVelocity_ = v;}
+   walberla::mesa_pd::Vec3& getLinearVelocityRef(const size_t /*p_idx*/) {return linearVelocity_;}
+   
+   const walberla::real_t& getInvMass(const size_t /*p_idx*/) const {return invMass_;}
+   void setInvMass(const size_t /*p_idx*/, const walberla::real_t& v) { invMass_ = v;}
+   walberla::real_t& getInvMassRef(const size_t /*p_idx*/) {return invMass_;}
+   
+   const walberla::mesa_pd::Vec3& getForce(const size_t /*p_idx*/) const {return force_;}
+   void setForce(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { force_ = v;}
+   walberla::mesa_pd::Vec3& getForceRef(const size_t /*p_idx*/) {return force_;}
+   
+   const walberla::mesa_pd::Vec3& getOldForce(const size_t /*p_idx*/) const {return oldForce_;}
+   void setOldForce(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { oldForce_ = v;}
+   walberla::mesa_pd::Vec3& getOldForceRef(const size_t /*p_idx*/) {return oldForce_;}
+   
+   const walberla::mesa_pd::Vec3& getOldTorque(const size_t /*p_idx*/) const {return oldTorque_;}
+   void setOldTorque(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { oldTorque_ = v;}
+   walberla::mesa_pd::Vec3& getOldTorqueRef(const size_t /*p_idx*/) {return oldTorque_;}
+   
+   const uint_t& getType(const size_t /*p_idx*/) const {return type_;}
+   void setType(const size_t /*p_idx*/, const uint_t& v) { type_ = v;}
+   uint_t& getTypeRef(const size_t /*p_idx*/) {return type_;}
+   
+   const int& getNextParticle(const size_t /*p_idx*/) const {return nextParticle_;}
+   void setNextParticle(const size_t /*p_idx*/, const int& v) { nextParticle_ = v;}
+   int& getNextParticleRef(const size_t /*p_idx*/) {return nextParticle_;}
+   
+   const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getOldContactHistory(const size_t /*p_idx*/) const {return oldContactHistory_;}
+   void setOldContactHistory(const size_t /*p_idx*/, const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& v) { oldContactHistory_ = v;}
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getOldContactHistoryRef(const size_t /*p_idx*/) {return oldContactHistory_;}
+   
+   const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getNewContactHistory(const size_t /*p_idx*/) const {return newContactHistory_;}
+   void setNewContactHistory(const size_t /*p_idx*/, const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& v) { newContactHistory_ = v;}
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getNewContactHistoryRef(const size_t /*p_idx*/) {return newContactHistory_;}
+   
+   const walberla::real_t& getTemperature(const size_t /*p_idx*/) const {return temperature_;}
+   void setTemperature(const size_t /*p_idx*/, const walberla::real_t& v) { temperature_ = v;}
+   walberla::real_t& getTemperatureRef(const size_t /*p_idx*/) {return temperature_;}
+   
+   const walberla::real_t& getHeatFlux(const size_t /*p_idx*/) const {return heatFlux_;}
+   void setHeatFlux(const size_t /*p_idx*/, const walberla::real_t& v) { heatFlux_ = v;}
+   walberla::real_t& getHeatFluxRef(const size_t /*p_idx*/) {return heatFlux_;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<data::Particle>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& uid) const {return uid == uid_ ? 0 : std::numeric_limits<size_t>::max();}
+   size_t size() const { return 1; }
+private:
+   walberla::id_t uid_;
+   walberla::mesa_pd::Vec3 position_;
+   walberla::real_t interactionRadius_;
+   walberla::mesa_pd::data::particle_flags::FlagT flags_;
+   int owner_;
+   std::vector<int> ghostOwners_;
+   size_t shapeID_;
+   walberla::mesa_pd::Rot3 rotation_;
+   walberla::mesa_pd::Vec3 angularVelocity_;
+   walberla::mesa_pd::Vec3 torque_;
+   walberla::mesa_pd::Vec3 linearVelocity_;
+   walberla::real_t invMass_;
+   walberla::mesa_pd::Vec3 force_;
+   walberla::mesa_pd::Vec3 oldForce_;
+   walberla::mesa_pd::Vec3 oldTorque_;
+   uint_t type_;
+   int nextParticle_;
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory> oldContactHistory_;
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory> newContactHistory_;
+   walberla::real_t temperature_;
+   walberla::real_t heatFlux_;
+};
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/data/ParticleStorage.h b/src/mesa_pd/data/ParticleStorage.h
new file mode 100644
index 000000000..633563ca6
--- /dev/null
+++ b/src/mesa_pd/data/ParticleStorage.h
@@ -0,0 +1,1071 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleStorage.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <atomic>
+#include <limits>
+#include <map>
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+#include <mesa_pd/data/ContactHistory.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/STLOverloads.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+#include <core/math/AABB.h>
+#include <core/OpenMP.h>
+#include <core/STLIO.h>
+#include <core/UniqueID.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+class ParticleStorage;
+
+class ParticleStorage
+{
+public:
+   class Particle
+   {
+   public:
+      constexpr Particle(ParticleStorage& storage, const size_t i) : storage_(storage), i_(i) {}
+      constexpr Particle(const Particle&)  = default;
+      constexpr Particle(Particle&&)  = default;
+
+      Particle& operator=(const Particle& rhs);
+      Particle& operator=(Particle&& rhs);
+
+      Particle* operator->(){return this;}
+
+      
+      const walberla::id_t& getUid() const {return storage_.getUid(i_);}
+      walberla::id_t& getUidRef() {return storage_.getUidRef(i_);}
+      void setUid(const walberla::id_t& v) { storage_.setUid(i_, v);}
+      
+      const walberla::mesa_pd::Vec3& getPosition() const {return storage_.getPosition(i_);}
+      walberla::mesa_pd::Vec3& getPositionRef() {return storage_.getPositionRef(i_);}
+      void setPosition(const walberla::mesa_pd::Vec3& v) { storage_.setPosition(i_, v);}
+      
+      const walberla::real_t& getInteractionRadius() const {return storage_.getInteractionRadius(i_);}
+      walberla::real_t& getInteractionRadiusRef() {return storage_.getInteractionRadiusRef(i_);}
+      void setInteractionRadius(const walberla::real_t& v) { storage_.setInteractionRadius(i_, v);}
+      
+      const walberla::mesa_pd::data::particle_flags::FlagT& getFlags() const {return storage_.getFlags(i_);}
+      walberla::mesa_pd::data::particle_flags::FlagT& getFlagsRef() {return storage_.getFlagsRef(i_);}
+      void setFlags(const walberla::mesa_pd::data::particle_flags::FlagT& v) { storage_.setFlags(i_, v);}
+      
+      const int& getOwner() const {return storage_.getOwner(i_);}
+      int& getOwnerRef() {return storage_.getOwnerRef(i_);}
+      void setOwner(const int& v) { storage_.setOwner(i_, v);}
+      
+      const std::vector<int>& getGhostOwners() const {return storage_.getGhostOwners(i_);}
+      std::vector<int>& getGhostOwnersRef() {return storage_.getGhostOwnersRef(i_);}
+      void setGhostOwners(const std::vector<int>& v) { storage_.setGhostOwners(i_, v);}
+      
+      const size_t& getShapeID() const {return storage_.getShapeID(i_);}
+      size_t& getShapeIDRef() {return storage_.getShapeIDRef(i_);}
+      void setShapeID(const size_t& v) { storage_.setShapeID(i_, v);}
+      
+      const walberla::mesa_pd::Rot3& getRotation() const {return storage_.getRotation(i_);}
+      walberla::mesa_pd::Rot3& getRotationRef() {return storage_.getRotationRef(i_);}
+      void setRotation(const walberla::mesa_pd::Rot3& v) { storage_.setRotation(i_, v);}
+      
+      const walberla::mesa_pd::Vec3& getAngularVelocity() const {return storage_.getAngularVelocity(i_);}
+      walberla::mesa_pd::Vec3& getAngularVelocityRef() {return storage_.getAngularVelocityRef(i_);}
+      void setAngularVelocity(const walberla::mesa_pd::Vec3& v) { storage_.setAngularVelocity(i_, v);}
+      
+      const walberla::mesa_pd::Vec3& getTorque() const {return storage_.getTorque(i_);}
+      walberla::mesa_pd::Vec3& getTorqueRef() {return storage_.getTorqueRef(i_);}
+      void setTorque(const walberla::mesa_pd::Vec3& v) { storage_.setTorque(i_, v);}
+      
+      const walberla::mesa_pd::Vec3& getLinearVelocity() const {return storage_.getLinearVelocity(i_);}
+      walberla::mesa_pd::Vec3& getLinearVelocityRef() {return storage_.getLinearVelocityRef(i_);}
+      void setLinearVelocity(const walberla::mesa_pd::Vec3& v) { storage_.setLinearVelocity(i_, v);}
+      
+      const walberla::real_t& getInvMass() const {return storage_.getInvMass(i_);}
+      walberla::real_t& getInvMassRef() {return storage_.getInvMassRef(i_);}
+      void setInvMass(const walberla::real_t& v) { storage_.setInvMass(i_, v);}
+      
+      const walberla::mesa_pd::Vec3& getForce() const {return storage_.getForce(i_);}
+      walberla::mesa_pd::Vec3& getForceRef() {return storage_.getForceRef(i_);}
+      void setForce(const walberla::mesa_pd::Vec3& v) { storage_.setForce(i_, v);}
+      
+      const walberla::mesa_pd::Vec3& getOldForce() const {return storage_.getOldForce(i_);}
+      walberla::mesa_pd::Vec3& getOldForceRef() {return storage_.getOldForceRef(i_);}
+      void setOldForce(const walberla::mesa_pd::Vec3& v) { storage_.setOldForce(i_, v);}
+      
+      const walberla::mesa_pd::Vec3& getOldTorque() const {return storage_.getOldTorque(i_);}
+      walberla::mesa_pd::Vec3& getOldTorqueRef() {return storage_.getOldTorqueRef(i_);}
+      void setOldTorque(const walberla::mesa_pd::Vec3& v) { storage_.setOldTorque(i_, v);}
+      
+      const uint_t& getType() const {return storage_.getType(i_);}
+      uint_t& getTypeRef() {return storage_.getTypeRef(i_);}
+      void setType(const uint_t& v) { storage_.setType(i_, v);}
+      
+      const int& getNextParticle() const {return storage_.getNextParticle(i_);}
+      int& getNextParticleRef() {return storage_.getNextParticleRef(i_);}
+      void setNextParticle(const int& v) { storage_.setNextParticle(i_, v);}
+      
+      const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getOldContactHistory() const {return storage_.getOldContactHistory(i_);}
+      std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getOldContactHistoryRef() {return storage_.getOldContactHistoryRef(i_);}
+      void setOldContactHistory(const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& v) { storage_.setOldContactHistory(i_, v);}
+      
+      const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getNewContactHistory() const {return storage_.getNewContactHistory(i_);}
+      std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getNewContactHistoryRef() {return storage_.getNewContactHistoryRef(i_);}
+      void setNewContactHistory(const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& v) { storage_.setNewContactHistory(i_, v);}
+      
+      const walberla::real_t& getTemperature() const {return storage_.getTemperature(i_);}
+      walberla::real_t& getTemperatureRef() {return storage_.getTemperatureRef(i_);}
+      void setTemperature(const walberla::real_t& v) { storage_.setTemperature(i_, v);}
+      
+      const walberla::real_t& getHeatFlux() const {return storage_.getHeatFlux(i_);}
+      walberla::real_t& getHeatFluxRef() {return storage_.getHeatFluxRef(i_);}
+      void setHeatFlux(const walberla::real_t& v) { storage_.setHeatFlux(i_, v);}
+      
+
+      size_t getIdx() const {return i_;}
+   public:
+      ParticleStorage& storage_;
+      const size_t i_;
+   };
+
+   class iterator
+   {
+   public:
+      using iterator_category = std::random_access_iterator_tag;
+      using value_type        = Particle;
+      using pointer           = Particle*;
+      using reference         = Particle&;
+      using difference_type   = std::ptrdiff_t;
+
+      explicit iterator(ParticleStorage* storage, const size_t i) : storage_(storage), i_(i) {}
+      iterator(const iterator& it)         = default;
+      iterator(iterator&& it)              = default;
+      iterator& operator=(const iterator&) = default;
+      iterator& operator=(iterator&&)      = default;
+
+
+      Particle operator*() const {return Particle{*storage_, i_};}
+      Particle operator->() const {return Particle{*storage_, i_};}
+      iterator& operator++(){ ++i_; return *this; }
+      iterator operator++(int){ iterator tmp(*this); ++(*this); return tmp; }
+      iterator& operator--(){ --i_; return *this; }
+      iterator operator--(int){ iterator tmp(*this); --(*this); return tmp; }
+
+      iterator& operator+=(const size_t n){ i_+=n; return *this; }
+      iterator& operator-=(const size_t n){ i_-=n; return *this; }
+
+      friend iterator operator+(const iterator& it, const size_t n);
+      friend iterator operator+(const size_t n, const iterator& it);
+      friend iterator operator-(const iterator& it, const size_t n);
+      friend difference_type operator-(const iterator& lhs, const iterator& rhs);
+
+      friend bool operator==(const iterator& lhs, const iterator& rhs);
+      friend bool operator!=(const iterator& lhs, const iterator& rhs);
+      friend bool operator<(const iterator& lhs, const iterator& rhs);
+      friend bool operator>(const iterator& lhs, const iterator& rhs);
+      friend bool operator<=(const iterator& lhs, const iterator& rhs);
+      friend bool operator>=(const iterator& lhs, const iterator& rhs);
+
+      friend void swap(iterator& lhs, iterator& rhs);
+
+      size_t getIdx() const {return i_;}
+   private:
+      ParticleStorage* storage_;
+      size_t i_;
+   };
+
+   explicit ParticleStorage(const size_t size);
+
+   iterator begin() { return iterator(this, 0); }
+   iterator end()   { return iterator(this, size()); }
+   iterator operator[](const size_t n) { return iterator(this, n); }
+
+   
+   const walberla::id_t& getUid(const size_t idx) const {return uid_[idx];}
+   walberla::id_t& getUidRef(const size_t idx) {return uid_[idx];}
+   void setUid(const size_t idx, const walberla::id_t& v) { uid_[idx] = v; }
+   
+   const walberla::mesa_pd::Vec3& getPosition(const size_t idx) const {return position_[idx];}
+   walberla::mesa_pd::Vec3& getPositionRef(const size_t idx) {return position_[idx];}
+   void setPosition(const size_t idx, const walberla::mesa_pd::Vec3& v) { position_[idx] = v; }
+   
+   const walberla::real_t& getInteractionRadius(const size_t idx) const {return interactionRadius_[idx];}
+   walberla::real_t& getInteractionRadiusRef(const size_t idx) {return interactionRadius_[idx];}
+   void setInteractionRadius(const size_t idx, const walberla::real_t& v) { interactionRadius_[idx] = v; }
+   
+   const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t idx) const {return flags_[idx];}
+   walberla::mesa_pd::data::particle_flags::FlagT& getFlagsRef(const size_t idx) {return flags_[idx];}
+   void setFlags(const size_t idx, const walberla::mesa_pd::data::particle_flags::FlagT& v) { flags_[idx] = v; }
+   
+   const int& getOwner(const size_t idx) const {return owner_[idx];}
+   int& getOwnerRef(const size_t idx) {return owner_[idx];}
+   void setOwner(const size_t idx, const int& v) { owner_[idx] = v; }
+   
+   const std::vector<int>& getGhostOwners(const size_t idx) const {return ghostOwners_[idx];}
+   std::vector<int>& getGhostOwnersRef(const size_t idx) {return ghostOwners_[idx];}
+   void setGhostOwners(const size_t idx, const std::vector<int>& v) { ghostOwners_[idx] = v; }
+   
+   const size_t& getShapeID(const size_t idx) const {return shapeID_[idx];}
+   size_t& getShapeIDRef(const size_t idx) {return shapeID_[idx];}
+   void setShapeID(const size_t idx, const size_t& v) { shapeID_[idx] = v; }
+   
+   const walberla::mesa_pd::Rot3& getRotation(const size_t idx) const {return rotation_[idx];}
+   walberla::mesa_pd::Rot3& getRotationRef(const size_t idx) {return rotation_[idx];}
+   void setRotation(const size_t idx, const walberla::mesa_pd::Rot3& v) { rotation_[idx] = v; }
+   
+   const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t idx) const {return angularVelocity_[idx];}
+   walberla::mesa_pd::Vec3& getAngularVelocityRef(const size_t idx) {return angularVelocity_[idx];}
+   void setAngularVelocity(const size_t idx, const walberla::mesa_pd::Vec3& v) { angularVelocity_[idx] = v; }
+   
+   const walberla::mesa_pd::Vec3& getTorque(const size_t idx) const {return torque_[idx];}
+   walberla::mesa_pd::Vec3& getTorqueRef(const size_t idx) {return torque_[idx];}
+   void setTorque(const size_t idx, const walberla::mesa_pd::Vec3& v) { torque_[idx] = v; }
+   
+   const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t idx) const {return linearVelocity_[idx];}
+   walberla::mesa_pd::Vec3& getLinearVelocityRef(const size_t idx) {return linearVelocity_[idx];}
+   void setLinearVelocity(const size_t idx, const walberla::mesa_pd::Vec3& v) { linearVelocity_[idx] = v; }
+   
+   const walberla::real_t& getInvMass(const size_t idx) const {return invMass_[idx];}
+   walberla::real_t& getInvMassRef(const size_t idx) {return invMass_[idx];}
+   void setInvMass(const size_t idx, const walberla::real_t& v) { invMass_[idx] = v; }
+   
+   const walberla::mesa_pd::Vec3& getForce(const size_t idx) const {return force_[idx];}
+   walberla::mesa_pd::Vec3& getForceRef(const size_t idx) {return force_[idx];}
+   void setForce(const size_t idx, const walberla::mesa_pd::Vec3& v) { force_[idx] = v; }
+   
+   const walberla::mesa_pd::Vec3& getOldForce(const size_t idx) const {return oldForce_[idx];}
+   walberla::mesa_pd::Vec3& getOldForceRef(const size_t idx) {return oldForce_[idx];}
+   void setOldForce(const size_t idx, const walberla::mesa_pd::Vec3& v) { oldForce_[idx] = v; }
+   
+   const walberla::mesa_pd::Vec3& getOldTorque(const size_t idx) const {return oldTorque_[idx];}
+   walberla::mesa_pd::Vec3& getOldTorqueRef(const size_t idx) {return oldTorque_[idx];}
+   void setOldTorque(const size_t idx, const walberla::mesa_pd::Vec3& v) { oldTorque_[idx] = v; }
+   
+   const uint_t& getType(const size_t idx) const {return type_[idx];}
+   uint_t& getTypeRef(const size_t idx) {return type_[idx];}
+   void setType(const size_t idx, const uint_t& v) { type_[idx] = v; }
+   
+   const int& getNextParticle(const size_t idx) const {return nextParticle_[idx];}
+   int& getNextParticleRef(const size_t idx) {return nextParticle_[idx];}
+   void setNextParticle(const size_t idx, const int& v) { nextParticle_[idx] = v; }
+   
+   const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getOldContactHistory(const size_t idx) const {return oldContactHistory_[idx];}
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getOldContactHistoryRef(const size_t idx) {return oldContactHistory_[idx];}
+   void setOldContactHistory(const size_t idx, const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& v) { oldContactHistory_[idx] = v; }
+   
+   const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getNewContactHistory(const size_t idx) const {return newContactHistory_[idx];}
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& getNewContactHistoryRef(const size_t idx) {return newContactHistory_[idx];}
+   void setNewContactHistory(const size_t idx, const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& v) { newContactHistory_[idx] = v; }
+   
+   const walberla::real_t& getTemperature(const size_t idx) const {return temperature_[idx];}
+   walberla::real_t& getTemperatureRef(const size_t idx) {return temperature_[idx];}
+   void setTemperature(const size_t idx, const walberla::real_t& v) { temperature_[idx] = v; }
+   
+   const walberla::real_t& getHeatFlux(const size_t idx) const {return heatFlux_[idx];}
+   walberla::real_t& getHeatFluxRef(const size_t idx) {return heatFlux_[idx];}
+   void setHeatFlux(const size_t idx, const walberla::real_t& v) { heatFlux_[idx] = v; }
+   
+
+   /**
+    * @brief creates a new particle and returns an iterator pointing to it
+    *
+    * \attention Use this function only if you know what you are doing!
+    * Messing with the uid might break the simulation!
+    * If you are unsure use create(bool) instead.
+    * @param uid unique id of the particle to be created
+    * @return iterator to the newly created particle
+    */
+   inline iterator create(const id_t& uid);
+   inline iterator create(const bool global = false);
+   inline iterator erase(iterator& it);
+   /// Finds the entry corresponding to \p uid.
+   /// \return iterator to the object or end iterator
+   inline iterator find(const id_t& uid);
+   inline void reserve(const size_t size);
+   inline void clear();
+   inline size_t size() const;
+
+   /**
+    * Calls the provided functor \p func for all Particles.
+    *
+    * Additional arguments can be provided.
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticle(const bool openmp,
+                               const Selector& selector,
+                               Accessor& acForPS,
+                               Func&& func,
+                               Args&&... args);
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticle(const bool openmp,
+                               const Selector& selector,
+                               Accessor& acForPS,
+                               Func&& func,
+                               Args&&... args) const;
+   /**
+    * Calls the provided functor \p func for all Particle pairs.
+    *
+    * Additional arguments can be provided. No pairs with twice the same Particle.
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, j, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticlePair(const bool openmp,
+                                   const Selector& selector,
+                                   Accessor& acForPS,
+                                   Func&& func,
+                                   Args&&... args);
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticlePair(const bool openmp,
+                                   const Selector& selector,
+                                   Accessor& acForPS,
+                                   Func&& func,
+                                   Args&&... args) const;
+   /**
+    * Calls the provided functor \p func for all Particle pairs.
+    *
+    * Additional arguments can be provided. No pairs with twice the same Particle.
+    * Index of the first particle i will be always smaller than j! No pair is called twice!
+    * Call syntax for the provided functor
+    * \code
+    * func( *this, i, j, std::forward<Args>(args)... );
+    * \endcode
+    * \param openmp enables/disables OpenMP parallelization of the kernel call
+    */
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticlePairHalf(const bool openmp,
+                                       const Selector& selector,
+                                       Accessor& acForPS,
+                                       Func&& func,
+                                       Args&&... args);
+   template <typename Selector, typename Accessor, typename Func, typename... Args>
+   inline void forEachParticlePairHalf(const bool openmp,
+                                       const Selector& selector,
+                                       Accessor& acForPS,
+                                       Func&& func,
+                                       Args&&... args) const;
+
+   private:
+   std::vector<walberla::id_t> uid_ {};
+   std::vector<walberla::mesa_pd::Vec3> position_ {};
+   std::vector<walberla::real_t> interactionRadius_ {};
+   std::vector<walberla::mesa_pd::data::particle_flags::FlagT> flags_ {};
+   std::vector<int> owner_ {};
+   std::vector<std::vector<int>> ghostOwners_ {};
+   std::vector<size_t> shapeID_ {};
+   std::vector<walberla::mesa_pd::Rot3> rotation_ {};
+   std::vector<walberla::mesa_pd::Vec3> angularVelocity_ {};
+   std::vector<walberla::mesa_pd::Vec3> torque_ {};
+   std::vector<walberla::mesa_pd::Vec3> linearVelocity_ {};
+   std::vector<walberla::real_t> invMass_ {};
+   std::vector<walberla::mesa_pd::Vec3> force_ {};
+   std::vector<walberla::mesa_pd::Vec3> oldForce_ {};
+   std::vector<walberla::mesa_pd::Vec3> oldTorque_ {};
+   std::vector<uint_t> type_ {};
+   std::vector<int> nextParticle_ {};
+   std::vector<std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>> oldContactHistory_ {};
+   std::vector<std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>> newContactHistory_ {};
+   std::vector<walberla::real_t> temperature_ {};
+   std::vector<walberla::real_t> heatFlux_ {};
+   std::unordered_map<id_t, size_t> uidToIdx_;
+   static_assert(std::is_same<decltype(uid_)::value_type, id_t>::value,
+                 "Property uid of type id_t is missing. This property is required!");
+};
+using Particle = ParticleStorage::Particle;
+
+inline
+ParticleStorage::Particle& ParticleStorage::Particle::operator=(const ParticleStorage::Particle& rhs)
+{
+   getUidRef() = rhs.getUid();
+   getPositionRef() = rhs.getPosition();
+   getInteractionRadiusRef() = rhs.getInteractionRadius();
+   getFlagsRef() = rhs.getFlags();
+   getOwnerRef() = rhs.getOwner();
+   getGhostOwnersRef() = rhs.getGhostOwners();
+   getShapeIDRef() = rhs.getShapeID();
+   getRotationRef() = rhs.getRotation();
+   getAngularVelocityRef() = rhs.getAngularVelocity();
+   getTorqueRef() = rhs.getTorque();
+   getLinearVelocityRef() = rhs.getLinearVelocity();
+   getInvMassRef() = rhs.getInvMass();
+   getForceRef() = rhs.getForce();
+   getOldForceRef() = rhs.getOldForce();
+   getOldTorqueRef() = rhs.getOldTorque();
+   getTypeRef() = rhs.getType();
+   getNextParticleRef() = rhs.getNextParticle();
+   getOldContactHistoryRef() = rhs.getOldContactHistory();
+   getNewContactHistoryRef() = rhs.getNewContactHistory();
+   getTemperatureRef() = rhs.getTemperature();
+   getHeatFluxRef() = rhs.getHeatFlux();
+   return *this;
+}
+
+inline
+ParticleStorage::Particle& ParticleStorage::Particle::operator=(ParticleStorage::Particle&& rhs)
+{
+   getUidRef() = std::move(rhs.getUidRef());
+   getPositionRef() = std::move(rhs.getPositionRef());
+   getInteractionRadiusRef() = std::move(rhs.getInteractionRadiusRef());
+   getFlagsRef() = std::move(rhs.getFlagsRef());
+   getOwnerRef() = std::move(rhs.getOwnerRef());
+   getGhostOwnersRef() = std::move(rhs.getGhostOwnersRef());
+   getShapeIDRef() = std::move(rhs.getShapeIDRef());
+   getRotationRef() = std::move(rhs.getRotationRef());
+   getAngularVelocityRef() = std::move(rhs.getAngularVelocityRef());
+   getTorqueRef() = std::move(rhs.getTorqueRef());
+   getLinearVelocityRef() = std::move(rhs.getLinearVelocityRef());
+   getInvMassRef() = std::move(rhs.getInvMassRef());
+   getForceRef() = std::move(rhs.getForceRef());
+   getOldForceRef() = std::move(rhs.getOldForceRef());
+   getOldTorqueRef() = std::move(rhs.getOldTorqueRef());
+   getTypeRef() = std::move(rhs.getTypeRef());
+   getNextParticleRef() = std::move(rhs.getNextParticleRef());
+   getOldContactHistoryRef() = std::move(rhs.getOldContactHistoryRef());
+   getNewContactHistoryRef() = std::move(rhs.getNewContactHistoryRef());
+   getTemperatureRef() = std::move(rhs.getTemperatureRef());
+   getHeatFluxRef() = std::move(rhs.getHeatFluxRef());
+   return *this;
+}
+
+inline
+std::ostream& operator<<( std::ostream& os, const ParticleStorage::Particle& p )
+{
+   os << "==========    ==========" << "\n" <<
+         "idx                 : " << p.getIdx() << "\n" <<
+         "uid                 : " << p.getUid() << "\n" <<
+         "position            : " << p.getPosition() << "\n" <<
+         "interactionRadius   : " << p.getInteractionRadius() << "\n" <<
+         "flags               : " << p.getFlags() << "\n" <<
+         "owner               : " << p.getOwner() << "\n" <<
+         "ghostOwners         : " << p.getGhostOwners() << "\n" <<
+         "shapeID             : " << p.getShapeID() << "\n" <<
+         "rotation            : " << p.getRotation() << "\n" <<
+         "angularVelocity     : " << p.getAngularVelocity() << "\n" <<
+         "torque              : " << p.getTorque() << "\n" <<
+         "linearVelocity      : " << p.getLinearVelocity() << "\n" <<
+         "invMass             : " << p.getInvMass() << "\n" <<
+         "force               : " << p.getForce() << "\n" <<
+         "oldForce            : " << p.getOldForce() << "\n" <<
+         "oldTorque           : " << p.getOldTorque() << "\n" <<
+         "type                : " << p.getType() << "\n" <<
+         "nextParticle        : " << p.getNextParticle() << "\n" <<
+         "oldContactHistory   : " << p.getOldContactHistory() << "\n" <<
+         "newContactHistory   : " << p.getNewContactHistory() << "\n" <<
+         "temperature         : " << p.getTemperature() << "\n" <<
+         "heatFlux            : " << p.getHeatFlux() << "\n" <<
+         "================================" << std::endl;
+   return os;
+}
+
+inline
+ParticleStorage::iterator operator+(const ParticleStorage::iterator& it, const size_t n)
+{
+   return ParticleStorage::iterator(it.storage_, it.i_+n);
+}
+
+inline
+ParticleStorage::iterator operator+(const size_t n, const ParticleStorage::iterator& it)
+{
+   return it + n;
+}
+
+inline
+ParticleStorage::iterator operator-(const ParticleStorage::iterator& it, const size_t n)
+{
+   return ParticleStorage::iterator(it.storage_, it.i_-n);
+}
+
+inline
+ParticleStorage::iterator::difference_type operator-(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   return int64_c(lhs.i_) - int64_c(rhs.i_);
+}
+
+inline bool operator==(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ == rhs.i_);
+}
+inline bool operator!=(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ != rhs.i_);
+}
+inline bool operator<(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ < rhs.i_);
+}
+inline bool operator>(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ > rhs.i_);
+}
+inline bool operator<=(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ <= rhs.i_);
+}
+inline bool operator>=(const ParticleStorage::iterator& lhs, const ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   return (lhs.i_ >= rhs.i_);
+}
+
+inline void swap(ParticleStorage::iterator& lhs, ParticleStorage::iterator& rhs)
+{
+   WALBERLA_ASSERT_EQUAL(lhs.storage_, rhs.storage_);
+   std::swap(lhs.i_, rhs.i_);
+}
+
+inline
+ParticleStorage::ParticleStorage(const size_t size)
+{
+   reserve(size);
+}
+
+
+inline ParticleStorage::iterator ParticleStorage::create(const id_t& uid)
+{
+   WALBERLA_ASSERT_EQUAL(uidToIdx_.find(uid),
+                         uidToIdx_.end(),
+                         "particle with the same uid(" << uid <<") already existing at index(" << uidToIdx_.find(uid)->second << ")");
+   uid_.emplace_back(UniqueID<data::Particle>::invalidID());
+   position_.emplace_back(real_t(0));
+   interactionRadius_.emplace_back(real_t(0));
+   flags_.emplace_back();
+   owner_.emplace_back(-1);
+   ghostOwners_.emplace_back();
+   shapeID_.emplace_back();
+   rotation_.emplace_back();
+   angularVelocity_.emplace_back(real_t(0));
+   torque_.emplace_back(real_t(0));
+   linearVelocity_.emplace_back(real_t(0));
+   invMass_.emplace_back(real_t(1));
+   force_.emplace_back(real_t(0));
+   oldForce_.emplace_back(real_t(0));
+   oldTorque_.emplace_back(real_t(0));
+   type_.emplace_back(0);
+   nextParticle_.emplace_back(-1);
+   oldContactHistory_.emplace_back();
+   newContactHistory_.emplace_back();
+   temperature_.emplace_back(real_t(0));
+   heatFlux_.emplace_back(real_t(0));
+   uid_.back() = uid;
+   uidToIdx_[uid] = uid_.size() - 1;
+   return iterator(this, size() - 1);
+}
+
+inline ParticleStorage::iterator ParticleStorage::create(const bool global)
+{
+   if (global)
+   {
+      auto it = create(UniqueID<Particle>::createGlobal());
+      data::particle_flags::set(flags_.back(), data::particle_flags::GLOBAL);
+      data::particle_flags::set(flags_.back(), data::particle_flags::NON_COMMUNICATING);
+      return it;
+   } else
+   {
+      return create(UniqueID<Particle>::create());
+   }
+}
+
+inline ParticleStorage::iterator ParticleStorage::erase(iterator& it)
+{
+   //swap with last element and pop
+   auto last = --end();
+   auto numElementsRemoved = uidToIdx_.erase(it->getUid());
+   WALBERLA_CHECK_EQUAL(numElementsRemoved,
+                        1,
+                        "Particle with uid " << it->getUid() << " cannot be removed (not existing).");
+   if (it != last) //skip swap if last element is removed
+   {
+      *it = *last;
+      uidToIdx_[it->getUid()] = it.getIdx();
+   }
+   uid_.pop_back();
+   position_.pop_back();
+   interactionRadius_.pop_back();
+   flags_.pop_back();
+   owner_.pop_back();
+   ghostOwners_.pop_back();
+   shapeID_.pop_back();
+   rotation_.pop_back();
+   angularVelocity_.pop_back();
+   torque_.pop_back();
+   linearVelocity_.pop_back();
+   invMass_.pop_back();
+   force_.pop_back();
+   oldForce_.pop_back();
+   oldTorque_.pop_back();
+   type_.pop_back();
+   nextParticle_.pop_back();
+   oldContactHistory_.pop_back();
+   newContactHistory_.pop_back();
+   temperature_.pop_back();
+   heatFlux_.pop_back();
+   return it;
+}
+
+inline ParticleStorage::iterator ParticleStorage::find(const id_t& uid)
+{
+   //linear search through uid vector
+   //auto it = std::find(uid_.begin(), uid_.end(), uid);
+   //if (it == uid_.end()) return end();
+   //return iterator(this, uint_c(std::distance(uid_.begin(), it)));
+
+   //use unordered_map for faster lookup
+   auto it = uidToIdx_.find(uid);
+   if (it == uidToIdx_.end()) return end();
+   WALBERLA_ASSERT_EQUAL(it->first, uid, "Lookup via uidToIdx map is not up to date!!!");
+   return iterator(this, it->second);
+}
+
+inline void ParticleStorage::reserve(const size_t size)
+{
+   uid_.reserve(size);
+   position_.reserve(size);
+   interactionRadius_.reserve(size);
+   flags_.reserve(size);
+   owner_.reserve(size);
+   ghostOwners_.reserve(size);
+   shapeID_.reserve(size);
+   rotation_.reserve(size);
+   angularVelocity_.reserve(size);
+   torque_.reserve(size);
+   linearVelocity_.reserve(size);
+   invMass_.reserve(size);
+   force_.reserve(size);
+   oldForce_.reserve(size);
+   oldTorque_.reserve(size);
+   type_.reserve(size);
+   nextParticle_.reserve(size);
+   oldContactHistory_.reserve(size);
+   newContactHistory_.reserve(size);
+   temperature_.reserve(size);
+   heatFlux_.reserve(size);
+}
+
+inline void ParticleStorage::clear()
+{
+   uid_.clear();
+   position_.clear();
+   interactionRadius_.clear();
+   flags_.clear();
+   owner_.clear();
+   ghostOwners_.clear();
+   shapeID_.clear();
+   rotation_.clear();
+   angularVelocity_.clear();
+   torque_.clear();
+   linearVelocity_.clear();
+   invMass_.clear();
+   force_.clear();
+   oldForce_.clear();
+   oldTorque_.clear();
+   type_.clear();
+   nextParticle_.clear();
+   oldContactHistory_.clear();
+   newContactHistory_.clear();
+   temperature_.clear();
+   heatFlux_.clear();
+   uidToIdx_.clear();
+}
+
+inline size_t ParticleStorage::size() const
+{
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), uid.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), position.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), interactionRadius.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), flags.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), owner.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), ghostOwners.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), shapeID.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), rotation.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), angularVelocity.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), torque.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), linearVelocity.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), invMass.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), force.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), oldForce.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), oldTorque.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), type.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), nextParticle.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), oldContactHistory.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), newContactHistory.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), temperature.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), heatFlux.size() );
+   return uid_.size();
+}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticle(const bool openmp,
+                                             const Selector& selector,
+                                             Accessor& acForPS,
+                                             Func&& func, Args&&... args) 
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      if (selector(uint64_c(i), acForPS))
+         func( uint64_c(i), std::forward<Args>(args)... );
+   }
+}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticle(const bool openmp,
+                                             const Selector& selector,
+                                             Accessor& acForPS,
+                                             Func&& func, Args&&... args) const
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      if (selector(uint64_c(i), acForPS))
+         func( uint64_c(i), std::forward<Args>(args)... );
+   }
+}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticlePair(const bool openmp,
+                                                 const Selector& selector,
+                                                 Accessor& acForPS,
+                                                 Func&& func, Args&&... args) 
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      for (int64_t j = 0; j < int64_c(len); ++j)
+      {
+         if (i!=j)
+         {
+            if (selector(uint64_c(i), uint64_c(j), acForPS))
+            {
+               func( uint64_c(i), uint64_c(j), std::forward<Args>(args)... );
+            }
+         }
+      }
+   }
+}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticlePair(const bool openmp,
+                                                 const Selector& selector,
+                                                 Accessor& acForPS,
+                                                 Func&& func, Args&&... args) const
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      for (int64_t j = 0; j < int64_c(len); ++j)
+      {
+         if (i!=j)
+         {
+            if (selector(uint64_c(i), uint64_c(j), acForPS))
+            {
+               func( uint64_c(i), uint64_c(j), std::forward<Args>(args)... );
+            }
+         }
+      }
+   }
+}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticlePairHalf(const bool openmp,
+                                                     const Selector& selector,
+                                                     Accessor& acForPS,
+                                                     Func&& func, Args&&... args) 
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      for (int64_t j = i+1; j < int64_c(len); ++j)
+      {
+         if (selector(uint64_c(i), uint64_c(j), acForPS))
+         {
+            func( uint64_c(i), uint64_c(j), std::forward<Args>(args)... );
+         }
+      }
+   }
+}
+template <typename Selector, typename Accessor, typename Func, typename... Args>
+inline void ParticleStorage::forEachParticlePairHalf(const bool openmp,
+                                                     const Selector& selector,
+                                                     Accessor& acForPS,
+                                                     Func&& func, Args&&... args) const
+{
+   static_assert (std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor" );
+
+   WALBERLA_UNUSED(openmp);
+   const uint64_t len = size();
+   #ifdef _OPENMP
+   #pragma omp parallel for schedule(static) if (openmp)
+   #endif
+   for (int64_t i = 0; i < int64_c(len); ++i)
+   {
+      for (int64_t j = i+1; j < int64_c(len); ++j)
+      {
+         if (selector(uint64_c(i), uint64_c(j), acForPS))
+         {
+            func( uint64_c(i), uint64_c(j), std::forward<Args>(args)... );
+         }
+      }
+   }
+}
+///Predicate that selects a certain property from a Particle
+class SelectParticleUid
+{
+public:
+   using return_type = walberla::id_t;
+   walberla::id_t& operator()(data::Particle& p) const {return p.getUidRef();}
+   walberla::id_t& operator()(data::Particle&& p) const {return p.getUidRef();}
+   const walberla::id_t& operator()(const data::Particle& p) const {return p.getUid();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticlePosition
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getPositionRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getPositionRef();}
+   const walberla::mesa_pd::Vec3& operator()(const data::Particle& p) const {return p.getPosition();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleInteractionRadius
+{
+public:
+   using return_type = walberla::real_t;
+   walberla::real_t& operator()(data::Particle& p) const {return p.getInteractionRadiusRef();}
+   walberla::real_t& operator()(data::Particle&& p) const {return p.getInteractionRadiusRef();}
+   const walberla::real_t& operator()(const data::Particle& p) const {return p.getInteractionRadius();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleFlags
+{
+public:
+   using return_type = walberla::mesa_pd::data::particle_flags::FlagT;
+   walberla::mesa_pd::data::particle_flags::FlagT& operator()(data::Particle& p) const {return p.getFlagsRef();}
+   walberla::mesa_pd::data::particle_flags::FlagT& operator()(data::Particle&& p) const {return p.getFlagsRef();}
+   const walberla::mesa_pd::data::particle_flags::FlagT& operator()(const data::Particle& p) const {return p.getFlags();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleOwner
+{
+public:
+   using return_type = int;
+   int& operator()(data::Particle& p) const {return p.getOwnerRef();}
+   int& operator()(data::Particle&& p) const {return p.getOwnerRef();}
+   const int& operator()(const data::Particle& p) const {return p.getOwner();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleGhostOwners
+{
+public:
+   using return_type = std::vector<int>;
+   std::vector<int>& operator()(data::Particle& p) const {return p.getGhostOwnersRef();}
+   std::vector<int>& operator()(data::Particle&& p) const {return p.getGhostOwnersRef();}
+   const std::vector<int>& operator()(const data::Particle& p) const {return p.getGhostOwners();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleShapeID
+{
+public:
+   using return_type = size_t;
+   size_t& operator()(data::Particle& p) const {return p.getShapeIDRef();}
+   size_t& operator()(data::Particle&& p) const {return p.getShapeIDRef();}
+   const size_t& operator()(const data::Particle& p) const {return p.getShapeID();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleRotation
+{
+public:
+   using return_type = walberla::mesa_pd::Rot3;
+   walberla::mesa_pd::Rot3& operator()(data::Particle& p) const {return p.getRotationRef();}
+   walberla::mesa_pd::Rot3& operator()(data::Particle&& p) const {return p.getRotationRef();}
+   const walberla::mesa_pd::Rot3& operator()(const data::Particle& p) const {return p.getRotation();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleAngularVelocity
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getAngularVelocityRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getAngularVelocityRef();}
+   const walberla::mesa_pd::Vec3& operator()(const data::Particle& p) const {return p.getAngularVelocity();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleTorque
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getTorqueRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getTorqueRef();}
+   const walberla::mesa_pd::Vec3& operator()(const data::Particle& p) const {return p.getTorque();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleLinearVelocity
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getLinearVelocityRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getLinearVelocityRef();}
+   const walberla::mesa_pd::Vec3& operator()(const data::Particle& p) const {return p.getLinearVelocity();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleInvMass
+{
+public:
+   using return_type = walberla::real_t;
+   walberla::real_t& operator()(data::Particle& p) const {return p.getInvMassRef();}
+   walberla::real_t& operator()(data::Particle&& p) const {return p.getInvMassRef();}
+   const walberla::real_t& operator()(const data::Particle& p) const {return p.getInvMass();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleForce
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getForceRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getForceRef();}
+   const walberla::mesa_pd::Vec3& operator()(const data::Particle& p) const {return p.getForce();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleOldForce
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getOldForceRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getOldForceRef();}
+   const walberla::mesa_pd::Vec3& operator()(const data::Particle& p) const {return p.getOldForce();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleOldTorque
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getOldTorqueRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getOldTorqueRef();}
+   const walberla::mesa_pd::Vec3& operator()(const data::Particle& p) const {return p.getOldTorque();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleType
+{
+public:
+   using return_type = uint_t;
+   uint_t& operator()(data::Particle& p) const {return p.getTypeRef();}
+   uint_t& operator()(data::Particle&& p) const {return p.getTypeRef();}
+   const uint_t& operator()(const data::Particle& p) const {return p.getType();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleNextParticle
+{
+public:
+   using return_type = int;
+   int& operator()(data::Particle& p) const {return p.getNextParticleRef();}
+   int& operator()(data::Particle&& p) const {return p.getNextParticleRef();}
+   const int& operator()(const data::Particle& p) const {return p.getNextParticle();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleOldContactHistory
+{
+public:
+   using return_type = std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>;
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& operator()(data::Particle& p) const {return p.getOldContactHistoryRef();}
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& operator()(data::Particle&& p) const {return p.getOldContactHistoryRef();}
+   const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& operator()(const data::Particle& p) const {return p.getOldContactHistory();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleNewContactHistory
+{
+public:
+   using return_type = std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>;
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& operator()(data::Particle& p) const {return p.getNewContactHistoryRef();}
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& operator()(data::Particle&& p) const {return p.getNewContactHistoryRef();}
+   const std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory>& operator()(const data::Particle& p) const {return p.getNewContactHistory();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleTemperature
+{
+public:
+   using return_type = walberla::real_t;
+   walberla::real_t& operator()(data::Particle& p) const {return p.getTemperatureRef();}
+   walberla::real_t& operator()(data::Particle&& p) const {return p.getTemperatureRef();}
+   const walberla::real_t& operator()(const data::Particle& p) const {return p.getTemperature();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleHeatFlux
+{
+public:
+   using return_type = walberla::real_t;
+   walberla::real_t& operator()(data::Particle& p) const {return p.getHeatFluxRef();}
+   walberla::real_t& operator()(data::Particle&& p) const {return p.getHeatFluxRef();}
+   const walberla::real_t& operator()(const data::Particle& p) const {return p.getHeatFlux();}
+};
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/data/STLOverloads.h b/src/mesa_pd/data/STLOverloads.h
new file mode 100644
index 000000000..28cf24885
--- /dev/null
+++ b/src/mesa_pd/data/STLOverloads.h
@@ -0,0 +1,60 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file STLOverloads.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+
+#include <map>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+template< typename Type >
+std::ostream& operator<<( std::ostream& os, const std::vector<Type>& v )
+{
+   os << "<";
+   for (auto it = v.begin(); it != v.end(); ++it)
+   {
+      os << *it;
+      if (it != v.end()-1)
+         os << ",";
+   }
+   os << ">";
+   return os;
+}
+
+template< typename Key, typename T, typename Compare, typename Allocator >
+std::ostream& operator<<( std::ostream& os, const std::map<Key, T, Compare, Allocator>& m )
+{
+   os << "{";
+   for (auto& v : m)
+   {
+      os << v.first << ":" << v.second << ", ";
+   }
+   os << "}";
+   return os;
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/data/ShapeStorage.h b/src/mesa_pd/data/ShapeStorage.h
new file mode 100644
index 000000000..867098b37
--- /dev/null
+++ b/src/mesa_pd/data/ShapeStorage.h
@@ -0,0 +1,126 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file GeometryStorage.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/shape/BaseShape.h>
+#include <mesa_pd/data/shape/Sphere.h>
+#include <mesa_pd/data/shape/HalfSpace.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+#include <core/math/AABB.h>
+
+#include <memory>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+struct ShapeStorage
+{
+   std::vector<std::unique_ptr<BaseShape>> shapes {};
+
+   template <typename ShapeT, typename... Args>
+   size_t create(Args&&... args);
+
+   template <typename ReturnType, typename func>
+   ReturnType singleDispatch( ParticleStorage& ps, size_t idx, func& f );
+
+   template <typename ReturnType, typename func>
+   ReturnType doubleDispatch( ParticleStorage& ps, size_t idx, size_t idy, func& f );
+};
+//Make sure that no two different shapes have the same unique identifier!
+static_assert( Sphere::SHAPE_TYPE != HalfSpace::SHAPE_TYPE, "Shape types have to be different!" );
+
+template <typename ShapeT, typename... Args>
+size_t ShapeStorage::create(Args&&... args)
+{
+   shapes.push_back(std::make_unique<ShapeT>(std::forward<Args>(args)...));
+   return shapes.size() - 1;
+}
+
+template <typename ReturnType, typename func>
+ReturnType ShapeStorage::singleDispatch( ParticleStorage& ps, size_t idx, func& f )
+{
+   WALBERLA_ASSERT_LESS( idx, ps.size() );
+
+   switch (shapes[ps.getShapeID(idx)]->getShapeType())
+   {
+      case Sphere::SHAPE_TYPE : return f(ps, idx, *static_cast<Sphere*>(shapes[ps.getShapeID(idx)].get()));
+      case HalfSpace::SHAPE_TYPE : return f(ps, idx, *static_cast<HalfSpace*>(shapes[ps.getShapeID(idx)].get()));
+      default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idx)]->getShapeType() << ") could not be determined!");
+   }
+}
+
+template <typename ReturnType, typename func>
+ReturnType ShapeStorage::doubleDispatch( ParticleStorage& ps, size_t idx, size_t idy, func& f )
+{
+   WALBERLA_ASSERT_LESS( idx, ps.size() );
+   WALBERLA_ASSERT_LESS( idy, ps.size() );
+
+   switch (shapes[ps.getShapeID(idx)]->getShapeType())
+   {
+      case Sphere::SHAPE_TYPE :
+         switch (shapes[ps.getShapeID(idy)]->getShapeType())
+         {
+            case Sphere::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<Sphere*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<Sphere*>(shapes[ps.getShapeID(idy)].get()));
+            case HalfSpace::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<Sphere*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<HalfSpace*>(shapes[ps.getShapeID(idy)].get()));
+            default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idy)]->getShapeType() << ") could not be determined!");
+         }
+      case HalfSpace::SHAPE_TYPE :
+         switch (shapes[ps.getShapeID(idy)]->getShapeType())
+         {
+            case Sphere::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<HalfSpace*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<Sphere*>(shapes[ps.getShapeID(idy)].get()));
+            case HalfSpace::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<HalfSpace*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<HalfSpace*>(shapes[ps.getShapeID(idy)].get()));
+            default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idy)]->getShapeType() << ") could not be determined!");
+         }
+      default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idx)]->getShapeType() << ") could not be determined!");
+   }
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/data/shape/BaseShape.h b/src/mesa_pd/data/shape/BaseShape.h
new file mode 100644
index 000000000..6e243477b
--- /dev/null
+++ b/src/mesa_pd/data/shape/BaseShape.h
@@ -0,0 +1,69 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BaseShape.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+/**
+ * Base class of all shape types.
+ *
+ * This class contains the basic properties which are inherent for all shapes.
+ * It also stores the shapeType identifier which allows to cast to the actual shape.
+ */
+class BaseShape
+{
+public:
+   explicit BaseShape(const int shapeType) : shapeType_(shapeType) {}
+   virtual ~BaseShape() = default;
+
+   ///Updates mass and inertia according to the actual shape.
+   virtual void updateMassAndInertia(const real_t density);
+
+   virtual real_t getVolume() const = 0;
+
+   real_t& getInvMass() {return invMass_;}
+   const real_t& getInvMass() const {return invMass_;}
+
+   Mat3& getInvInertiaBF() {return invInertiaBF_;}
+   const Mat3& getInvInertiaBF() const {return invInertiaBF_;}
+
+   const int& getShapeType() const {return shapeType_;}
+
+   static const int INVALID_SHAPE = -1; ///< Unique *invalid* shape type identifier.\ingroup mesa_pd_shape
+private:
+   real_t invMass_      = real_t(0);       ///< inverse mass
+   Mat3   invInertiaBF_ = Mat3(real_t(0)); ///< inverse inertia matrix in the body frame
+   int    shapeType_    = INVALID_SHAPE;   ///< \ingroup mesa_pd_shape
+};
+
+inline
+void BaseShape::updateMassAndInertia(const real_t /*density*/)
+{
+   WALBERLA_ABORT("updateMassAndInertia not implemented!");
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/data/shape/HalfSpace.h b/src/mesa_pd/data/shape/HalfSpace.h
new file mode 100644
index 000000000..734d6b4c6
--- /dev/null
+++ b/src/mesa_pd/data/shape/HalfSpace.h
@@ -0,0 +1,65 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file HalfSpace.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/shape/BaseShape.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+/**
+ * Half space shape class.
+ *
+ * \attention
+ * The HalfSpace does not obay any rotation. The rotation is given purely be the normal of the half space!
+ */
+class HalfSpace : public BaseShape
+{
+public:
+   explicit HalfSpace(const Vec3& normal) : BaseShape(HalfSpace::SHAPE_TYPE), normal_(normal) { updateMassAndInertia(real_t(1.0)); }
+
+   void updateMassAndInertia(const real_t density) override;
+
+   real_t getVolume() const override { return std::numeric_limits<real_t>::infinity(); }
+
+   const Vec3& getNormal() const { return normal_; }
+
+   static const int SHAPE_TYPE = 0; ///< Unique shape type identifier for planes.\ingroup mesa_pd_shape
+private:
+   /**
+    * Normal of the plane in reference to the global world frame.
+    *
+    * The normal of the plane is always pointing towards the halfspace outside the plane.
+   **/
+   Vec3   normal_;
+};
+
+inline
+void HalfSpace::updateMassAndInertia(const real_t /*density*/)
+{
+   getInvMass()      = real_t(0);
+   getInvInertiaBF() = Mat3(real_t(0));
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/data/shape/Sphere.h b/src/mesa_pd/data/shape/Sphere.h
new file mode 100644
index 000000000..7244eb3ed
--- /dev/null
+++ b/src/mesa_pd/data/shape/Sphere.h
@@ -0,0 +1,60 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file Sphere.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/shape/BaseShape.h>
+
+#include <core/math/Constants.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+class Sphere : public BaseShape
+{
+public:
+   explicit Sphere(const real_t& radius) : BaseShape(Sphere::SHAPE_TYPE), radius_(radius) {}
+
+   const real_t& getRadius() const { return radius_; }
+
+   void updateMassAndInertia(const real_t density) override;
+
+   real_t getVolume() const override { return (real_t(4) / real_t(3)) * math::M_PI * getRadius() * getRadius() * getRadius(); }
+
+   static const int SHAPE_TYPE = 1; ///< Unique shape type identifier for spheres.\ingroup mesa_pd_shape
+
+private:
+      real_t radius_; ///< radius of the sphere
+};
+
+inline
+void Sphere::updateMassAndInertia(const real_t density)
+{
+   const real_t m = (real_c(4.0)/real_c(3.0) * math::M_PI) * getRadius() * getRadius() * getRadius() * density;
+   const Mat3   I = Mat3::makeDiagonalMatrix( real_c(0.4) * m * getRadius() * getRadius() );
+
+   getInvMass()      = real_t(1.0) / m;
+   getInvInertiaBF() = I.getInverse();
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/data/shape/shape_group.dox b/src/mesa_pd/data/shape/shape_group.dox
new file mode 100644
index 000000000..ce831631b
--- /dev/null
+++ b/src/mesa_pd/data/shape/shape_group.dox
@@ -0,0 +1,11 @@
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * \defgroup mesa_pd_shape Particle shapes of MESA-PD module
+ * \ingroup mesa_pd
+ * Description of particle shapes available in the MESA-PD module.
+**/
+
+}
+}
diff --git a/src/mesa_pd/domain/BlockForestDataHandling.cpp b/src/mesa_pd/domain/BlockForestDataHandling.cpp
new file mode 100644
index 000000000..683c5aa15
--- /dev/null
+++ b/src/mesa_pd/domain/BlockForestDataHandling.cpp
@@ -0,0 +1,208 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BlockForestDataHandling.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/domain/BlockForestDataHandling.h>
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <mesa_pd/mpi/notifications/ParticleCopyNotification.h>
+
+#include "blockforest/BlockDataHandling.h"
+#include "blockforest/BlockForest.h"
+#include "core/Abort.h"
+
+namespace walberla{
+namespace mesa_pd{
+namespace domain {
+
+namespace internal {
+ParticleDeleter::ParticleDeleter(const std::shared_ptr<data::ParticleStorage>& ps,
+                                 const math::AABB& aabb) :
+   ps_(ps),
+   aabb_(aabb)
+{}
+
+ParticleDeleter::~ParticleDeleter()
+{
+   for (auto pIt = ps_->begin(); pIt != ps_->end(); )
+   {
+      if (aabb_.contains(pIt->getPosition()))
+      {
+         pIt = ps_->erase(pIt);
+         continue;
+      }
+      ++pIt;
+   }
+}
+
+bool operator==(const ParticleDeleter& lhs, const ParticleDeleter& rhs)
+{
+   return ((lhs.ps_ == rhs.ps_) && (lhs.aabb_ == rhs.aabb_));
+}
+} //namespace internal
+
+internal::ParticleDeleter* BlockForestDataHandling::initialize( IBlock * const block )
+{
+   return new internal::ParticleDeleter(ps_, block->getAABB());
+}
+
+void BlockForestDataHandling::serialize( IBlock * const block, const BlockDataID & /*id*/, mpi::SendBuffer & buffer )
+{
+   decltype(ps_->size()) numOfParticles = 0;
+
+   //allocate bytes to store the number of particles which are sent
+   //this number is only known at the end -> this value is written at the end
+   auto ptr = buffer.allocate<decltype(ps_->size())>();
+
+   for( auto pIt = ps_->begin(); pIt != ps_->end(); ++pIt)
+   {
+      //since ps_ is process local we have to skip particles which are not located on this block
+      if ( !block->getAABB().contains( pIt->getPosition()) ) continue;
+      //skip ghosts
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST)) continue;
+      //skip globals
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GLOBAL)) continue;
+
+      buffer << ParticleCopyNotification( *pIt );
+      ++numOfParticles;
+   }
+
+   *ptr = numOfParticles;
+}
+
+internal::ParticleDeleter* BlockForestDataHandling::deserialize( IBlock * const block )
+{
+   return initialize(block);
+}
+
+void BlockForestDataHandling::deserialize( IBlock * const block, const BlockDataID & id, mpi::RecvBuffer & buffer )
+{
+   deserializeImpl( block, id, buffer);
+}
+
+void BlockForestDataHandling::serializeCoarseToFine( Block * const block, const BlockDataID & /*id*/, mpi::SendBuffer & buffer, const uint_t child )
+{
+   // get child aabb
+   const auto childID   = BlockID(block->getId(), child);
+   const auto childAABB = block->getForest().getAABBFromBlockId(childID);
+   //WALBERLA_LOG_DEVEL( (child & uint_t(1)) << (child & uint_t(2)) << (child & uint_t(4)) << "\naabb: " << aabb << "\nchild: " << childAABB );
+
+   decltype(ps_->size()) numOfParticles = 0;
+
+   //allocate bytes to store the number of particles which are sent
+   //this number is only known at the end -> this value is written at the end
+   auto ptr = buffer.allocate<decltype(ps_->size())>();
+
+   for (auto pIt = ps_->begin(); pIt != ps_->end(); ++pIt)
+   {
+      //since ps_ is process local we have to skip particles which are not located on this block
+      if ( !block->getAABB().contains( pIt->getPosition()) ) continue;
+      //skip ghosts
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST)) continue;
+      //skip globals
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GLOBAL)) continue;
+
+      if( childAABB.contains( pIt->getPosition()) )
+      {
+         buffer << ParticleCopyNotification( *pIt );
+         ++numOfParticles;
+      }
+   }
+   *ptr = numOfParticles;
+}
+
+void BlockForestDataHandling::serializeFineToCoarse( Block * const block, const BlockDataID & /*id*/, mpi::SendBuffer & buffer )
+{
+   decltype(ps_->size()) numOfParticles = 0;
+
+   //allocate bytes to store the number of particles which are sent
+   //this number is only known at the end -> this value is written at the end
+   auto ptr = buffer.allocate<decltype(ps_->size())>();
+
+   for (auto pIt = ps_->begin(); pIt != ps_->end(); ++pIt)
+   {
+      //since ps_ is process local we have to skip particles which are not located on this block
+      if ( !block->getAABB().contains( pIt->getPosition()) ) continue;
+      //skip ghosts
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST)) continue;
+      //skip globals
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GLOBAL)) continue;
+
+      buffer << ParticleCopyNotification( *pIt );
+      ++numOfParticles;
+   }
+
+   *ptr = numOfParticles;
+}
+
+internal::ParticleDeleter* BlockForestDataHandling::deserializeCoarseToFine( Block * const block )
+{
+   return initialize(block);
+}
+
+internal::ParticleDeleter* BlockForestDataHandling::deserializeFineToCoarse( Block * const block )
+{
+   return initialize(block);
+}
+
+void BlockForestDataHandling::deserializeCoarseToFine( Block * const block, const BlockDataID & id, mpi::RecvBuffer & buffer )
+{
+   deserializeImpl( block, id, buffer);
+}
+
+
+void BlockForestDataHandling::deserializeFineToCoarse( Block * const block, const BlockDataID & id, mpi::RecvBuffer & buffer, const uint_t /*child*/ )
+{
+   deserializeImpl( block, id, buffer);
+}
+
+
+void BlockForestDataHandling::deserializeImpl( IBlock * const block, const BlockDataID & /*id*/, mpi::RecvBuffer & buffer )
+{
+   decltype(ps_->size()) numBodies = 0;
+   buffer >> numBodies;
+
+   while( numBodies > 0 )
+   {
+      typename ParticleCopyNotification::Parameters objparam;
+      buffer >> objparam;
+
+      auto pIt = createNewParticle(*ps_, objparam);
+      WALBERLA_CHECK(!data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST));
+      pIt->setOwner( MPIManager::instance()->rank() );
+
+      if ( !block->getAABB().contains( pIt->getPosition()) )
+      {
+         WALBERLA_ABORT("Loaded particle not contained within block!\n" << "aabb: " << block->getAABB() << "\nparticle:" << *pIt );
+      }
+
+      --numBodies;
+   }
+}
+
+std::shared_ptr<BlockForestDataHandling> createBlockForestDataHandling(const std::shared_ptr<data::ParticleStorage>& ps)
+{
+   return make_shared<BlockForestDataHandling >( ps );
+}
+
+} //namespace domain
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/domain/BlockForestDataHandling.h b/src/mesa_pd/domain/BlockForestDataHandling.h
new file mode 100644
index 000000000..4df5a2f2c
--- /dev/null
+++ b/src/mesa_pd/domain/BlockForestDataHandling.h
@@ -0,0 +1,98 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BlockForestDataHandling.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "blockforest/BlockDataHandling.h"
+#include "blockforest/BlockForest.h"
+#include "core/Abort.h"
+
+namespace walberla{
+namespace mesa_pd{
+
+namespace data { class ParticleStorage; }
+
+namespace domain {
+
+namespace internal {
+/**
+ * Class which gets initialized by BlockForestDataHandling
+ *
+ * This class is needed to delete particles if blocks get destroyed.
+ * Since the data of MESA_PD is isolated from the blocks one has to
+ * take care of correct removal of particles.
+ *
+ * If a block gets moved by the load balancing the remaining block is
+ * deleted. Therefore all particles have to be destroyed.
+ * However, if the DataHandling is used for checkpointing the particles
+ * should not be deleted after serialization. Coupling the deletion
+ * to the lifetime of the block solves both problems.
+ *
+ * \attention Has to be defined in the header file since data management
+ * of blocks needs complete types.
+ */
+class ParticleDeleter
+{
+   friend bool operator==(const ParticleDeleter& lhs, const ParticleDeleter& rhs);
+public:
+   ParticleDeleter(const std::shared_ptr<data::ParticleStorage>& ps,
+                   const math::AABB& aabb);
+
+   ~ParticleDeleter();
+private:
+   std::shared_ptr<data::ParticleStorage> ps_;
+   math::AABB aabb_; ///< AABB of the associated block.
+};
+
+bool operator==(const ParticleDeleter& lhs, const ParticleDeleter& rhs);
+}
+
+class BlockForestDataHandling: public blockforest::BlockDataHandling<internal::ParticleDeleter>
+{
+public:
+   BlockForestDataHandling(const std::shared_ptr<data::ParticleStorage>& ps) : ps_(ps) {}
+   virtual ~BlockForestDataHandling() {}
+
+   virtual internal::ParticleDeleter* initialize( IBlock * const block ) WALBERLA_OVERRIDE;
+
+   virtual void serialize( IBlock * const block, const BlockDataID & id, mpi::SendBuffer & buffer ) WALBERLA_OVERRIDE;
+   virtual internal::ParticleDeleter* deserialize( IBlock * const block ) WALBERLA_OVERRIDE;
+   virtual void deserialize( IBlock * const block, const BlockDataID & id, mpi::RecvBuffer & buffer ) WALBERLA_OVERRIDE;
+
+   virtual void serializeCoarseToFine( Block * const block, const BlockDataID & id, mpi::SendBuffer & buffer, const uint_t child ) WALBERLA_OVERRIDE;
+   virtual void serializeFineToCoarse( Block * const block, const BlockDataID & id, mpi::SendBuffer & buffer ) WALBERLA_OVERRIDE;
+
+   virtual internal::ParticleDeleter* deserializeCoarseToFine( Block * const block ) WALBERLA_OVERRIDE;
+   virtual internal::ParticleDeleter* deserializeFineToCoarse( Block * const block ) WALBERLA_OVERRIDE;
+
+   virtual void deserializeCoarseToFine( Block * const block, const BlockDataID & id, mpi::RecvBuffer & buffer ) WALBERLA_OVERRIDE;
+   virtual void deserializeFineToCoarse( Block * const block, const BlockDataID & id, mpi::RecvBuffer & buffer, const uint_t child ) WALBERLA_OVERRIDE;
+
+private:
+   void deserializeImpl( IBlock * const block, const BlockDataID & id, mpi::RecvBuffer & buffer );
+
+   std::shared_ptr<data::ParticleStorage> ps_;
+};
+
+std::shared_ptr<BlockForestDataHandling> createBlockForestDataHandling(const std::shared_ptr<data::ParticleStorage>& ps);
+
+} //namespace domain
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/domain/BlockForestDomain.cpp b/src/mesa_pd/domain/BlockForestDomain.cpp
new file mode 100644
index 000000000..483eaa9b8
--- /dev/null
+++ b/src/mesa_pd/domain/BlockForestDomain.cpp
@@ -0,0 +1,246 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BlockForestDomain.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include "BlockForestDomain.h"
+
+#include <core/debug/CheckFunctions.h>
+#include <core/math/AABB.h>
+#include <core/mpi/MPIManager.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace domain {
+
+
+/// \post neighborSubdomains_ is sorted by rank
+BlockForestDomain::BlockForestDomain(const std::shared_ptr<blockforest::BlockForest>& blockForest)
+   : blockForest_(blockForest)
+{
+   ownRank_ = mpi::MPIManager::instance()->rank();
+
+   periodic_[0] = blockForest_->isPeriodic(0);
+   periodic_[1] = blockForest_->isPeriodic(1);
+   periodic_[2] = blockForest_->isPeriodic(2);
+
+   if (blockForest_->empty()) return;
+
+   unionOfLocalAABBs_ = blockForest_->begin()->getAABB();
+   for (auto& iBlk : *blockForest_)
+   {
+      const Block& blk = *static_cast<blockforest::Block*>(&iBlk);
+      localAABBs_.push_back(blk.getAABB());
+      unionOfLocalAABBs_.merge(blk.getAABB());
+      for (uint_t nb = 0; nb < blk.getNeighborhoodSize(); ++nb)
+      {
+         if (int_c(blk.getNeighborProcess(nb)) == ownRank_) continue;
+
+         //check if neighbor aabb is already present
+         const BlockID& nbBlkId = blk.getNeighborId(nb);
+         if (std::find_if(neighborSubdomains_.begin(),
+                          neighborSubdomains_.end(),
+                          [&nbBlkId](const auto& subdomain){return subdomain.blockID == nbBlkId;}) ==
+             neighborSubdomains_.end())
+         {
+            neighborSubdomains_.emplace_back(int_c(blk.getNeighborProcess(nb)), nbBlkId, blk.getNeighborAABB(nb));
+         }
+      }
+   }
+
+   //sort by rank
+   std::sort(neighborSubdomains_.begin(),
+             neighborSubdomains_.end(),
+             [](const auto& lhs, const auto& rhs){ return lhs.rank < rhs.rank; });
+
+   //generate list of neighbor processes
+   int prevRank = -1;
+   for (auto& subdomain : neighborSubdomains_)
+   {
+      if ((prevRank != subdomain.rank) && (subdomain.rank != ownRank_))
+      {
+         neighborProcesses_.emplace_back(uint_c(subdomain.rank));
+         prevRank = subdomain.rank;
+      }
+   }
+}
+
+bool BlockForestDomain::isContainedInProcessSubdomain(const uint_t rank, const Vec3& pt) const
+{
+   if (blockForest_->empty()) return false;
+
+   if (uint_c(ownRank_) == rank)
+   {
+      // check if point is in local subdomain by checking all aabbs
+      for (auto& aabb : localAABBs_)
+      {
+         if (aabb.contains(pt)) return true;
+      }
+   } else
+   {
+      WALBERLA_ASSERT(std::is_sorted(neighborSubdomains_.begin(),
+                                     neighborSubdomains_.end(),
+                                     [](const auto& lhs, const auto& rhs){ return lhs.rank < rhs.rank;}));
+
+      auto begin = std::find_if(neighborSubdomains_.begin(),
+                                neighborSubdomains_.end(),
+                                [&rank](const auto& subdomain){ return subdomain.rank == int_c(rank); });
+      if (begin == neighborSubdomains_.end()) return false; //no information for rank available
+      auto end = std::find_if(begin,
+                              neighborSubdomains_.end(),
+                              [&rank](const auto& subdomain){ return subdomain.rank != int_c(rank); });
+
+      for (auto it = begin; it != end; ++it)
+      {
+         if (it->aabb.contains(pt)) return true;
+      }
+   }
+   return false;
+}
+
+bool   BlockForestDomain::isContainedInLocalSubdomain(const Vec3& pt,
+                                                      const real_t& radius) const
+{
+   for (auto& aabb : localAABBs_)
+   {
+      if (isInsideAABB(pt, radius, aabb)) return true;
+   }
+   return false;
+}
+
+bool BlockForestDomain::isContainedInProcessSubdomain(const Vec3& pt, const real_t& radius) const
+{
+   if (blockForest_->empty()) return false;
+
+   //completely contained in local aabb?
+   for (auto& aabb : localAABBs_)
+   {
+      if (aabb.contains(pt))
+      {
+         if (isInsideAABB(pt, radius, aabb))
+         {
+            return true;
+         }
+
+         break;
+      }
+   }
+
+   //intersects one of the neighboring subdomains?
+   for (auto& subdomain : neighborSubdomains_)
+   {
+      if (sqDistancePointToAABB(pt, subdomain.aabb) < radius*radius) return false;
+   }
+
+   return true;
+}
+
+int BlockForestDomain::findContainingProcessRank(const Vec3& pt) const
+{
+   if (blockForest_->empty()) return -1;
+
+   if (isContainedInProcessSubdomain(uint_c(ownRank_), pt)) return ownRank_;
+   for( uint_t rank : getNeighborProcesses() )
+   {
+      if (isContainedInProcessSubdomain(rank, pt)) return int_c(rank);
+   }
+   return -1;
+}
+void BlockForestDomain::periodicallyMapToDomain(Vec3& pt) const
+{
+   blockForest_->mapToPeriodicDomain(pt);
+}
+
+std::vector<uint_t> BlockForestDomain::getNeighborProcesses() const
+{
+   return neighborProcesses_;
+}
+
+bool BlockForestDomain::intersectsWithProcessSubdomain(const uint_t rank, const Vec3& pt, const real_t& radius) const
+{
+   if (blockForest_->empty()) return false;
+
+   WALBERLA_ASSERT(std::is_sorted(neighborSubdomains_.begin(),
+                                  neighborSubdomains_.end(),
+                                  [](const auto& lhs, const auto& rhs){ return lhs.rank < rhs.rank;}));
+
+   WALBERLA_CHECK_UNEQUAL(uint_c(ownRank_), rank, "checking own domain is currently not implemented");
+
+   if (isInsideGlobalDomain(pt, radius))
+   {
+      size_t idx = 0;
+      WALBERLA_ASSERT_LESS(idx, neighborSubdomains_.size());
+      while (neighborSubdomains_[idx].rank != int_c(rank))
+      {
+         ++idx;
+         WALBERLA_ASSERT_LESS(idx, neighborSubdomains_.size());
+      }
+      while (neighborSubdomains_[idx].rank == int_c(rank))
+      {
+         if (sqDistancePointToAABB(pt, neighborSubdomains_[idx].aabb) <= radius * radius) return true;
+         ++idx;
+         if (idx >= neighborSubdomains_.size()) break;
+         WALBERLA_ASSERT_LESS(idx, neighborSubdomains_.size());
+      }
+   } else
+   {
+      size_t idx = 0;
+      WALBERLA_ASSERT_LESS(idx, neighborSubdomains_.size());
+      while (neighborSubdomains_[idx].rank != int_c(rank))
+      {
+         ++idx;
+         WALBERLA_ASSERT_LESS(idx, neighborSubdomains_.size());
+      }
+      while (neighborSubdomains_[idx].rank == int_c(rank))
+      {
+         if (sqDistancePointToAABBPeriodic(pt, neighborSubdomains_[idx].aabb, blockForest_->getDomain(), periodic_) <= radius * radius) return true;
+         ++idx;
+         if (idx >= neighborSubdomains_.size()) break;
+         WALBERLA_ASSERT_LESS(idx, neighborSubdomains_.size());
+      }
+   }
+
+   return false;
+}
+
+void BlockForestDomain::correctParticlePosition(Vec3& pt) const
+{
+   const Vec3 center = unionOfLocalAABBs_.center();
+   const Vec3 dis = pt - center;
+
+   const auto& domain = blockForest_->getDomain();
+
+   if (periodic_[0] && (-domain.xSize() * 0.5 > dis[0])) pt[0] += domain.xSize();
+   if (periodic_[0] && (+domain.xSize() * 0.5 < dis[0])) pt[0] -= domain.xSize();
+
+   if (periodic_[1] && (-domain.ySize() * 0.5 > dis[1])) pt[1] += domain.ySize();
+   if (periodic_[1] && (+domain.ySize() * 0.5 < dis[1])) pt[1] -= domain.ySize();
+
+   if (periodic_[2] && (-domain.zSize() * 0.5 > dis[2])) pt[2] += domain.zSize();
+   if (periodic_[2] && (+domain.zSize() * 0.5 < dis[2])) pt[2] -= domain.zSize();
+}
+
+
+bool BlockForestDomain::isInsideGlobalDomain(const Vec3& pt, const real_t& radius) const
+{
+   return isInsideAABB(pt, radius, blockForest_->getDomain());
+}
+
+} //namespace domain
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/domain/BlockForestDomain.h b/src/mesa_pd/domain/BlockForestDomain.h
new file mode 100644
index 000000000..897b56348
--- /dev/null
+++ b/src/mesa_pd/domain/BlockForestDomain.h
@@ -0,0 +1,139 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BlockForestDomain.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/domain/IDomain.h>
+
+#include <blockforest/BlockForest.h>
+
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+namespace domain {
+
+class BlockForestDomain : public IDomain
+{
+public:
+   BlockForestDomain(const std::shared_ptr<blockforest::BlockForest>& blockForest);
+
+   bool   isContainedInProcessSubdomain(const uint_t rank, const Vec3& pt) const override;
+   bool   isContainedInLocalSubdomain(const Vec3& pt, const real_t& radius) const override;
+   /// Is the sphere defined by \p pt and \p radius completely inside the local subdomin?
+   /// \attention Also take into account periodicity!
+   /// \param pt center of the sphere
+   /// \param radius radius of the sphere
+   bool   isContainedInProcessSubdomain(const Vec3& pt, const real_t& radius) const;
+   int    findContainingProcessRank(const Vec3& pt) const override;
+   void   periodicallyMapToDomain(Vec3& pt) const override;
+   std::vector<uint_t> getNeighborProcesses() const override;
+   bool   intersectsWithProcessSubdomain(const uint_t rank, const Vec3& pt, const real_t& radius) const override;
+   void   correctParticlePosition(Vec3& pt) const override;
+
+   size_t getNumLocalAABBs() const {return localAABBs_.size();}
+   size_t getNumNeighborSubdomains() const {return neighborSubdomains_.size();}
+   size_t getNumNeighborProcesses() const {return neighborProcesses_.size();}
+private:
+   bool isInsideGlobalDomain(const Vec3& pt, const real_t& radius) const;
+
+   std::shared_ptr<blockforest::BlockForest> blockForest_;
+
+   struct Subdomain
+   {
+      Subdomain(const int r, const BlockID& id, const math::AABB& ab) : rank(r), blockID(id), aabb(ab) {}
+      int rank;
+      BlockID blockID;
+      math::AABB aabb;
+   };
+
+   int ownRank_ = -1;
+   std::array< bool, 3 > periodic_;
+
+   std::vector<math::AABB> localAABBs_;
+   math::AABB              unionOfLocalAABBs_;
+   std::vector<Subdomain>  neighborSubdomains_;
+   std::vector<uint_t>     neighborProcesses_;
+};
+
+} //namespace domain
+
+inline real_t sqDistanceLineToPoint( const real_t& pt, const real_t& min, const real_t& max  )
+{
+   if (pt < min)
+      return (min - pt) * (min - pt);
+   if (pt > max)
+      return (pt - max) * (pt - max);
+   return real_t(0);
+}
+
+inline real_t sqDistancePointToAABB( const Vec3& pt, const math::AABB& aabb )
+{
+   real_t sq = 0.0;
+
+   sq += sqDistanceLineToPoint( pt[0], aabb.xMin(), aabb.xMax() );
+   sq += sqDistanceLineToPoint( pt[1], aabb.yMin(), aabb.yMax() );
+   sq += sqDistanceLineToPoint( pt[2], aabb.zMin(), aabb.zMax() );
+
+   return sq;
+}
+
+inline real_t sqDistancePointToAABBPeriodic( Vec3 pt,
+                                             const math::AABB& aabb,
+                                             const math::AABB& domain,
+                                             const std::array< bool, 3 >& periodic )
+{
+   auto size = domain.sizes() * real_t(0.5);
+   auto d = pt - aabb.center();
+
+   if (periodic[0] && (d[0] < -size[0])) pt[0] += domain.sizes()[0];
+   if (periodic[0] && (d[0] > +size[0])) pt[0] -= domain.sizes()[0];
+
+   if (periodic[1] && (d[1] < -size[1])) pt[1] += domain.sizes()[1];
+   if (periodic[1] && (d[1] > +size[1])) pt[1] -= domain.sizes()[1];
+
+   if (periodic[2] && (d[2] < -size[2])) pt[2] += domain.sizes()[2];
+   if (periodic[2] && (d[2] > +size[2])) pt[2] -= domain.sizes()[2];
+
+   real_t sq = 0.0;
+
+   sq += sqDistanceLineToPoint( pt[0], aabb.xMin(), aabb.xMax() );
+   sq += sqDistanceLineToPoint( pt[1], aabb.yMin(), aabb.yMax() );
+   sq += sqDistanceLineToPoint( pt[2], aabb.zMin(), aabb.zMax() );
+
+   return sq;
+}
+
+inline bool isInsideAABB( const Vec3& pt,
+                          const real_t radius,
+                          const math::AABB& aabb)
+{
+   if (!aabb.contains(pt)) return false;
+   if ((pt[0] - aabb.xMin()) < radius) return false;
+   if ((aabb.xMax() - pt[0]) < radius) return false;
+   if ((pt[1] - aabb.yMin()) < radius) return false;
+   if ((aabb.yMax() - pt[1]) < radius) return false;
+   if ((pt[2] - aabb.zMin()) < radius) return false;
+   if ((aabb.zMax() - pt[2]) < radius) return false;
+   return true;
+}
+
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/domain/IDomain.h b/src/mesa_pd/domain/IDomain.h
new file mode 100644
index 000000000..532a10e02
--- /dev/null
+++ b/src/mesa_pd/domain/IDomain.h
@@ -0,0 +1,74 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file IDomain.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace domain {
+
+/**
+ * Abstract base class for the local subdomain
+ */
+class IDomain
+{
+public:
+   virtual ~IDomain() = default;
+
+   /// Is the point \p pt located inside the subdomain of the process with rank \p rank?
+   /// \attention This function is supposed to check the local subdomain and next neighbor subdomains (periodicity).
+   /// No global check is required!
+   /// \return If you have no information about the specified process return false.
+   virtual bool isContainedInProcessSubdomain(const uint_t rank, const Vec3& pt) const = 0;
+
+   /// Is the sphere located inside the local subdomain?
+   /// \attention This function is used for an early out. Therefore returning true has to be correct, returning false
+   /// can also be wrong.
+   virtual bool isContainedInLocalSubdomain(const Vec3& /*pt*/, const real_t& /*radius*/) const { return false; }
+
+   /// Find the process rank which is responsible for \p pt.
+   /// \attention No global information is required, just check local process and adjacent processes (periodicity!).
+   /// \pre \p pt will be always inside the global domain.
+   /// \return Returns the process rank or -1 if cannot be found within next neighbors.
+   virtual int findContainingProcessRank(const Vec3& pt) const = 0;
+
+   /// Map the point \p pt periodically into the global domain.
+   /// \post pt has to be located inside the global domain
+   virtual void periodicallyMapToDomain(Vec3& pt) const = 0;
+
+   /// Returns a vector of ranks from all neighboring processes.
+   virtual std::vector<uint_t> getNeighborProcesses() const = 0;
+
+   /// Does the sphere defined by \p pt and \p radius intersect with the subdomain
+   /// of process \p rank?
+   /// \param pt center of the sphere
+   /// \param radius radius of the sphere
+   /// \return If you have no information about the specified process return false.
+   virtual bool intersectsWithProcessSubdomain(const uint_t rank, const Vec3& pt, const real_t& radius) const = 0;
+
+   /// Correct the particle position in regard to the local subdomain.
+   virtual void correctParticlePosition(Vec3& pt) const = 0;
+};
+
+} //namespace domain
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/domain/InfiniteDomain.h b/src/mesa_pd/domain/InfiniteDomain.h
new file mode 100644
index 000000000..27a1c2ea7
--- /dev/null
+++ b/src/mesa_pd/domain/InfiniteDomain.h
@@ -0,0 +1,43 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file InfiniteDomain.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/domain/IDomain.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace domain {
+
+class InfiniteDomain : public IDomain
+{
+public:
+   bool   isContainedInProcessSubdomain(const uint_t /*rank*/, const Vec3& /*pt*/) const override {return true;}
+   int    findContainingProcessRank(const Vec3& /*pt*/) const override {return mpi::MPIManager::instance()->rank();}
+   void   periodicallyMapToDomain(Vec3& /*pt*/) const override {}
+   std::vector<uint_t> getNeighborProcesses() const override {return {};}
+   bool   intersectsWithProcessSubdomain(const uint_t rank, const Vec3& /*pt*/, const real_t& /*radius*/) const override
+   { return int_c(rank)==mpi::MPIManager::instance()->rank() ? true : false;}
+   void   correctParticlePosition(Vec3& /*pt*/) const override {}
+};
+
+} //namespace domain
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/domain/InfoCollection.h b/src/mesa_pd/domain/InfoCollection.h
new file mode 100644
index 000000000..d247e65df
--- /dev/null
+++ b/src/mesa_pd/domain/InfoCollection.h
@@ -0,0 +1,119 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file InfoCollection.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <blockforest/BlockForest.h>
+#include <core/mpi/BufferSystem.h>
+#include <pe/amr/BlockInfo.h>
+#include <pe/amr/InfoCollection.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace domain {
+
+template <typename Accessor>
+void createWithNeighborhood(Accessor& ac, const BlockForest& bf, pe::InfoCollection& ic )
+{
+   ic.clear();
+
+   mpi::BufferSystem bs( MPIManager::instance()->comm(), 756 );
+
+   for (size_t idx = 0; idx < ac.size(); ++idx)
+   {
+      for (auto blockIt = bf.begin(); blockIt != bf.end(); ++blockIt)
+      {
+         const blockforest::Block* block   = static_cast<const blockforest::Block*> (&(*blockIt));
+
+         pe::BlockInfo& info = ic[block->getId()];
+         if (block->getAABB().contains(ac.getPosition(idx)))
+         {
+            if (data::particle_flags::isSet( ac.getFlags(idx), data::particle_flags::GHOST))
+            {
+               ++info.communicationWeight;
+            } else
+            {
+               ++info.computationalWeight;
+            }
+
+         }
+
+         for (uint_t branchID = 0; branchID < 8; ++branchID)
+         {
+            const auto childID   = BlockID(block->getId(), branchID);
+            const auto childAABB = bf.getAABBFromBlockId(childID);
+            pe::BlockInfo& childInfo = ic[childID];
+            if (childAABB.contains(ac.getPosition(idx)))
+            {
+               if (data::particle_flags::isSet( ac.getFlags(idx), data::particle_flags::GHOST))
+               {
+                  ++childInfo.communicationWeight;
+               } else
+               {
+                  ++childInfo.computationalWeight;
+               }
+            }
+         }
+      }
+   }
+
+   for (auto blockIt = bf.begin(); blockIt != bf.end(); ++blockIt)
+   {
+      const blockforest::Block* block   = static_cast<const blockforest::Block*> (&(*blockIt));
+
+      pe::BlockInfo& info = ic[block->getId()];
+      for( uint_t nb = uint_t(0); nb < block->getNeighborhoodSize(); ++nb )
+      {
+         bs.sendBuffer( block->getNeighborProcess(nb) ) << pe::InfoCollection::value_type(block->getId(), info);
+      }
+
+      for (uint_t branchID = 0; branchID < 8; ++branchID)
+      {
+         const auto childID   = BlockID(block->getId(), branchID);
+
+         pe::BlockInfo& childInfo = ic[childID];
+
+         for( uint_t nb = uint_t(0); nb < block->getNeighborhoodSize(); ++nb )
+         {
+            bs.sendBuffer( block->getNeighborProcess(nb) ) << pe::InfoCollection::value_type(childID, childInfo);
+         }
+      }
+   }
+
+   // size of buffer is unknown and changes with each send
+   bs.setReceiverInfoFromSendBufferState(false, true);
+   bs.sendAll();
+
+   for( auto recvIt = bs.begin(); recvIt != bs.end(); ++recvIt )
+   {
+      while( !recvIt.buffer().isEmpty() )
+      {
+         pe::InfoCollectionPair val;
+         recvIt.buffer() >> val;
+         ic.insert(val);
+      }
+   }
+}
+
+} //namespace domain
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/kernel/DoubleCast.h b/src/mesa_pd/kernel/DoubleCast.h
new file mode 100644
index 000000000..4d0b09ab6
--- /dev/null
+++ b/src/mesa_pd/kernel/DoubleCast.h
@@ -0,0 +1,102 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SingleCast.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/shape/BaseShape.h>
+#include <mesa_pd/data/shape/Sphere.h>
+#include <mesa_pd/data/shape/HalfSpace.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const BaseShape*& getShape(const size_t p_idx) const;
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class DoubleCast
+{
+public:
+   template <typename Accessor, typename func, typename... Args>
+   auto operator()( size_t idx, size_t idy, Accessor& ac, func& f, Args&&... args );
+};
+
+template <typename Accessor, typename func, typename... Args>
+auto DoubleCast::operator()( size_t idx, size_t idy, Accessor& ac, func& f, Args&&... args )
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   using namespace mesa_pd::data;
+
+   switch (ac.getShape(idx)->getShapeType())
+   {
+      case Sphere::SHAPE_TYPE :
+         switch (ac.getShape(idy)->getShapeType())
+         {
+            case Sphere::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<Sphere*>(ac.getShape(idx)),
+                                                   *static_cast<Sphere*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case HalfSpace::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<Sphere*>(ac.getShape(idx)),
+                                                   *static_cast<HalfSpace*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            default : WALBERLA_ABORT("Shape type (" << ac.getShape(idy)->getShapeType() << ") could not be determined!");
+         }
+      case HalfSpace::SHAPE_TYPE :
+         switch (ac.getShape(idy)->getShapeType())
+         {
+            case Sphere::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<HalfSpace*>(ac.getShape(idx)),
+                                                   *static_cast<Sphere*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case HalfSpace::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<HalfSpace*>(ac.getShape(idx)),
+                                                   *static_cast<HalfSpace*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            default : WALBERLA_ABORT("Shape type (" << ac.getShape(idy)->getShapeType() << ") could not be determined!");
+         }
+      default : WALBERLA_ABORT("Shape type (" << ac.getShape(idx)->getShapeType() << ") could not be determined!");
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/ExplicitEuler.h b/src/mesa_pd/kernel/ExplicitEuler.h
new file mode 100644
index 000000000..47e93bc62
--- /dev/null
+++ b/src/mesa_pd/kernel/ExplicitEuler.h
@@ -0,0 +1,88 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ExplicitEuler.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Kernel which explicitly integrates all particles in time.
+ * This integrator integrates velocity and position.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ * void setPosition(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t p_idx) const;
+ * void setLinearVelocity(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::real_t& getInvMass(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getForce(const size_t p_idx) const;
+ * void setForce(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t p_idx) const;
+ *
+ * \endcode
+ *
+ * \pre  All forces acting on the particles have to be set.
+ * \post All forces are reset to 0.
+ * \ingroup mesa_pd_kernel
+ */
+class ExplicitEuler
+{
+public:
+   explicit ExplicitEuler(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+private:
+   real_t dt_ = real_t(0.0);
+};
+
+template <typename Accessor>
+inline void ExplicitEuler::operator()(const size_t idx,
+                                      Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(idx), data::particle_flags::FIXED))
+   {
+      ac.setPosition      (idx, ac.getInvMass(idx) * ac.getForce(idx) * dt_ * dt_ + ac.getLinearVelocity(idx) * dt_ + ac.getPosition(idx));
+      ac.setLinearVelocity(idx, ac.getInvMass(idx) * ac.getForce(idx) * dt_ + ac.getLinearVelocity(idx));
+   }
+   ac.setForce         (idx, Vec3(real_t(0), real_t(0), real_t(0)));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/ExplicitEulerWithShape.h b/src/mesa_pd/kernel/ExplicitEulerWithShape.h
new file mode 100644
index 000000000..6746d156e
--- /dev/null
+++ b/src/mesa_pd/kernel/ExplicitEulerWithShape.h
@@ -0,0 +1,114 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ExplicitEulerWithShape.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Kernel which explicitly integrates all particles in time.
+ * This integrator integrates velocity and position as well as angular velocity and rotation.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ * void setPosition(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t p_idx) const;
+ * void setLinearVelocity(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::real_t& getInvMass(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getForce(const size_t p_idx) const;
+ * void setForce(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Rot3& getRotation(const size_t p_idx) const;
+ * void setRotation(const size_t p_idx, const walberla::mesa_pd::Rot3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t p_idx) const;
+ * void setAngularVelocity(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Mat3& getInvInertiaBF(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getTorque(const size_t p_idx) const;
+ * void setTorque(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t p_idx) const;
+ *
+ * \endcode
+ *
+ * \pre  All forces and torques acting on the particles have to be set.
+ * \post All forces and torques are reset to 0.
+ * \ingroup mesa_pd_kernel
+ */
+class ExplicitEulerWithShape
+{
+public:
+   explicit ExplicitEulerWithShape(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+private:
+   real_t dt_ = real_t(0.0);
+};
+
+template <typename Accessor>
+inline void ExplicitEulerWithShape::operator()(const size_t idx,
+                                               Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(idx), data::particle_flags::FIXED))
+   {
+      ac.setPosition      (idx, ac.getInvMass(idx) * ac.getForce(idx) * dt_ * dt_ + ac.getLinearVelocity(idx) * dt_ + ac.getPosition(idx));
+      ac.setLinearVelocity(idx, ac.getInvMass(idx) * ac.getForce(idx) * dt_ + ac.getLinearVelocity(idx));
+
+      const Vec3 wdot = math::transformMatrixRART(ac.getRotation(idx).getMatrix(),
+                                                  ac.getInvInertiaBF(idx)) * ac.getTorque(idx);
+
+      // Calculating the rotation angle
+      const Vec3 phi( ac.getAngularVelocity(idx) * dt_ + wdot * dt_ * dt_);
+
+      // Calculating the new orientation
+      auto rotation = ac.getRotation(idx);
+      rotation.rotate( phi );
+      ac.setRotation(idx, rotation);
+
+      ac.setAngularVelocity(idx, wdot * dt_ + ac.getAngularVelocity(idx));
+   }
+
+   ac.setForce (idx, Vec3(real_t(0), real_t(0), real_t(0)));
+   ac.setTorque(idx, Vec3(real_t(0), real_t(0), real_t(0)));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/ForceLJ.h b/src/mesa_pd/kernel/ForceLJ.h
new file mode 100644
index 000000000..dd40c699a
--- /dev/null
+++ b/src/mesa_pd/kernel/ForceLJ.h
@@ -0,0 +1,171 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ForceLJ.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Kernel which calculates the Lennard Jones froce between two particles.
+ *
+ * This kernel uses the type property of a particle to decide on the material parameters.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ *
+ * walberla::mesa_pd::Vec3& getForceRef(const size_t p_idx);
+ *
+ * const uint_t& getType(const size_t p_idx) const;
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class ForceLJ
+{
+public:
+   ForceLJ(const uint_t numParticleTypes);
+   ForceLJ(const ForceLJ& other) = default;
+   ForceLJ(ForceLJ&& other) = default;
+   ForceLJ& operator=(const ForceLJ& other) = default;
+   ForceLJ& operator=(ForceLJ&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx, const size_t np_idx, Accessor& ac) const;
+
+   
+   /// assumes this parameter is symmetric
+   void setEpsilon(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setSigma(const size_t type1, const size_t type2, const real_t& val);
+
+   
+   real_t getEpsilon(const size_t type1, const size_t type2) const;
+   real_t getSigma(const size_t type1, const size_t type2) const;
+private:
+   uint_t numParticleTypes_;
+   
+   std::vector<real_t> epsilon {};
+   std::vector<real_t> sigma {};
+};
+
+ForceLJ::ForceLJ(const uint_t numParticleTypes)
+{
+   numParticleTypes_ = numParticleTypes;
+   
+   epsilon.resize(numParticleTypes * numParticleTypes, real_t(0));
+   sigma.resize(numParticleTypes * numParticleTypes, real_t(0));
+}
+
+
+inline void ForceLJ::setEpsilon(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   epsilon[numParticleTypes_*type1 + type2] = val;
+   epsilon[numParticleTypes_*type2 + type1] = val;
+}
+inline void ForceLJ::setSigma(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   sigma[numParticleTypes_*type1 + type2] = val;
+   sigma[numParticleTypes_*type2 + type1] = val;
+}
+
+
+inline real_t ForceLJ::getEpsilon(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( epsilon[numParticleTypes_*type1 + type2],
+                                epsilon[numParticleTypes_*type2 + type1],
+                                "parameter matrix for epsilon not symmetric!");
+   return epsilon[numParticleTypes_*type1 + type2];
+}
+inline real_t ForceLJ::getSigma(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( sigma[numParticleTypes_*type1 + type2],
+                                sigma[numParticleTypes_*type2 + type1],
+                                "parameter matrix for sigma not symmetric!");
+   return sigma[numParticleTypes_*type1 + type2];
+}
+
+template <typename Accessor>
+inline void ForceLJ::operator()(const size_t p_idx, const size_t np_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx != np_idx)
+   {
+      Vec3 dir = ac.getPosition(p_idx) - ac.getPosition(np_idx);
+      const real_t rsq = sqrLength(dir);
+      const real_t sr2 = real_t(1.0) / rsq;
+      const real_t sr2sigma = sr2 * getSigma(ac.getType(p_idx), ac.getType(np_idx)) * getSigma(ac.getType(p_idx), ac.getType(np_idx));
+      const real_t sr6 = sr2sigma * sr2sigma * sr2sigma;
+      const real_t force = real_t(48) * sr6 * ( sr6 - real_t(0.5) ) * sr2 * getEpsilon(ac.getType(p_idx), ac.getType(np_idx));
+      const Vec3 f = force * dir;
+
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[0]  += f[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[1]  += f[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(p_idx)[2]  += f[2];
+
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(np_idx)[0]  -= f[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(np_idx)[1]  -= f[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getForceRef(np_idx)[2]  -= f[2];
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/HeatConduction.h b/src/mesa_pd/kernel/HeatConduction.h
new file mode 100644
index 000000000..12826c523
--- /dev/null
+++ b/src/mesa_pd/kernel/HeatConduction.h
@@ -0,0 +1,128 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file HeatConduction.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/ParticleFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <core/math/Constants.h>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Basic DEM kernel
+ *
+ * This DEM kernel supports spring&dashpot in normal direction as well as friction in tangential direction.
+ *
+ * \code
+ * const walberla::real_t& getTemperature(const size_t p_idx) const;
+ *
+ * const walberla::real_t& getHeatFlux(const size_t p_idx) const;
+ * void setHeatFlux(const size_t p_idx, const walberla::real_t& v);
+ * walberla::real_t& getHeatFluxRef(const size_t p_idx);
+ *
+ * const uint_t& getType(const size_t p_idx) const;
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class HeatConduction
+{
+public:
+   HeatConduction(const uint_t numParticleTypes);
+   HeatConduction(const HeatConduction& other) = default;
+   HeatConduction(HeatConduction&& other) = default;
+   HeatConduction& operator=(const HeatConduction& other) = default;
+   HeatConduction& operator=(HeatConduction&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac) const;
+
+   
+   /// assumes this parameter is symmetric
+   void setConductance(const size_t type1, const size_t type2, const real_t& val);
+
+   
+   real_t getConductance(const size_t type1, const size_t type2) const;
+
+private:
+   uint_t numParticleTypes_;
+   
+   std::vector<real_t> conductance_ {};
+};
+
+HeatConduction::HeatConduction(const uint_t numParticleTypes)
+{
+   numParticleTypes_ = numParticleTypes;
+   
+   conductance_.resize(numParticleTypes * numParticleTypes, real_t(0));
+}
+
+
+inline void HeatConduction::setConductance(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   conductance_[numParticleTypes_*type1 + type2] = val;
+   conductance_[numParticleTypes_*type2 + type1] = val;
+}
+
+
+inline real_t HeatConduction::getConductance(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( conductance_[numParticleTypes_*type1 + type2],
+                                conductance_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for conductance not symmetric!");
+   return conductance_[numParticleTypes_*type1 + type2];
+}
+
+template <typename Accessor>
+inline void HeatConduction::operator()(const size_t p_idx1,
+                                       const size_t p_idx2,
+                                       Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx1 != p_idx2)
+   {
+      auto deltaT = ac.getTemperature(p_idx1) - ac.getTemperature(p_idx2);
+      ac.getHeatFluxRef(p_idx1) -= deltaT * getConductance(ac.getType(p_idx1), ac.getType(p_idx2));
+      ac.getHeatFluxRef(p_idx2) += deltaT * getConductance(ac.getType(p_idx1), ac.getType(p_idx2));
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/InsertParticleIntoLinkedCells.h b/src/mesa_pd/kernel/InsertParticleIntoLinkedCells.h
new file mode 100644
index 000000000..8ec5b660a
--- /dev/null
+++ b/src/mesa_pd/kernel/InsertParticleIntoLinkedCells.h
@@ -0,0 +1,91 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file InsertParticleIntoLinkedCells.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/LinkedCells.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Inserts a particle into the data::LinkedCells data structure
+ *
+ * \attention Make sure to data::LinkedCells::clear() the data structure before
+ * reinserting new particles.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t p_idx) const;
+ *
+ * const size_t& getNextParticle(const size_t p_idx) const;
+ * void setNextParticle(const size_t p_idx, const size_t& v);
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class InsertParticleIntoLinkedCells
+{
+public:
+   template <typename Accessor>
+   void operator()(const size_t p_idx, Accessor& ac, data::LinkedCells& lc) const;
+};
+
+template <typename Accessor>
+inline void InsertParticleIntoLinkedCells::operator()(const size_t p_idx, Accessor& ac, data::LinkedCells& lc) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   const auto& minCorner = lc.domain_.minCorner();
+   if (data::particle_flags::isSet(ac.getFlags(p_idx), data::particle_flags::INFINITE))
+   {
+      ac.setNextParticle(p_idx, lc.infiniteParticles_.exchange(int_c(p_idx)));
+   } else
+   {
+      int hash0 = static_cast<int>(std::floor((ac.getPosition(p_idx)[0] - minCorner[0]) * lc.invCellDiameter_[0]));
+      int hash1 = static_cast<int>(std::floor((ac.getPosition(p_idx)[1] - minCorner[1]) * lc.invCellDiameter_[1]));
+      int hash2 = static_cast<int>(std::floor((ac.getPosition(p_idx)[2] - minCorner[2]) * lc.invCellDiameter_[2]));
+      if (hash0 < 0) hash0 = 0;
+      if (hash0 >= lc.numCellsPerDim_[0]) hash0 = lc.numCellsPerDim_[0] - 1;
+      if (hash1 < 0) hash1 = 0;
+      if (hash1 >= lc.numCellsPerDim_[1]) hash1 = lc.numCellsPerDim_[1] - 1;
+      if (hash2 < 0) hash2 = 0;
+      if (hash2 >= lc.numCellsPerDim_[2]) hash2 = lc.numCellsPerDim_[2] - 1;
+      int cell_idx = getCellIdx(lc, hash0, hash1, hash2);
+      ac.setNextParticle(p_idx, lc.cells_[uint_c(cell_idx)].exchange(int_c(p_idx)));
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/LinearSpringDashpot.h b/src/mesa_pd/kernel/LinearSpringDashpot.h
new file mode 100644
index 000000000..5239f29b6
--- /dev/null
+++ b/src/mesa_pd/kernel/LinearSpringDashpot.h
@@ -0,0 +1,357 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LinearSpringDashpot.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/ParticleFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <core/logging/Logging.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Advanced DEM kernel
+ *
+ * This model is a linearized version of
+ * Edward Biegert, Bernhard Vowinckel, Eckart Meiburg
+ * A collision model for grain-resolving simulations of flows over dense, mobile, polydisperse granular sediment beds
+ * https://doi.org/10.1016/j.jcp.2017.03.035
+ *
+ * \code
+ * const walberla::id_t& getUid(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t p_idx) const;
+ *
+ * walberla::mesa_pd::Vec3& getForceRef(const size_t p_idx);
+ *
+ * const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t p_idx) const;
+ *
+ * walberla::mesa_pd::Vec3& getTorqueRef(const size_t p_idx);
+ *
+ * const uint_t& getType(const size_t p_idx) const;
+ *
+ * const std::map<walberla::id_t, walberla::mesa_pd::Vec3>& getContactHistory(const size_t p_idx) const;
+ * void setContactHistory(const size_t p_idx, const std::map<walberla::id_t, walberla::mesa_pd::Vec3>& v);
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class LinearSpringDashpot
+{
+public:
+   LinearSpringDashpot(const uint_t numParticleTypes);
+   LinearSpringDashpot(const LinearSpringDashpot& other) = default;
+   LinearSpringDashpot(LinearSpringDashpot&& other) = default;
+   LinearSpringDashpot& operator=(const LinearSpringDashpot& other) = default;
+   LinearSpringDashpot& operator=(LinearSpringDashpot&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac,
+                   const Vec3& contactPoint,
+                   const Vec3& contactNormal,
+                   const real_t& penetrationDepth,
+                   const real_t& dt) const;
+
+   
+   /// assumes this parameter is symmetric
+   void setStiffnessN(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setStiffnessT(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setDampingN(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setDampingT(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setFrictionCoefficientStatic(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setFrictionCoefficientDynamic(const size_t type1, const size_t type2, const real_t& val);
+
+   
+   real_t getStiffnessN(const size_t type1, const size_t type2) const;
+   real_t getStiffnessT(const size_t type1, const size_t type2) const;
+   real_t getDampingN(const size_t type1, const size_t type2) const;
+   real_t getDampingT(const size_t type1, const size_t type2) const;
+   real_t getFrictionCoefficientStatic(const size_t type1, const size_t type2) const;
+   real_t getFrictionCoefficientDynamic(const size_t type1, const size_t type2) const;
+private:
+   uint_t numParticleTypes_;
+   
+   std::vector<real_t> stiffnessN_ {};
+   std::vector<real_t> stiffnessT_ {};
+   std::vector<real_t> dampingN_ {};
+   std::vector<real_t> dampingT_ {};
+   std::vector<real_t> frictionCoefficientStatic_ {};
+   std::vector<real_t> frictionCoefficientDynamic_ {};
+};
+
+LinearSpringDashpot::LinearSpringDashpot(const uint_t numParticleTypes)
+{
+   numParticleTypes_ = numParticleTypes;
+   
+   stiffnessN_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   stiffnessT_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   dampingN_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   dampingT_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   frictionCoefficientStatic_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   frictionCoefficientDynamic_.resize(numParticleTypes * numParticleTypes, real_t(0));
+}
+
+
+inline void LinearSpringDashpot::setStiffnessN(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   stiffnessN_[numParticleTypes_*type1 + type2] = val;
+   stiffnessN_[numParticleTypes_*type2 + type1] = val;
+}
+inline void LinearSpringDashpot::setStiffnessT(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   stiffnessT_[numParticleTypes_*type1 + type2] = val;
+   stiffnessT_[numParticleTypes_*type2 + type1] = val;
+}
+inline void LinearSpringDashpot::setDampingN(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   dampingN_[numParticleTypes_*type1 + type2] = val;
+   dampingN_[numParticleTypes_*type2 + type1] = val;
+}
+inline void LinearSpringDashpot::setDampingT(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   dampingT_[numParticleTypes_*type1 + type2] = val;
+   dampingT_[numParticleTypes_*type2 + type1] = val;
+}
+inline void LinearSpringDashpot::setFrictionCoefficientStatic(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   frictionCoefficientStatic_[numParticleTypes_*type1 + type2] = val;
+   frictionCoefficientStatic_[numParticleTypes_*type2 + type1] = val;
+}
+inline void LinearSpringDashpot::setFrictionCoefficientDynamic(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   frictionCoefficientDynamic_[numParticleTypes_*type1 + type2] = val;
+   frictionCoefficientDynamic_[numParticleTypes_*type2 + type1] = val;
+}
+
+
+inline real_t LinearSpringDashpot::getStiffnessN(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( stiffnessN_[numParticleTypes_*type1 + type2],
+                                stiffnessN_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for stiffnessN not symmetric!");
+   return stiffnessN_[numParticleTypes_*type1 + type2];
+}
+inline real_t LinearSpringDashpot::getStiffnessT(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( stiffnessT_[numParticleTypes_*type1 + type2],
+                                stiffnessT_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for stiffnessT not symmetric!");
+   return stiffnessT_[numParticleTypes_*type1 + type2];
+}
+inline real_t LinearSpringDashpot::getDampingN(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( dampingN_[numParticleTypes_*type1 + type2],
+                                dampingN_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for dampingN not symmetric!");
+   return dampingN_[numParticleTypes_*type1 + type2];
+}
+inline real_t LinearSpringDashpot::getDampingT(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( dampingT_[numParticleTypes_*type1 + type2],
+                                dampingT_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for dampingT not symmetric!");
+   return dampingT_[numParticleTypes_*type1 + type2];
+}
+inline real_t LinearSpringDashpot::getFrictionCoefficientStatic(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( frictionCoefficientStatic_[numParticleTypes_*type1 + type2],
+                                frictionCoefficientStatic_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for frictionCoefficientStatic not symmetric!");
+   return frictionCoefficientStatic_[numParticleTypes_*type1 + type2];
+}
+inline real_t LinearSpringDashpot::getFrictionCoefficientDynamic(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( frictionCoefficientDynamic_[numParticleTypes_*type1 + type2],
+                                frictionCoefficientDynamic_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for frictionCoefficientDynamic not symmetric!");
+   return frictionCoefficientDynamic_[numParticleTypes_*type1 + type2];
+}
+
+template <typename Accessor>
+inline void LinearSpringDashpot::operator()(const size_t p_idx1,
+                                            const size_t p_idx2,
+                                            Accessor& ac,
+                                            const Vec3& contactPoint,
+                                            const Vec3& contactNormal,
+                                            const real_t& penetrationDepth,
+                                            const real_t& dt) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx1 != p_idx2)
+   {
+
+      WALBERLA_ASSERT_FLOAT_EQUAL(math::sqrLength(contactNormal), real_t(1));
+
+      real_t delta = -penetrationDepth;
+      if (delta < real_t(0)) return;
+
+
+      const Vec3 relVel ( -(getVelocityAtWFPoint(p_idx1, ac, contactPoint) - getVelocityAtWFPoint(p_idx2, ac, contactPoint)) );
+      const Vec3 relVelN( math::dot(relVel, contactNormal) * contactNormal );
+      const Vec3 relVelT( relVel - relVelN );
+
+
+      // calculate the normal force based on a linear spring-dashpot force model
+      Vec3 fN = getStiffnessN(ac.getType(p_idx1), ac.getType(p_idx2)) * delta * contactNormal +
+                getDampingN(ac.getType(p_idx1), ac.getType(p_idx2)) * relVelN;
+
+
+      // get further collision properties from the contact history or initialize them
+      Vec3 tangentialSpringDisplacement( real_t(0) );
+      real_t impactVelocityMagnitude(real_t(0));
+      bool isSticking = false;
+      auto contactHistory = ac.getOldContactHistoryRef(p_idx1).find(ac.getUid(p_idx2)); //TODO assert symmetry
+      if(contactHistory != ac.getOldContactHistoryRef(p_idx1).end())
+      {
+         // get infos from the contact history
+         tangentialSpringDisplacement = contactHistory->second.getTangentialSpringDisplacement();
+         isSticking = contactHistory->second.getIsSticking();
+         impactVelocityMagnitude = contactHistory->second.getImpactVelocityMagnitude();
+
+      }else
+      {
+         // new contact: initialize values
+         impactVelocityMagnitude = relVel.length();
+      }
+
+      //TODO: move to own tangential integration kernel?
+      Vec3 rotatedTangentialDisplacement = tangentialSpringDisplacement - contactNormal * (contactNormal * tangentialSpringDisplacement);
+      Vec3 newTangentialSpringDisplacement = rotatedTangentialDisplacement.sqrLength() <= real_t(0) ? // avoid division by zero
+                                             Vec3(real_t(0)) :
+                                             ( rotatedTangentialDisplacement * std::sqrt((tangentialSpringDisplacement.sqrLength() / rotatedTangentialDisplacement.sqrLength())));
+      newTangentialSpringDisplacement = newTangentialSpringDisplacement + dt * relVelT;
+
+      // calculate the tangential force based on a linear spring-dashpot force model
+      const real_t stiffnessT = getStiffnessT(ac.getType(p_idx1), ac.getType(p_idx2));
+      const real_t dampingT = getDampingT(ac.getType(p_idx1), ac.getType(p_idx2));
+      Vec3 fTLS = stiffnessT * newTangentialSpringDisplacement +
+                  dampingT * relVelT;
+
+      const Vec3 t = fTLS.getNormalizedOrZero(); // tangential unit vector
+
+      // calculate friction force
+      const real_t fFrictionAbsStatic = getFrictionCoefficientStatic(ac.getType(p_idx1), ac.getType(p_idx2)) * fN.length(); // sticking, rolling
+      const real_t fFrictionAbsDynamic = getFrictionCoefficientDynamic(ac.getType(p_idx1), ac.getType(p_idx2)) * fN.length(); // sliding
+
+      const real_t tangentialVelocityThreshold = real_t(1e-8);
+
+      real_t fFrictionAbs;
+      if( isSticking && relVelT.length() < tangentialVelocityThreshold && fTLS.length() < fFrictionAbsStatic  )
+      {
+         fFrictionAbs = fFrictionAbsStatic;
+      }
+      else if( isSticking && fTLS.length() < fFrictionAbsDynamic )
+      {
+         // sticking
+         fFrictionAbs = fFrictionAbsDynamic;
+      }
+      else
+      {
+         // slipping
+         fFrictionAbs = fFrictionAbsDynamic;
+
+         isSticking = false;
+
+         // reset displacement vector
+         if(stiffnessT > real_t(0) ) newTangentialSpringDisplacement = ( fFrictionAbs * t - dampingT * relVelT ) / stiffnessT;
+
+         // if tangential force falls below coulomb limit, we are back in sticking
+         if( fTLS.length() < fFrictionAbsDynamic )
+         {
+            //TODO really?
+            isSticking = true;
+         }
+      }
+
+      const real_t fTabs( std::min( fTLS.length(), fFrictionAbs) );
+      const Vec3   fT   ( fTabs * t );
+
+      //TODO check if tangential spring displacement is same for symmetric case
+      auto& ch1 = ac.getNewContactHistoryRef(p_idx1)[ac.getUid(p_idx2)];
+      ch1.setTangentialSpringDisplacement(newTangentialSpringDisplacement);
+      ch1.setIsSticking(isSticking);
+      ch1.setImpactVelocityMagnitude(impactVelocityMagnitude);
+
+      auto& ch2 = ac.getNewContactHistoryRef(p_idx2)[ac.getUid(p_idx1)];
+      ch2.setTangentialSpringDisplacement(newTangentialSpringDisplacement);
+      ch2.setIsSticking(isSticking);
+      ch2.setImpactVelocityMagnitude(impactVelocityMagnitude);
+
+      // Add normal force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fN, contactPoint );
+      addForceAtWFPosAtomic( p_idx2, ac, -fN, contactPoint );
+
+      // Add tangential force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fT, contactPoint );
+      addForceAtWFPosAtomic( p_idx2, ac, -fT, contactPoint );
+
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/NonLinearSpringDashpot.h b/src/mesa_pd/kernel/NonLinearSpringDashpot.h
new file mode 100644
index 000000000..987cff4ef
--- /dev/null
+++ b/src/mesa_pd/kernel/NonLinearSpringDashpot.h
@@ -0,0 +1,377 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file NonLinearSpringDashpot.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/ParticleFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <core/logging/Logging.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Advanced DEM kernel
+ *
+ * This model is the model from
+ * Edward Biegert, Bernhard Vowinckel, Eckart Meiburg
+ * A collision model for grain-resolving simulations of flows over dense, mobile, polydisperse granular sediment beds
+ * https://doi.org/10.1016/j.jcp.2017.03.035
+ *
+ * \code
+ * const walberla::id_t& getUid(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t p_idx) const;
+ *
+ * walberla::mesa_pd::Vec3& getForceRef(const size_t p_idx);
+ *
+ * const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t p_idx) const;
+ *
+ * walberla::mesa_pd::Vec3& getTorqueRef(const size_t p_idx);
+ *
+ * const uint_t& getType(const size_t p_idx) const;
+ *
+ * const std::map<walberla::id_t, walberla::mesa_pd::Vec3>& getContactHistory(const size_t p_idx) const;
+ * void setContactHistory(const size_t p_idx, const std::map<walberla::id_t, walberla::mesa_pd::Vec3>& v);
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class NonLinearSpringDashpot
+{
+public:
+   NonLinearSpringDashpot(const uint_t numParticleTypes, const real_t collisionTime);
+   NonLinearSpringDashpot(const NonLinearSpringDashpot& other) = default;
+   NonLinearSpringDashpot(NonLinearSpringDashpot&& other) = default;
+   NonLinearSpringDashpot& operator=(const NonLinearSpringDashpot& other) = default;
+   NonLinearSpringDashpot& operator=(NonLinearSpringDashpot&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac,
+                   const Vec3& contactPoint,
+                   const Vec3& contactNormal,
+                   const real_t& penetrationDepth,
+                   const real_t& dt) const;
+
+   void setCOR(const size_t type1, const size_t type2, const real_t& val);
+   
+   /// assumes this parameter is symmetric
+   void setLnCORsqr(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setMeff(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setStiffnessT(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setDampingT(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setFrictionCoefficientStatic(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setFrictionCoefficientDynamic(const size_t type1, const size_t type2, const real_t& val);
+
+   
+   real_t getLnCORsqr(const size_t type1, const size_t type2) const;
+   real_t getMeff(const size_t type1, const size_t type2) const;
+   real_t getStiffnessT(const size_t type1, const size_t type2) const;
+   real_t getDampingT(const size_t type1, const size_t type2) const;
+   real_t getFrictionCoefficientStatic(const size_t type1, const size_t type2) const;
+   real_t getFrictionCoefficientDynamic(const size_t type1, const size_t type2) const;
+private:
+   uint_t numParticleTypes_;
+   real_t collisionTime_;
+   
+   std::vector<real_t> lnCORsqr_ {};
+   std::vector<real_t> meff_ {};
+   std::vector<real_t> stiffnessT_ {};
+   std::vector<real_t> dampingT_ {};
+   std::vector<real_t> frictionCoefficientStatic_ {};
+   std::vector<real_t> frictionCoefficientDynamic_ {};
+};
+
+NonLinearSpringDashpot::NonLinearSpringDashpot(const uint_t numParticleTypes, const real_t collisionTime)
+{
+   numParticleTypes_ = numParticleTypes;
+   
+   lnCORsqr_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   meff_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   stiffnessT_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   dampingT_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   frictionCoefficientStatic_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   frictionCoefficientDynamic_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   collisionTime_ = collisionTime;
+}
+
+inline void NonLinearSpringDashpot::setCOR(const size_t type1, const size_t type2, const real_t& val)
+{
+   auto lnVal = std::log(val);
+   auto lnValSqr = lnVal * lnVal;
+   setLnCORsqr(type1, type2, lnValSqr);
+}
+
+
+inline void NonLinearSpringDashpot::setLnCORsqr(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   lnCORsqr_[numParticleTypes_*type1 + type2] = val;
+   lnCORsqr_[numParticleTypes_*type2 + type1] = val;
+}
+inline void NonLinearSpringDashpot::setMeff(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   meff_[numParticleTypes_*type1 + type2] = val;
+   meff_[numParticleTypes_*type2 + type1] = val;
+}
+inline void NonLinearSpringDashpot::setStiffnessT(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   stiffnessT_[numParticleTypes_*type1 + type2] = val;
+   stiffnessT_[numParticleTypes_*type2 + type1] = val;
+}
+inline void NonLinearSpringDashpot::setDampingT(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   dampingT_[numParticleTypes_*type1 + type2] = val;
+   dampingT_[numParticleTypes_*type2 + type1] = val;
+}
+inline void NonLinearSpringDashpot::setFrictionCoefficientStatic(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   frictionCoefficientStatic_[numParticleTypes_*type1 + type2] = val;
+   frictionCoefficientStatic_[numParticleTypes_*type2 + type1] = val;
+}
+inline void NonLinearSpringDashpot::setFrictionCoefficientDynamic(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   frictionCoefficientDynamic_[numParticleTypes_*type1 + type2] = val;
+   frictionCoefficientDynamic_[numParticleTypes_*type2 + type1] = val;
+}
+
+
+inline real_t NonLinearSpringDashpot::getLnCORsqr(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( lnCORsqr_[numParticleTypes_*type1 + type2],
+                                lnCORsqr_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for lnCORsqr not symmetric!");
+   return lnCORsqr_[numParticleTypes_*type1 + type2];
+}
+inline real_t NonLinearSpringDashpot::getMeff(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( meff_[numParticleTypes_*type1 + type2],
+                                meff_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for meff not symmetric!");
+   return meff_[numParticleTypes_*type1 + type2];
+}
+inline real_t NonLinearSpringDashpot::getStiffnessT(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( stiffnessT_[numParticleTypes_*type1 + type2],
+                                stiffnessT_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for stiffnessT not symmetric!");
+   return stiffnessT_[numParticleTypes_*type1 + type2];
+}
+inline real_t NonLinearSpringDashpot::getDampingT(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( dampingT_[numParticleTypes_*type1 + type2],
+                                dampingT_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for dampingT not symmetric!");
+   return dampingT_[numParticleTypes_*type1 + type2];
+}
+inline real_t NonLinearSpringDashpot::getFrictionCoefficientStatic(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( frictionCoefficientStatic_[numParticleTypes_*type1 + type2],
+                                frictionCoefficientStatic_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for frictionCoefficientStatic not symmetric!");
+   return frictionCoefficientStatic_[numParticleTypes_*type1 + type2];
+}
+inline real_t NonLinearSpringDashpot::getFrictionCoefficientDynamic(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( frictionCoefficientDynamic_[numParticleTypes_*type1 + type2],
+                                frictionCoefficientDynamic_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for frictionCoefficientDynamic not symmetric!");
+   return frictionCoefficientDynamic_[numParticleTypes_*type1 + type2];
+}
+
+template <typename Accessor>
+inline void NonLinearSpringDashpot::operator()(const size_t p_idx1,
+                                            const size_t p_idx2,
+                                            Accessor& ac,
+                                            const Vec3& contactPoint,
+                                            const Vec3& contactNormal,
+                                            const real_t& penetrationDepth,
+                                            const real_t& dt) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx1 != p_idx2)
+   {
+
+      WALBERLA_ASSERT_FLOAT_EQUAL(math::sqrLength(contactNormal), real_t(1));
+
+      real_t delta = -penetrationDepth;
+      if (delta < real_t(0)) return;
+
+
+      const Vec3 relVel ( -(getVelocityAtWFPoint(p_idx1, ac, contactPoint) - getVelocityAtWFPoint(p_idx2, ac, contactPoint)) );
+      const Vec3 relVelN( math::dot(relVel, contactNormal) * contactNormal );
+      const Vec3 relVelT( relVel - relVelN );
+
+      // get further collision properties from the contact history or initialize them
+      Vec3 tangentialSpringDisplacement( real_t(0) );
+      real_t impactVelocityMagnitude(real_t(0));
+      bool isSticking = false;
+      auto contactHistory = ac.getOldContactHistoryRef(p_idx1).find(ac.getUid(p_idx2)); //TODO assert symmetry
+      if(contactHistory != ac.getOldContactHistoryRef(p_idx1).end())
+      {
+         // get infos from the contact history
+         tangentialSpringDisplacement = contactHistory->second.getTangentialSpringDisplacement();
+         isSticking = contactHistory->second.getIsSticking();
+         impactVelocityMagnitude = contactHistory->second.getImpactVelocityMagnitude();
+
+      }else
+      {
+         // new contact: initialize values
+         impactVelocityMagnitude = relVel.length();
+      }
+
+      // ACTM: adapt collision coefficients
+      const real_t A = real_t(0.716);
+      const real_t B = real_t(0.830);
+      const real_t C = real_t(0.744);
+      const real_t alpha = real_t(1.111);
+      const real_t tau_c0 = real_t(3.218);
+      const real_t nu = getLnCORsqr(ac.getType(p_idx1), ac.getType(p_idx2));
+      const real_t lambda = (-real_t(0.5) * C * nu + std::sqrt(real_t(0.25) * C * C * nu * nu + alpha * alpha * tau_c0 * tau_c0 * nu)) / (alpha * alpha * tau_c0 * tau_c0);
+      const real_t tStar = std::sqrt(real_t(1) - A * lambda - B * lambda * lambda) * collisionTime_ / tau_c0;
+      const real_t meff = getMeff(ac.getType(p_idx1), ac.getType(p_idx2));
+      const real_t dn = real_t(2) * lambda * meff / tStar;
+      const real_t kn = meff / std::sqrt(impactVelocityMagnitude * std::pow(tStar, real_t(5)));
+
+      // calculate the normal force based on a non-linear spring-dashpot force model
+      Vec3 fN = kn * std::pow(delta, real_t(3)/real_t(2)) * contactNormal + dn * relVelN;
+
+      //TODO: move to own tangential integration kernel?
+      Vec3 rotatedTangentialDisplacement = tangentialSpringDisplacement - contactNormal * (contactNormal * tangentialSpringDisplacement);
+      Vec3 newTangentialSpringDisplacement = rotatedTangentialDisplacement.sqrLength() <= real_t(0) ? // avoid division by zero
+                                             Vec3(real_t(0)) :
+                                             ( rotatedTangentialDisplacement * std::sqrt((tangentialSpringDisplacement.sqrLength() / rotatedTangentialDisplacement.sqrLength())));
+      newTangentialSpringDisplacement = newTangentialSpringDisplacement + dt * relVelT;
+
+      // calculate the tangential force based on a linear spring-dashpot force model
+      const real_t stiffnessT = getStiffnessT(ac.getType(p_idx1), ac.getType(p_idx2));
+      const real_t dampingT = getDampingT(ac.getType(p_idx1), ac.getType(p_idx2));
+      Vec3 fTLS = stiffnessT * newTangentialSpringDisplacement +
+                  dampingT * relVelT;
+
+      const Vec3 t = fTLS.getNormalizedOrZero(); // tangential unit vector
+
+      // calculate friction force
+      const real_t fFrictionAbsStatic = getFrictionCoefficientStatic(ac.getType(p_idx1), ac.getType(p_idx2)) * fN.length(); // sticking, rolling
+      const real_t fFrictionAbsDynamic = getFrictionCoefficientDynamic(ac.getType(p_idx1), ac.getType(p_idx2)) * fN.length(); // sliding
+
+      const real_t tangentialVelocityThreshold = real_t(1e-8);
+
+      real_t fFrictionAbs;
+      if( isSticking && relVelT.length() < tangentialVelocityThreshold && fTLS.length() < fFrictionAbsStatic  )
+      {
+         fFrictionAbs = fFrictionAbsStatic;
+      }
+      else if( isSticking && fTLS.length() < fFrictionAbsDynamic )
+      {
+         // sticking
+         fFrictionAbs = fFrictionAbsDynamic;
+      }
+      else
+      {
+         // slipping
+         fFrictionAbs = fFrictionAbsDynamic;
+
+         isSticking = false;
+
+         // reset displacement vector
+         if(stiffnessT > real_t(0) ) newTangentialSpringDisplacement = ( fFrictionAbs * t - dampingT * relVelT ) / stiffnessT;
+
+         // if tangential force falls below coulomb limit, we are back in sticking
+         if( fTLS.length() < fFrictionAbsDynamic )
+         {
+            //TODO really?
+            isSticking = true;
+         }
+      }
+
+      const real_t fTabs( std::min( fTLS.length(), fFrictionAbs) );
+      const Vec3   fT   ( fTabs * t );
+
+      //TODO check if tangential spring displacement is same for symmetric case
+      auto& ch1 = ac.getNewContactHistoryRef(p_idx1)[ac.getUid(p_idx2)];
+      ch1.setTangentialSpringDisplacement(newTangentialSpringDisplacement);
+      ch1.setIsSticking(isSticking);
+      ch1.setImpactVelocityMagnitude(impactVelocityMagnitude);
+
+      auto& ch2 = ac.getNewContactHistoryRef(p_idx2)[ac.getUid(p_idx1)];
+      ch2.setTangentialSpringDisplacement(newTangentialSpringDisplacement);
+      ch2.setIsSticking(isSticking);
+      ch2.setImpactVelocityMagnitude(impactVelocityMagnitude);
+
+      // Add normal force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fN, contactPoint );
+      addForceAtWFPosAtomic( p_idx2, ac, -fN, contactPoint );
+
+      // Add tangential force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fT, contactPoint );
+      addForceAtWFPosAtomic( p_idx2, ac, -fT, contactPoint );
+
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/ParticleSelector.h b/src/mesa_pd/kernel/ParticleSelector.h
new file mode 100644
index 000000000..e7a11f674
--- /dev/null
+++ b/src/mesa_pd/kernel/ParticleSelector.h
@@ -0,0 +1,78 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleSelector.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+class SelectAll
+{
+public:
+   template <typename Accessor>
+   bool operator()(const size_t /*idx*/, Accessor& /*ac*/) const { return true; }
+
+   template <typename Accessor>
+   bool operator()(const size_t /*idx*/, const size_t /*jdx*/, Accessor& /*ac*/) const { return true; }
+};
+
+class SelectLocal
+{
+public:
+   template <typename Accessor>
+   bool operator()(const size_t idx, Accessor& ac) const
+   {
+      using namespace walberla::mesa_pd::data::particle_flags;
+      if (isSet(ac.getFlags(idx), GHOST)) return false;
+      if (isSet(ac.getFlags(idx), GLOBAL)) return false;
+      return true;
+   }
+};
+
+class SelectGhost
+{
+public:
+   template <typename Accessor>
+   bool operator()(const size_t idx, Accessor& ac) const
+   {
+      using namespace walberla::mesa_pd::data::particle_flags;
+      if (isSet(ac.getFlags(idx), GHOST)) return true;
+      return false;
+   }
+};
+
+class ExcludeInfiniteInfinite
+{
+public:
+   template <typename Accessor>
+   bool operator()(const size_t idx, const size_t jdx, Accessor& ac) const
+   {
+      using namespace walberla::mesa_pd::data::particle_flags;
+      if (isSet(ac.getFlags(idx), INFINITE) && isSet(ac.getFlags(jdx), INFINITE)) return false;
+      return true;
+   }
+};
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/kernel/SingleCast.h b/src/mesa_pd/kernel/SingleCast.h
new file mode 100644
index 000000000..0514d5a65
--- /dev/null
+++ b/src/mesa_pd/kernel/SingleCast.h
@@ -0,0 +1,73 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SingleCast.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/data/shape/BaseShape.h>
+#include <mesa_pd/data/shape/Sphere.h>
+#include <mesa_pd/data/shape/HalfSpace.h>
+
+#include <core/Abort.h>
+#include <core/debug/Debug.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const BaseShape*& getShape(const size_t p_idx) const;
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class SingleCast
+{
+public:
+   template <typename Accessor, typename func, typename... Args>
+   auto operator()( size_t idx, Accessor& ac, func& f, Args&&... args );
+};
+
+template <typename Accessor, typename func, typename... Args>
+auto SingleCast::operator()( size_t idx, Accessor& ac, func& f, Args&&... args )
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   using namespace mesa_pd::data;
+   switch (ac.getShape(idx)->getShapeType())
+   {
+      case Sphere::SHAPE_TYPE : return f(idx, *static_cast<Sphere*>(ac.getShape(idx)), std::forward<Args>(args)...);
+      case HalfSpace::SHAPE_TYPE : return f(idx, *static_cast<HalfSpace*>(ac.getShape(idx)), std::forward<Args>(args)...);
+      default : WALBERLA_ABORT("Shape type (" << ac.getShape(idx)->getShapeType() << ") could not be determined!");
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/SpringDashpot.h b/src/mesa_pd/kernel/SpringDashpot.h
new file mode 100644
index 000000000..fdc9fc6a7
--- /dev/null
+++ b/src/mesa_pd/kernel/SpringDashpot.h
@@ -0,0 +1,255 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SpringDashpot.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/common/ParticleFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+#include <core/math/Constants.h>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Basic DEM kernel
+ *
+ * This DEM kernel supports spring&dashpot in normal direction as well as friction in tangential direction.
+ *
+ * \code
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t p_idx) const;
+ *
+ * walberla::mesa_pd::Vec3& getForceRef(const size_t p_idx);
+ *
+ * const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t p_idx) const;
+ *
+ * walberla::mesa_pd::Vec3& getTorqueRef(const size_t p_idx);
+ *
+ * const uint_t& getType(const size_t p_idx) const;
+ *
+ * const std::map<walberla::id_t, walberla::mesa_pd::Vec3>& getContactHistory(const size_t p_idx) const;
+ * void setContactHistory(const size_t p_idx, const std::map<walberla::id_t, walberla::mesa_pd::Vec3>& v);
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class SpringDashpot
+{
+public:
+   SpringDashpot(const uint_t numParticleTypes);
+   SpringDashpot(const SpringDashpot& other) = default;
+   SpringDashpot(SpringDashpot&& other) = default;
+   SpringDashpot& operator=(const SpringDashpot& other) = default;
+   SpringDashpot& operator=(SpringDashpot&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac,
+                   const Vec3& contactPoint,
+                   const Vec3& contactNormal,
+                   const real_t& penetrationDepth) const;
+
+   
+   /// assumes this parameter is symmetric
+   void setStiffness(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setDampingN(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setDampingT(const size_t type1, const size_t type2, const real_t& val);
+   /// assumes this parameter is symmetric
+   void setFriction(const size_t type1, const size_t type2, const real_t& val);
+
+   
+   real_t getStiffness(const size_t type1, const size_t type2) const;
+   real_t getDampingN(const size_t type1, const size_t type2) const;
+   real_t getDampingT(const size_t type1, const size_t type2) const;
+   real_t getFriction(const size_t type1, const size_t type2) const;
+
+   inline
+   real_t calcCoefficientOfRestitution(const size_t type1,
+                                       const size_t type2,
+                                       const real_t meff)
+   {
+      auto a = real_t(0.5) * getDampingN(type1, type2) / meff;
+      return std::exp(-a * math::M_PI / std::sqrt(getStiffness(type1, type2) / meff - a*a));
+   }
+
+   inline
+   real_t calcCollisionTime(const size_t type1,
+                            const size_t type2,
+                            const real_t meff)
+   {
+      auto a = real_t(0.5) * getDampingN(type1, type2) / meff;
+      return math::M_PI / std::sqrt( getStiffness(type1, type2)/meff - a*a);
+   }
+
+   inline
+   void setParametersFromCOR(const size_t type1,
+                             const size_t type2,
+                             const real_t cor,
+                             const real_t collisionTime,
+                             const real_t meff)
+   {
+      const real_t lnDryResCoeff = std::log(cor);
+      setStiffness(type1, type2, math::M_PI * math::M_PI * meff / ( collisionTime * collisionTime * ( real_t(1) - lnDryResCoeff * lnDryResCoeff / ( math::M_PI * math::M_PI + lnDryResCoeff* lnDryResCoeff ))  ));
+      setDampingN( type1, type2, - real_t(2) * std::sqrt( meff * getStiffness(type1, type2) ) * ( lnDryResCoeff / std::sqrt( math::M_PI * math::M_PI + ( lnDryResCoeff * lnDryResCoeff ) ) ));
+   }
+private:
+   uint_t numParticleTypes_;
+   
+   std::vector<real_t> stiffness_ {};
+   std::vector<real_t> dampingN_ {};
+   std::vector<real_t> dampingT_ {};
+   std::vector<real_t> friction_ {};
+};
+
+SpringDashpot::SpringDashpot(const uint_t numParticleTypes)
+{
+   numParticleTypes_ = numParticleTypes;
+   
+   stiffness_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   dampingN_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   dampingT_.resize(numParticleTypes * numParticleTypes, real_t(0));
+   friction_.resize(numParticleTypes * numParticleTypes, real_t(0));
+}
+
+
+inline void SpringDashpot::setStiffness(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   stiffness_[numParticleTypes_*type1 + type2] = val;
+   stiffness_[numParticleTypes_*type2 + type1] = val;
+}
+inline void SpringDashpot::setDampingN(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   dampingN_[numParticleTypes_*type1 + type2] = val;
+   dampingN_[numParticleTypes_*type2 + type1] = val;
+}
+inline void SpringDashpot::setDampingT(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   dampingT_[numParticleTypes_*type1 + type2] = val;
+   dampingT_[numParticleTypes_*type2 + type1] = val;
+}
+inline void SpringDashpot::setFriction(const size_t type1, const size_t type2, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   friction_[numParticleTypes_*type1 + type2] = val;
+   friction_[numParticleTypes_*type2 + type1] = val;
+}
+
+
+inline real_t SpringDashpot::getStiffness(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( stiffness_[numParticleTypes_*type1 + type2],
+                                stiffness_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for stiffness not symmetric!");
+   return stiffness_[numParticleTypes_*type1 + type2];
+}
+inline real_t SpringDashpot::getDampingN(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( dampingN_[numParticleTypes_*type1 + type2],
+                                dampingN_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for dampingN not symmetric!");
+   return dampingN_[numParticleTypes_*type1 + type2];
+}
+inline real_t SpringDashpot::getDampingT(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( dampingT_[numParticleTypes_*type1 + type2],
+                                dampingT_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for dampingT not symmetric!");
+   return dampingT_[numParticleTypes_*type1 + type2];
+}
+inline real_t SpringDashpot::getFriction(const size_t type1, const size_t type2) const
+{
+   WALBERLA_ASSERT_LESS( type1, numParticleTypes_ );
+   WALBERLA_ASSERT_LESS( type2, numParticleTypes_ );
+   WALBERLA_ASSERT_FLOAT_EQUAL( friction_[numParticleTypes_*type1 + type2],
+                                friction_[numParticleTypes_*type2 + type1],
+                                "parameter matrix for friction not symmetric!");
+   return friction_[numParticleTypes_*type1 + type2];
+}
+
+template <typename Accessor>
+inline void SpringDashpot::operator()(const size_t p_idx1,
+                                      const size_t p_idx2,
+                                      Accessor& ac,
+                                      const Vec3& contactPoint,
+                                      const Vec3& contactNormal,
+                                      const real_t& penetrationDepth) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (p_idx1 != p_idx2)
+   {
+      // Global position of contact
+      const Vec3& gpos( contactPoint );
+      // The absolute value of the penetration length
+      real_t delta = -penetrationDepth;
+      if (delta < real_t(0)) return;
+
+      const Vec3   relVel ( -(getVelocityAtWFPoint(p_idx1, ac, gpos) - getVelocityAtWFPoint(p_idx2, ac, gpos)) );
+      const real_t relVelN( math::dot(relVel, contactNormal) );
+      const Vec3   relVelT( relVel - ( relVelN * contactNormal ) );
+
+      // Calculating the normal force based on a linear spring-dashpot force model
+      real_t fNabs = getStiffness(ac.getType(p_idx1), ac.getType(p_idx2)) * delta + getDampingN(ac.getType(p_idx1), ac.getType(p_idx2)) * relVelN;
+      const Vec3& fN = fNabs * contactNormal;
+
+      // Calculating the tangential force based on the model by Haff and Werner
+      const real_t fTabs( std::min( getDampingT(ac.getType(p_idx1), ac.getType(p_idx2)) * relVelT.length(), getFriction(ac.getType(p_idx1), ac.getType(p_idx2)) * fNabs ) );
+      const Vec3   fT   ( fTabs * relVelT.getNormalizedOrZero() );
+
+      // Add normal force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fN, gpos );
+      addForceAtWFPosAtomic( p_idx2, ac, -fN, gpos );
+
+      // Add tangential force at contact point
+      addForceAtWFPosAtomic( p_idx1, ac,  fT, gpos );
+      addForceAtWFPosAtomic( p_idx2, ac, -fT, gpos );
+   }
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/TemperatureIntegration.h b/src/mesa_pd/kernel/TemperatureIntegration.h
new file mode 100644
index 000000000..bdfc5732d
--- /dev/null
+++ b/src/mesa_pd/kernel/TemperatureIntegration.h
@@ -0,0 +1,117 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file TemperatureIntegration.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Kernel which explicitly integrates all particles in time.
+ * This integrator integrates velocity and position.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const walberla::real_t& getTemperature(const size_t p_idx) const;
+ * void setTemperature(const size_t p_idx, const walberla::real_t& v);
+ *
+ * const walberla::real_t& getHeatFlux(const size_t p_idx) const;
+ * void setHeatFlux(const size_t p_idx, const walberla::real_t& v);
+ *
+ * const uint_t& getType(const size_t p_idx) const;
+ *
+ * \endcode
+ *
+ * \pre  All forces acting on the particles have to be set.
+ * \post All forces are reset to 0.
+ * \ingroup mesa_pd_kernel
+ */
+class TemperatureIntegration
+{
+public:
+   TemperatureIntegration(const real_t dt, const uint_t numParticleTypes);
+   TemperatureIntegration(const TemperatureIntegration& other) = default;
+   TemperatureIntegration(TemperatureIntegration&& other) = default;
+   TemperatureIntegration& operator=(const TemperatureIntegration& other) = default;
+   TemperatureIntegration& operator=(TemperatureIntegration&& other) = default;
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   
+   /// assumes this parameter is symmetric
+   void setInvHeatCapacity(const size_t type, const real_t& val);
+
+   
+   real_t getInvHeatCapacity(const size_t type) const;
+private:
+   real_t dt_ = real_t(0.0);
+
+   uint_t numParticleTypes_;
+   
+   std::vector<real_t> invHeatCapacity_ {};
+};
+
+TemperatureIntegration::TemperatureIntegration(const real_t dt, const uint_t numParticleTypes)
+   : dt_(dt)
+{
+   numParticleTypes_ = numParticleTypes;
+   
+   invHeatCapacity_.resize(numParticleTypes, real_t(0));
+}
+
+
+inline void TemperatureIntegration::setInvHeatCapacity(const size_t type, const real_t& val)
+{
+   WALBERLA_ASSERT_LESS( type, numParticleTypes_ );
+   invHeatCapacity_[type] = val;
+}
+
+
+inline real_t TemperatureIntegration::getInvHeatCapacity(const size_t type) const
+{
+   WALBERLA_ASSERT_LESS( type, numParticleTypes_ );
+   return invHeatCapacity_[type];
+}
+
+template <typename Accessor>
+inline void TemperatureIntegration::operator()(const size_t idx,
+                                               Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   //formula for heat capacity
+   ac.setTemperature(idx, getInvHeatCapacity(ac.getType(idx)) * ac.getHeatFlux(idx) * dt_ + ac.getTemperature(idx));
+   ac.setHeatFlux   (idx, real_t(0));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/VelocityVerlet.h b/src/mesa_pd/kernel/VelocityVerlet.h
new file mode 100644
index 000000000..f4874bff0
--- /dev/null
+++ b/src/mesa_pd/kernel/VelocityVerlet.h
@@ -0,0 +1,118 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file VelocityVerlet.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Velocity verlet integration for all particles.
+ *
+ * Velocit verlet integration is a two part kernel. preForceUpdate has to be
+ * called before the force calculation and postFroceUpdate afterwards. The
+ * integration is only complete when both functions are called. The integration
+ * is symplectic.
+ * \attention The force calculation has to be independent of velocity.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ * void setPosition(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t p_idx) const;
+ * void setLinearVelocity(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::real_t& getInvMass(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getForce(const size_t p_idx) const;
+ * void setForce(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getOldForce(const size_t p_idx) const;
+ * void setOldForce(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t p_idx) const;
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class VelocityVerletPreForceUpdate
+{
+public:
+   VelocityVerletPreForceUpdate(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   real_t dt_;
+};
+
+/// \see VelocityVerletPreForceUpdate
+class VelocityVerletPostForceUpdate
+{
+public:
+   VelocityVerletPostForceUpdate(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   real_t dt_;
+};
+
+template <typename Accessor>
+inline void VelocityVerletPreForceUpdate::operator()(const size_t p_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(p_idx), data::particle_flags::FIXED))
+   {
+      ac.setPosition(p_idx, ac.getPosition(p_idx) +
+                            ac.getLinearVelocity(p_idx) * dt_ +
+                            real_t(0.5) * ac.getInvMass(p_idx) * ac.getOldForce(p_idx) * dt_ * dt_);
+   }
+}
+
+template <typename Accessor>
+inline void VelocityVerletPostForceUpdate::operator()(const size_t p_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(p_idx), data::particle_flags::FIXED))
+   {
+      ac.setLinearVelocity(p_idx, ac.getLinearVelocity(p_idx) +
+                                  real_t(0.5) * ac.getInvMass(p_idx) * (ac.getOldForce(p_idx) + ac.getForce(p_idx)) * dt_);
+   }
+   ac.setOldForce(p_idx,       ac.getForce(p_idx));
+   ac.setForce(p_idx,          Vec3(real_t(0), real_t(0), real_t(0)));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/VelocityVerletWithShape.h b/src/mesa_pd/kernel/VelocityVerletWithShape.h
new file mode 100644
index 000000000..36c5b2f77
--- /dev/null
+++ b/src/mesa_pd/kernel/VelocityVerletWithShape.h
@@ -0,0 +1,155 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file VelocityVerletWithShape.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace kernel {
+
+/**
+ * Velocity verlet integration for all particles.
+ *
+ * Velocit verlet integration is a two part kernel. preForceUpdate has to be
+ * called before the force calculation and postFroceUpdate afterwards. The
+ * integration is only complete when both functions are called. The integration
+ * is symplectic.
+ * \attention The force calculation has to be independent of velocity.
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const walberla::mesa_pd::Vec3& getPosition(const size_t p_idx) const;
+ * void setPosition(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t p_idx) const;
+ * void setLinearVelocity(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::real_t& getInvMass(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getForce(const size_t p_idx) const;
+ * void setForce(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getOldForce(const size_t p_idx) const;
+ * void setOldForce(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Rot3& getRotation(const size_t p_idx) const;
+ * void setRotation(const size_t p_idx, const walberla::mesa_pd::Rot3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t p_idx) const;
+ * void setAngularVelocity(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Mat3& getInvInertiaBF(const size_t p_idx) const;
+ *
+ * const walberla::mesa_pd::Vec3& getTorque(const size_t p_idx) const;
+ * void setTorque(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::Vec3& getOldTorque(const size_t p_idx) const;
+ * void setOldTorque(const size_t p_idx, const walberla::mesa_pd::Vec3& v);
+ *
+ * const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t p_idx) const;
+ *
+ * \endcode
+ * \ingroup mesa_pd_kernel
+ */
+class VelocityVerletWithShapePreForceUpdate
+{
+public:
+   VelocityVerletWithShapePreForceUpdate(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   real_t dt_;
+};
+
+/// \see VelocityVerletPreForceUpdate
+class VelocityVerletWithShapePostForceUpdate
+{
+public:
+   VelocityVerletWithShapePostForceUpdate(const real_t dt) : dt_(dt) {}
+
+   template <typename Accessor>
+   void operator()(const size_t i, Accessor& ac) const;
+
+   real_t dt_;
+};
+
+template <typename Accessor>
+inline void VelocityVerletWithShapePreForceUpdate::operator()(const size_t p_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(p_idx), data::particle_flags::FIXED))
+   {
+      ac.setPosition(p_idx, ac.getPosition(p_idx) +
+                            ac.getLinearVelocity(p_idx) * dt_ +
+                            real_t(0.5) * ac.getInvMass(p_idx) * ac.getOldForce(p_idx) * dt_ * dt_);
+
+      const Vec3 wdot = math::transformMatrixRART(ac.getRotation(p_idx).getMatrix(),
+                                                  ac.getInvInertiaBF(p_idx)) * ac.getOldTorque(p_idx);
+
+      // Calculating the rotation angle
+      const Vec3 phi( ac.getAngularVelocity(p_idx) * dt_ + real_t(0.5) * wdot * dt_ * dt_);
+
+      // Calculating the new orientation
+      auto rotation = ac.getRotation(p_idx);
+      rotation.rotate( phi );
+      ac.setRotation(p_idx, rotation);
+   }
+}
+
+template <typename Accessor>
+inline void VelocityVerletWithShapePostForceUpdate::operator()(const size_t p_idx, Accessor& ac) const
+{
+   static_assert(std::is_base_of<data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   if (!data::particle_flags::isSet( ac.getFlags(p_idx), data::particle_flags::FIXED))
+   {
+      ac.setLinearVelocity(p_idx, ac.getLinearVelocity(p_idx) +
+                                  real_t(0.5) * ac.getInvMass(p_idx) * (ac.getOldForce(p_idx) + ac.getForce(p_idx)) * dt_);
+
+
+      const auto torque = ac.getOldTorque(p_idx) + ac.getTorque(p_idx);
+      const Vec3 wdot = math::transformMatrixRART(ac.getRotation(p_idx).getMatrix(),
+                                                  ac.getInvInertiaBF(p_idx)) * torque;
+
+      ac.setAngularVelocity(p_idx, ac.getAngularVelocity(p_idx) +
+                                   real_t(0.5) * wdot * dt_ );
+   }
+
+   ac.setOldForce(p_idx,       ac.getForce(p_idx));
+   ac.setForce(p_idx,          Vec3(real_t(0), real_t(0), real_t(0)));
+
+   ac.setOldTorque(p_idx,      ac.getTorque(p_idx));
+   ac.setTorque(p_idx,         Vec3(real_t(0), real_t(0), real_t(0)));
+}
+
+} //namespace kernel
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/kernel/mesa_pd_kernel.dox b/src/mesa_pd/kernel/mesa_pd_kernel.dox
new file mode 100644
index 000000000..e74f90c1b
--- /dev/null
+++ b/src/mesa_pd/kernel/mesa_pd_kernel.dox
@@ -0,0 +1,11 @@
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * \defgroup mesa_pd_kernel Particle and particle-particle kernels available in the MESA-PD module.
+ * \ingroup mesa_pd
+ * Description of all kernels available in the MESA-PD module.
+**/
+
+}
+}
diff --git a/src/mesa_pd/mesa_pd_module.dox b/src/mesa_pd/mesa_pd_module.dox
new file mode 100644
index 000000000..21c3832ea
--- /dev/null
+++ b/src/mesa_pd/mesa_pd_module.dox
@@ -0,0 +1,10 @@
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * \defgroup mesa_pd MESA-PD - Modular and Extensible Software Architecture for Particle Dynamics
+ * The particle dynamics module of the waLBerla multi-physics framework.
+**/
+
+}
+}
diff --git a/src/mesa_pd/mpi/BroadcastProperty.h b/src/mesa_pd/mpi/BroadcastProperty.h
new file mode 100644
index 000000000..856e030ec
--- /dev/null
+++ b/src/mesa_pd/mpi/BroadcastProperty.h
@@ -0,0 +1,135 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BroadcastProperty.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/mpi/BufferSystem.h>
+#include <core/logging/Logging.h>
+
+#include <type_traits>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Broadcast a property from the master particle to all corresponding ghost particles.
+ *
+ * \par Usage:
+ * The property which will be broadcasted can be selected by the Notification template
+ * (see ForceTorqueNotification).
+ * void update(data::Particle&& p, const Notification::Parameters& objparam)
+ * will be called to update the ghost particles with the information transmitted.
+ *
+ * \post
+ * - all ghost particles got updated
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class BroadcastProperty
+{
+public:
+   template <typename Notification>
+   void operator()(data::ParticleStorage& ps) const;
+
+   int64_t getBytesSent() const { return bs.getBytesSent(); }
+   int64_t getBytesReceived() const { return bs.getBytesReceived(); }
+
+   int64_t getNumberOfSends() const { return bs.getNumberOfSends(); }
+   int64_t getNumberOfReceives() const { return bs.getNumberOfReceives(); }
+private:
+   mutable walberla::mpi::BufferSystem bs = walberla::mpi::BufferSystem(walberla::mpi::MPIManager::instance()->comm() );
+
+   int numProcesses_ = walberla::mpi::MPIManager::instance()->numProcesses();
+};
+
+template <typename Notification>
+void BroadcastProperty::operator()(data::ParticleStorage& ps) const
+{
+   if (numProcesses_ == 1) return;
+
+   std::set<int> recvRanks; // potential message senders
+
+   WALBERLA_LOG_DETAIL( "Assembling of property reduction message starts...");
+
+   for( auto p : ps )
+   {
+      if (data::particle_flags::isSet( p.getFlags(), data::particle_flags::GHOST))
+      {
+         // Will receive message from the particles owner
+         recvRanks.insert(p.getOwner());
+      } else
+      {
+         //local particles should send the property to all ghost particles
+         for (auto& ghostRank : p.getGhostOwners())
+         {
+            auto& sb = bs.sendBuffer(ghostRank);
+            if (sb.isEmpty())
+            {
+               // fill empty buffers with a dummy byte to force transmission
+               sb << walberla::uint8_c(0);
+            }
+            sb << Notification( p );
+         }
+      }
+   }
+
+   WALBERLA_LOG_DETAIL( "Assembling of property broadcasting message ended." );
+
+   bs.setReceiverInfo(recvRanks, true);
+   bs.sendAll();
+
+   // Receiving the updates for the remote rigid bodies from the connected processes
+   WALBERLA_LOG_DETAIL( "Parsing of property broadcasting message starts..." );
+   for( auto it = bs.begin(); it != bs.end(); ++it )
+   {
+      walberla::uint8_t tmp;
+      it.buffer() >> tmp;
+      while( !it.buffer().isEmpty() )
+      {
+         typename Notification::Parameters objparam;
+         it.buffer() >> objparam;
+
+         WALBERLA_LOG_DETAIL( "Received reduction notification from neighboring process with rank " << it.rank() );
+
+         auto pIt = ps.find( objparam.uid_ );
+         WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+         update(*pIt, objparam);
+
+         WALBERLA_LOG_DETAIL( "Processed broadcasting notification for particle " << objparam.uid_ << "."  );
+      }
+   }
+   WALBERLA_LOG_DETAIL( "Parsing of property broadcasting message ended." );
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/ClearNextNeighborSync.h b/src/mesa_pd/mpi/ClearNextNeighborSync.h
new file mode 100644
index 000000000..efadbe262
--- /dev/null
+++ b/src/mesa_pd/mpi/ClearNextNeighborSync.h
@@ -0,0 +1,79 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ClearNextNeighborSync.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/Flags.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Clear all ghost particles and reset ghost owner information
+ *
+ * This kernel requires the following particle accessor interface
+ * \code
+ * const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t p_idx) const;
+ *
+ * std::vector<int>& getGhostOwnersRef(const size_t p_idx);
+ *
+ * \endcode
+ *
+ * \post All ghost particles are deleted.
+ * \post All ghost owners are reset.
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class ClearNextNeighborSync
+{
+public:
+   template <typename Accessor>
+   void operator()(Accessor& ac) const;
+};
+
+template <typename Accessor>
+void ClearNextNeighborSync::operator()(Accessor& ac) const
+{
+   for (size_t idx = 0; idx < ac.size(); )
+   {
+      if (data::particle_flags::isSet( ac.getFlags(idx), data::particle_flags::GHOST))
+      {
+         //ghost particle
+         idx = ac.erase(idx);
+         continue;
+      } else
+      {
+         //local particle
+         ac.getGhostOwnersRef(idx).clear();
+      }
+      ++idx;
+   }
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/ContactFilter.h b/src/mesa_pd/mpi/ContactFilter.h
new file mode 100644
index 000000000..cfcada06b
--- /dev/null
+++ b/src/mesa_pd/mpi/ContactFilter.h
@@ -0,0 +1,158 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ContactFilter.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/domain/IDomain.h>
+
+#include <core/logging/Logging.h>
+#include <core/mpi/MPIManager.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Contact filter for parallel execution of collision detection.
+ *
+ * This contact filter decides if a contact should be treated on this process.
+ * Contact filtering has to be applied to avoid double treatment by multiple processes.
+ */
+class ContactFilter
+{
+public:
+   ContactFilter() = default;
+   ContactFilter(const ContactFilter& other) = default;
+   ContactFilter(ContactFilter&& other) = default;
+   ContactFilter& operator=(const ContactFilter& other) = default;
+   ContactFilter& operator=(ContactFilter&& other) = default;
+
+   /**
+    * \param idx1 index of the first collision partner
+    * \param idx2 index of the second collision partner
+    * \param ac accessor data structure to access particle properties
+    * \param contactPoint contact point of the two particles
+    * \param domain domain datastructure
+    * \return true if the contact should be treated by this process
+    */
+   template <typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    Accessor& ac,
+                    const Vec3& contactPoint,
+                    const domain::IDomain& domain ) const;
+private:
+   uint_t myRank_ = uint_c( walberla::mpi::MPIManager::instance()->rank() );
+};
+
+template <typename Accessor>
+inline bool ContactFilter::operator()( const size_t idx1,
+                                       const size_t idx2,
+                                       Accessor& ac,
+                                       const Vec3& contactPoint,
+                                       const domain::IDomain& domain ) const
+{
+   /* Contact filtering rules
+    *
+    * L: Local body
+    * G: Global body
+    * R: Remote body
+    *
+    * Option 1:              Option 2:
+    * +---+---+---+---+      +---+---+---+---+
+    * |   | L | G | R |      |   | L | G | R |
+    * +---+---+---+---+      +---+---+---+---+
+    * | L | + | + | * |      | L |§/+| + | § |
+    * +---+---+---+---+      +---+---+---+---+
+    * | G | + | ~ | - |      | G | + | ~ | - |
+    * +---+---+---+---+      +---+---+---+---+
+    * | R | * | - | # |      | R | § | - | § |
+    * +---+---+---+---+      +---+---+---+---+
+    *
+    *  + Accept contact unconditionally
+    *  - Reject contact unconditionally
+    *  * Accept contact if we own the contact point
+    *  # Accept contact if we own the contact point and the owners of the involved bodies are not the same
+    *  ~ Accept contact only on root process
+    *  § Accept contact if we are the owner with the smallest rank witnessing the contact or if none of the owners witness the contact and we are the process with the smallest rank witnessing the contact
+    *
+    * Note: Local-global contacts actually require a reduction of the contact reactions applied to the global body (unless it is fixed).
+    * => MPI_Allreduce for all non-fixed global bodies before time-integration.
+    */
+
+   if( !data::particle_flags::isSet( ac.getFlags(idx1), data::particle_flags::GHOST) &&
+       !data::particle_flags::isSet( ac.getFlags(idx2), data::particle_flags::GHOST) )
+   {
+      // local-local, local-global, global-global contacts
+
+      if( data::particle_flags::isSet( ac.getFlags(idx1), data::particle_flags::GLOBAL) &&
+          data::particle_flags::isSet( ac.getFlags(idx2), data::particle_flags::GLOBAL) )
+      {
+         // Resolve global-global contacts only on root process
+         if( myRank_ != 0 )
+         {
+            WALBERLA_LOG_DETAIL( "Rejecting global-global contact " << contactPoint << " on non-root process." );
+            return false;
+         }
+      } else
+      {
+         // Always resolve local-local and local-global contacts even if they are outside of our domain
+         return true;
+      }
+   } else
+   {
+      // local-remote, global-remote or remote-remote contact
+
+      if( data::particle_flags::isSet( ac.getFlags(idx1), data::particle_flags::GLOBAL) ||
+          data::particle_flags::isSet( ac.getFlags(idx2), data::particle_flags::GLOBAL) )
+      {
+         // Never resolve remote-global contacts
+         WALBERLA_LOG_DETAIL( "Rejecting global-remote contact " << contactPoint << "." );
+         return false;
+      } else if( data::particle_flags::isSet( ac.getFlags(idx1), data::particle_flags::GHOST) &&
+                 data::particle_flags::isSet( ac.getFlags(idx2), data::particle_flags::GHOST) &&
+                 ac.getOwner(idx1) == ac.getOwner(idx2) )
+      {
+         WALBERLA_LOG_DETAIL( "Rejecting remote-remote contact since it will be a local-local contact at the owner process: " << contactPoint << "." );
+         return false;
+      } else
+      {
+         if( !domain.isContainedInProcessSubdomain(myRank_, contactPoint ) )
+         {
+            if( data::particle_flags::isSet( ac.getFlags(idx1), data::particle_flags::GHOST) &&
+                data::particle_flags::isSet( ac.getFlags(idx2), data::particle_flags::GHOST) )
+            {
+               WALBERLA_LOG_DETAIL( "Rejecting remote-remote contact " << contactPoint << " since we don't own it." );
+               return false;
+            } else
+            {
+               WALBERLA_LOG_DETAIL( "Rejecting remote-local contact " << contactPoint << " since we don't own it." );
+               return false;
+            }
+         }
+      }
+   }
+   return true;
+}
+
+} //namespace mpi
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/mpi/ReduceContactHistory.h b/src/mesa_pd/mpi/ReduceContactHistory.h
new file mode 100644
index 000000000..80606f776
--- /dev/null
+++ b/src/mesa_pd/mpi/ReduceContactHistory.h
@@ -0,0 +1,113 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ReduceContactHistory.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ContactHistory.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <mesa_pd/kernel/ParticleSelector.h>
+
+#include <mesa_pd/mpi/notifications/ContactHistoryNotification.h>
+#include <mesa_pd/mpi/ReduceProperty.h>
+
+#include <core/logging/Logging.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Reduces all contact history from ghost particles to the master particle.
+ *
+ * \attention
+ * This kernel *only* reduces the contact history. You have to manually
+ * broadcast it to ghost particles if needed. This can be done by either
+ * using BroadcastProperty or SyncNextNeighbors.
+ *
+ * \par Usage:
+ * The contact history aggregated by a call to this kernel is available in
+ * oldContactHistory. Use this data to write the current contact history
+ * to newContactHistory. The next call to this kernel will delete all
+ * contact data which is not stored in newContactHistory!
+ *
+ * \pre
+ * - up to date contact information has to be stored in newContactHistory
+ * - only contact information of local contacts should be stored
+ * - data in oldContactHistory will be overwritten
+ *
+ * \post
+ * - oldContactHistory for the master particle contains the reduced data
+ * - oldContactHistory for ghost particles is empty
+ * - newContactHistory is empty for master as well as ghost particles
+ *
+ * \internal
+ * Two contact histories are needed to decided which contacts are still persistent and
+ * which have been separated. OldContactHistory stores all contact information from the last
+ * time step. This can be used to populate newContactHistory. Processes should only populate
+ * newContactHistory if they have a persisting contact! During the reduce operation newContactHistory
+ * will be reduced effectively eliminating all contacts that have separated. Subsequently it
+ * is swapped with oldContactHistory to create the initial state. This is oldContactHistory
+ * has all information, newContactHistory is empty.
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class ReduceContactHistory
+{
+public:
+   void operator()(data::ParticleStorage& ps) const;
+private:
+   ReduceProperty RP_;
+
+   int numProcesses_ = walberla::mpi::MPIManager::instance()->numProcesses();
+};
+
+void ReduceContactHistory::operator()(data::ParticleStorage& ps) const
+{
+   //no need to reduce if run with only one process
+   if (numProcesses_ != 1)
+   {
+      RP_.operator()<ContactHistoryNotification>(ps);
+   }
+
+   const auto size = ps.size();
+   for (size_t idx = 0; idx < size; ++idx)
+   {
+      if (!data::particle_flags::isSet( ps.getFlags(idx), data::particle_flags::GHOST) )
+      {
+         std::swap(ps.getOldContactHistoryRef(idx), ps.getNewContactHistoryRef(idx));
+         ps.getNewContactHistoryRef(idx).clear();
+      } else
+      {
+         ps.getOldContactHistoryRef(idx).clear();
+         ps.getNewContactHistoryRef(idx).clear();
+      }
+   }
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/ReduceProperty.h b/src/mesa_pd/mpi/ReduceProperty.h
new file mode 100644
index 000000000..22ca078f1
--- /dev/null
+++ b/src/mesa_pd/mpi/ReduceProperty.h
@@ -0,0 +1,144 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SyncProperty.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/mpi/BufferSystem.h>
+#include <core/logging/Logging.h>
+
+#include <type_traits>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Reduce a property from all ghost particles to the corresponding master particle.
+ *
+ * \attention
+ * This kernel does not broadcast the reduced property to all ghost particles. Use
+ * BroadcastProperty or SyncNextNeighbors for that.
+ *
+ * \par Usage:
+ * The property which will be reduced can be selected by the Notification template
+ * (see ForceTorqueNotification).
+ * void reduce(data::Particle&& p, const Notification::Parameters& objparam)
+ * will be called to reduce the all incoming properties.
+ *
+ * \pre
+ * - the property is up to date on all ghost particles
+ *
+ * \post
+ * - the property at all ghost particles stays unchanged
+ * - the property at the master particle is the reduced one
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class ReduceProperty
+{
+public:
+   template <typename Notification>
+   void operator()(data::ParticleStorage& ps) const;
+
+   int64_t getBytesSent() const { return bs.getBytesSent(); }
+   int64_t getBytesReceived() const { return bs.getBytesReceived(); }
+
+   int64_t getNumberOfSends() const { return bs.getNumberOfSends(); }
+   int64_t getNumberOfReceives() const { return bs.getNumberOfReceives(); }
+private:
+   mutable walberla::mpi::BufferSystem bs = walberla::mpi::BufferSystem(walberla::mpi::MPIManager::instance()->comm() );
+
+   int numProcesses_ = walberla::mpi::MPIManager::instance()->numProcesses();
+};
+
+template <typename Notification>
+void ReduceProperty::operator()(data::ParticleStorage& ps) const
+{
+   if (numProcesses_ == 1) return;
+
+   std::set<int> recvRanks; // potential message senders
+
+   WALBERLA_LOG_DETAIL( "Assembling of property reduction message starts...");
+
+   for( auto p : ps )
+   {
+      if (data::particle_flags::isSet( p.getFlags(), data::particle_flags::GHOST))
+      {
+         //ghost particles should send their property
+         auto& sb = bs.sendBuffer(p.getOwner());
+         if (sb.isEmpty())
+         {
+            // fill empty buffers with a dummy byte to force transmission
+            sb << walberla::uint8_c(0);
+         }
+
+         sb << Notification( p );
+      } else
+      {
+         //local particles should receive the property and sum it up
+         for (auto& ghostRank : p.getGhostOwners())
+         {
+            recvRanks.insert(ghostRank);
+         }
+      }
+   }
+
+   WALBERLA_LOG_DETAIL( "Assembling of property reduction message ended." );
+
+   bs.setReceiverInfo(recvRanks, true);
+   bs.sendAll();
+
+   // Receiving the updates for the remote rigid bodies from the connected processes
+   WALBERLA_LOG_DETAIL( "Parsing of property reduction message starts..." );
+   for( auto it = bs.begin(); it != bs.end(); ++it )
+   {
+      walberla::uint8_t tmp;
+      it.buffer() >> tmp;
+      while( !it.buffer().isEmpty() )
+      {
+         typename Notification::Parameters objparam;
+         it.buffer() >> objparam;
+
+         WALBERLA_LOG_DETAIL( "Received reduction notification from neighboring process with rank " << it.rank() );
+
+         auto pIt = ps.find( objparam.uid_ );
+         WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+         reduce(*pIt, objparam);
+
+         WALBERLA_LOG_DETAIL( "Processed reduction notification for particle " << objparam.uid_ << "."  );
+      }
+   }
+   WALBERLA_LOG_DETAIL( "Parsing of property reduction message ended." );
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/SyncNextNeighbors.cpp b/src/mesa_pd/mpi/SyncNextNeighbors.cpp
new file mode 100644
index 000000000..bdbfc24eb
--- /dev/null
+++ b/src/mesa_pd/mpi/SyncNextNeighbors.cpp
@@ -0,0 +1,267 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SyncNextNeighbors.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include "SyncNextNeighbors.h"
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+void SyncNextNeighbors::operator()(data::ParticleStorage& ps,
+                                   const domain::IDomain& domain,
+                                   const real_t dx) const
+{
+   if (numProcesses_ == 1) return;
+
+   neighborRanks_ = domain.getNeighborProcesses();
+   for( uint_t nbProcessRank : neighborRanks_ )
+   {
+      if (bs.sendBuffer(nbProcessRank).isEmpty())
+      {
+         // fill empty buffers with a dummy byte to force transmission
+         bs.sendBuffer(nbProcessRank) << walberla::uint8_c(0);
+      }
+   }
+   generateSynchronizationMessages(ps, domain, dx);
+
+   // size of buffer is unknown and changes with each send
+   bs.setReceiverInfoFromSendBufferState(false, true);
+   bs.sendAll();
+
+   // Receiving the updates for the remote rigid bodies from the connected processes
+   WALBERLA_LOG_DETAIL( "Parsing of particle synchronization response starts..." );
+   ParseMessage parseMessage;
+   for( auto it = bs.begin(); it != bs.end(); ++it )
+   {
+      walberla::uint8_t tmp;
+      it.buffer() >> tmp;
+      while( !it.buffer().isEmpty() )
+      {
+         parseMessage(it.rank(), it.buffer(), ps, domain);
+      }
+   }
+   WALBERLA_LOG_DETAIL( "Parsing of particle synchronization response ended." );
+}
+
+/**
+ * Removes a particle from the local storage and informs ghost particle holders.
+ *
+ * This function removes the particle from the particle storage and generates deletion notifications.
+ */
+inline
+data::ParticleStorage::iterator removeAndNotify( walberla::mpi::BufferSystem& bs,
+                                                 data::ParticleStorage& ps,
+                                                 data::ParticleStorage::iterator& pIt )
+{
+   WALBERLA_ASSERT( !data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST),
+                    "Trying to remove ghost particle from the particle storage." );
+
+   WALBERLA_ASSERT( !data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GLOBAL),
+                    "Trying to remove a global particle from the particle storage." );
+
+   if( !pIt->getGhostOwners().empty() )
+   {
+      // Notify registered processes (intersecting or interacting) of particle removal since they possess a shadow copy.
+      for( auto ghostRank : pIt->getGhostOwnersRef() )
+      {
+         WALBERLA_LOG_DETAIL( "__Notify registered process " << ghostRank << " of deletion of particle " << pIt->getUid() );
+         auto& sb = bs.sendBuffer(ghostRank);
+         if (sb.isEmpty()) sb << walberla::uint8_c(0);
+         packNotification(sb, ParticleRemovalNotification( *pIt ));
+      }
+   }
+
+   pIt->getGhostOwnersRef().clear();
+   return ps.erase( pIt );
+}
+
+void SyncNextNeighbors::generateSynchronizationMessages(data::ParticleStorage& ps,
+                                                        const domain::IDomain& domain,
+                                                        const real_t dx) const
+{
+   const uint_t ownRank = uint_c(rank_);
+
+   WALBERLA_LOG_DETAIL( "Assembling of particle synchronization message starts..." );
+
+   // position update
+   for( auto pIt = ps.begin(); pIt != ps.end(); )
+   {
+      //skip all ghost particles
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST))
+      {
+         ++pIt;
+         continue;
+      }
+
+      //skip all particles that do not communicate (create ghost particles) on other processes
+      if (data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::NON_COMMUNICATING))
+      {
+         ++pIt;
+         continue;
+      }
+
+      if (domain.isContainedInLocalSubdomain(pIt->getPosition(), pIt->getInteractionRadius()))
+      {
+         //no sync needed
+         //just delete ghost particles if there are any
+
+         for (const auto& ghostOwner : pIt->getGhostOwners() )
+         {
+            auto& buffer( bs.sendBuffer(ghostOwner) );
+
+            WALBERLA_LOG_DETAIL( "Sending removal notification for particle " << pIt->getUid() << " to process " << ghostOwner );
+
+            packNotification(buffer, ParticleRemovalNotification( *pIt ));
+         }
+
+         pIt->getGhostOwnersRef().clear();
+
+         ++pIt;
+         continue;
+      }
+
+      //correct position to make sure particle is always inside the domain!
+      //everything is decided by the master particle therefore ghost particles are not touched
+      if (!data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::FIXED) &&
+          !data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST))
+      {
+         domain.periodicallyMapToDomain( pIt->getPositionRef() );
+      }
+
+      // Note: At this point we know that the particle was locally owned before the position update.
+      WALBERLA_CHECK_EQUAL(pIt->getOwner(), ownRank);
+
+      WALBERLA_LOG_DETAIL( "Processing local particle " << pIt->getUid() );
+
+      // Update nearest neighbor processes.
+      for( uint_t nbProcessRank : neighborRanks_ )
+      {
+         if( domain.intersectsWithProcessSubdomain( nbProcessRank, pIt->getPosition(), pIt->getInteractionRadius() + dx ) )
+         {
+            auto ghostOwnerIt = std::find( pIt->getGhostOwners().begin(), pIt->getGhostOwners().end(), nbProcessRank );
+            if( ghostOwnerIt != pIt->getGhostOwners().end() )
+            {
+               // already a ghost there -> update
+               auto& buffer( bs.sendBuffer(nbProcessRank) );
+               WALBERLA_LOG_DETAIL( "Sending update notification for particle " << pIt->getUid() << " to process " << (nbProcessRank) );
+               packNotification(buffer, ParticleUpdateNotification( *pIt ));
+            } else
+            {
+               // no ghost there -> create ghost
+               auto& buffer( bs.sendBuffer(nbProcessRank) );
+               WALBERLA_LOG_DETAIL( "Sending shadow copy notification for particle " << pIt->getUid() << " to process " << (nbProcessRank) );
+               packNotification(buffer, ParticleCopyNotification( *pIt ));
+               pIt->getGhostOwnersRef().emplace_back( int_c(nbProcessRank) );
+            }
+         }
+         else
+         {
+            //no overlap with neighboring process -> delete if ghost is there
+            auto ghostOwnerIt = std::find( pIt->getGhostOwners().begin(), pIt->getGhostOwners().end(), nbProcessRank );
+            if( ghostOwnerIt != pIt->getGhostOwners().end() )
+            {
+               // In case the rigid particle no longer intersects the remote process nor interacts with it but is registered,
+               // send removal notification.
+               auto& buffer( bs.sendBuffer(nbProcessRank) );
+
+               WALBERLA_LOG_DETAIL( "Sending removal notification for particle " << pIt->getUid() << " to process " << nbProcessRank );
+
+               packNotification(buffer, ParticleRemovalNotification( *pIt ));
+
+               pIt->getGhostOwnersRef().erase(ghostOwnerIt);
+            }
+         }
+      }
+
+      //particle has left subdomain?
+      const auto ownerRank = domain.findContainingProcessRank( pIt->getPosition() );
+      if( ownerRank != int_c(ownRank) )
+      {
+         WALBERLA_LOG_DETAIL( "Local particle " << pIt->getUid() << " is no longer on process " << ownRank << " but on process " << ownerRank );
+
+         if( ownerRank < 0 ) {
+            // No owner found: Outflow condition.
+            WALBERLA_LOG_DETAIL( "Sending deletion notifications for particle " << pIt->getUid() << " due to outflow." );
+
+            // Registered processes receive removal notification in the remove() routine.
+            pIt = removeAndNotify( bs, ps, pIt );
+
+            continue;
+         }
+
+         WALBERLA_LOG_DETAIL( "Sending migration notification for particle " << pIt->getUid() << " to process " << ownerRank << "." );
+         //WALBERLA_LOG_DETAIL( "Process registration list before migration: " << pIt->getGhostOwners() );
+
+         // Set new owner and transform to ghost particle
+         pIt->setOwner(ownerRank);
+         data::particle_flags::set( pIt->getFlagsRef(), data::particle_flags::GHOST );
+
+         // currently position is mapped to periodically to global domain,
+         // this might not be the correct position for a ghost particle
+         domain.correctParticlePosition( pIt->getPositionRef() );
+
+         // Correct registration list (exclude new owner and us - the old owner) and
+         // notify registered processes (except for new owner) of (remote) migration since they possess a ghost particle.
+         auto ownerIt = std::find( pIt->getGhostOwners().begin(), pIt->getGhostOwners().end(), ownerRank );
+         WALBERLA_CHECK_UNEQUAL(ownerIt, pIt->getGhostOwners().end(), "New owner has to be former ghost owner!" );
+
+         pIt->getGhostOwnersRef().erase( ownerIt );
+
+         for( auto ghostRank : pIt->getGhostOwners() )
+         {
+            auto& buffer( bs.sendBuffer(ghostRank) );
+
+            WALBERLA_LOG_DETAIL( "Sending remote migration notification for particle " << pIt->getUid() <<
+                                 " to process " << ghostRank );
+
+            packNotification(buffer, ParticleRemoteMigrationNotification( *pIt, ownerRank ));
+         }
+
+         pIt->getGhostOwnersRef().emplace_back( int_c(ownRank) );
+
+         // Send migration notification to new owner
+         auto& buffer( bs.sendBuffer(ownerRank) );
+         packNotification(buffer, ParticleMigrationNotification( *pIt ));
+
+         pIt->getGhostOwnersRef().clear();
+
+         continue;
+
+      } else
+      {
+         // particle still is locally owned after position update.
+         WALBERLA_LOG_DETAIL( "Owner of particle " << pIt->getUid() << " is still process " << pIt->getOwner() );
+      }
+
+      ++pIt;
+   }
+
+   WALBERLA_LOG_DETAIL( "Assembling of particle synchronization message ended." );
+}
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/SyncNextNeighbors.h b/src/mesa_pd/mpi/SyncNextNeighbors.h
new file mode 100644
index 000000000..e1007e7f9
--- /dev/null
+++ b/src/mesa_pd/mpi/SyncNextNeighbors.h
@@ -0,0 +1,79 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SyncNextNeighbors.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/Flags.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/IDomain.h>
+#include <mesa_pd/mpi/notifications/PackNotification.h>
+#include <mesa_pd/mpi/notifications/ParseMessage.h>
+#include <mesa_pd/mpi/notifications/ParticleCopyNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleMigrationNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleRemoteMigrationNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleRemovalNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleUpdateNotification.h>
+
+#include <core/mpi/BufferSystem.h>
+#include <core/logging/Logging.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace mpi {
+
+/**
+ * Kernel which updates all ghost particles.
+ *
+ * \ingroup mesa_pd_mpi
+ */
+class SyncNextNeighbors
+{
+public:
+   void operator()(data::ParticleStorage& ps,
+                   const domain::IDomain& domain,
+                   const real_t dx = real_t(0)) const;
+
+   int64_t getBytesSent() const { return bs.getBytesSent(); }
+   int64_t getBytesReceived() const { return bs.getBytesReceived(); }
+
+   int64_t getNumberOfSends() const { return bs.getNumberOfSends(); }
+   int64_t getNumberOfReceives() const { return bs.getNumberOfReceives(); }
+private:
+   void generateSynchronizationMessages(data::ParticleStorage& ps,
+                                        const domain::IDomain& domain,
+                                        const real_t dx) const;
+   mutable std::vector<uint_t> neighborRanks_; ///cache for neighbor ranks -> will be updated in operator()
+
+   mutable walberla::mpi::BufferSystem bs = walberla::mpi::BufferSystem( walberla::mpi::MPIManager::instance()->comm() );
+
+   int numProcesses_ = walberla::mpi::MPIManager::instance()->numProcesses();
+   int rank_         = walberla::mpi::MPIManager::instance()->rank();
+};
+
+}  // namespace mpi
+}  // namespace mesa_pd
+}  // namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/mesa_pd_mpi.dox b/src/mesa_pd/mpi/mesa_pd_mpi.dox
new file mode 100644
index 000000000..a6e2e3e8e
--- /dev/null
+++ b/src/mesa_pd/mpi/mesa_pd_mpi.dox
@@ -0,0 +1,11 @@
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * \defgroup mesa_pd_mpi Communication functionality for MESA-PD module.
+ * \ingroup mesa_pd
+ * Description of all communication functions available in the MESA-PD module.
+**/
+
+}
+}
diff --git a/src/mesa_pd/mpi/notifications/ContactHistoryNotification.h b/src/mesa_pd/mpi/notifications/ContactHistoryNotification.h
new file mode 100644
index 000000000..c306f6c89
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/ContactHistoryNotification.h
@@ -0,0 +1,101 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ContactHistoryNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ContactHistory.h>
+#include <mesa_pd/data/DataTypes.h>
+
+#include <core/mpi/BufferDataTypeExtensions.h>
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Trasmits the contact history
+ */
+class ContactHistoryNotification
+{
+public:
+   struct Parameters
+   {
+      id_t uid_;
+      std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory> contactHistory_;
+   };
+
+   inline explicit ContactHistoryNotification( const data::Particle& p ) : p_(p) {}
+
+   const data::Particle& p_;
+};
+
+void reduce(data::Particle&& p, const ContactHistoryNotification::Parameters& objparam)
+{
+   auto& ch = p.getNewContactHistoryRef();
+   for (auto& entry : objparam.contactHistory_)
+   {
+      auto ret = ch.insert(entry);
+      WALBERLA_CHECK(ret.second, "entry already present: " << entry.first << " / " << entry.second);
+   }
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ContactHistoryNotification& obj )
+{
+   buf.addDebugMarker( "ft" );
+   buf << obj.p_.getUid();
+   buf << obj.p_.getNewContactHistory();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ContactHistoryNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "ft" );
+   buf >> objparam.uid_;
+   buf >> objparam.contactHistory_;
+   return buf;
+}
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/notifications/ForceTorqueNotification.h b/src/mesa_pd/mpi/notifications/ForceTorqueNotification.h
new file mode 100644
index 000000000..c46f58724
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/ForceTorqueNotification.h
@@ -0,0 +1,113 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticlePropertyNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Trasmits force and torque information.
+ */
+class ForceTorqueNotification
+{
+public:
+   struct Parameters
+   {
+      id_t uid_;
+      Vec3 force_;
+      Vec3 torque_;
+   };
+
+   inline explicit ForceTorqueNotification( const data::Particle& p ) : p_(p) {}
+
+   const data::Particle& p_;
+};
+
+void reduce(data::Particle&& p, const ForceTorqueNotification::Parameters& objparam)
+{
+   p.getForceRef()  += objparam.force_;
+   p.getTorqueRef() += objparam.torque_;
+}
+
+void update(data::Particle&& p, const ForceTorqueNotification::Parameters& objparam)
+{
+   p.setForce(  objparam.force_  );
+   p.setTorque( objparam.torque_ );
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ForceTorqueNotification& obj )
+{
+   buf.addDebugMarker( "ft" );
+   buf << obj.p_.getUid();
+   buf << obj.p_.getForce();
+   buf << obj.p_.getTorque();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ForceTorqueNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "ft" );
+   buf >> objparam.uid_;
+   buf >> objparam.force_;
+   buf >> objparam.torque_;
+   return buf;
+}
+
+template< >
+struct BufferSizeTrait< mesa_pd::ForceTorqueNotification > {
+   static const bool constantSize = true;
+   static const uint_t size = BufferSizeTrait<id_t>::size +
+                              BufferSizeTrait<mesa_pd::Vec3>::size +
+                              BufferSizeTrait<mesa_pd::Vec3>::size +
+                              mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/notifications/HeatFluxNotification.h b/src/mesa_pd/mpi/notifications/HeatFluxNotification.h
new file mode 100644
index 000000000..6fee2c541
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/HeatFluxNotification.h
@@ -0,0 +1,107 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file HeatFluxNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Trasmits force and torque information.
+ */
+class HeatFluxNotification
+{
+public:
+   struct Parameters
+   {
+      id_t uid_;
+      real_t heatFlux_;
+   };
+
+   inline explicit HeatFluxNotification( const data::Particle& p ) : p_(p) {}
+
+   const data::Particle& p_;
+};
+
+void reduce(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
+{
+   p.getHeatFluxRef()  += objparam.heatFlux_;
+}
+
+void update(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
+{
+   p.setHeatFlux( objparam.heatFlux_ );
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::HeatFluxNotification& obj )
+{
+   buf.addDebugMarker( "hf" );
+   buf << obj.p_.getUid();
+   buf << obj.p_.getHeatFlux();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::HeatFluxNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "hf" );
+   buf >> objparam.uid_;
+   buf >> objparam.heatFlux_;
+   return buf;
+}
+
+template< >
+struct BufferSizeTrait< mesa_pd::HeatFluxNotification > {
+   static const bool constantSize = true;
+   static const uint_t size = BufferSizeTrait<id_t>::size +
+                              BufferSizeTrait<real_t>::size +
+                              mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/notifications/NotificationType.h b/src/mesa_pd/mpi/notifications/NotificationType.h
new file mode 100644
index 000000000..572ad573b
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/NotificationType.h
@@ -0,0 +1,74 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file NotificationType.h
+//! \author Tobias Preclik
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//! \brief Header file for the notification types
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <core/DataTypes.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+//=================================================================================================
+//
+//  NOTIFICATION TYPES
+//
+//=================================================================================================
+
+//*************************************************************************************************
+//! Associate a unique number to notifications for identifying/tagging them.
+enum NotificationType : uint8_t
+{
+   PARTICLE_DELETION_NOTIFICATION = 1,
+   PARTICLE_REMOVAL_NOTIFICATION,
+   PARTICLE_COPY_NOTIFICATION,
+   PARTICLE_FORCE_NOTIFICATION,
+   PARTICLE_UPDATE_NOTIFICATION,
+   PARTICLE_MIGRATION_NOTIFICATION,
+   PARTICLE_REMOTE_MIGRATION_NOTIFICATION,
+   PARTICLE_VELOCITY_UPDATE_NOTIFICATION,
+   PARTICLE_VELOCITY_CORRECTION_NOTIFICATION,
+   NEW_GHOST_PARTICLE_NOTIFICATION,
+   PARTICLE_REMOVAL_INFORMATION_NOTIFICATION
+};
+//*************************************************************************************************
+
+
+
+
+//=================================================================================================
+//
+//  NOTIFICATION UTILITY FUNCTIONS
+//
+//=================================================================================================
+
+/**
+ * Returns the notification type of the template parameter.
+ *
+ * To be specialized by the notifications.
+ * \return The notification type of the template parameter.
+ */
+template< typename V >
+struct NotificationTrait
+{};
+
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/src/mesa_pd/mpi/notifications/PackNotification.h b/src/mesa_pd/mpi/notifications/PackNotification.h
new file mode 100644
index 000000000..fe3d5212b
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/PackNotification.h
@@ -0,0 +1,38 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file PackNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "NotificationType.h"
+
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+template <class T>
+inline void packNotification(walberla::mpi::SendBuffer& sb, const T& notification)
+{
+   sb << NotificationTrait< T >::id;
+   sb << notification;
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
diff --git a/src/mesa_pd/mpi/notifications/ParseMessage.h b/src/mesa_pd/mpi/notifications/ParseMessage.h
new file mode 100644
index 000000000..6baade76d
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/ParseMessage.h
@@ -0,0 +1,193 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParseMessage.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//! \brief Parsing of messages
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/IDomain.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+#include <mesa_pd/mpi/notifications/ParticleCopyNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleMigrationNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleRemoteMigrationNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleRemovalNotification.h>
+#include <mesa_pd/mpi/notifications/ParticleUpdateNotification.h>
+
+#include <core/debug/Debug.h>
+#include <core/logging/Logging.h>
+#include <core/mpi/RecvBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+class ParseMessage
+{
+public:
+   void operator()(int sender,
+                   walberla::mpi::RecvBuffer& rb,
+                   data::ParticleStorage& ps,
+                   const domain::IDomain& domain);
+private:
+   int receiver_ = int_c( walberla::mpi::MPIManager::instance()->rank() );
+};
+
+inline
+void ParseMessage::operator()(int sender,
+                              walberla::mpi::RecvBuffer& rb,
+                              data::ParticleStorage& ps,
+                              const domain::IDomain& domain)
+{
+   NotificationType notificationType;
+   rb >> notificationType;
+
+   switch( notificationType ) {
+   case PARTICLE_COPY_NOTIFICATION: {
+      typename ParticleCopyNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_COPY_NOTIFICATION for particle " << objparam.uid << "from neighboring process with rank " << sender );
+
+      WALBERLA_CHECK_EQUAL( ps.find(objparam.uid), ps.end(), "Ghost particle with id " << objparam.uid << " already existend.");
+
+      auto pIt = createNewParticle(ps, objparam);
+
+      domain.correctParticlePosition(pIt->getPositionRef());
+
+      WALBERLA_CHECK(!data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST));
+      data::particle_flags::set(pIt->getFlagsRef(), data::particle_flags::GHOST);
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_COPY_NOTIFICATION for particle " << objparam.uid << "."  );
+
+      break;
+   }
+   case PARTICLE_UPDATE_NOTIFICATION: {
+      typename ParticleUpdateNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_UPDATE_NOTIFICATION for particle " << objparam.uid <<
+                           " from neighboring process with rank " << sender );
+
+      auto pIt = ps.find( objparam.uid );
+      WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+      WALBERLA_CHECK_EQUAL( pIt->getOwner(), sender, "Update notifications must be sent by owner." );
+      WALBERLA_CHECK(data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST),
+                     "Update notification must only concern shadow copies.");
+      pIt->setUid(objparam.uid);
+      pIt->setPosition(objparam.position);
+      pIt->setRotation(objparam.rotation);
+      pIt->setAngularVelocity(objparam.angularVelocity);
+      pIt->setLinearVelocity(objparam.linearVelocity);
+      pIt->setOldContactHistory(objparam.oldContactHistory);
+      pIt->setTemperature(objparam.temperature);
+
+      domain.correctParticlePosition(pIt->getPositionRef());
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_UPDATE_NOTIFICATION." );
+
+      break;
+   }
+   case PARTICLE_MIGRATION_NOTIFICATION: {
+      ParticleMigrationNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_MIGRATION_NOTIFICATION for particle " << objparam.uid_ <<
+                           " from neighboring process with rank " << sender );
+
+      auto pIt = ps.find( objparam.uid_ );
+      WALBERLA_CHECK_UNEQUAL( pIt, ps.end(),
+                              "Object with id: " << objparam.uid_ << " not found! Cannot transfer ownership!" );
+      WALBERLA_CHECK_EQUAL( sender, pIt->getOwner(), "Migration notifications must be sent by previous owner.");
+      WALBERLA_CHECK(data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST),
+                     "Migration notification must only concern ghost particles");
+
+      WALBERLA_CHECK( domain.isContainedInProcessSubdomain( uint_c(receiver_), pIt->getPosition() ),
+                      "Receiving particle migration even though we do not own it." );
+
+      pIt->setOwner(receiver_);
+      data::particle_flags::unset(pIt->getFlagsRef(), data::particle_flags::GHOST);
+      pIt->setGhostOwners(objparam.ghostOwners_);
+      pIt->setOldForce(objparam.oldForce_);
+      pIt->setOldTorque(objparam.oldTorque_);
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_MIGRATION_NOTIFICATION." );
+
+      break;
+   }
+   case PARTICLE_REMOTE_MIGRATION_NOTIFICATION: {
+      ParticleRemoteMigrationNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_REMOTE_MIGRATION_NOTIFICATION for particle " << objparam.uid_ <<
+                           " from neighboring process with rank " << sender <<
+                           " (previous owner):\nnew owner = " << objparam.newOwner_ );
+
+      auto pIt = ps.find( objparam.uid_ );
+      WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+      WALBERLA_CHECK_EQUAL( sender, pIt->getOwner(),
+                            "Remote migration notifications must be sent by previous owner." );
+      WALBERLA_CHECK(data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST),
+                     "Particles in remote migration notifications must be available as ghost particles in local process.");
+      WALBERLA_CHECK_UNEQUAL( objparam.newOwner_, receiver_,
+                              "Particles in remote migration notifications may not migrate to local process." );
+
+      pIt->setOwner(objparam.newOwner_);
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_REMOTE_MIGRATION_NOTIFICATION." );
+
+      break;
+   }
+   case PARTICLE_REMOVAL_NOTIFICATION: {
+      ParticleRemovalNotification::Parameters objparam;
+      rb >> objparam;
+
+      WALBERLA_LOG_DETAIL( "Received PARTICLE_REMOVAL_NOTIFICATION for particle " << objparam.uid_ <<
+                           " from neighboring process with rank " << sender << " (owner)." );
+
+      // Remove ghost particle as prompted.
+      auto pIt = ps.find( objparam.uid_ );
+      WALBERLA_CHECK_UNEQUAL( pIt, ps.end() );
+
+      WALBERLA_CHECK(data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST),
+                     "Only ghost particles should be removed by this message.");
+
+      WALBERLA_CHECK_EQUAL( pIt->getOwner(), sender,
+                            "Only owner is allowed to send removal notifications." );
+
+      ps.erase(pIt);
+
+      WALBERLA_LOG_DETAIL( "Processed PARTICLE_REMOVAL_NOTIFICATION" );
+
+      break;
+   }
+   default:
+      throw std::runtime_error( "Received invalid notification type." );
+   }
+}
+
+}  // namespace mesa_pd
+}  // namespace walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/notifications/ParticleCopyNotification.h b/src/mesa_pd/mpi/notifications/ParticleCopyNotification.h
new file mode 100644
index 000000000..e58a0e750
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/ParticleCopyNotification.h
@@ -0,0 +1,150 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleCopyNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * A complete particle copy for a new ghost particle.
+ *
+ * Copies all properties marked COPY or ALWAYS.
+ */
+class ParticleCopyNotification
+{
+public:
+   struct Parameters
+   {
+      walberla::id_t uid {UniqueID<data::Particle>::invalidID()};
+      walberla::mesa_pd::Vec3 position {real_t(0)};
+      walberla::real_t interactionRadius {real_t(0)};
+      walberla::mesa_pd::data::particle_flags::FlagT flags {};
+      int owner {-1};
+      size_t shapeID {};
+      walberla::mesa_pd::Rot3 rotation {};
+      walberla::mesa_pd::Vec3 angularVelocity {real_t(0)};
+      walberla::mesa_pd::Vec3 linearVelocity {real_t(0)};
+      walberla::real_t invMass {real_t(1)};
+      uint_t type {0};
+      std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory> oldContactHistory {};
+      walberla::real_t temperature {real_t(0)};
+   };
+
+   inline explicit ParticleCopyNotification( const data::Particle& particle ) : particle_(particle) {}
+   const data::Particle& particle_;
+};
+
+inline data::ParticleStorage::iterator createNewParticle(data::ParticleStorage& ps, const ParticleCopyNotification::Parameters& data)
+{
+   WALBERLA_ASSERT_EQUAL(ps.find(data.uid), ps.end(), "Particle with same uid already existent!");
+
+   auto pIt = ps.create(data.uid);
+   pIt->setUid(data.uid);
+   pIt->setPosition(data.position);
+   pIt->setInteractionRadius(data.interactionRadius);
+   pIt->setFlags(data.flags);
+   pIt->setOwner(data.owner);
+   pIt->setShapeID(data.shapeID);
+   pIt->setRotation(data.rotation);
+   pIt->setAngularVelocity(data.angularVelocity);
+   pIt->setLinearVelocity(data.linearVelocity);
+   pIt->setInvMass(data.invMass);
+   pIt->setType(data.type);
+   pIt->setOldContactHistory(data.oldContactHistory);
+   pIt->setTemperature(data.temperature);
+   return pIt;
+}
+
+template<>
+struct NotificationTrait<ParticleCopyNotification>
+{
+   static const NotificationType id = PARTICLE_COPY_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleCopyNotification& obj )
+{
+   buf.addDebugMarker( "cn" );
+   buf << obj.particle_.getUid();
+   buf << obj.particle_.getPosition();
+   buf << obj.particle_.getInteractionRadius();
+   buf << obj.particle_.getFlags();
+   buf << obj.particle_.getOwner();
+   buf << obj.particle_.getShapeID();
+   buf << obj.particle_.getRotation();
+   buf << obj.particle_.getAngularVelocity();
+   buf << obj.particle_.getLinearVelocity();
+   buf << obj.particle_.getInvMass();
+   buf << obj.particle_.getType();
+   buf << obj.particle_.getOldContactHistory();
+   buf << obj.particle_.getTemperature();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleCopyNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "cn" );
+   buf >> objparam.uid;
+   buf >> objparam.position;
+   buf >> objparam.interactionRadius;
+   buf >> objparam.flags;
+   buf >> objparam.owner;
+   buf >> objparam.shapeID;
+   buf >> objparam.rotation;
+   buf >> objparam.angularVelocity;
+   buf >> objparam.linearVelocity;
+   buf >> objparam.invMass;
+   buf >> objparam.type;
+   buf >> objparam.oldContactHistory;
+   buf >> objparam.temperature;
+   return buf;
+}
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/notifications/ParticleMigrationNotification.h b/src/mesa_pd/mpi/notifications/ParticleMigrationNotification.h
new file mode 100644
index 000000000..095f62019
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/ParticleMigrationNotification.h
@@ -0,0 +1,104 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleMigrationNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/BufferDataTypeExtensions.h>
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Migrate the particle to this process. Making the receiver the new owner.
+ */
+class ParticleMigrationNotification {
+public:
+   struct Parameters {
+      id_t uid_;
+      std::vector<int> ghostOwners_ {};
+      walberla::mesa_pd::Vec3 oldForce_ {real_t(0)};
+      walberla::mesa_pd::Vec3 oldTorque_ {real_t(0)};
+   };
+
+   inline explicit ParticleMigrationNotification( const data::Particle& particle ) : particle_(particle) {}
+   const data::Particle& particle_;
+};
+
+template<>
+struct NotificationTrait<ParticleMigrationNotification>
+{
+   static const NotificationType id = PARTICLE_MIGRATION_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleMigrationNotification& obj )
+{
+   buf.addDebugMarker( "mn" );
+   buf << obj.particle_.getUid();
+   buf << obj.particle_.getGhostOwners();
+   buf << obj.particle_.getOldForce();
+   buf << obj.particle_.getOldTorque();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleMigrationNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "mn" );
+   buf >> objparam.uid_;
+   buf >> objparam.ghostOwners_;
+   buf >> objparam.oldForce_;
+   buf >> objparam.oldTorque_;
+   return buf;
+}
+
+template<>
+struct BufferSizeTrait< mesa_pd::ParticleMigrationNotification > {
+   static const bool constantSize = false;
+};
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/notifications/ParticleRemoteMigrationNotification.h b/src/mesa_pd/mpi/notifications/ParticleRemoteMigrationNotification.h
new file mode 100644
index 000000000..dc406fa1d
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/ParticleRemoteMigrationNotification.h
@@ -0,0 +1,99 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleRemoteMigrationNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * The ownership for one of the ghost particles has changed.
+ */
+class ParticleRemoteMigrationNotification {
+public:
+   struct Parameters {
+      id_t uid_;
+      int newOwner_;
+   };
+
+   inline ParticleRemoteMigrationNotification( const data::Particle& particle, const int& newOwner )
+      : particle_(particle), newOwner_(newOwner) {}
+   const data::Particle& particle_;
+   const int newOwner_;
+};
+
+template<>
+struct NotificationTrait<ParticleRemoteMigrationNotification>
+{
+   static const NotificationType id = PARTICLE_REMOTE_MIGRATION_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleRemoteMigrationNotification& obj )
+{
+   buf.addDebugMarker( "rm" );
+   buf << obj.particle_.getUid();
+   buf << obj.newOwner_;
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleRemoteMigrationNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "rm" );
+   buf >> objparam.uid_;
+   buf >> objparam.newOwner_;
+   return buf;
+}
+
+template <>
+struct BufferSizeTrait< mesa_pd::ParticleRemoteMigrationNotification > {
+   static const bool constantSize = true;
+   static const uint_t size = BufferSizeTrait<id_t>::size + BufferSizeTrait<int>::size + mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/notifications/ParticleRemovalNotification.h b/src/mesa_pd/mpi/notifications/ParticleRemovalNotification.h
new file mode 100644
index 000000000..8740e43fd
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/ParticleRemovalNotification.h
@@ -0,0 +1,94 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleRemovalNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * The specified particle should be removed from the process.
+ */
+class ParticleRemovalNotification {
+public:
+   struct Parameters {
+      id_t uid_;
+   };
+
+   inline explicit ParticleRemovalNotification( const data::Particle& particle ) : particle_(particle) {}
+   const data::Particle& particle_;
+};
+
+template<>
+struct NotificationTrait<ParticleRemovalNotification>
+{
+   static const NotificationType id = PARTICLE_REMOVAL_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleRemovalNotification& obj )
+{
+   buf.addDebugMarker( "un" );
+   buf << obj.particle_.getUid();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleRemovalNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "un" );
+   buf >> objparam.uid_;
+   return buf;
+}
+
+template<>
+struct BufferSizeTrait< mesa_pd::ParticleRemovalNotification > {
+   static const bool constantSize = true;
+   static const uint_t size = BufferSizeTrait<id_t>::size + mpi::BUFFER_DEBUG_OVERHEAD;
+};
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/mpi/notifications/ParticleUpdateNotification.h b/src/mesa_pd/mpi/notifications/ParticleUpdateNotification.h
new file mode 100644
index 000000000..5fdd625c1
--- /dev/null
+++ b/src/mesa_pd/mpi/notifications/ParticleUpdateNotification.h
@@ -0,0 +1,109 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleUpdateNotification.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/mpi/notifications/NotificationType.h>
+
+#include <core/mpi/Datatype.h>
+#include <core/mpi/RecvBuffer.h>
+#include <core/mpi/SendBuffer.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+/**
+ * Updates a ghost particle.
+ *
+ * Sends all properties marked as ALWAYS.
+ */
+class ParticleUpdateNotification {
+public:
+   struct Parameters {
+   walberla::id_t uid {UniqueID<data::Particle>::invalidID()};
+   walberla::mesa_pd::Vec3 position {real_t(0)};
+   walberla::mesa_pd::Rot3 rotation {};
+   walberla::mesa_pd::Vec3 angularVelocity {real_t(0)};
+   walberla::mesa_pd::Vec3 linearVelocity {real_t(0)};
+   std::map<walberla::id_t, walberla::mesa_pd::data::ContactHistory> oldContactHistory {};
+   walberla::real_t temperature {real_t(0)};
+   };
+
+   inline explicit ParticleUpdateNotification( const data::Particle& particle ) : particle_(particle) {}
+   const data::Particle& particle_;
+};
+
+template<>
+struct NotificationTrait<ParticleUpdateNotification>
+{
+   static const NotificationType id = PARTICLE_UPDATE_NOTIFICATION;
+};
+
+}  // namespace mesa_pd
+}  // namespace walberla
+
+//======================================================================================================================
+//
+//  Send/Recv Buffer Serialization Specialization
+//
+//======================================================================================================================
+
+namespace walberla {
+namespace mpi {
+
+template< typename T,    // Element type of SendBuffer
+          typename G>    // Growth policy of SendBuffer
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ParticleUpdateNotification& obj )
+{
+   buf.addDebugMarker( "un" );
+   buf << obj.particle_.getUid();
+   buf << obj.particle_.getPosition();
+   buf << obj.particle_.getRotation();
+   buf << obj.particle_.getAngularVelocity();
+   buf << obj.particle_.getLinearVelocity();
+   buf << obj.particle_.getOldContactHistory();
+   buf << obj.particle_.getTemperature();
+   return buf;
+}
+
+template< typename T>    // Element type  of RecvBuffer
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ParticleUpdateNotification::Parameters& objparam )
+{
+   buf.readDebugMarker( "un" );
+   buf >> objparam.uid;
+   buf >> objparam.position;
+   buf >> objparam.rotation;
+   buf >> objparam.angularVelocity;
+   buf >> objparam.linearVelocity;
+   buf >> objparam.oldContactHistory;
+   buf >> objparam.temperature;
+   return buf;
+}
+
+} // mpi
+} // walberla
\ No newline at end of file
diff --git a/src/mesa_pd/vtk/OutputSelector.h b/src/mesa_pd/vtk/OutputSelector.h
new file mode 100644
index 000000000..85a543680
--- /dev/null
+++ b/src/mesa_pd/vtk/OutputSelector.h
@@ -0,0 +1,72 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file OutputSelector.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include "WriteOutput.h"
+
+#include <vtk/Base64Writer.h>
+#include <vtk/VTKTrait.h>
+
+#include <ostream>
+#include <string>
+
+namespace walberla {
+namespace mesa_pd {
+namespace vtk {
+
+class IOutputSelector
+{
+public:
+   IOutputSelector( char const * const ts, const uint_t c) : type_string(ts), components(c) {}
+   virtual ~IOutputSelector() {}
+   virtual void push( std::ostream& os , const data::Particle&& p, const uint_t component ) = 0;
+   virtual void push( walberla::vtk::Base64Writer& b64, const data::Particle&& p, const uint_t component ) = 0;
+
+   const std::string type_string;
+   const uint_t components;
+};
+
+template <typename Selector>
+class OutputSelector : public IOutputSelector
+{
+public:
+   OutputSelector(Selector s) :
+      IOutputSelector(walberla::vtk::VTKTrait<typename Selector::return_type>::type_string,
+                      walberla::vtk::VTKTrait<typename Selector::return_type>::components),
+      selector_(s) {}
+   inline void push( std::ostream& os , const data::Particle&& p, const uint_t component ) override
+   {
+      writeOutput( os, selector_(p), component );
+   }
+   inline void push( walberla::vtk::Base64Writer& b64, const data::Particle&& p, const uint_t component ) override
+   {
+      writeOutput( b64, selector_(p), component );
+   }
+private:
+   Selector selector_;
+};
+
+} // namespace vtk
+} // namespace pe
+} // namespace walberla
+
diff --git a/src/mesa_pd/vtk/ParticleVtkOutput.cpp b/src/mesa_pd/vtk/ParticleVtkOutput.cpp
new file mode 100644
index 000000000..0210b4e29
--- /dev/null
+++ b/src/mesa_pd/vtk/ParticleVtkOutput.cpp
@@ -0,0 +1,89 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleVtkOutput.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include "ParticleVtkOutput.h"
+
+namespace walberla {
+namespace mesa_pd {
+namespace vtk {
+
+std::vector< ParticleVtkOutput::Attributes > ParticleVtkOutput::getAttributes() const
+{
+   std::vector< Attributes > attributes;
+   for (const auto& s : selectors_)
+   {
+      attributes.emplace_back( s.second->type_string, s.first, s.second->components );
+   }
+   return attributes;
+}
+
+std::vector< math::Vector3< real_t > > ParticleVtkOutput::getPoints()
+{
+   std::vector< math::Vector3< real_t > > result;
+   result.reserve(ps_->size());
+
+   particleIndices_.clear();
+   particleIndices_.reserve(ps_->size());
+
+   for (auto pIt = ps_->begin(); pIt != ps_->end(); ++pIt)
+   {
+      if (particleSelector_(pIt))
+      {
+         particleIndices_.emplace_back(pIt.getIdx());
+         result.emplace_back(pIt->getPosition());
+      }
+   }
+
+   return result;
+}
+
+void ParticleVtkOutput::push( std::ostream& os, const uint_t data, const uint_t point, const uint_t component )
+{
+   WALBERLA_ASSERT_LESS( data, selectors_.size() );
+   WALBERLA_ASSERT_LESS( point, particleIndices_.size() );
+   WALBERLA_ASSERT_LESS( particleIndices_[point], ps_->size() );
+
+   selectors_[data].second->push(os, *(*ps_)[particleIndices_[point]], component);
+}
+
+void ParticleVtkOutput::push( walberla::vtk::Base64Writer& b64, const uint_t data, const uint_t point, const uint_t component )
+{
+   WALBERLA_ASSERT_LESS( data, selectors_.size() );
+   WALBERLA_ASSERT_LESS( point, particleIndices_.size() );
+   WALBERLA_ASSERT_LESS( particleIndices_[point], ps_->size() );
+
+   selectors_[data].second->push(b64, *(*ps_)[particleIndices_[point]], component);
+}
+
+void ParticleVtkOutput::addOutput(const std::string& name, std::shared_ptr<IOutputSelector> selector)
+{
+   if ( std::find_if(selectors_.begin(), selectors_.end(), [&name](const auto& item){return item.first==name;} ) !=
+        selectors_.end() )
+   {
+      WALBERLA_LOG_WARNING("Output " << name << " already registered!");
+      return;
+   }
+   selectors_.emplace_back(name, selector);
+}
+
+} // namespace vtk
+} // namespace pe
+} // namespace walberla
+
diff --git a/src/mesa_pd/vtk/ParticleVtkOutput.h b/src/mesa_pd/vtk/ParticleVtkOutput.h
new file mode 100644
index 000000000..651403cea
--- /dev/null
+++ b/src/mesa_pd/vtk/ParticleVtkOutput.h
@@ -0,0 +1,76 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleVtkOutput.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include "OutputSelector.h"
+
+#include "core/Set.h"
+#include "core/uid/SUID.h"
+
+#include "vtk/Base64Writer.h"
+#include "vtk/PointDataSource.h"
+#include "vtk/UtilityFunctions.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace vtk {
+
+class ParticleVtkOutput : public walberla::vtk::PointDataSource
+{
+public:
+   using ParticleSelectorFunc = std::function<bool (const data::ParticleStorage::iterator& pIt)>;
+
+   ParticleVtkOutput( const std::shared_ptr<data::ParticleStorage>& ps )
+      : ps_(ps) { }
+
+   std::vector< Attributes > getAttributes() const override;
+   void configure() override {}
+   std::vector< Vector3< real_t > > getPoints() override;
+   void push( std::ostream& os , const uint_t data, const uint_t point, const uint_t component ) override;
+   void push( walberla::vtk::Base64Writer& b64, const uint_t data, const uint_t point, const uint_t component ) override;
+
+   template <typename T>
+   void addOutput(const std::string& name) { addOutput(name, std::make_shared<OutputSelector<T>>(T())); }
+
+   void addOutput(const std::string& name, std::shared_ptr<IOutputSelector> selector);
+
+   ///sets a function which decides which particles should be written to file
+   void setParticleSelector( const ParticleSelectorFunc& func) {particleSelector_ = func;}
+
+   ///returns the number of particles written during the last write
+   size_t getParticlesWritten() const {return particleIndices_.size();}
+
+private:
+   const std::shared_ptr<data::ParticleStorage>& ps_;
+   std::vector<size_t> particleIndices_; ///< stores indices of particles which got selected by particleSelector_
+   std::vector<std::pair<std::string, std::shared_ptr<IOutputSelector>>> selectors_;
+   ParticleSelectorFunc particleSelector_ = [](const data::ParticleStorage::iterator& /*pIt*/){return true;};
+};
+
+} // namespace vtk
+} // namespace pe
+} // namespace walberla
+
diff --git a/src/mesa_pd/vtk/WriteOutput.h b/src/mesa_pd/vtk/WriteOutput.h
new file mode 100644
index 000000000..ab22c7513
--- /dev/null
+++ b/src/mesa_pd/vtk/WriteOutput.h
@@ -0,0 +1,73 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file WriteOutput.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+
+#include <core/debug/CheckFunctions.h>
+#include <vtk/Base64Writer.h>
+#include <vtk/UtilityFunctions.h>
+
+#include <ostream>
+
+namespace walberla {
+namespace mesa_pd {
+namespace vtk {
+
+template <typename T>
+inline
+void writeOutput(std::ostream& os, const T& data, const uint_t component)
+{
+   static_assert (std::is_arithmetic<T>::value, "this function only supports arithmetic data types" );
+   WALBERLA_ASSERT_EQUAL(component, 0);
+   WALBERLA_UNUSED(component);
+   walberla::vtk::toStream(os, data);
+}
+
+template <>
+inline
+void writeOutput(std::ostream& os, const Vec3& data, const uint_t component)
+{
+   WALBERLA_ASSERT_LESS(component, 3);
+   walberla::vtk::toStream(os, data[component]);
+}
+
+template <typename T>
+inline
+void writeOutput(walberla::vtk::Base64Writer& b64, const T& data, const uint_t component)
+{
+   static_assert (std::is_arithmetic<T>::value, "this function only supports arithmetic data types" );
+   WALBERLA_ASSERT_EQUAL(component, 0);
+   WALBERLA_UNUSED(component);
+   b64 << data;
+}
+
+template <>
+inline
+void writeOutput(walberla::vtk::Base64Writer& b64, const Vec3& data, const uint_t component)
+{
+   WALBERLA_ASSERT_LESS(component, 3);
+   b64 << data[component];
+}
+
+} // namespace vtk
+} // namespace pe
+} // namespace walberla
diff --git a/src/pe/ccd/HashGrids.h b/src/pe/ccd/HashGrids.h
index f3998e26b..24ecec304 100644
--- a/src/pe/ccd/HashGrids.h
+++ b/src/pe/ccd/HashGrids.h
@@ -233,7 +233,7 @@ private:
 
       size_t xyCellCount_;   /*!< \brief Number of cells allocated in x-axis direction multiplied by
                                          the number of cells allocated in y-axis direction. */
-      size_t xyzCellCount_;  //!< Total number of allocated cells.
+   public: size_t xyzCellCount_;  //!< Total number of allocated cells.
 
       size_t enlargementThreshold_;  /*!< \brief The enlargement threshold - the moment the number
                                                  of assigned bodies exceeds this threshold, the
diff --git a/src/vtk/BlockCellDataWriter.h b/src/vtk/BlockCellDataWriter.h
index eeaa41eca..d0eabef2c 100644
--- a/src/vtk/BlockCellDataWriter.h
+++ b/src/vtk/BlockCellDataWriter.h
@@ -76,7 +76,7 @@ namespace internal {
 *   assigned a specific string in VTK. This string must be returned by the pure virtual member function "typeString()".
 *   This function should always be implemented by calling the utility function "vtk::typeToString()":
 *   \code
-*      const std::string& typeString() const { return vtk::typeToString< [DataType] >(); }
+*      std::string typeString() const { return vtk::typeToString< [DataType] >(); }
 *   \endcode
 *
 *   Additionally, every class derived from BlockCellDataWriter can implement two further push functions that are used
@@ -143,11 +143,11 @@ public:
    *   Every data type is assigned a specific string in VTK. This string must be returned by this function, which should
    *   always be implemented by calling the utility function "vtk::typeToString()":
    *   \code
-   *      const std::string& typeString() const { return vtk::typeToString< [DataType] >(); }
+   *      std::string typeString() const { return vtk::typeToString< [DataType] >(); }
    *   \endcode
    */
    //*******************************************************************************************************************
-   virtual const std::string & typeString() const = 0;
+   virtual std::string typeString() const = 0;
 
    const std::string & identifier() const { return identifier_; };
 
@@ -255,7 +255,7 @@ public:
 
    uint_t fSize() const { return F_SIZE; }
 
-   const std::string & typeString() const { return vtk::typeToString< T >(); }
+   std::string typeString() const { return vtk::typeToString< T >(); }
 
 protected:
    virtual T evaluate( const cell_idx_t x, const cell_idx_t y, const cell_idx_t z, const cell_idx_t f ) = 0;
diff --git a/src/vtk/UtilityFunctions.h b/src/vtk/UtilityFunctions.h
index fe1a0bb18..05e62b835 100644
--- a/src/vtk/UtilityFunctions.h
+++ b/src/vtk/UtilityFunctions.h
@@ -21,6 +21,8 @@
 
 #pragma once
 
+#include "VTKTrait.h"
+
 #include "core/DataTypes.h"
 
 #include <ostream>
@@ -40,50 +42,10 @@ template<>             inline void toStream( std::ostream& os, const uint8_t val
 
 
 template< typename T >
-inline const std::string& typeToString();
-
-template<> inline const std::string& typeToString< int8_t >() {
-   static std::string type( "Int8" );
-   return type;
-}
-template<> inline const std::string& typeToString< int16_t >() {
-   static std::string type( "Int16" );
-   return type;
-}
-template<> inline const std::string& typeToString< int32_t >() {
-   static std::string type( "Int32" );
-   return type;
-}
-template<> inline const std::string& typeToString< int64_t >() {
-   static std::string type( "Int64" );
-   return type;
-}
-template<> inline const std::string& typeToString< uint8_t >() {
-   static std::string type( "UInt8" );
-   return type;
-}
-template<> inline const std::string& typeToString< uint16_t >() {
-   static std::string type( "UInt16" );
-   return type;
+inline std::string typeToString()
+{
+   return VTKTrait<T>::type_string;
 }
-template<> inline const std::string& typeToString< uint32_t >() {
-   static std::string type( "UInt32" );
-   return type;
-}
-template<> inline const std::string& typeToString< uint64_t >() {
-   static std::string type( "UInt64" );
-   return type;
-}
-template<> inline const std::string& typeToString< float >() {
-   static std::string type( "Float32" );
-   return type;
-}
-template<> inline const std::string& typeToString< double >() {
-   static std::string type( "Float64" );
-   return type;
-}
-
-
 
 } // namespace vtk
 } // namespace walberla
diff --git a/src/vtk/VTKTrait.h b/src/vtk/VTKTrait.h
new file mode 100644
index 000000000..3592dc87f
--- /dev/null
+++ b/src/vtk/VTKTrait.h
@@ -0,0 +1,121 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file VTKTrait.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <core/DataTypes.h>
+#include <core/math/Vector3.h>
+
+namespace walberla {
+namespace vtk {
+
+template <typename T>
+struct VTKTrait {};
+
+template <>
+struct VTKTrait<int8_t>
+{
+   using type = int8_t;
+   constexpr static char const * const type_string = "Int8";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<int16_t>
+{
+   using type = int16_t;
+   constexpr static char const * const type_string = "Int16";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<int32_t>
+{
+   using type = int32_t;
+   constexpr static char const * const type_string = "Int32";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<int64_t>
+{
+   using type = int64_t;
+   constexpr static char const * const type_string = "Int64";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<uint8_t>
+{
+   using type = uint8_t;
+   constexpr static char const * const type_string = "UInt8";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<uint16_t>
+{
+   using type = uint16_t;
+   constexpr static char const * const type_string = "UInt16";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<uint32_t>
+{
+   using type = uint32_t;
+   constexpr static char const * const type_string = "UInt32";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<uint64_t>
+{
+   using type = uint64_t;
+   constexpr static char const * const type_string = "UInt64";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<float>
+{
+   using type = float;
+   constexpr static char const * const type_string = "Float32";
+   constexpr static const uint_t components = 1;
+};
+
+template <>
+struct VTKTrait<double>
+{
+   using type = double;
+   constexpr static char const * const type_string = "Float64";
+   constexpr static const uint_t components = 1;
+};
+
+template <typename T>
+struct VTKTrait<math::Vector3<T>>
+{
+   using type = typename VTKTrait<T>::type;
+   constexpr static char const * const type_string = VTKTrait<T>::type_string;
+   constexpr static const uint_t components = 3;
+};
+
+} // namespace vtk
+} // namespace walberla
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e259e3a7d..ef1f0177a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -14,6 +14,7 @@ add_subdirectory( gather )
 add_subdirectory( geometry )
 add_subdirectory( gui )
 add_subdirectory( lbm )
+add_subdirectory( mesa_pd )
 add_subdirectory( mesh )
 add_subdirectory( pde )
 add_subdirectory( pe )
diff --git a/tests/mesa_pd/CMakeLists.txt b/tests/mesa_pd/CMakeLists.txt
new file mode 100644
index 000000000..57a73030a
--- /dev/null
+++ b/tests/mesa_pd/CMakeLists.txt
@@ -0,0 +1,124 @@
+###################################################################################################
+#
+# Tests for MESA_PD
+#
+###################################################################################################
+
+waLBerla_compile_test( NAME   MESA_PD_COLLISIONDETECTION_AnalyticContactDetection FILES collision_detection/AnalyticContactDetection.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_COLLISIONDETECTION_AnalyticContactDetection )
+
+waLBerla_compile_test( NAME   MESA_PD_COMMON_IntersectionRatio FILES common/IntersectionRatio.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_COMMON_IntersectionRatio )
+
+waLBerla_compile_test( NAME   MESA_PD_ContactDetection FILES ContactDetection.cpp DEPENDS blockforest core pe)
+waLBerla_execute_test( NAME   MESA_PD_ContactDetection PROCESSES 8 )
+
+waLBerla_compile_test( NAME   MESA_PD_Data_Flags FILES data/Flags.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Data_Flags )
+
+waLBerla_compile_test( NAME   MESA_PD_Data_ParticleStorage FILES data/ParticleStorage.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Data_ParticleStorage )
+
+waLBerla_compile_test( NAME   MESA_PD_Domain_BlockForestDomain FILES domain/BlockForestDomain.cpp DEPENDS blockforest core )
+waLBerla_execute_test( NAME   MESA_PD_Domain_BlockForestDomain )
+
+waLBerla_compile_test( NAME   MESA_PD_Domain_BlockForestSync FILES domain/BlockForestSync.cpp DEPENDS blockforest core pe)
+waLBerla_execute_test( NAME   MESA_PD_Domain_BlockForestSync2 COMMAND $<TARGET_FILE:MESA_PD_Domain_BlockForestSync> PROCESSES 2 )
+waLBerla_execute_test( NAME   MESA_PD_Domain_BlockForestSync4 COMMAND $<TARGET_FILE:MESA_PD_Domain_BlockForestSync> PROCESSES 4 )
+waLBerla_execute_test( NAME   MESA_PD_Domain_BlockForestSync8 COMMAND $<TARGET_FILE:MESA_PD_Domain_BlockForestSync> PROCESSES 8 )
+
+waLBerla_compile_test( NAME   MESA_PD_Domain_BlockForestSyncPeriodic FILES domain/BlockForestSyncPeriodic.cpp DEPENDS blockforest core pe)
+waLBerla_execute_test( NAME   MESA_PD_Domain_BlockForestSyncPeriodic PROCESSES 8 )
+
+waLBerla_compile_test( NAME   MESA_PD_Domain_DistanceCalculation FILES domain/DistanceCalculation.cpp DEPENDS blockforest core )
+waLBerla_execute_test( NAME   MESA_PD_Domain_DistanceCalculation )
+
+waLBerla_compile_test( NAME   MESA_PD_Domain_DynamicRefinement FILES domain/DynamicRefinement.cpp DEPENDS blockforest core pe )
+waLBerla_execute_test( NAME   MESA_PD_Domain_DynamicRefinement PROCESSES 8)
+
+waLBerla_compile_test( NAME   MESA_PD_Domain_SerializeDeserialize FILES domain/SerializeDeserialize.cpp DEPENDS blockforest core pe)
+waLBerla_execute_test( NAME   MESA_PD_Domain_SerializeDeserialize PROCESSES 8 )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_ClearNextNeighborSync FILES kernel/ClearNextNeighborSync.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_ClearNextNeighborSync PROCESSES 2 )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionSD FILES kernel/CoefficientOfRestitutionSD.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionSDEuler COMMAND $<TARGET_FILE:MESA_PD_Kernel_CoefficientOfRestitutionSD> )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionSDVelocityVerlet COMMAND $<TARGET_FILE:MESA_PD_Kernel_CoefficientOfRestitutionSD> --useVV )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionLSD FILES kernel/CoefficientOfRestitutionLSD.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionLSDEuler COMMAND $<TARGET_FILE:MESA_PD_Kernel_CoefficientOfRestitutionLSD> )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionLSDVelocityVerlet COMMAND $<TARGET_FILE:MESA_PD_Kernel_CoefficientOfRestitutionLSD> --useVV )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionNLSD FILES kernel/CoefficientOfRestitutionNLSD.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionNLSDEuler COMMAND $<TARGET_FILE:MESA_PD_Kernel_CoefficientOfRestitutionNLSD> )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_CoefficientOfRestitutionVelocityVerlet COMMAND $<TARGET_FILE:MESA_PD_Kernel_CoefficientOfRestitutionNLSD> --useVV )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_DoubleCast FILES kernel/DoubleCast.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_DoubleCast )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_ExplicitEuler FILES kernel/ExplicitEuler.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_ExplicitEuler )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_ExplicitEulerWithShape FILES kernel/ExplicitEulerWithShape.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_ExplicitEulerWithShape )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_ForceLJ FILES kernel/ForceLJ.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_ForceLJ )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_GenerateAnalyticContacts FILES kernel/GenerateAnalyticContacts.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_GenerateAnalyticContacts PROCESSES 27 )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_GenerateLinkedCells FILES kernel/GenerateLinkedCells.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_GenerateLinkedCells )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_HeatConduction FILES kernel/HeatConduction.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_HeatConduction )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_IntegratorAccuracy FILES kernel/IntegratorAccuracy.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_IntegratorAccuracyEuler COMMAND $<TARGET_FILE:MESA_PD_Kernel_IntegratorAccuracy> )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_IntegratorAccuracyVelocityVerlet COMMAND $<TARGET_FILE:MESA_PD_Kernel_IntegratorAccuracy> --useVV )
+
+aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/kernel/interfaces MESA_PD_INTERFACE_CHECKS)
+waLBerla_compile_test( NAME   MESA_PD_Kernel_Interfaces FILES kernel/Interfaces.cpp ${MESA_PD_INTERFACE_CHECKS} DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_Interfaces )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_LinearSpringDashpot FILES kernel/LinearSpringDashpot.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_LinearSpringDashpot )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_LinkedCellsVsBruteForce FILES kernel/LinkedCellsVsBruteForce.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_LinkedCellsVsBruteForce PROCESSES 27 )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_SingleCast FILES kernel/SingleCast.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_SingleCast )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_SpringDashpot FILES kernel/SpringDashpot.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_SpringDashpot )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_SyncNextNeighbors FILES kernel/SyncNextNeighbors.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_SyncNextNeighbors PROCESSES 27 )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_TemperatureIntegration FILES kernel/TemperatureIntegration.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_TemperatureIntegration )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_VelocityVerlet FILES kernel/VelocityVerlet.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_VelocityVerlet )
+
+waLBerla_compile_test( NAME   MESA_PD_Kernel_VelocityVerletWithShape FILES kernel/VelocityVerletWithShape.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Kernel_VelocityVerletWithShape )
+
+waLBerla_compile_test( NAME   MESA_PD_MPI_BroadcastProperty FILES mpi/BroadcastProperty.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_MPI_BroadcastProperty PROCESSES 8 )
+
+waLBerla_compile_test( NAME   MESA_PD_MPI_Notifications FILES mpi/Notifications.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_MPI_Notifications )
+
+waLBerla_compile_test( NAME   MESA_PD_MPI_ReduceContactHistory FILES mpi/ReduceContactHistory.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_MPI_ReduceContactHistory PROCESSES 8 )
+
+waLBerla_compile_test( NAME   MESA_PD_MPI_ReduceProperty FILES mpi/ReduceProperty.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_MPI_ReduceProperty PROCESSES 8 )
+
+waLBerla_compile_test( NAME   MESA_PD_VTK_Outputs FILES vtk/VTKOutputs.cpp DEPENDS blockforest core vtk )
+waLBerla_execute_test( NAME   MESA_PD_VTK_Outputs PROCESSES 8 )
+
diff --git a/tests/mesa_pd/ContactDetection.cpp b/tests/mesa_pd/ContactDetection.cpp
new file mode 100644
index 000000000..1a435e2dc
--- /dev/null
+++ b/tests/mesa_pd/ContactDetection.cpp
@@ -0,0 +1,226 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   ContactDetection.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/vtk/ParticleVtkOutput.h>
+
+#include <mesa_pd/collision_detection/AnalyticContactDetection.h>
+#include <mesa_pd/data/LinkedCells.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/kernel/DoubleCast.h>
+#include <mesa_pd/kernel/InsertParticleIntoLinkedCells.h>
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/mpi/ContactFilter.h>
+#include <mesa_pd/mpi/notifications/ForceTorqueNotification.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Abort.h>
+#include <core/Environment.h>
+#include <core/math/Random.h>
+#include <core/mpi/Reduce.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/waLBerlaBuildInfo.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace walberla {
+namespace mesa_pd {
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+      : ParticleAccessor(ps)
+      , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+int main( const int particlesPerAxis = 2, const real_t radius = real_t(0.9) )
+{
+   using namespace walberla::timing;
+
+   walberla::mpi::MPIManager::instance()->resetMPI();
+
+   logging::Logging::instance()->setStreamLogLevel(logging::Logging::INFO);
+   //   logging::Logging::instance()->setFileLogLevel(logging::Logging::DETAIL);
+   //   logging::Logging::instance()->includeLoggingToFile("CollisionDetection");
+
+   math::seedRandomGenerator( static_cast<unsigned int>(1337 * walberla::mpi::MPIManager::instance()->worldRank()) );
+
+   const real_t spacing(1.0);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** BLOCKFOREST ***");
+   const int centerParticles  = particlesPerAxis * particlesPerAxis * particlesPerAxis;
+   const int faceParticles    = particlesPerAxis * particlesPerAxis * 6;
+   const int edgeParticles    = particlesPerAxis * 4 * 3;
+   const int cornerParticles  = 8;
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(particlesPerAxis);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(centerParticles);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(faceParticles);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(edgeParticles);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cornerParticles);
+
+   // create forest
+   const real_t eps = real_c(0.01); //shift to make contact points unambigous
+   auto forest = blockforest::createBlockForest( math::AABB(real_t(0),
+                                                            real_t(0),
+                                                            real_t(0),
+                                                            real_c(particlesPerAxis) * real_t(2),
+                                                            real_c(particlesPerAxis) * real_t(2),
+                                                            real_c(particlesPerAxis) * real_t(2)).getTranslated(Vec3(eps,eps,eps)),
+                                                 Vector3<uint_t>(2,2,2),
+                                                 Vector3<bool>(true, true, true) );
+   domain::BlockForestDomain domain(forest);
+
+   auto localDomain = forest->begin()->getAABB();
+   WALBERLA_CHECK_EQUAL(forest->size(), 1, "please run with 8 processes -> 1 process per block");
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - START ***");
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+   ParticleAccessorWithShape accessor(ps, ss);
+   data::LinkedCells         lc(localDomain.getExtended(spacing), spacing );
+
+   auto  smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+   for (auto& iBlk : *forest)
+   {
+      for (auto pt : grid_generator::SCGrid(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing))
+      {
+         WALBERLA_CHECK(iBlk.getAABB().contains(pt));
+
+         auto p                       = ps->create();
+         p->setPosition( pt );
+         p->setInteractionRadius( radius );
+         p->setShapeID( smallSphere );
+         p->setOwner( walberla::mpi::MPIManager::instance()->rank() );
+      }
+   }
+   int64_t numParticles = int64_c(ps->size());
+   walberla::mpi::reduceInplace(numParticles, walberla::mpi::SUM);
+   WALBERLA_LOG_INFO_ON_ROOT("#particles created: " << numParticles);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - END ***");
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SIMULATION - START ***");
+   // Init kernels
+   kernel::InsertParticleIntoLinkedCells ipilc;
+   mpi::SyncNextNeighbors                SNN;
+
+   // initial sync
+   WALBERLA_CHECK_EQUAL(ps->size(), centerParticles);
+   SNN(*ps, domain);
+   WALBERLA_CHECK_EQUAL(ps->size(), centerParticles + faceParticles + edgeParticles + cornerParticles);
+
+   lc.clear();
+   ps->forEachParticle(true, kernel::SelectAll(), accessor, ipilc, accessor, lc);
+
+   WALBERLA_CHECK_EQUAL(lc.cells_.size(), centerParticles + faceParticles + edgeParticles + cornerParticles);
+   int cell0 = 0;
+   int cell1 = 0;
+   for (const auto& idx : lc.cells_)
+   {
+      int particleCounter = 0;
+      int p_idx = idx;
+      while (p_idx != -1)
+      {
+         ++particleCounter;
+         p_idx = ps->getNextParticle(uint_c(p_idx));
+      }
+      if (particleCounter==0)
+      {
+         ++cell0;
+      } else if (particleCounter==1)
+      {
+         ++cell1;
+      } else {
+         WALBERLA_CHECK(false);
+      }
+   }
+   WALBERLA_CHECK_EQUAL(cell0, 0);
+   WALBERLA_CHECK_EQUAL(cell1, centerParticles + faceParticles + edgeParticles + cornerParticles); //ghost particles only at face, no ghost particle at edges and corners
+
+   std::atomic<int64_t> contactsChecked (0);
+   std::atomic<int64_t> contactsDetected(0);
+   std::atomic<int64_t> contactsTreated (0);
+   lc.forEachParticlePairHalf(true,
+                              kernel::SelectAll(),
+                              accessor,
+                              [&](const size_t idx1, const size_t idx2, auto& ac)
+   {
+      collision_detection::AnalyticContactDetection acd;
+      kernel::DoubleCast double_cast;
+      mpi::ContactFilter contact_filter;
+      ++contactsChecked;
+      if (double_cast(idx1, idx2, ac, acd, ac ))
+      {
+         ++contactsDetected;
+         if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), domain))
+         {
+            ++contactsTreated;
+         }
+      }
+   },
+   accessor );
+
+   WALBERLA_LOG_DEVEL_ON_ROOT( "contacts checked/detected/treated: " << contactsChecked << " / " << contactsDetected << " / " << contactsTreated );
+
+   WALBERLA_CHECK_EQUAL(contactsChecked, (centerParticles*26 + faceParticles*17 + edgeParticles*11 + cornerParticles*7) / 2 );
+   WALBERLA_CHECK_EQUAL(contactsDetected, (centerParticles*26 + faceParticles*17 + edgeParticles*11 + cornerParticles*7) / 2 );
+   //TODO: ContactsTreated
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace mesa_pd
+} // namespace walberla
+
+int main( int argc, char* argv[] )
+{
+   walberla::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mesa_pd::main( 2 );
+   walberla::mesa_pd::main( 3 );
+   walberla::mesa_pd::main( 4 );
+   walberla::mesa_pd::main( 5 );
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/IntegratorWithShapeEvaluation.ipynb b/tests/mesa_pd/IntegratorWithShapeEvaluation.ipynb
new file mode 100644
index 000000000..9427965a0
--- /dev/null
+++ b/tests/mesa_pd/IntegratorWithShapeEvaluation.ipynb
@@ -0,0 +1,4544 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 37,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/svg+xml": [
+       "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n",
+       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
+       "  \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
+       "<!-- Created with matplotlib (https://matplotlib.org/) -->\n",
+       "<svg height=\"80.76pt\" version=\"1.1\" viewBox=\"0 0 584.4 80.76\" width=\"584.4pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+       " <defs>\n",
+       "  <style type=\"text/css\">\n",
+       "*{stroke-linecap:butt;stroke-linejoin:round;}\n",
+       "  </style>\n",
+       " </defs>\n",
+       " <g id=\"figure_1\">\n",
+       "  <g id=\"patch_1\">\n",
+       "   <path d=\"M 0 80.76 \n",
+       "L 584.4 80.76 \n",
+       "L 584.4 0 \n",
+       "L 0 0 \n",
+       "z\n",
+       "\" style=\"fill:#ffffff;\"/>\n",
+       "  </g>\n",
+       "  <g id=\"axes_1\">\n",
+       "   <g id=\"patch_2\">\n",
+       "    <path d=\"M 13.2 67.56 \n",
+       "L 571.2 67.56 \n",
+       "L 571.2 13.2 \n",
+       "L 13.2 13.2 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;\"/>\n",
+       "   </g>\n",
+       "   <g clip-path=\"url(#pa9373736c4)\">\n",
+       "    <image height=\"55\" id=\"imagee33b4dc9f0\" transform=\"scale(1 -1)translate(0 -55)\" width=\"558\" x=\"13.2\" xlink:href=\"data:image/png;base64,\n",
+       "iVBORw0KGgoAAAANSUhEUgAAAi4AAAA3CAYAAAArDMGlAAAABHNCSVQICAgIfAhkiAAAAWRJREFUeJzt1rEtBHAcQGG0cgtILKOh1OiMYAA5V16hV2t1BlBZwSQ6UVzBCpp/fnnyfRO88h1f3X/8HP1jL5vtdMJSu8uz6YRl7p4O0wlLvW9uphOW+v56nE5Y6vp8P52w1PPhbTphmduLz+mEpV5PH6YTljqZDgAA+CvjAgBkGBcAIMO4AAAZxgUAyDAuAECGcQEAMowLAJBhXACADOMCAGQYFwAgw7gAABnGBQDIMC4AQIZxAQAyjAsAkGFcAIAM4wIAZBgXACDDuAAAGcYFAMgwLgBAhnEBADKMCwCQYVwAgAzjAgBkGBcAIMO4AAAZxgUAyDAuAECGcQEAMowLAJBhXACADOMCAGQYFwAgw7gAABnGBQDIMC4AQIZxAQAyjAsAkGFcAIAM4wIAZBgXACDDuAAAGcYFAMgwLgBAhnEBADKMCwCQYVwAgAzjAgBkGBcAIMO4AAAZxgUAyDAuAEDGL8ZmEJfpsHZYAAAAAElFTkSuQmCC\" y=\"-12.56\"/>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_1\">\n",
+       "    <g id=\"xtick_1\">\n",
+       "     <g id=\"line2d_1\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 13.2 67.56 \n",
+       "L 13.2 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_2\">\n",
+       "     <g id=\"line2d_2\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 69 67.56 \n",
+       "L 69 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_3\">\n",
+       "     <g id=\"line2d_3\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 124.8 67.56 \n",
+       "L 124.8 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_4\">\n",
+       "     <g id=\"line2d_4\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 180.6 67.56 \n",
+       "L 180.6 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_5\">\n",
+       "     <g id=\"line2d_5\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 236.4 67.56 \n",
+       "L 236.4 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_6\">\n",
+       "     <g id=\"line2d_6\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 292.2 67.56 \n",
+       "L 292.2 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_7\">\n",
+       "     <g id=\"line2d_7\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 348 67.56 \n",
+       "L 348 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_8\">\n",
+       "     <g id=\"line2d_8\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 403.8 67.56 \n",
+       "L 403.8 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_9\">\n",
+       "     <g id=\"line2d_9\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 459.6 67.56 \n",
+       "L 459.6 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_10\">\n",
+       "     <g id=\"line2d_10\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 515.4 67.56 \n",
+       "L 515.4 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_2\">\n",
+       "    <g id=\"ytick_1\">\n",
+       "     <g id=\"line2d_11\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 13.2 13.2 \n",
+       "L 571.2 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_2\">\n",
+       "     <g id=\"line2d_12\">\n",
+       "      <path clip-path=\"url(#pa9373736c4)\" d=\"M 13.2 67.56 \n",
+       "L 571.2 67.56 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"patch_3\">\n",
+       "    <path d=\"M 13.2 67.56 \n",
+       "L 13.2 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_4\">\n",
+       "    <path d=\"M 571.2 67.56 \n",
+       "L 571.2 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_5\">\n",
+       "    <path d=\"M 13.2 67.56 \n",
+       "L 571.2 67.56 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_6\">\n",
+       "    <path d=\"M 13.2 13.2 \n",
+       "L 571.2 13.2 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "  </g>\n",
+       " </g>\n",
+       " <defs>\n",
+       "  <clipPath id=\"pa9373736c4\">\n",
+       "   <rect height=\"54.36\" width=\"558\" x=\"13.2\" y=\"13.2\"/>\n",
+       "  </clipPath>\n",
+       " </defs>\n",
+       "</svg>\n"
+      ],
+      "text/plain": [
+       "<Figure size 720x72 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "import numpy as np\n",
+    "import matplotlib.pyplot as plt\n",
+    "import seaborn as sns\n",
+    "\n",
+    "%matplotlib inline\n",
+    "%config InlineBackend.figure_format = 'svg' \n",
+    "\n",
+    "#darkgrid, whitegrid, dark, white, and ticks\n",
+    "sns.set_style(\"darkgrid\")\n",
+    "#paper, notebook, talk, and poster\n",
+    "sns.set_context(\"notebook\")\n",
+    "#deep, muted, pastel, bright, dark, and colorblind\n",
+    "current_palette = sns.color_palette(\"muted\")\n",
+    "sns.palplot(current_palette)\n",
+    "sns.set_palette(\"muted\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 38,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "data = np.loadtxt(\"VelocityVerletWithShape.txt\").transpose()\n",
+    "x = np.arange(len(data[0]))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 39,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/svg+xml": [
+       "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n",
+       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
+       "  \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
+       "<!-- Created with matplotlib (https://matplotlib.org/) -->\n",
+       "<svg height=\"267.104062pt\" version=\"1.1\" viewBox=\"0 0 382.193437 267.104062\" width=\"382.193437pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+       " <defs>\n",
+       "  <style type=\"text/css\">\n",
+       "*{stroke-linecap:butt;stroke-linejoin:round;}\n",
+       "  </style>\n",
+       " </defs>\n",
+       " <g id=\"figure_1\">\n",
+       "  <g id=\"patch_1\">\n",
+       "   <path d=\"M 0 267.104062 \n",
+       "L 382.193437 267.104062 \n",
+       "L 382.193437 0 \n",
+       "L 0 0 \n",
+       "z\n",
+       "\" style=\"fill:#ffffff;\"/>\n",
+       "  </g>\n",
+       "  <g id=\"axes_1\">\n",
+       "   <g id=\"patch_2\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 368.993438 239.758125 \n",
+       "L 368.993438 22.318125 \n",
+       "L 34.193438 22.318125 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_1\">\n",
+       "    <g id=\"xtick_1\">\n",
+       "     <g id=\"line2d_1\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 49.411619 239.758125 \n",
+       "L 49.411619 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_1\">\n",
+       "      <!-- 0 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 31.78125 66.40625 \n",
+       "Q 24.171875 66.40625 20.328125 58.90625 \n",
+       "Q 16.5 51.421875 16.5 36.375 \n",
+       "Q 16.5 21.390625 20.328125 13.890625 \n",
+       "Q 24.171875 6.390625 31.78125 6.390625 \n",
+       "Q 39.453125 6.390625 43.28125 13.890625 \n",
+       "Q 47.125 21.390625 47.125 36.375 \n",
+       "Q 47.125 51.421875 43.28125 58.90625 \n",
+       "Q 39.453125 66.40625 31.78125 66.40625 \n",
+       "z\n",
+       "M 31.78125 74.21875 \n",
+       "Q 44.046875 74.21875 50.515625 64.515625 \n",
+       "Q 56.984375 54.828125 56.984375 36.375 \n",
+       "Q 56.984375 17.96875 50.515625 8.265625 \n",
+       "Q 44.046875 -1.421875 31.78125 -1.421875 \n",
+       "Q 19.53125 -1.421875 13.0625 8.265625 \n",
+       "Q 6.59375 17.96875 6.59375 36.375 \n",
+       "Q 6.59375 54.828125 13.0625 64.515625 \n",
+       "Q 19.53125 74.21875 31.78125 74.21875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-48\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(45.912244 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_2\">\n",
+       "     <g id=\"line2d_2\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 100.172733 239.758125 \n",
+       "L 100.172733 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_2\">\n",
+       "      <!-- 500 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 10.796875 72.90625 \n",
+       "L 49.515625 72.90625 \n",
+       "L 49.515625 64.59375 \n",
+       "L 19.828125 64.59375 \n",
+       "L 19.828125 46.734375 \n",
+       "Q 21.96875 47.46875 24.109375 47.828125 \n",
+       "Q 26.265625 48.1875 28.421875 48.1875 \n",
+       "Q 40.625 48.1875 47.75 41.5 \n",
+       "Q 54.890625 34.8125 54.890625 23.390625 \n",
+       "Q 54.890625 11.625 47.5625 5.09375 \n",
+       "Q 40.234375 -1.421875 26.90625 -1.421875 \n",
+       "Q 22.3125 -1.421875 17.546875 -0.640625 \n",
+       "Q 12.796875 0.140625 7.71875 1.703125 \n",
+       "L 7.71875 11.625 \n",
+       "Q 12.109375 9.234375 16.796875 8.0625 \n",
+       "Q 21.484375 6.890625 26.703125 6.890625 \n",
+       "Q 35.15625 6.890625 40.078125 11.328125 \n",
+       "Q 45.015625 15.765625 45.015625 23.390625 \n",
+       "Q 45.015625 31 40.078125 35.4375 \n",
+       "Q 35.15625 39.890625 26.703125 39.890625 \n",
+       "Q 22.75 39.890625 18.8125 39.015625 \n",
+       "Q 14.890625 38.140625 10.796875 36.28125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-53\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(89.674608 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_3\">\n",
+       "     <g id=\"line2d_3\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 150.933846 239.758125 \n",
+       "L 150.933846 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_3\">\n",
+       "      <!-- 1000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 12.40625 8.296875 \n",
+       "L 28.515625 8.296875 \n",
+       "L 28.515625 63.921875 \n",
+       "L 10.984375 60.40625 \n",
+       "L 10.984375 69.390625 \n",
+       "L 28.421875 72.90625 \n",
+       "L 38.28125 72.90625 \n",
+       "L 38.28125 8.296875 \n",
+       "L 54.390625 8.296875 \n",
+       "L 54.390625 0 \n",
+       "L 12.40625 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-49\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(136.936346 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_4\">\n",
+       "     <g id=\"line2d_4\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 201.69496 239.758125 \n",
+       "L 201.69496 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_4\">\n",
+       "      <!-- 1500 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(187.69746 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_5\">\n",
+       "     <g id=\"line2d_5\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 252.456073 239.758125 \n",
+       "L 252.456073 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_5\">\n",
+       "      <!-- 2000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 19.1875 8.296875 \n",
+       "L 53.609375 8.296875 \n",
+       "L 53.609375 0 \n",
+       "L 7.328125 0 \n",
+       "L 7.328125 8.296875 \n",
+       "Q 12.9375 14.109375 22.625 23.890625 \n",
+       "Q 32.328125 33.6875 34.8125 36.53125 \n",
+       "Q 39.546875 41.84375 41.421875 45.53125 \n",
+       "Q 43.3125 49.21875 43.3125 52.78125 \n",
+       "Q 43.3125 58.59375 39.234375 62.25 \n",
+       "Q 35.15625 65.921875 28.609375 65.921875 \n",
+       "Q 23.96875 65.921875 18.8125 64.3125 \n",
+       "Q 13.671875 62.703125 7.8125 59.421875 \n",
+       "L 7.8125 69.390625 \n",
+       "Q 13.765625 71.78125 18.9375 73 \n",
+       "Q 24.125 74.21875 28.421875 74.21875 \n",
+       "Q 39.75 74.21875 46.484375 68.546875 \n",
+       "Q 53.21875 62.890625 53.21875 53.421875 \n",
+       "Q 53.21875 48.921875 51.53125 44.890625 \n",
+       "Q 49.859375 40.875 45.40625 35.40625 \n",
+       "Q 44.1875 33.984375 37.640625 27.21875 \n",
+       "Q 31.109375 20.453125 19.1875 8.296875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-50\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(238.458573 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_6\">\n",
+       "     <g id=\"line2d_6\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 303.217187 239.758125 \n",
+       "L 303.217187 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_6\">\n",
+       "      <!-- 2500 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(289.219687 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_7\">\n",
+       "     <g id=\"line2d_7\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 353.9783 239.758125 \n",
+       "L 353.9783 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_7\">\n",
+       "      <!-- 3000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 40.578125 39.3125 \n",
+       "Q 47.65625 37.796875 51.625 33 \n",
+       "Q 55.609375 28.21875 55.609375 21.1875 \n",
+       "Q 55.609375 10.40625 48.1875 4.484375 \n",
+       "Q 40.765625 -1.421875 27.09375 -1.421875 \n",
+       "Q 22.515625 -1.421875 17.65625 -0.515625 \n",
+       "Q 12.796875 0.390625 7.625 2.203125 \n",
+       "L 7.625 11.71875 \n",
+       "Q 11.71875 9.328125 16.59375 8.109375 \n",
+       "Q 21.484375 6.890625 26.8125 6.890625 \n",
+       "Q 36.078125 6.890625 40.9375 10.546875 \n",
+       "Q 45.796875 14.203125 45.796875 21.1875 \n",
+       "Q 45.796875 27.640625 41.28125 31.265625 \n",
+       "Q 36.765625 34.90625 28.71875 34.90625 \n",
+       "L 20.21875 34.90625 \n",
+       "L 20.21875 43.015625 \n",
+       "L 29.109375 43.015625 \n",
+       "Q 36.375 43.015625 40.234375 45.921875 \n",
+       "Q 44.09375 48.828125 44.09375 54.296875 \n",
+       "Q 44.09375 59.90625 40.109375 62.90625 \n",
+       "Q 36.140625 65.921875 28.71875 65.921875 \n",
+       "Q 24.65625 65.921875 20.015625 65.03125 \n",
+       "Q 15.375 64.15625 9.8125 62.3125 \n",
+       "L 9.8125 71.09375 \n",
+       "Q 15.4375 72.65625 20.34375 73.4375 \n",
+       "Q 25.25 74.21875 29.59375 74.21875 \n",
+       "Q 40.828125 74.21875 47.359375 69.109375 \n",
+       "Q 53.90625 64.015625 53.90625 55.328125 \n",
+       "Q 53.90625 49.265625 50.4375 45.09375 \n",
+       "Q 46.96875 40.921875 40.578125 39.3125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-51\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(339.9808 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-51\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_2\">\n",
+       "    <g id=\"ytick_1\">\n",
+       "     <g id=\"line2d_8\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 34.193438 230.032879 \n",
+       "L 368.993438 230.032879 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_8\">\n",
+       "      <!-- 0.0 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 10.6875 12.40625 \n",
+       "L 21 12.40625 \n",
+       "L 21 0 \n",
+       "L 10.6875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-46\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 234.212019)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_2\">\n",
+       "     <g id=\"line2d_9\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 34.193438 198.386229 \n",
+       "L 368.993438 198.386229 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_9\">\n",
+       "      <!-- 0.2 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 202.56537)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-50\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_3\">\n",
+       "     <g id=\"line2d_10\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 34.193438 166.73958 \n",
+       "L 368.993438 166.73958 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_10\">\n",
+       "      <!-- 0.4 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 37.796875 64.3125 \n",
+       "L 12.890625 25.390625 \n",
+       "L 37.796875 25.390625 \n",
+       "z\n",
+       "M 35.203125 72.90625 \n",
+       "L 47.609375 72.90625 \n",
+       "L 47.609375 25.390625 \n",
+       "L 58.015625 25.390625 \n",
+       "L 58.015625 17.1875 \n",
+       "L 47.609375 17.1875 \n",
+       "L 47.609375 0 \n",
+       "L 37.796875 0 \n",
+       "L 37.796875 17.1875 \n",
+       "L 4.890625 17.1875 \n",
+       "L 4.890625 26.703125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-52\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 170.91872)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-52\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_4\">\n",
+       "     <g id=\"line2d_11\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 34.193438 135.09293 \n",
+       "L 368.993438 135.09293 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_11\">\n",
+       "      <!-- 0.6 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 33.015625 40.375 \n",
+       "Q 26.375 40.375 22.484375 35.828125 \n",
+       "Q 18.609375 31.296875 18.609375 23.390625 \n",
+       "Q 18.609375 15.53125 22.484375 10.953125 \n",
+       "Q 26.375 6.390625 33.015625 6.390625 \n",
+       "Q 39.65625 6.390625 43.53125 10.953125 \n",
+       "Q 47.40625 15.53125 47.40625 23.390625 \n",
+       "Q 47.40625 31.296875 43.53125 35.828125 \n",
+       "Q 39.65625 40.375 33.015625 40.375 \n",
+       "z\n",
+       "M 52.59375 71.296875 \n",
+       "L 52.59375 62.3125 \n",
+       "Q 48.875 64.0625 45.09375 64.984375 \n",
+       "Q 41.3125 65.921875 37.59375 65.921875 \n",
+       "Q 27.828125 65.921875 22.671875 59.328125 \n",
+       "Q 17.53125 52.734375 16.796875 39.40625 \n",
+       "Q 19.671875 43.65625 24.015625 45.921875 \n",
+       "Q 28.375 48.1875 33.59375 48.1875 \n",
+       "Q 44.578125 48.1875 50.953125 41.515625 \n",
+       "Q 57.328125 34.859375 57.328125 23.390625 \n",
+       "Q 57.328125 12.15625 50.6875 5.359375 \n",
+       "Q 44.046875 -1.421875 33.015625 -1.421875 \n",
+       "Q 20.359375 -1.421875 13.671875 8.265625 \n",
+       "Q 6.984375 17.96875 6.984375 36.375 \n",
+       "Q 6.984375 53.65625 15.1875 63.9375 \n",
+       "Q 23.390625 74.21875 37.203125 74.21875 \n",
+       "Q 40.921875 74.21875 44.703125 73.484375 \n",
+       "Q 48.484375 72.75 52.59375 71.296875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-54\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 139.272071)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-54\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_5\">\n",
+       "     <g id=\"line2d_12\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 34.193438 103.446281 \n",
+       "L 368.993438 103.446281 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_12\">\n",
+       "      <!-- 0.8 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 31.78125 34.625 \n",
+       "Q 24.75 34.625 20.71875 30.859375 \n",
+       "Q 16.703125 27.09375 16.703125 20.515625 \n",
+       "Q 16.703125 13.921875 20.71875 10.15625 \n",
+       "Q 24.75 6.390625 31.78125 6.390625 \n",
+       "Q 38.8125 6.390625 42.859375 10.171875 \n",
+       "Q 46.921875 13.96875 46.921875 20.515625 \n",
+       "Q 46.921875 27.09375 42.890625 30.859375 \n",
+       "Q 38.875 34.625 31.78125 34.625 \n",
+       "z\n",
+       "M 21.921875 38.8125 \n",
+       "Q 15.578125 40.375 12.03125 44.71875 \n",
+       "Q 8.5 49.078125 8.5 55.328125 \n",
+       "Q 8.5 64.0625 14.71875 69.140625 \n",
+       "Q 20.953125 74.21875 31.78125 74.21875 \n",
+       "Q 42.671875 74.21875 48.875 69.140625 \n",
+       "Q 55.078125 64.0625 55.078125 55.328125 \n",
+       "Q 55.078125 49.078125 51.53125 44.71875 \n",
+       "Q 48 40.375 41.703125 38.8125 \n",
+       "Q 48.828125 37.15625 52.796875 32.3125 \n",
+       "Q 56.78125 27.484375 56.78125 20.515625 \n",
+       "Q 56.78125 9.90625 50.3125 4.234375 \n",
+       "Q 43.84375 -1.421875 31.78125 -1.421875 \n",
+       "Q 19.734375 -1.421875 13.25 4.234375 \n",
+       "Q 6.78125 9.90625 6.78125 20.515625 \n",
+       "Q 6.78125 27.484375 10.78125 32.3125 \n",
+       "Q 14.796875 37.15625 21.921875 38.8125 \n",
+       "z\n",
+       "M 18.3125 54.390625 \n",
+       "Q 18.3125 48.734375 21.84375 45.5625 \n",
+       "Q 25.390625 42.390625 31.78125 42.390625 \n",
+       "Q 38.140625 42.390625 41.71875 45.5625 \n",
+       "Q 45.3125 48.734375 45.3125 54.390625 \n",
+       "Q 45.3125 60.0625 41.71875 63.234375 \n",
+       "Q 38.140625 66.40625 31.78125 66.40625 \n",
+       "Q 25.390625 66.40625 21.84375 63.234375 \n",
+       "Q 18.3125 60.0625 18.3125 54.390625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-56\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 107.625421)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-56\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_6\">\n",
+       "     <g id=\"line2d_13\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 34.193438 71.799631 \n",
+       "L 368.993438 71.799631 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_13\">\n",
+       "      <!-- 1.0 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 75.978772)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_7\">\n",
+       "     <g id=\"line2d_14\">\n",
+       "      <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 34.193438 40.152982 \n",
+       "L 368.993438 40.152982 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_14\">\n",
+       "      <!-- 1.2 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 44.332123)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-50\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"line2d_15\">\n",
+       "    <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 49.411619 229.44082 \n",
+       "L 58.447098 177.389627 \n",
+       "L 63.320164 150.631752 \n",
+       "L 67.381053 129.560779 \n",
+       "L 70.832809 112.7697 \n",
+       "L 73.979998 98.513201 \n",
+       "L 76.924143 86.198382 \n",
+       "L 79.563721 76.07541 \n",
+       "L 82.000254 67.557398 \n",
+       "L 84.335265 60.178982 \n",
+       "L 86.568754 53.873387 \n",
+       "L 88.700721 48.567826 \n",
+       "L 90.528121 44.58826 \n",
+       "L 92.253999 41.323908 \n",
+       "L 93.979877 38.548497 \n",
+       "L 95.604233 36.388613 \n",
+       "L 97.025544 34.863245 \n",
+       "L 98.446855 33.67966 \n",
+       "L 99.665122 32.940711 \n",
+       "L 100.883388 32.454935 \n",
+       "L 102.000133 32.233408 \n",
+       "L 103.116877 32.225496 \n",
+       "L 104.335144 32.462846 \n",
+       "L 105.553411 32.953369 \n",
+       "L 106.8732 33.773018 \n",
+       "L 108.192989 34.890144 \n",
+       "L 109.512778 36.301585 \n",
+       "L 110.934089 38.149749 \n",
+       "L 112.3554 40.333368 \n",
+       "L 113.979756 43.23853 \n",
+       "L 115.705634 46.792449 \n",
+       "L 117.634556 51.327414 \n",
+       "L 119.563478 56.438348 \n",
+       "L 121.695445 62.740778 \n",
+       "L 123.928934 70.049572 \n",
+       "L 126.365467 78.813953 \n",
+       "L 128.903523 88.771413 \n",
+       "L 131.746145 100.859326 \n",
+       "L 134.791812 114.814232 \n",
+       "L 138.142046 131.232197 \n",
+       "L 141.898368 150.777168 \n",
+       "L 146.263824 174.694281 \n",
+       "L 152.050591 207.740662 \n",
+       "L 155.908435 229.874254 \n",
+       "L 165.045436 177.236773 \n",
+       "L 169.918503 150.486493 \n",
+       "L 173.979392 129.424066 \n",
+       "L 177.431147 112.642006 \n",
+       "L 180.578337 98.394684 \n",
+       "L 183.522481 86.089518 \n",
+       "L 186.162059 75.975882 \n",
+       "L 188.598592 67.467205 \n",
+       "L 190.933604 60.098283 \n",
+       "L 193.06557 54.071178 \n",
+       "L 195.096015 48.97765 \n",
+       "L 197.024937 44.740164 \n",
+       "L 198.750815 41.455242 \n",
+       "L 200.375171 38.809582 \n",
+       "L 201.898004 36.727232 \n",
+       "L 203.319315 35.133823 \n",
+       "L 204.740627 33.883781 \n",
+       "L 206.060415 33.030904 \n",
+       "L 207.278682 32.508734 \n",
+       "L 208.496949 32.24132 \n",
+       "L 209.613693 32.219167 \n",
+       "L 210.83196 32.440694 \n",
+       "L 212.050227 32.916976 \n",
+       "L 213.268494 33.646431 \n",
+       "L 214.588283 34.723999 \n",
+       "L 215.908071 36.095882 \n",
+       "L 217.329383 37.901323 \n",
+       "L 218.852216 40.211528 \n",
+       "L 220.476572 43.09612 \n",
+       "L 222.20245 46.629469 \n",
+       "L 224.02985 50.889108 \n",
+       "L 226.060294 56.231062 \n",
+       "L 228.090739 62.194873 \n",
+       "L 230.42575 69.794816 \n",
+       "L 232.862283 78.53483 \n",
+       "L 235.400339 88.468238 \n",
+       "L 238.242961 100.531308 \n",
+       "L 241.288628 114.462163 \n",
+       "L 244.638862 130.856868 \n",
+       "L 248.395184 150.380161 \n",
+       "L 252.76064 174.278286 \n",
+       "L 258.445885 206.722115 \n",
+       "L 262.405251 229.758067 \n",
+       "L 262.506774 229.715632 \n",
+       "L 271.643774 177.08392 \n",
+       "L 276.516841 150.341394 \n",
+       "L 280.476208 129.797496 \n",
+       "L 284.029486 112.514312 \n",
+       "L 287.176675 98.276326 \n",
+       "L 290.120819 85.980812 \n",
+       "L 292.760397 75.876511 \n",
+       "L 295.196931 67.37543 \n",
+       "L 297.531942 60.016002 \n",
+       "L 299.663909 53.999973 \n",
+       "L 301.592831 49.153289 \n",
+       "L 303.521753 44.892068 \n",
+       "L 305.349153 41.407772 \n",
+       "L 306.973509 38.768441 \n",
+       "L 308.496342 36.692421 \n",
+       "L 310.019176 35.007237 \n",
+       "L 311.440487 33.787258 \n",
+       "L 312.760276 32.964446 \n",
+       "L 313.978543 32.469176 \n",
+       "L 315.095287 32.238155 \n",
+       "L 316.212032 32.222332 \n",
+       "L 317.328776 32.420123 \n",
+       "L 318.445521 32.833112 \n",
+       "L 319.562265 33.458133 \n",
+       "L 320.882054 34.472408 \n",
+       "L 322.201843 35.78258 \n",
+       "L 323.623154 37.523145 \n",
+       "L 325.145988 39.762146 \n",
+       "L 326.770343 42.572368 \n",
+       "L 328.496221 46.029765 \n",
+       "L 330.323621 50.208705 \n",
+       "L 332.354066 55.463631 \n",
+       "L 334.486033 61.655298 \n",
+       "L 336.617999 68.509962 \n",
+       "L 338.953011 76.748376 \n",
+       "L 341.491066 86.52545 \n",
+       "L 344.232166 97.985019 \n",
+       "L 347.277833 111.724886 \n",
+       "L 350.628067 127.935249 \n",
+       "L 353.775256 144.072351 \n",
+       "L 353.775256 144.072351 \n",
+       "\" style=\"fill:none;stroke:#4878d0;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"line2d_16\">\n",
+       "    <path clip-path=\"url(#pf85c6f24ae)\" d=\"M 49.411619 229.440825 \n",
+       "L 58.447098 177.389943 \n",
+       "L 63.320164 150.632226 \n",
+       "L 67.381053 129.561254 \n",
+       "L 70.934331 112.294051 \n",
+       "L 74.08152 98.072363 \n",
+       "L 76.924143 86.199173 \n",
+       "L 79.563721 76.07636 \n",
+       "L 82.101776 67.220361 \n",
+       "L 84.436788 59.876756 \n",
+       "L 86.670277 53.605973 \n",
+       "L 88.802243 48.333641 \n",
+       "L 90.731166 44.180018 \n",
+       "L 92.558566 40.800156 \n",
+       "L 94.182921 38.255765 \n",
+       "L 95.705755 36.269938 \n",
+       "L 97.127066 34.768305 \n",
+       "L 98.548377 33.610037 \n",
+       "L 99.766644 32.891658 \n",
+       "L 100.883388 32.454935 \n",
+       "L 102.101655 32.223914 \n",
+       "L 103.319922 32.249231 \n",
+       "L 104.436666 32.494493 \n",
+       "L 105.654933 33.007169 \n",
+       "L 106.8732 33.7746 \n",
+       "L 108.091466 34.793622 \n",
+       "L 109.411255 36.18291 \n",
+       "L 110.832567 38.007339 \n",
+       "L 112.253878 40.167223 \n",
+       "L 113.878233 43.045486 \n",
+       "L 115.502589 46.350978 \n",
+       "L 117.431511 50.82265 \n",
+       "L 119.360434 55.875037 \n",
+       "L 121.4924 62.112592 \n",
+       "L 123.725889 69.35651 \n",
+       "L 126.162423 78.053801 \n",
+       "L 128.700479 87.945594 \n",
+       "L 131.543101 99.965466 \n",
+       "L 134.588768 113.854231 \n",
+       "L 137.939001 130.208428 \n",
+       "L 141.695324 149.693903 \n",
+       "L 146.060779 173.558641 \n",
+       "L 151.644502 205.389158 \n",
+       "L 155.908435 229.874489 \n",
+       "L 165.045436 177.237248 \n",
+       "L 169.918503 150.487285 \n",
+       "L 173.87787 129.935159 \n",
+       "L 177.431147 112.642797 \n",
+       "L 180.578337 98.395634 \n",
+       "L 183.522481 86.090467 \n",
+       "L 186.162059 75.976989 \n",
+       "L 188.598592 67.467205 \n",
+       "L 190.933604 60.098283 \n",
+       "L 193.06557 54.072761 \n",
+       "L 195.096015 48.979233 \n",
+       "L 197.024937 44.741746 \n",
+       "L 198.750815 41.456824 \n",
+       "L 200.375171 38.811164 \n",
+       "L 201.898004 36.727232 \n",
+       "L 203.319315 35.135406 \n",
+       "L 204.639104 33.962897 \n",
+       "L 205.958893 33.087868 \n",
+       "L 207.17716 32.543545 \n",
+       "L 208.395427 32.255561 \n",
+       "L 209.512171 32.21442 \n",
+       "L 210.628916 32.386894 \n",
+       "L 211.847182 32.820453 \n",
+       "L 213.065449 33.508768 \n",
+       "L 214.283716 34.450256 \n",
+       "L 215.603505 35.754098 \n",
+       "L 217.024816 37.488334 \n",
+       "L 218.446127 39.559607 \n",
+       "L 219.968961 42.148303 \n",
+       "L 221.694838 45.542406 \n",
+       "L 223.420716 49.412792 \n",
+       "L 225.349639 54.291123 \n",
+       "L 227.380083 60.038154 \n",
+       "L 229.613572 67.065293 \n",
+       "L 231.948583 75.164145 \n",
+       "L 234.486639 84.798493 \n",
+       "L 237.227739 96.114702 \n",
+       "L 240.171884 109.240308 \n",
+       "L 243.420595 124.775015 \n",
+       "L 247.075395 143.397011 \n",
+       "L 251.237806 165.816447 \n",
+       "L 256.41544 195.011905 \n",
+       "L 262.405251 229.757603 \n",
+       "L 262.506774 229.716099 \n",
+       "L 271.643774 177.084553 \n",
+       "L 276.516841 150.342185 \n",
+       "L 280.476208 129.798604 \n",
+       "L 284.029486 112.515419 \n",
+       "L 287.176675 98.277592 \n",
+       "L 290.019297 86.38842 \n",
+       "L 292.658875 76.249309 \n",
+       "L 295.196931 67.377012 \n",
+       "L 297.531942 60.017584 \n",
+       "L 299.663909 53.999973 \n",
+       "L 301.694353 48.915939 \n",
+       "L 303.623276 44.686365 \n",
+       "L 305.349153 41.409354 \n",
+       "L 306.973509 38.770023 \n",
+       "L 308.496342 36.694003 \n",
+       "L 309.917654 35.108506 \n",
+       "L 311.338965 33.864793 \n",
+       "L 312.557232 33.072044 \n",
+       "L 313.775498 32.534051 \n",
+       "L 314.993765 32.250814 \n",
+       "L 316.212032 32.222332 \n",
+       "L 317.328776 32.421706 \n",
+       "L 318.445521 32.833112 \n",
+       "L 319.663787 33.526174 \n",
+       "L 320.983576 34.564184 \n",
+       "L 322.303365 35.896508 \n",
+       "L 323.724677 37.660808 \n",
+       "L 325.24751 39.926708 \n",
+       "L 326.871866 42.763831 \n",
+       "L 328.597743 46.249709 \n",
+       "L 330.425143 50.457131 \n",
+       "L 332.455588 55.743704 \n",
+       "L 334.587555 61.967017 \n",
+       "L 336.821044 69.196695 \n",
+       "L 339.156055 77.500142 \n",
+       "L 341.694111 87.343516 \n",
+       "L 344.435211 98.869384 \n",
+       "L 347.480878 112.676184 \n",
+       "L 350.831111 128.951265 \n",
+       "L 353.775256 144.072351 \n",
+       "L 353.775256 144.072351 \n",
+       "\" style=\"fill:none;stroke:#ee854a;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_3\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 34.193438 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_4\">\n",
+       "    <path d=\"M 368.993438 239.758125 \n",
+       "L 368.993438 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_5\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 368.993437 239.758125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_6\">\n",
+       "    <path d=\"M 34.193438 22.318125 \n",
+       "L 368.993437 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"text_15\">\n",
+       "    <!-- Position -->\n",
+       "    <defs>\n",
+       "     <path d=\"M 19.671875 64.796875 \n",
+       "L 19.671875 37.40625 \n",
+       "L 32.078125 37.40625 \n",
+       "Q 38.96875 37.40625 42.71875 40.96875 \n",
+       "Q 46.484375 44.53125 46.484375 51.125 \n",
+       "Q 46.484375 57.671875 42.71875 61.234375 \n",
+       "Q 38.96875 64.796875 32.078125 64.796875 \n",
+       "z\n",
+       "M 9.8125 72.90625 \n",
+       "L 32.078125 72.90625 \n",
+       "Q 44.34375 72.90625 50.609375 67.359375 \n",
+       "Q 56.890625 61.8125 56.890625 51.125 \n",
+       "Q 56.890625 40.328125 50.609375 34.8125 \n",
+       "Q 44.34375 29.296875 32.078125 29.296875 \n",
+       "L 19.671875 29.296875 \n",
+       "L 19.671875 0 \n",
+       "L 9.8125 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-80\"/>\n",
+       "     <path d=\"M 30.609375 48.390625 \n",
+       "Q 23.390625 48.390625 19.1875 42.75 \n",
+       "Q 14.984375 37.109375 14.984375 27.296875 \n",
+       "Q 14.984375 17.484375 19.15625 11.84375 \n",
+       "Q 23.34375 6.203125 30.609375 6.203125 \n",
+       "Q 37.796875 6.203125 41.984375 11.859375 \n",
+       "Q 46.1875 17.53125 46.1875 27.296875 \n",
+       "Q 46.1875 37.015625 41.984375 42.703125 \n",
+       "Q 37.796875 48.390625 30.609375 48.390625 \n",
+       "z\n",
+       "M 30.609375 56 \n",
+       "Q 42.328125 56 49.015625 48.375 \n",
+       "Q 55.71875 40.765625 55.71875 27.296875 \n",
+       "Q 55.71875 13.875 49.015625 6.21875 \n",
+       "Q 42.328125 -1.421875 30.609375 -1.421875 \n",
+       "Q 18.84375 -1.421875 12.171875 6.21875 \n",
+       "Q 5.515625 13.875 5.515625 27.296875 \n",
+       "Q 5.515625 40.765625 12.171875 48.375 \n",
+       "Q 18.84375 56 30.609375 56 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-111\"/>\n",
+       "     <path d=\"M 44.28125 53.078125 \n",
+       "L 44.28125 44.578125 \n",
+       "Q 40.484375 46.53125 36.375 47.5 \n",
+       "Q 32.28125 48.484375 27.875 48.484375 \n",
+       "Q 21.1875 48.484375 17.84375 46.4375 \n",
+       "Q 14.5 44.390625 14.5 40.28125 \n",
+       "Q 14.5 37.15625 16.890625 35.375 \n",
+       "Q 19.28125 33.59375 26.515625 31.984375 \n",
+       "L 29.59375 31.296875 \n",
+       "Q 39.15625 29.25 43.1875 25.515625 \n",
+       "Q 47.21875 21.78125 47.21875 15.09375 \n",
+       "Q 47.21875 7.46875 41.1875 3.015625 \n",
+       "Q 35.15625 -1.421875 24.609375 -1.421875 \n",
+       "Q 20.21875 -1.421875 15.453125 -0.5625 \n",
+       "Q 10.6875 0.296875 5.421875 2 \n",
+       "L 5.421875 11.28125 \n",
+       "Q 10.40625 8.6875 15.234375 7.390625 \n",
+       "Q 20.0625 6.109375 24.8125 6.109375 \n",
+       "Q 31.15625 6.109375 34.5625 8.28125 \n",
+       "Q 37.984375 10.453125 37.984375 14.40625 \n",
+       "Q 37.984375 18.0625 35.515625 20.015625 \n",
+       "Q 33.0625 21.96875 24.703125 23.78125 \n",
+       "L 21.578125 24.515625 \n",
+       "Q 13.234375 26.265625 9.515625 29.90625 \n",
+       "Q 5.8125 33.546875 5.8125 39.890625 \n",
+       "Q 5.8125 47.609375 11.28125 51.796875 \n",
+       "Q 16.75 56 26.8125 56 \n",
+       "Q 31.78125 56 36.171875 55.265625 \n",
+       "Q 40.578125 54.546875 44.28125 53.078125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-115\"/>\n",
+       "     <path d=\"M 9.421875 54.6875 \n",
+       "L 18.40625 54.6875 \n",
+       "L 18.40625 0 \n",
+       "L 9.421875 0 \n",
+       "z\n",
+       "M 9.421875 75.984375 \n",
+       "L 18.40625 75.984375 \n",
+       "L 18.40625 64.59375 \n",
+       "L 9.421875 64.59375 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-105\"/>\n",
+       "     <path d=\"M 18.3125 70.21875 \n",
+       "L 18.3125 54.6875 \n",
+       "L 36.8125 54.6875 \n",
+       "L 36.8125 47.703125 \n",
+       "L 18.3125 47.703125 \n",
+       "L 18.3125 18.015625 \n",
+       "Q 18.3125 11.328125 20.140625 9.421875 \n",
+       "Q 21.96875 7.515625 27.59375 7.515625 \n",
+       "L 36.8125 7.515625 \n",
+       "L 36.8125 0 \n",
+       "L 27.59375 0 \n",
+       "Q 17.1875 0 13.234375 3.875 \n",
+       "Q 9.28125 7.765625 9.28125 18.015625 \n",
+       "L 9.28125 47.703125 \n",
+       "L 2.6875 47.703125 \n",
+       "L 2.6875 54.6875 \n",
+       "L 9.28125 54.6875 \n",
+       "L 9.28125 70.21875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-116\"/>\n",
+       "     <path d=\"M 54.890625 33.015625 \n",
+       "L 54.890625 0 \n",
+       "L 45.90625 0 \n",
+       "L 45.90625 32.71875 \n",
+       "Q 45.90625 40.484375 42.875 44.328125 \n",
+       "Q 39.84375 48.1875 33.796875 48.1875 \n",
+       "Q 26.515625 48.1875 22.3125 43.546875 \n",
+       "Q 18.109375 38.921875 18.109375 30.90625 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 21.34375 51.125 25.703125 53.5625 \n",
+       "Q 30.078125 56 35.796875 56 \n",
+       "Q 45.21875 56 50.046875 50.171875 \n",
+       "Q 54.890625 44.34375 54.890625 33.015625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-110\"/>\n",
+       "    </defs>\n",
+       "    <g style=\"fill:#262626;\" transform=\"translate(178.021875 16.318125)scale(0.12 -0.12)\">\n",
+       "     <use xlink:href=\"#DejaVuSans-80\"/>\n",
+       "     <use x=\"60.255859\" xlink:href=\"#DejaVuSans-111\"/>\n",
+       "     <use x=\"121.4375\" xlink:href=\"#DejaVuSans-115\"/>\n",
+       "     <use x=\"173.537109\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "     <use x=\"201.320312\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "     <use x=\"240.529297\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "     <use x=\"268.3125\" xlink:href=\"#DejaVuSans-111\"/>\n",
+       "     <use x=\"329.494141\" xlink:href=\"#DejaVuSans-110\"/>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"legend_1\">\n",
+       "    <g id=\"patch_7\">\n",
+       "     <path d=\"M 281.041562 234.258125 \n",
+       "L 361.293438 234.258125 \n",
+       "Q 363.493438 234.258125 363.493438 232.058125 \n",
+       "L 363.493438 200.86625 \n",
+       "Q 363.493438 198.66625 361.293438 198.66625 \n",
+       "L 281.041562 198.66625 \n",
+       "Q 278.841563 198.66625 278.841563 200.86625 \n",
+       "L 278.841563 232.058125 \n",
+       "Q 278.841563 234.258125 281.041562 234.258125 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_17\">\n",
+       "     <path d=\"M 283.241562 207.574531 \n",
+       "L 305.241562 207.574531 \n",
+       "\" style=\"fill:none;stroke:#4878d0;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_18\"/>\n",
+       "    <g id=\"text_16\">\n",
+       "     <!-- numeric -->\n",
+       "     <defs>\n",
+       "      <path d=\"M 8.5 21.578125 \n",
+       "L 8.5 54.6875 \n",
+       "L 17.484375 54.6875 \n",
+       "L 17.484375 21.921875 \n",
+       "Q 17.484375 14.15625 20.5 10.265625 \n",
+       "Q 23.53125 6.390625 29.59375 6.390625 \n",
+       "Q 36.859375 6.390625 41.078125 11.03125 \n",
+       "Q 45.3125 15.671875 45.3125 23.6875 \n",
+       "L 45.3125 54.6875 \n",
+       "L 54.296875 54.6875 \n",
+       "L 54.296875 0 \n",
+       "L 45.3125 0 \n",
+       "L 45.3125 8.40625 \n",
+       "Q 42.046875 3.421875 37.71875 1 \n",
+       "Q 33.40625 -1.421875 27.6875 -1.421875 \n",
+       "Q 18.265625 -1.421875 13.375 4.4375 \n",
+       "Q 8.5 10.296875 8.5 21.578125 \n",
+       "z\n",
+       "M 31.109375 56 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-117\"/>\n",
+       "      <path d=\"M 52 44.1875 \n",
+       "Q 55.375 50.25 60.0625 53.125 \n",
+       "Q 64.75 56 71.09375 56 \n",
+       "Q 79.640625 56 84.28125 50.015625 \n",
+       "Q 88.921875 44.046875 88.921875 33.015625 \n",
+       "L 88.921875 0 \n",
+       "L 79.890625 0 \n",
+       "L 79.890625 32.71875 \n",
+       "Q 79.890625 40.578125 77.09375 44.375 \n",
+       "Q 74.3125 48.1875 68.609375 48.1875 \n",
+       "Q 61.625 48.1875 57.5625 43.546875 \n",
+       "Q 53.515625 38.921875 53.515625 30.90625 \n",
+       "L 53.515625 0 \n",
+       "L 44.484375 0 \n",
+       "L 44.484375 32.71875 \n",
+       "Q 44.484375 40.625 41.703125 44.40625 \n",
+       "Q 38.921875 48.1875 33.109375 48.1875 \n",
+       "Q 26.21875 48.1875 22.15625 43.53125 \n",
+       "Q 18.109375 38.875 18.109375 30.90625 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 21.1875 51.21875 25.484375 53.609375 \n",
+       "Q 29.78125 56 35.6875 56 \n",
+       "Q 41.65625 56 45.828125 52.96875 \n",
+       "Q 50 49.953125 52 44.1875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-109\"/>\n",
+       "      <path d=\"M 56.203125 29.59375 \n",
+       "L 56.203125 25.203125 \n",
+       "L 14.890625 25.203125 \n",
+       "Q 15.484375 15.921875 20.484375 11.0625 \n",
+       "Q 25.484375 6.203125 34.421875 6.203125 \n",
+       "Q 39.59375 6.203125 44.453125 7.46875 \n",
+       "Q 49.3125 8.734375 54.109375 11.28125 \n",
+       "L 54.109375 2.78125 \n",
+       "Q 49.265625 0.734375 44.1875 -0.34375 \n",
+       "Q 39.109375 -1.421875 33.890625 -1.421875 \n",
+       "Q 20.796875 -1.421875 13.15625 6.1875 \n",
+       "Q 5.515625 13.8125 5.515625 26.8125 \n",
+       "Q 5.515625 40.234375 12.765625 48.109375 \n",
+       "Q 20.015625 56 32.328125 56 \n",
+       "Q 43.359375 56 49.78125 48.890625 \n",
+       "Q 56.203125 41.796875 56.203125 29.59375 \n",
+       "z\n",
+       "M 47.21875 32.234375 \n",
+       "Q 47.125 39.59375 43.09375 43.984375 \n",
+       "Q 39.0625 48.390625 32.421875 48.390625 \n",
+       "Q 24.90625 48.390625 20.390625 44.140625 \n",
+       "Q 15.875 39.890625 15.1875 32.171875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-101\"/>\n",
+       "      <path d=\"M 41.109375 46.296875 \n",
+       "Q 39.59375 47.171875 37.8125 47.578125 \n",
+       "Q 36.03125 48 33.890625 48 \n",
+       "Q 26.265625 48 22.1875 43.046875 \n",
+       "Q 18.109375 38.09375 18.109375 28.8125 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 20.953125 51.171875 25.484375 53.578125 \n",
+       "Q 30.03125 56 36.53125 56 \n",
+       "Q 37.453125 56 38.578125 55.875 \n",
+       "Q 39.703125 55.765625 41.0625 55.515625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-114\"/>\n",
+       "      <path d=\"M 48.78125 52.59375 \n",
+       "L 48.78125 44.1875 \n",
+       "Q 44.96875 46.296875 41.140625 47.34375 \n",
+       "Q 37.3125 48.390625 33.40625 48.390625 \n",
+       "Q 24.65625 48.390625 19.8125 42.84375 \n",
+       "Q 14.984375 37.3125 14.984375 27.296875 \n",
+       "Q 14.984375 17.28125 19.8125 11.734375 \n",
+       "Q 24.65625 6.203125 33.40625 6.203125 \n",
+       "Q 37.3125 6.203125 41.140625 7.25 \n",
+       "Q 44.96875 8.296875 48.78125 10.40625 \n",
+       "L 48.78125 2.09375 \n",
+       "Q 45.015625 0.34375 40.984375 -0.53125 \n",
+       "Q 36.96875 -1.421875 32.421875 -1.421875 \n",
+       "Q 20.0625 -1.421875 12.78125 6.34375 \n",
+       "Q 5.515625 14.109375 5.515625 27.296875 \n",
+       "Q 5.515625 40.671875 12.859375 48.328125 \n",
+       "Q 20.21875 56 33.015625 56 \n",
+       "Q 37.15625 56 41.109375 55.140625 \n",
+       "Q 45.0625 54.296875 48.78125 52.59375 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-99\"/>\n",
+       "     </defs>\n",
+       "     <g style=\"fill:#262626;\" transform=\"translate(314.041562 211.424531)scale(0.11 -0.11)\">\n",
+       "      <use xlink:href=\"#DejaVuSans-110\"/>\n",
+       "      <use x=\"63.378906\" xlink:href=\"#DejaVuSans-117\"/>\n",
+       "      <use x=\"126.757812\" xlink:href=\"#DejaVuSans-109\"/>\n",
+       "      <use x=\"224.169922\" xlink:href=\"#DejaVuSans-101\"/>\n",
+       "      <use x=\"285.693359\" xlink:href=\"#DejaVuSans-114\"/>\n",
+       "      <use x=\"326.806641\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "      <use x=\"354.589844\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_19\">\n",
+       "     <path d=\"M 283.241562 223.720469 \n",
+       "L 305.241562 223.720469 \n",
+       "\" style=\"fill:none;stroke:#ee854a;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_20\"/>\n",
+       "    <g id=\"text_17\">\n",
+       "     <!-- analytic -->\n",
+       "     <defs>\n",
+       "      <path d=\"M 34.28125 27.484375 \n",
+       "Q 23.390625 27.484375 19.1875 25 \n",
+       "Q 14.984375 22.515625 14.984375 16.5 \n",
+       "Q 14.984375 11.71875 18.140625 8.90625 \n",
+       "Q 21.296875 6.109375 26.703125 6.109375 \n",
+       "Q 34.1875 6.109375 38.703125 11.40625 \n",
+       "Q 43.21875 16.703125 43.21875 25.484375 \n",
+       "L 43.21875 27.484375 \n",
+       "z\n",
+       "M 52.203125 31.203125 \n",
+       "L 52.203125 0 \n",
+       "L 43.21875 0 \n",
+       "L 43.21875 8.296875 \n",
+       "Q 40.140625 3.328125 35.546875 0.953125 \n",
+       "Q 30.953125 -1.421875 24.3125 -1.421875 \n",
+       "Q 15.921875 -1.421875 10.953125 3.296875 \n",
+       "Q 6 8.015625 6 15.921875 \n",
+       "Q 6 25.140625 12.171875 29.828125 \n",
+       "Q 18.359375 34.515625 30.609375 34.515625 \n",
+       "L 43.21875 34.515625 \n",
+       "L 43.21875 35.40625 \n",
+       "Q 43.21875 41.609375 39.140625 45 \n",
+       "Q 35.0625 48.390625 27.6875 48.390625 \n",
+       "Q 23 48.390625 18.546875 47.265625 \n",
+       "Q 14.109375 46.140625 10.015625 43.890625 \n",
+       "L 10.015625 52.203125 \n",
+       "Q 14.9375 54.109375 19.578125 55.046875 \n",
+       "Q 24.21875 56 28.609375 56 \n",
+       "Q 40.484375 56 46.34375 49.84375 \n",
+       "Q 52.203125 43.703125 52.203125 31.203125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-97\"/>\n",
+       "      <path d=\"M 9.421875 75.984375 \n",
+       "L 18.40625 75.984375 \n",
+       "L 18.40625 0 \n",
+       "L 9.421875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-108\"/>\n",
+       "      <path d=\"M 32.171875 -5.078125 \n",
+       "Q 28.375 -14.84375 24.75 -17.8125 \n",
+       "Q 21.140625 -20.796875 15.09375 -20.796875 \n",
+       "L 7.90625 -20.796875 \n",
+       "L 7.90625 -13.28125 \n",
+       "L 13.1875 -13.28125 \n",
+       "Q 16.890625 -13.28125 18.9375 -11.515625 \n",
+       "Q 21 -9.765625 23.484375 -3.21875 \n",
+       "L 25.09375 0.875 \n",
+       "L 2.984375 54.6875 \n",
+       "L 12.5 54.6875 \n",
+       "L 29.59375 11.921875 \n",
+       "L 46.6875 54.6875 \n",
+       "L 56.203125 54.6875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-121\"/>\n",
+       "     </defs>\n",
+       "     <g style=\"fill:#262626;\" transform=\"translate(314.041562 227.570469)scale(0.11 -0.11)\">\n",
+       "      <use xlink:href=\"#DejaVuSans-97\"/>\n",
+       "      <use x=\"61.279297\" xlink:href=\"#DejaVuSans-110\"/>\n",
+       "      <use x=\"124.658203\" xlink:href=\"#DejaVuSans-97\"/>\n",
+       "      <use x=\"185.9375\" xlink:href=\"#DejaVuSans-108\"/>\n",
+       "      <use x=\"213.720703\" xlink:href=\"#DejaVuSans-121\"/>\n",
+       "      <use x=\"272.900391\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "      <use x=\"312.109375\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "      <use x=\"339.892578\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "  </g>\n",
+       " </g>\n",
+       " <defs>\n",
+       "  <clipPath id=\"pf85c6f24ae\">\n",
+       "   <rect height=\"217.44\" width=\"334.8\" x=\"34.193438\" y=\"22.318125\"/>\n",
+       "  </clipPath>\n",
+       " </defs>\n",
+       "</svg>\n"
+      ],
+      "text/plain": [
+       "<Figure size 432x288 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "plt.title(\"Position\")\n",
+    "plt.plot(x, data[0], label=\"numeric\")\n",
+    "plt.plot(x, data[1], label=\"analytic\")\n",
+    "plt.legend()\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 40,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/svg+xml": [
+       "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n",
+       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
+       "  \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
+       "<!-- Created with matplotlib (https://matplotlib.org/) -->\n",
+       "<svg height=\"267.104062pt\" version=\"1.1\" viewBox=\"0 0 382.193437 267.104062\" width=\"382.193437pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+       " <defs>\n",
+       "  <style type=\"text/css\">\n",
+       "*{stroke-linecap:butt;stroke-linejoin:round;}\n",
+       "  </style>\n",
+       " </defs>\n",
+       " <g id=\"figure_1\">\n",
+       "  <g id=\"patch_1\">\n",
+       "   <path d=\"M 0 267.104062 \n",
+       "L 382.193437 267.104062 \n",
+       "L 382.193437 0 \n",
+       "L 0 0 \n",
+       "z\n",
+       "\" style=\"fill:#ffffff;\"/>\n",
+       "  </g>\n",
+       "  <g id=\"axes_1\">\n",
+       "   <g id=\"patch_2\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 368.993438 239.758125 \n",
+       "L 368.993438 22.318125 \n",
+       "L 34.193438 22.318125 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_1\">\n",
+       "    <g id=\"xtick_1\">\n",
+       "     <g id=\"line2d_1\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 49.411619 239.758125 \n",
+       "L 49.411619 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_1\">\n",
+       "      <!-- 0 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 31.78125 66.40625 \n",
+       "Q 24.171875 66.40625 20.328125 58.90625 \n",
+       "Q 16.5 51.421875 16.5 36.375 \n",
+       "Q 16.5 21.390625 20.328125 13.890625 \n",
+       "Q 24.171875 6.390625 31.78125 6.390625 \n",
+       "Q 39.453125 6.390625 43.28125 13.890625 \n",
+       "Q 47.125 21.390625 47.125 36.375 \n",
+       "Q 47.125 51.421875 43.28125 58.90625 \n",
+       "Q 39.453125 66.40625 31.78125 66.40625 \n",
+       "z\n",
+       "M 31.78125 74.21875 \n",
+       "Q 44.046875 74.21875 50.515625 64.515625 \n",
+       "Q 56.984375 54.828125 56.984375 36.375 \n",
+       "Q 56.984375 17.96875 50.515625 8.265625 \n",
+       "Q 44.046875 -1.421875 31.78125 -1.421875 \n",
+       "Q 19.53125 -1.421875 13.0625 8.265625 \n",
+       "Q 6.59375 17.96875 6.59375 36.375 \n",
+       "Q 6.59375 54.828125 13.0625 64.515625 \n",
+       "Q 19.53125 74.21875 31.78125 74.21875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-48\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(45.912244 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_2\">\n",
+       "     <g id=\"line2d_2\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 100.172733 239.758125 \n",
+       "L 100.172733 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_2\">\n",
+       "      <!-- 500 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 10.796875 72.90625 \n",
+       "L 49.515625 72.90625 \n",
+       "L 49.515625 64.59375 \n",
+       "L 19.828125 64.59375 \n",
+       "L 19.828125 46.734375 \n",
+       "Q 21.96875 47.46875 24.109375 47.828125 \n",
+       "Q 26.265625 48.1875 28.421875 48.1875 \n",
+       "Q 40.625 48.1875 47.75 41.5 \n",
+       "Q 54.890625 34.8125 54.890625 23.390625 \n",
+       "Q 54.890625 11.625 47.5625 5.09375 \n",
+       "Q 40.234375 -1.421875 26.90625 -1.421875 \n",
+       "Q 22.3125 -1.421875 17.546875 -0.640625 \n",
+       "Q 12.796875 0.140625 7.71875 1.703125 \n",
+       "L 7.71875 11.625 \n",
+       "Q 12.109375 9.234375 16.796875 8.0625 \n",
+       "Q 21.484375 6.890625 26.703125 6.890625 \n",
+       "Q 35.15625 6.890625 40.078125 11.328125 \n",
+       "Q 45.015625 15.765625 45.015625 23.390625 \n",
+       "Q 45.015625 31 40.078125 35.4375 \n",
+       "Q 35.15625 39.890625 26.703125 39.890625 \n",
+       "Q 22.75 39.890625 18.8125 39.015625 \n",
+       "Q 14.890625 38.140625 10.796875 36.28125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-53\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(89.674608 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_3\">\n",
+       "     <g id=\"line2d_3\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 150.933846 239.758125 \n",
+       "L 150.933846 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_3\">\n",
+       "      <!-- 1000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 12.40625 8.296875 \n",
+       "L 28.515625 8.296875 \n",
+       "L 28.515625 63.921875 \n",
+       "L 10.984375 60.40625 \n",
+       "L 10.984375 69.390625 \n",
+       "L 28.421875 72.90625 \n",
+       "L 38.28125 72.90625 \n",
+       "L 38.28125 8.296875 \n",
+       "L 54.390625 8.296875 \n",
+       "L 54.390625 0 \n",
+       "L 12.40625 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-49\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(136.936346 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_4\">\n",
+       "     <g id=\"line2d_4\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 201.69496 239.758125 \n",
+       "L 201.69496 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_4\">\n",
+       "      <!-- 1500 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(187.69746 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_5\">\n",
+       "     <g id=\"line2d_5\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 252.456073 239.758125 \n",
+       "L 252.456073 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_5\">\n",
+       "      <!-- 2000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 19.1875 8.296875 \n",
+       "L 53.609375 8.296875 \n",
+       "L 53.609375 0 \n",
+       "L 7.328125 0 \n",
+       "L 7.328125 8.296875 \n",
+       "Q 12.9375 14.109375 22.625 23.890625 \n",
+       "Q 32.328125 33.6875 34.8125 36.53125 \n",
+       "Q 39.546875 41.84375 41.421875 45.53125 \n",
+       "Q 43.3125 49.21875 43.3125 52.78125 \n",
+       "Q 43.3125 58.59375 39.234375 62.25 \n",
+       "Q 35.15625 65.921875 28.609375 65.921875 \n",
+       "Q 23.96875 65.921875 18.8125 64.3125 \n",
+       "Q 13.671875 62.703125 7.8125 59.421875 \n",
+       "L 7.8125 69.390625 \n",
+       "Q 13.765625 71.78125 18.9375 73 \n",
+       "Q 24.125 74.21875 28.421875 74.21875 \n",
+       "Q 39.75 74.21875 46.484375 68.546875 \n",
+       "Q 53.21875 62.890625 53.21875 53.421875 \n",
+       "Q 53.21875 48.921875 51.53125 44.890625 \n",
+       "Q 49.859375 40.875 45.40625 35.40625 \n",
+       "Q 44.1875 33.984375 37.640625 27.21875 \n",
+       "Q 31.109375 20.453125 19.1875 8.296875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-50\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(238.458573 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_6\">\n",
+       "     <g id=\"line2d_6\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 303.217187 239.758125 \n",
+       "L 303.217187 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_6\">\n",
+       "      <!-- 2500 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(289.219687 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_7\">\n",
+       "     <g id=\"line2d_7\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 353.9783 239.758125 \n",
+       "L 353.9783 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_7\">\n",
+       "      <!-- 3000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 40.578125 39.3125 \n",
+       "Q 47.65625 37.796875 51.625 33 \n",
+       "Q 55.609375 28.21875 55.609375 21.1875 \n",
+       "Q 55.609375 10.40625 48.1875 4.484375 \n",
+       "Q 40.765625 -1.421875 27.09375 -1.421875 \n",
+       "Q 22.515625 -1.421875 17.65625 -0.515625 \n",
+       "Q 12.796875 0.390625 7.625 2.203125 \n",
+       "L 7.625 11.71875 \n",
+       "Q 11.71875 9.328125 16.59375 8.109375 \n",
+       "Q 21.484375 6.890625 26.8125 6.890625 \n",
+       "Q 36.078125 6.890625 40.9375 10.546875 \n",
+       "Q 45.796875 14.203125 45.796875 21.1875 \n",
+       "Q 45.796875 27.640625 41.28125 31.265625 \n",
+       "Q 36.765625 34.90625 28.71875 34.90625 \n",
+       "L 20.21875 34.90625 \n",
+       "L 20.21875 43.015625 \n",
+       "L 29.109375 43.015625 \n",
+       "Q 36.375 43.015625 40.234375 45.921875 \n",
+       "Q 44.09375 48.828125 44.09375 54.296875 \n",
+       "Q 44.09375 59.90625 40.109375 62.90625 \n",
+       "Q 36.140625 65.921875 28.71875 65.921875 \n",
+       "Q 24.65625 65.921875 20.015625 65.03125 \n",
+       "Q 15.375 64.15625 9.8125 62.3125 \n",
+       "L 9.8125 71.09375 \n",
+       "Q 15.4375 72.65625 20.34375 73.4375 \n",
+       "Q 25.25 74.21875 29.59375 74.21875 \n",
+       "Q 40.828125 74.21875 47.359375 69.109375 \n",
+       "Q 53.90625 64.015625 53.90625 55.328125 \n",
+       "Q 53.90625 49.265625 50.4375 45.09375 \n",
+       "Q 46.96875 40.921875 40.578125 39.3125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-51\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(339.9808 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-51\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_2\">\n",
+       "    <g id=\"ytick_1\">\n",
+       "     <g id=\"line2d_8\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 34.193438 229.953652 \n",
+       "L 368.993438 229.953652 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_8\">\n",
+       "      <!-- 0.0 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 10.6875 12.40625 \n",
+       "L 21 12.40625 \n",
+       "L 21 0 \n",
+       "L 10.6875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-46\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 234.132793)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_2\">\n",
+       "     <g id=\"line2d_9\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 34.193438 203.528033 \n",
+       "L 368.993438 203.528033 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_9\">\n",
+       "      <!-- 0.5 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 207.707174)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_3\">\n",
+       "     <g id=\"line2d_10\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 34.193438 177.102415 \n",
+       "L 368.993438 177.102415 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_10\">\n",
+       "      <!-- 1.0 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 181.281555)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_4\">\n",
+       "     <g id=\"line2d_11\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 34.193438 150.676796 \n",
+       "L 368.993438 150.676796 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_11\">\n",
+       "      <!-- 1.5 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 154.855936)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_5\">\n",
+       "     <g id=\"line2d_12\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 34.193438 124.251177 \n",
+       "L 368.993438 124.251177 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_12\">\n",
+       "      <!-- 2.0 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 128.430317)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_6\">\n",
+       "     <g id=\"line2d_13\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 34.193438 97.825558 \n",
+       "L 368.993438 97.825558 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_13\">\n",
+       "      <!-- 2.5 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 102.004698)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_7\">\n",
+       "     <g id=\"line2d_14\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 34.193438 71.399939 \n",
+       "L 368.993438 71.399939 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_14\">\n",
+       "      <!-- 3.0 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 75.57908)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-51\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_8\">\n",
+       "     <g id=\"line2d_15\">\n",
+       "      <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 34.193438 44.97432 \n",
+       "L 368.993438 44.97432 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_15\">\n",
+       "      <!-- 3.5 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 49.153461)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-51\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"line2d_16\">\n",
+       "    <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 49.411619 32.20229 \n",
+       "L 50.528364 32.329133 \n",
+       "L 51.645108 32.670023 \n",
+       "L 52.761853 33.224433 \n",
+       "L 53.98012 34.072695 \n",
+       "L 55.299908 35.276118 \n",
+       "L 56.72122 36.90235 \n",
+       "L 58.142531 38.866831 \n",
+       "L 59.665364 41.343968 \n",
+       "L 61.28972 44.405112 \n",
+       "L 63.015598 48.123197 \n",
+       "L 64.94452 52.834885 \n",
+       "L 66.974965 58.413333 \n",
+       "L 69.106931 64.931476 \n",
+       "L 71.441943 72.816352 \n",
+       "L 73.979998 82.230215 \n",
+       "L 76.619576 92.896651 \n",
+       "L 79.563721 105.769099 \n",
+       "L 82.71091 120.560575 \n",
+       "L 86.264188 138.384655 \n",
+       "L 90.223555 159.418919 \n",
+       "L 94.995099 186.015934 \n",
+       "L 102.000133 226.482166 \n",
+       "L 102.609266 229.874372 \n",
+       "L 111.746267 177.254785 \n",
+       "L 116.517811 151.053625 \n",
+       "L 120.5787 129.963339 \n",
+       "L 124.030456 113.149774 \n",
+       "L 127.279167 98.427005 \n",
+       "L 130.223312 86.121651 \n",
+       "L 132.86289 76.00751 \n",
+       "L 135.299423 67.496875 \n",
+       "L 137.634435 60.126241 \n",
+       "L 139.766401 54.098558 \n",
+       "L 141.796846 49.002641 \n",
+       "L 143.725768 44.761858 \n",
+       "L 145.451646 41.474511 \n",
+       "L 147.076002 38.826136 \n",
+       "L 148.598835 36.740626 \n",
+       "L 150.121668 35.045158 \n",
+       "L 151.441457 33.893001 \n",
+       "L 152.659724 33.092833 \n",
+       "L 153.877991 32.546351 \n",
+       "L 154.994735 32.268882 \n",
+       "L 156.11148 32.205989 \n",
+       "L 157.228224 32.357673 \n",
+       "L 158.344969 32.722875 \n",
+       "L 159.563236 33.365017 \n",
+       "L 160.781502 34.261374 \n",
+       "L 162.101291 35.516591 \n",
+       "L 163.522602 37.19726 \n",
+       "L 165.045436 39.373674 \n",
+       "L 166.669791 42.118239 \n",
+       "L 168.395669 45.506003 \n",
+       "L 170.223069 49.613073 \n",
+       "L 172.151992 54.516082 \n",
+       "L 174.182436 60.289023 \n",
+       "L 176.415925 67.340435 \n",
+       "L 178.750936 75.464728 \n",
+       "L 181.288992 85.124877 \n",
+       "L 184.030092 96.465695 \n",
+       "L 186.974237 109.615083 \n",
+       "L 190.222948 125.171317 \n",
+       "L 193.877748 143.813005 \n",
+       "L 198.04016 166.247827 \n",
+       "L 203.116271 194.868781 \n",
+       "L 209.207605 229.715812 \n",
+       "L 218.344605 177.101886 \n",
+       "L 223.217672 150.36603 \n",
+       "L 227.278561 129.316439 \n",
+       "L 230.831839 112.069495 \n",
+       "L 233.979028 97.86731 \n",
+       "L 236.923172 85.607937 \n",
+       "L 239.56275 75.537662 \n",
+       "L 241.999284 67.069837 \n",
+       "L 244.334295 59.74307 \n",
+       "L 246.466262 53.75661 \n",
+       "L 248.496706 48.701389 \n",
+       "L 250.425629 44.500244 \n",
+       "L 252.151507 41.248836 \n",
+       "L 253.775862 38.634814 \n",
+       "L 255.298696 36.5826 \n",
+       "L 256.720007 35.018204 \n",
+       "L 258.039796 33.872389 \n",
+       "L 259.258062 33.077506 \n",
+       "L 260.476329 32.536838 \n",
+       "L 261.694596 32.250913 \n",
+       "L 262.81134 32.21286 \n",
+       "L 263.928085 32.388855 \n",
+       "L 265.044829 32.778897 \n",
+       "L 266.263096 33.447994 \n",
+       "L 267.481363 34.370776 \n",
+       "L 268.801152 35.654533 \n",
+       "L 270.222463 37.366384 \n",
+       "L 271.745296 39.575566 \n",
+       "L 273.26813 42.167919 \n",
+       "L 274.994008 45.563611 \n",
+       "L 276.821408 49.678609 \n",
+       "L 278.75033 54.589017 \n",
+       "L 280.780775 60.370414 \n",
+       "L 283.014264 67.430282 \n",
+       "L 285.450797 75.934575 \n",
+       "L 287.988853 85.636477 \n",
+       "L 290.729953 97.020105 \n",
+       "L 293.674097 110.211245 \n",
+       "L 296.922809 125.808703 \n",
+       "L 300.577609 144.488973 \n",
+       "L 304.841542 167.519957 \n",
+       "L 310.120698 197.357334 \n",
+       "L 315.704421 229.758229 \n",
+       "L 315.805943 229.557252 \n",
+       "L 324.942943 176.949146 \n",
+       "L 329.81601 150.221218 \n",
+       "L 333.775377 129.689569 \n",
+       "L 337.328655 112.417785 \n",
+       "L 340.475844 98.190231 \n",
+       "L 343.318466 86.31033 \n",
+       "L 345.958044 76.179805 \n",
+       "L 348.394578 67.653315 \n",
+       "L 350.729589 60.267354 \n",
+       "L 352.861556 54.224344 \n",
+       "L 353.775256 51.845509 \n",
+       "L 353.775256 51.845509 \n",
+       "\" style=\"fill:none;stroke:#4878d0;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"line2d_17\">\n",
+       "    <path clip-path=\"url(#p5f90ed8c70)\" d=\"M 49.411619 32.203347 \n",
+       "L 50.528364 32.33019 \n",
+       "L 51.645108 32.670552 \n",
+       "L 52.863375 33.286269 \n",
+       "L 54.081642 34.155672 \n",
+       "L 55.401431 35.38182 \n",
+       "L 56.822742 37.032365 \n",
+       "L 58.244053 39.020628 \n",
+       "L 59.868409 41.703885 \n",
+       "L 61.492764 44.818409 \n",
+       "L 63.320164 48.829289 \n",
+       "L 65.249087 53.63241 \n",
+       "L 67.279531 59.304405 \n",
+       "L 69.411498 65.917152 \n",
+       "L 71.746509 73.901388 \n",
+       "L 74.284565 83.417253 \n",
+       "L 76.924143 94.182522 \n",
+       "L 79.868287 107.156444 \n",
+       "L 83.116999 122.540911 \n",
+       "L 86.670277 140.48972 \n",
+       "L 90.731166 162.191495 \n",
+       "L 95.604233 189.48509 \n",
+       "L 102.609266 229.874489 \n",
+       "L 111.746267 177.255155 \n",
+       "L 116.619334 150.5119 \n",
+       "L 120.5787 129.963867 \n",
+       "L 124.030456 113.150831 \n",
+       "L 127.177645 98.869898 \n",
+       "L 130.12179 86.529134 \n",
+       "L 132.761368 76.380111 \n",
+       "L 135.299423 67.497404 \n",
+       "L 137.634435 60.127298 \n",
+       "L 139.766401 54.099086 \n",
+       "L 141.796846 49.003698 \n",
+       "L 143.725768 44.762915 \n",
+       "L 145.451646 41.475568 \n",
+       "L 147.076002 38.827193 \n",
+       "L 148.598835 36.741683 \n",
+       "L 150.020146 35.146632 \n",
+       "L 151.339935 33.971749 \n",
+       "L 152.659724 33.093362 \n",
+       "L 153.877991 32.547408 \n",
+       "L 154.994735 32.269939 \n",
+       "L 156.11148 32.207046 \n",
+       "L 157.228224 32.358201 \n",
+       "L 158.344969 32.723932 \n",
+       "L 159.563236 33.366074 \n",
+       "L 160.781502 34.261903 \n",
+       "L 162.101291 35.51712 \n",
+       "L 163.522602 37.198317 \n",
+       "L 165.045436 39.374731 \n",
+       "L 166.568269 41.934845 \n",
+       "L 168.294147 45.29407 \n",
+       "L 170.121547 49.372072 \n",
+       "L 172.050469 54.244427 \n",
+       "L 174.080914 59.986186 \n",
+       "L 176.314403 67.00483 \n",
+       "L 178.649414 75.096883 \n",
+       "L 181.18747 84.722679 \n",
+       "L 183.92857 96.030201 \n",
+       "L 186.872715 109.146293 \n",
+       "L 190.121426 124.670287 \n",
+       "L 193.776226 143.280793 \n",
+       "L 197.938637 165.687604 \n",
+       "L 203.116271 194.868622 \n",
+       "L 209.207605 229.716162 \n",
+       "L 218.344605 177.102573 \n",
+       "L 223.217672 150.367087 \n",
+       "L 227.177039 129.826982 \n",
+       "L 230.628794 113.022931 \n",
+       "L 233.775983 98.751512 \n",
+       "L 236.618606 86.827744 \n",
+       "L 239.359706 76.280751 \n",
+       "L 241.796239 67.745276 \n",
+       "L 244.131251 60.349802 \n",
+       "L 246.263217 54.297807 \n",
+       "L 248.293662 49.179164 \n",
+       "L 250.121062 45.124946 \n",
+       "L 251.84694 41.788447 \n",
+       "L 253.471295 39.092506 \n",
+       "L 254.994129 36.962601 \n",
+       "L 256.516962 35.221681 \n",
+       "L 257.938273 33.950609 \n",
+       "L 259.258062 33.078563 \n",
+       "L 260.476329 32.537895 \n",
+       "L 261.593074 32.265711 \n",
+       "L 262.709818 32.208104 \n",
+       "L 263.826563 32.364543 \n",
+       "L 264.943307 32.73503 \n",
+       "L 266.161574 33.383515 \n",
+       "L 267.379841 34.285157 \n",
+       "L 268.69963 35.546188 \n",
+       "L 270.120941 37.233728 \n",
+       "L 271.643774 39.417012 \n",
+       "L 273.166607 41.983997 \n",
+       "L 274.892485 45.351149 \n",
+       "L 276.719885 49.43655 \n",
+       "L 278.648808 54.316833 \n",
+       "L 280.679252 60.067048 \n",
+       "L 282.912741 67.094677 \n",
+       "L 285.247752 75.195186 \n",
+       "L 287.785808 84.830495 \n",
+       "L 290.526908 96.146474 \n",
+       "L 293.471053 109.27155 \n",
+       "L 296.719764 124.804529 \n",
+       "L 300.273042 142.89128 \n",
+       "L 304.435453 165.278007 \n",
+       "L 309.410043 193.278329 \n",
+       "L 315.704421 229.757649 \n",
+       "L 315.805943 229.557835 \n",
+       "L 324.942943 176.950203 \n",
+       "L 329.714488 150.764 \n",
+       "L 333.775377 129.690626 \n",
+       "L 337.328655 112.418842 \n",
+       "L 340.475844 98.191288 \n",
+       "L 343.318466 86.311387 \n",
+       "L 346.059566 75.809846 \n",
+       "L 348.4961 67.317181 \n",
+       "L 350.831111 59.965045 \n",
+       "L 352.963078 53.954802 \n",
+       "L 353.775256 51.846566 \n",
+       "L 353.775256 51.846566 \n",
+       "\" style=\"fill:none;stroke:#ee854a;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_3\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 34.193438 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_4\">\n",
+       "    <path d=\"M 368.993438 239.758125 \n",
+       "L 368.993438 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_5\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 368.993437 239.758125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_6\">\n",
+       "    <path d=\"M 34.193438 22.318125 \n",
+       "L 368.993437 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"text_16\">\n",
+       "    <!-- Linear Velocity -->\n",
+       "    <defs>\n",
+       "     <path d=\"M 9.8125 72.90625 \n",
+       "L 19.671875 72.90625 \n",
+       "L 19.671875 8.296875 \n",
+       "L 55.171875 8.296875 \n",
+       "L 55.171875 0 \n",
+       "L 9.8125 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-76\"/>\n",
+       "     <path d=\"M 9.421875 54.6875 \n",
+       "L 18.40625 54.6875 \n",
+       "L 18.40625 0 \n",
+       "L 9.421875 0 \n",
+       "z\n",
+       "M 9.421875 75.984375 \n",
+       "L 18.40625 75.984375 \n",
+       "L 18.40625 64.59375 \n",
+       "L 9.421875 64.59375 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-105\"/>\n",
+       "     <path d=\"M 54.890625 33.015625 \n",
+       "L 54.890625 0 \n",
+       "L 45.90625 0 \n",
+       "L 45.90625 32.71875 \n",
+       "Q 45.90625 40.484375 42.875 44.328125 \n",
+       "Q 39.84375 48.1875 33.796875 48.1875 \n",
+       "Q 26.515625 48.1875 22.3125 43.546875 \n",
+       "Q 18.109375 38.921875 18.109375 30.90625 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 21.34375 51.125 25.703125 53.5625 \n",
+       "Q 30.078125 56 35.796875 56 \n",
+       "Q 45.21875 56 50.046875 50.171875 \n",
+       "Q 54.890625 44.34375 54.890625 33.015625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-110\"/>\n",
+       "     <path d=\"M 56.203125 29.59375 \n",
+       "L 56.203125 25.203125 \n",
+       "L 14.890625 25.203125 \n",
+       "Q 15.484375 15.921875 20.484375 11.0625 \n",
+       "Q 25.484375 6.203125 34.421875 6.203125 \n",
+       "Q 39.59375 6.203125 44.453125 7.46875 \n",
+       "Q 49.3125 8.734375 54.109375 11.28125 \n",
+       "L 54.109375 2.78125 \n",
+       "Q 49.265625 0.734375 44.1875 -0.34375 \n",
+       "Q 39.109375 -1.421875 33.890625 -1.421875 \n",
+       "Q 20.796875 -1.421875 13.15625 6.1875 \n",
+       "Q 5.515625 13.8125 5.515625 26.8125 \n",
+       "Q 5.515625 40.234375 12.765625 48.109375 \n",
+       "Q 20.015625 56 32.328125 56 \n",
+       "Q 43.359375 56 49.78125 48.890625 \n",
+       "Q 56.203125 41.796875 56.203125 29.59375 \n",
+       "z\n",
+       "M 47.21875 32.234375 \n",
+       "Q 47.125 39.59375 43.09375 43.984375 \n",
+       "Q 39.0625 48.390625 32.421875 48.390625 \n",
+       "Q 24.90625 48.390625 20.390625 44.140625 \n",
+       "Q 15.875 39.890625 15.1875 32.171875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-101\"/>\n",
+       "     <path d=\"M 34.28125 27.484375 \n",
+       "Q 23.390625 27.484375 19.1875 25 \n",
+       "Q 14.984375 22.515625 14.984375 16.5 \n",
+       "Q 14.984375 11.71875 18.140625 8.90625 \n",
+       "Q 21.296875 6.109375 26.703125 6.109375 \n",
+       "Q 34.1875 6.109375 38.703125 11.40625 \n",
+       "Q 43.21875 16.703125 43.21875 25.484375 \n",
+       "L 43.21875 27.484375 \n",
+       "z\n",
+       "M 52.203125 31.203125 \n",
+       "L 52.203125 0 \n",
+       "L 43.21875 0 \n",
+       "L 43.21875 8.296875 \n",
+       "Q 40.140625 3.328125 35.546875 0.953125 \n",
+       "Q 30.953125 -1.421875 24.3125 -1.421875 \n",
+       "Q 15.921875 -1.421875 10.953125 3.296875 \n",
+       "Q 6 8.015625 6 15.921875 \n",
+       "Q 6 25.140625 12.171875 29.828125 \n",
+       "Q 18.359375 34.515625 30.609375 34.515625 \n",
+       "L 43.21875 34.515625 \n",
+       "L 43.21875 35.40625 \n",
+       "Q 43.21875 41.609375 39.140625 45 \n",
+       "Q 35.0625 48.390625 27.6875 48.390625 \n",
+       "Q 23 48.390625 18.546875 47.265625 \n",
+       "Q 14.109375 46.140625 10.015625 43.890625 \n",
+       "L 10.015625 52.203125 \n",
+       "Q 14.9375 54.109375 19.578125 55.046875 \n",
+       "Q 24.21875 56 28.609375 56 \n",
+       "Q 40.484375 56 46.34375 49.84375 \n",
+       "Q 52.203125 43.703125 52.203125 31.203125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-97\"/>\n",
+       "     <path d=\"M 41.109375 46.296875 \n",
+       "Q 39.59375 47.171875 37.8125 47.578125 \n",
+       "Q 36.03125 48 33.890625 48 \n",
+       "Q 26.265625 48 22.1875 43.046875 \n",
+       "Q 18.109375 38.09375 18.109375 28.8125 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 20.953125 51.171875 25.484375 53.578125 \n",
+       "Q 30.03125 56 36.53125 56 \n",
+       "Q 37.453125 56 38.578125 55.875 \n",
+       "Q 39.703125 55.765625 41.0625 55.515625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-114\"/>\n",
+       "     <path id=\"DejaVuSans-32\"/>\n",
+       "     <path d=\"M 28.609375 0 \n",
+       "L 0.78125 72.90625 \n",
+       "L 11.078125 72.90625 \n",
+       "L 34.1875 11.53125 \n",
+       "L 57.328125 72.90625 \n",
+       "L 67.578125 72.90625 \n",
+       "L 39.796875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-86\"/>\n",
+       "     <path d=\"M 9.421875 75.984375 \n",
+       "L 18.40625 75.984375 \n",
+       "L 18.40625 0 \n",
+       "L 9.421875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-108\"/>\n",
+       "     <path d=\"M 30.609375 48.390625 \n",
+       "Q 23.390625 48.390625 19.1875 42.75 \n",
+       "Q 14.984375 37.109375 14.984375 27.296875 \n",
+       "Q 14.984375 17.484375 19.15625 11.84375 \n",
+       "Q 23.34375 6.203125 30.609375 6.203125 \n",
+       "Q 37.796875 6.203125 41.984375 11.859375 \n",
+       "Q 46.1875 17.53125 46.1875 27.296875 \n",
+       "Q 46.1875 37.015625 41.984375 42.703125 \n",
+       "Q 37.796875 48.390625 30.609375 48.390625 \n",
+       "z\n",
+       "M 30.609375 56 \n",
+       "Q 42.328125 56 49.015625 48.375 \n",
+       "Q 55.71875 40.765625 55.71875 27.296875 \n",
+       "Q 55.71875 13.875 49.015625 6.21875 \n",
+       "Q 42.328125 -1.421875 30.609375 -1.421875 \n",
+       "Q 18.84375 -1.421875 12.171875 6.21875 \n",
+       "Q 5.515625 13.875 5.515625 27.296875 \n",
+       "Q 5.515625 40.765625 12.171875 48.375 \n",
+       "Q 18.84375 56 30.609375 56 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-111\"/>\n",
+       "     <path d=\"M 48.78125 52.59375 \n",
+       "L 48.78125 44.1875 \n",
+       "Q 44.96875 46.296875 41.140625 47.34375 \n",
+       "Q 37.3125 48.390625 33.40625 48.390625 \n",
+       "Q 24.65625 48.390625 19.8125 42.84375 \n",
+       "Q 14.984375 37.3125 14.984375 27.296875 \n",
+       "Q 14.984375 17.28125 19.8125 11.734375 \n",
+       "Q 24.65625 6.203125 33.40625 6.203125 \n",
+       "Q 37.3125 6.203125 41.140625 7.25 \n",
+       "Q 44.96875 8.296875 48.78125 10.40625 \n",
+       "L 48.78125 2.09375 \n",
+       "Q 45.015625 0.34375 40.984375 -0.53125 \n",
+       "Q 36.96875 -1.421875 32.421875 -1.421875 \n",
+       "Q 20.0625 -1.421875 12.78125 6.34375 \n",
+       "Q 5.515625 14.109375 5.515625 27.296875 \n",
+       "Q 5.515625 40.671875 12.859375 48.328125 \n",
+       "Q 20.21875 56 33.015625 56 \n",
+       "Q 37.15625 56 41.109375 55.140625 \n",
+       "Q 45.0625 54.296875 48.78125 52.59375 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-99\"/>\n",
+       "     <path d=\"M 18.3125 70.21875 \n",
+       "L 18.3125 54.6875 \n",
+       "L 36.8125 54.6875 \n",
+       "L 36.8125 47.703125 \n",
+       "L 18.3125 47.703125 \n",
+       "L 18.3125 18.015625 \n",
+       "Q 18.3125 11.328125 20.140625 9.421875 \n",
+       "Q 21.96875 7.515625 27.59375 7.515625 \n",
+       "L 36.8125 7.515625 \n",
+       "L 36.8125 0 \n",
+       "L 27.59375 0 \n",
+       "Q 17.1875 0 13.234375 3.875 \n",
+       "Q 9.28125 7.765625 9.28125 18.015625 \n",
+       "L 9.28125 47.703125 \n",
+       "L 2.6875 47.703125 \n",
+       "L 2.6875 54.6875 \n",
+       "L 9.28125 54.6875 \n",
+       "L 9.28125 70.21875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-116\"/>\n",
+       "     <path d=\"M 32.171875 -5.078125 \n",
+       "Q 28.375 -14.84375 24.75 -17.8125 \n",
+       "Q 21.140625 -20.796875 15.09375 -20.796875 \n",
+       "L 7.90625 -20.796875 \n",
+       "L 7.90625 -13.28125 \n",
+       "L 13.1875 -13.28125 \n",
+       "Q 16.890625 -13.28125 18.9375 -11.515625 \n",
+       "Q 21 -9.765625 23.484375 -3.21875 \n",
+       "L 25.09375 0.875 \n",
+       "L 2.984375 54.6875 \n",
+       "L 12.5 54.6875 \n",
+       "L 29.59375 11.921875 \n",
+       "L 46.6875 54.6875 \n",
+       "L 56.203125 54.6875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-121\"/>\n",
+       "    </defs>\n",
+       "    <g style=\"fill:#262626;\" transform=\"translate(157.041562 16.318125)scale(0.12 -0.12)\">\n",
+       "     <use xlink:href=\"#DejaVuSans-76\"/>\n",
+       "     <use x=\"55.712891\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "     <use x=\"83.496094\" xlink:href=\"#DejaVuSans-110\"/>\n",
+       "     <use x=\"146.875\" xlink:href=\"#DejaVuSans-101\"/>\n",
+       "     <use x=\"208.398438\" xlink:href=\"#DejaVuSans-97\"/>\n",
+       "     <use x=\"269.677734\" xlink:href=\"#DejaVuSans-114\"/>\n",
+       "     <use x=\"310.791016\" xlink:href=\"#DejaVuSans-32\"/>\n",
+       "     <use x=\"342.578125\" xlink:href=\"#DejaVuSans-86\"/>\n",
+       "     <use x=\"410.876953\" xlink:href=\"#DejaVuSans-101\"/>\n",
+       "     <use x=\"472.400391\" xlink:href=\"#DejaVuSans-108\"/>\n",
+       "     <use x=\"500.183594\" xlink:href=\"#DejaVuSans-111\"/>\n",
+       "     <use x=\"561.365234\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     <use x=\"616.345703\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "     <use x=\"644.128906\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "     <use x=\"683.337891\" xlink:href=\"#DejaVuSans-121\"/>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"legend_1\">\n",
+       "    <g id=\"patch_7\">\n",
+       "     <path d=\"M 281.041562 63.41 \n",
+       "L 361.293438 63.41 \n",
+       "Q 363.493438 63.41 363.493438 61.21 \n",
+       "L 363.493438 30.018125 \n",
+       "Q 363.493438 27.818125 361.293438 27.818125 \n",
+       "L 281.041562 27.818125 \n",
+       "Q 278.841563 27.818125 278.841563 30.018125 \n",
+       "L 278.841563 61.21 \n",
+       "Q 278.841563 63.41 281.041562 63.41 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_18\">\n",
+       "     <path d=\"M 283.241562 36.726406 \n",
+       "L 305.241562 36.726406 \n",
+       "\" style=\"fill:none;stroke:#4878d0;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_19\"/>\n",
+       "    <g id=\"text_17\">\n",
+       "     <!-- numeric -->\n",
+       "     <defs>\n",
+       "      <path d=\"M 8.5 21.578125 \n",
+       "L 8.5 54.6875 \n",
+       "L 17.484375 54.6875 \n",
+       "L 17.484375 21.921875 \n",
+       "Q 17.484375 14.15625 20.5 10.265625 \n",
+       "Q 23.53125 6.390625 29.59375 6.390625 \n",
+       "Q 36.859375 6.390625 41.078125 11.03125 \n",
+       "Q 45.3125 15.671875 45.3125 23.6875 \n",
+       "L 45.3125 54.6875 \n",
+       "L 54.296875 54.6875 \n",
+       "L 54.296875 0 \n",
+       "L 45.3125 0 \n",
+       "L 45.3125 8.40625 \n",
+       "Q 42.046875 3.421875 37.71875 1 \n",
+       "Q 33.40625 -1.421875 27.6875 -1.421875 \n",
+       "Q 18.265625 -1.421875 13.375 4.4375 \n",
+       "Q 8.5 10.296875 8.5 21.578125 \n",
+       "z\n",
+       "M 31.109375 56 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-117\"/>\n",
+       "      <path d=\"M 52 44.1875 \n",
+       "Q 55.375 50.25 60.0625 53.125 \n",
+       "Q 64.75 56 71.09375 56 \n",
+       "Q 79.640625 56 84.28125 50.015625 \n",
+       "Q 88.921875 44.046875 88.921875 33.015625 \n",
+       "L 88.921875 0 \n",
+       "L 79.890625 0 \n",
+       "L 79.890625 32.71875 \n",
+       "Q 79.890625 40.578125 77.09375 44.375 \n",
+       "Q 74.3125 48.1875 68.609375 48.1875 \n",
+       "Q 61.625 48.1875 57.5625 43.546875 \n",
+       "Q 53.515625 38.921875 53.515625 30.90625 \n",
+       "L 53.515625 0 \n",
+       "L 44.484375 0 \n",
+       "L 44.484375 32.71875 \n",
+       "Q 44.484375 40.625 41.703125 44.40625 \n",
+       "Q 38.921875 48.1875 33.109375 48.1875 \n",
+       "Q 26.21875 48.1875 22.15625 43.53125 \n",
+       "Q 18.109375 38.875 18.109375 30.90625 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 21.1875 51.21875 25.484375 53.609375 \n",
+       "Q 29.78125 56 35.6875 56 \n",
+       "Q 41.65625 56 45.828125 52.96875 \n",
+       "Q 50 49.953125 52 44.1875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-109\"/>\n",
+       "     </defs>\n",
+       "     <g style=\"fill:#262626;\" transform=\"translate(314.041562 40.576406)scale(0.11 -0.11)\">\n",
+       "      <use xlink:href=\"#DejaVuSans-110\"/>\n",
+       "      <use x=\"63.378906\" xlink:href=\"#DejaVuSans-117\"/>\n",
+       "      <use x=\"126.757812\" xlink:href=\"#DejaVuSans-109\"/>\n",
+       "      <use x=\"224.169922\" xlink:href=\"#DejaVuSans-101\"/>\n",
+       "      <use x=\"285.693359\" xlink:href=\"#DejaVuSans-114\"/>\n",
+       "      <use x=\"326.806641\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "      <use x=\"354.589844\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_20\">\n",
+       "     <path d=\"M 283.241562 52.872344 \n",
+       "L 305.241562 52.872344 \n",
+       "\" style=\"fill:none;stroke:#ee854a;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_21\"/>\n",
+       "    <g id=\"text_18\">\n",
+       "     <!-- analytic -->\n",
+       "     <g style=\"fill:#262626;\" transform=\"translate(314.041562 56.722344)scale(0.11 -0.11)\">\n",
+       "      <use xlink:href=\"#DejaVuSans-97\"/>\n",
+       "      <use x=\"61.279297\" xlink:href=\"#DejaVuSans-110\"/>\n",
+       "      <use x=\"124.658203\" xlink:href=\"#DejaVuSans-97\"/>\n",
+       "      <use x=\"185.9375\" xlink:href=\"#DejaVuSans-108\"/>\n",
+       "      <use x=\"213.720703\" xlink:href=\"#DejaVuSans-121\"/>\n",
+       "      <use x=\"272.900391\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "      <use x=\"312.109375\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "      <use x=\"339.892578\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "  </g>\n",
+       " </g>\n",
+       " <defs>\n",
+       "  <clipPath id=\"p5f90ed8c70\">\n",
+       "   <rect height=\"217.44\" width=\"334.8\" x=\"34.193438\" y=\"22.318125\"/>\n",
+       "  </clipPath>\n",
+       " </defs>\n",
+       "</svg>\n"
+      ],
+      "text/plain": [
+       "<Figure size 432x288 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "plt.title(\"Linear Velocity\")\n",
+    "plt.plot(x, data[2], label=\"numeric\")\n",
+    "plt.plot(x, data[3], label=\"analytic\")\n",
+    "plt.legend()\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 41,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/svg+xml": [
+       "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n",
+       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
+       "  \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
+       "<!-- Created with matplotlib (https://matplotlib.org/) -->\n",
+       "<svg height=\"267.104062pt\" version=\"1.1\" viewBox=\"0 0 382.193437 267.104062\" width=\"382.193437pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+       " <defs>\n",
+       "  <style type=\"text/css\">\n",
+       "*{stroke-linecap:butt;stroke-linejoin:round;}\n",
+       "  </style>\n",
+       " </defs>\n",
+       " <g id=\"figure_1\">\n",
+       "  <g id=\"patch_1\">\n",
+       "   <path d=\"M 0 267.104062 \n",
+       "L 382.193437 267.104062 \n",
+       "L 382.193437 0 \n",
+       "L 0 0 \n",
+       "z\n",
+       "\" style=\"fill:#ffffff;\"/>\n",
+       "  </g>\n",
+       "  <g id=\"axes_1\">\n",
+       "   <g id=\"patch_2\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 368.993438 239.758125 \n",
+       "L 368.993438 22.318125 \n",
+       "L 34.193438 22.318125 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_1\">\n",
+       "    <g id=\"xtick_1\">\n",
+       "     <g id=\"line2d_1\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 49.411619 239.758125 \n",
+       "L 49.411619 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_1\">\n",
+       "      <!-- 0 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 31.78125 66.40625 \n",
+       "Q 24.171875 66.40625 20.328125 58.90625 \n",
+       "Q 16.5 51.421875 16.5 36.375 \n",
+       "Q 16.5 21.390625 20.328125 13.890625 \n",
+       "Q 24.171875 6.390625 31.78125 6.390625 \n",
+       "Q 39.453125 6.390625 43.28125 13.890625 \n",
+       "Q 47.125 21.390625 47.125 36.375 \n",
+       "Q 47.125 51.421875 43.28125 58.90625 \n",
+       "Q 39.453125 66.40625 31.78125 66.40625 \n",
+       "z\n",
+       "M 31.78125 74.21875 \n",
+       "Q 44.046875 74.21875 50.515625 64.515625 \n",
+       "Q 56.984375 54.828125 56.984375 36.375 \n",
+       "Q 56.984375 17.96875 50.515625 8.265625 \n",
+       "Q 44.046875 -1.421875 31.78125 -1.421875 \n",
+       "Q 19.53125 -1.421875 13.0625 8.265625 \n",
+       "Q 6.59375 17.96875 6.59375 36.375 \n",
+       "Q 6.59375 54.828125 13.0625 64.515625 \n",
+       "Q 19.53125 74.21875 31.78125 74.21875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-48\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(45.912244 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_2\">\n",
+       "     <g id=\"line2d_2\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 100.172733 239.758125 \n",
+       "L 100.172733 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_2\">\n",
+       "      <!-- 500 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 10.796875 72.90625 \n",
+       "L 49.515625 72.90625 \n",
+       "L 49.515625 64.59375 \n",
+       "L 19.828125 64.59375 \n",
+       "L 19.828125 46.734375 \n",
+       "Q 21.96875 47.46875 24.109375 47.828125 \n",
+       "Q 26.265625 48.1875 28.421875 48.1875 \n",
+       "Q 40.625 48.1875 47.75 41.5 \n",
+       "Q 54.890625 34.8125 54.890625 23.390625 \n",
+       "Q 54.890625 11.625 47.5625 5.09375 \n",
+       "Q 40.234375 -1.421875 26.90625 -1.421875 \n",
+       "Q 22.3125 -1.421875 17.546875 -0.640625 \n",
+       "Q 12.796875 0.140625 7.71875 1.703125 \n",
+       "L 7.71875 11.625 \n",
+       "Q 12.109375 9.234375 16.796875 8.0625 \n",
+       "Q 21.484375 6.890625 26.703125 6.890625 \n",
+       "Q 35.15625 6.890625 40.078125 11.328125 \n",
+       "Q 45.015625 15.765625 45.015625 23.390625 \n",
+       "Q 45.015625 31 40.078125 35.4375 \n",
+       "Q 35.15625 39.890625 26.703125 39.890625 \n",
+       "Q 22.75 39.890625 18.8125 39.015625 \n",
+       "Q 14.890625 38.140625 10.796875 36.28125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-53\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(89.674608 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_3\">\n",
+       "     <g id=\"line2d_3\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 150.933846 239.758125 \n",
+       "L 150.933846 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_3\">\n",
+       "      <!-- 1000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 12.40625 8.296875 \n",
+       "L 28.515625 8.296875 \n",
+       "L 28.515625 63.921875 \n",
+       "L 10.984375 60.40625 \n",
+       "L 10.984375 69.390625 \n",
+       "L 28.421875 72.90625 \n",
+       "L 38.28125 72.90625 \n",
+       "L 38.28125 8.296875 \n",
+       "L 54.390625 8.296875 \n",
+       "L 54.390625 0 \n",
+       "L 12.40625 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-49\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(136.936346 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_4\">\n",
+       "     <g id=\"line2d_4\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 201.69496 239.758125 \n",
+       "L 201.69496 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_4\">\n",
+       "      <!-- 1500 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(187.69746 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_5\">\n",
+       "     <g id=\"line2d_5\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 252.456073 239.758125 \n",
+       "L 252.456073 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_5\">\n",
+       "      <!-- 2000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 19.1875 8.296875 \n",
+       "L 53.609375 8.296875 \n",
+       "L 53.609375 0 \n",
+       "L 7.328125 0 \n",
+       "L 7.328125 8.296875 \n",
+       "Q 12.9375 14.109375 22.625 23.890625 \n",
+       "Q 32.328125 33.6875 34.8125 36.53125 \n",
+       "Q 39.546875 41.84375 41.421875 45.53125 \n",
+       "Q 43.3125 49.21875 43.3125 52.78125 \n",
+       "Q 43.3125 58.59375 39.234375 62.25 \n",
+       "Q 35.15625 65.921875 28.609375 65.921875 \n",
+       "Q 23.96875 65.921875 18.8125 64.3125 \n",
+       "Q 13.671875 62.703125 7.8125 59.421875 \n",
+       "L 7.8125 69.390625 \n",
+       "Q 13.765625 71.78125 18.9375 73 \n",
+       "Q 24.125 74.21875 28.421875 74.21875 \n",
+       "Q 39.75 74.21875 46.484375 68.546875 \n",
+       "Q 53.21875 62.890625 53.21875 53.421875 \n",
+       "Q 53.21875 48.921875 51.53125 44.890625 \n",
+       "Q 49.859375 40.875 45.40625 35.40625 \n",
+       "Q 44.1875 33.984375 37.640625 27.21875 \n",
+       "Q 31.109375 20.453125 19.1875 8.296875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-50\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(238.458573 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_6\">\n",
+       "     <g id=\"line2d_6\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 303.217187 239.758125 \n",
+       "L 303.217187 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_6\">\n",
+       "      <!-- 2500 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(289.219687 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_7\">\n",
+       "     <g id=\"line2d_7\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 353.9783 239.758125 \n",
+       "L 353.9783 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_7\">\n",
+       "      <!-- 3000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 40.578125 39.3125 \n",
+       "Q 47.65625 37.796875 51.625 33 \n",
+       "Q 55.609375 28.21875 55.609375 21.1875 \n",
+       "Q 55.609375 10.40625 48.1875 4.484375 \n",
+       "Q 40.765625 -1.421875 27.09375 -1.421875 \n",
+       "Q 22.515625 -1.421875 17.65625 -0.515625 \n",
+       "Q 12.796875 0.390625 7.625 2.203125 \n",
+       "L 7.625 11.71875 \n",
+       "Q 11.71875 9.328125 16.59375 8.109375 \n",
+       "Q 21.484375 6.890625 26.8125 6.890625 \n",
+       "Q 36.078125 6.890625 40.9375 10.546875 \n",
+       "Q 45.796875 14.203125 45.796875 21.1875 \n",
+       "Q 45.796875 27.640625 41.28125 31.265625 \n",
+       "Q 36.765625 34.90625 28.71875 34.90625 \n",
+       "L 20.21875 34.90625 \n",
+       "L 20.21875 43.015625 \n",
+       "L 29.109375 43.015625 \n",
+       "Q 36.375 43.015625 40.234375 45.921875 \n",
+       "Q 44.09375 48.828125 44.09375 54.296875 \n",
+       "Q 44.09375 59.90625 40.109375 62.90625 \n",
+       "Q 36.140625 65.921875 28.71875 65.921875 \n",
+       "Q 24.65625 65.921875 20.015625 65.03125 \n",
+       "Q 15.375 64.15625 9.8125 62.3125 \n",
+       "L 9.8125 71.09375 \n",
+       "Q 15.4375 72.65625 20.34375 73.4375 \n",
+       "Q 25.25 74.21875 29.59375 74.21875 \n",
+       "Q 40.828125 74.21875 47.359375 69.109375 \n",
+       "Q 53.90625 64.015625 53.90625 55.328125 \n",
+       "Q 53.90625 49.265625 50.4375 45.09375 \n",
+       "Q 46.96875 40.921875 40.578125 39.3125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-51\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(339.9808 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-51\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_2\">\n",
+       "    <g id=\"ytick_1\">\n",
+       "     <g id=\"line2d_8\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 34.193438 230.03288 \n",
+       "L 368.993438 230.03288 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_8\">\n",
+       "      <!-- 0.0 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 10.6875 12.40625 \n",
+       "L 21 12.40625 \n",
+       "L 21 0 \n",
+       "L 10.6875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-46\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 234.21202)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_2\">\n",
+       "     <g id=\"line2d_9\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 34.193438 203.660496 \n",
+       "L 368.993438 203.660496 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_9\">\n",
+       "      <!-- 0.2 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 207.839637)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-50\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_3\">\n",
+       "     <g id=\"line2d_10\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 34.193438 177.288112 \n",
+       "L 368.993438 177.288112 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_10\">\n",
+       "      <!-- 0.4 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 37.796875 64.3125 \n",
+       "L 12.890625 25.390625 \n",
+       "L 37.796875 25.390625 \n",
+       "z\n",
+       "M 35.203125 72.90625 \n",
+       "L 47.609375 72.90625 \n",
+       "L 47.609375 25.390625 \n",
+       "L 58.015625 25.390625 \n",
+       "L 58.015625 17.1875 \n",
+       "L 47.609375 17.1875 \n",
+       "L 47.609375 0 \n",
+       "L 37.796875 0 \n",
+       "L 37.796875 17.1875 \n",
+       "L 4.890625 17.1875 \n",
+       "L 4.890625 26.703125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-52\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 181.467253)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-52\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_4\">\n",
+       "     <g id=\"line2d_11\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 34.193438 150.915728 \n",
+       "L 368.993438 150.915728 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_11\">\n",
+       "      <!-- 0.6 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 33.015625 40.375 \n",
+       "Q 26.375 40.375 22.484375 35.828125 \n",
+       "Q 18.609375 31.296875 18.609375 23.390625 \n",
+       "Q 18.609375 15.53125 22.484375 10.953125 \n",
+       "Q 26.375 6.390625 33.015625 6.390625 \n",
+       "Q 39.65625 6.390625 43.53125 10.953125 \n",
+       "Q 47.40625 15.53125 47.40625 23.390625 \n",
+       "Q 47.40625 31.296875 43.53125 35.828125 \n",
+       "Q 39.65625 40.375 33.015625 40.375 \n",
+       "z\n",
+       "M 52.59375 71.296875 \n",
+       "L 52.59375 62.3125 \n",
+       "Q 48.875 64.0625 45.09375 64.984375 \n",
+       "Q 41.3125 65.921875 37.59375 65.921875 \n",
+       "Q 27.828125 65.921875 22.671875 59.328125 \n",
+       "Q 17.53125 52.734375 16.796875 39.40625 \n",
+       "Q 19.671875 43.65625 24.015625 45.921875 \n",
+       "Q 28.375 48.1875 33.59375 48.1875 \n",
+       "Q 44.578125 48.1875 50.953125 41.515625 \n",
+       "Q 57.328125 34.859375 57.328125 23.390625 \n",
+       "Q 57.328125 12.15625 50.6875 5.359375 \n",
+       "Q 44.046875 -1.421875 33.015625 -1.421875 \n",
+       "Q 20.359375 -1.421875 13.671875 8.265625 \n",
+       "Q 6.984375 17.96875 6.984375 36.375 \n",
+       "Q 6.984375 53.65625 15.1875 63.9375 \n",
+       "Q 23.390625 74.21875 37.203125 74.21875 \n",
+       "Q 40.921875 74.21875 44.703125 73.484375 \n",
+       "Q 48.484375 72.75 52.59375 71.296875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-54\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 155.094869)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-54\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_5\">\n",
+       "     <g id=\"line2d_12\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 34.193438 124.543345 \n",
+       "L 368.993438 124.543345 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_12\">\n",
+       "      <!-- 0.8 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 31.78125 34.625 \n",
+       "Q 24.75 34.625 20.71875 30.859375 \n",
+       "Q 16.703125 27.09375 16.703125 20.515625 \n",
+       "Q 16.703125 13.921875 20.71875 10.15625 \n",
+       "Q 24.75 6.390625 31.78125 6.390625 \n",
+       "Q 38.8125 6.390625 42.859375 10.171875 \n",
+       "Q 46.921875 13.96875 46.921875 20.515625 \n",
+       "Q 46.921875 27.09375 42.890625 30.859375 \n",
+       "Q 38.875 34.625 31.78125 34.625 \n",
+       "z\n",
+       "M 21.921875 38.8125 \n",
+       "Q 15.578125 40.375 12.03125 44.71875 \n",
+       "Q 8.5 49.078125 8.5 55.328125 \n",
+       "Q 8.5 64.0625 14.71875 69.140625 \n",
+       "Q 20.953125 74.21875 31.78125 74.21875 \n",
+       "Q 42.671875 74.21875 48.875 69.140625 \n",
+       "Q 55.078125 64.0625 55.078125 55.328125 \n",
+       "Q 55.078125 49.078125 51.53125 44.71875 \n",
+       "Q 48 40.375 41.703125 38.8125 \n",
+       "Q 48.828125 37.15625 52.796875 32.3125 \n",
+       "Q 56.78125 27.484375 56.78125 20.515625 \n",
+       "Q 56.78125 9.90625 50.3125 4.234375 \n",
+       "Q 43.84375 -1.421875 31.78125 -1.421875 \n",
+       "Q 19.734375 -1.421875 13.25 4.234375 \n",
+       "Q 6.78125 9.90625 6.78125 20.515625 \n",
+       "Q 6.78125 27.484375 10.78125 32.3125 \n",
+       "Q 14.796875 37.15625 21.921875 38.8125 \n",
+       "z\n",
+       "M 18.3125 54.390625 \n",
+       "Q 18.3125 48.734375 21.84375 45.5625 \n",
+       "Q 25.390625 42.390625 31.78125 42.390625 \n",
+       "Q 38.140625 42.390625 41.71875 45.5625 \n",
+       "Q 45.3125 48.734375 45.3125 54.390625 \n",
+       "Q 45.3125 60.0625 41.71875 63.234375 \n",
+       "Q 38.140625 66.40625 31.78125 66.40625 \n",
+       "Q 25.390625 66.40625 21.84375 63.234375 \n",
+       "Q 18.3125 60.0625 18.3125 54.390625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-56\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 128.722485)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-56\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_6\">\n",
+       "     <g id=\"line2d_13\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 34.193438 98.170961 \n",
+       "L 368.993438 98.170961 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_13\">\n",
+       "      <!-- 1.0 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 102.350101)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_7\">\n",
+       "     <g id=\"line2d_14\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 34.193438 71.798577 \n",
+       "L 368.993438 71.798577 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_14\">\n",
+       "      <!-- 1.2 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 75.977718)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-50\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_8\">\n",
+       "     <g id=\"line2d_15\">\n",
+       "      <path clip-path=\"url(#peb70614cab)\" d=\"M 34.193438 45.426193 \n",
+       "L 368.993438 45.426193 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_15\">\n",
+       "      <!-- 1.4 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 49.605334)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-52\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"line2d_16\">\n",
+       "    <path clip-path=\"url(#peb70614cab)\" d=\"M 49.411619 229.539496 \n",
+       "L 59.665364 180.248676 \n",
+       "L 65.147564 154.991976 \n",
+       "L 69.716065 134.964128 \n",
+       "L 73.675432 118.565516 \n",
+       "L 77.330232 104.360296 \n",
+       "L 80.680465 92.225307 \n",
+       "L 83.726132 82.000734 \n",
+       "L 86.568754 73.204225 \n",
+       "L 89.309855 65.446788 \n",
+       "L 91.746388 59.174117 \n",
+       "L 94.182921 53.515922 \n",
+       "L 96.314888 49.081406 \n",
+       "L 98.446855 45.144009 \n",
+       "L 100.375777 42.017563 \n",
+       "L 102.203177 39.444937 \n",
+       "L 103.929055 37.368111 \n",
+       "L 105.654933 35.638083 \n",
+       "L 107.279289 34.327375 \n",
+       "L 108.802122 33.383244 \n",
+       "L 110.223433 32.748988 \n",
+       "L 111.644744 32.35604 \n",
+       "L 113.066056 32.20308 \n",
+       "L 114.487367 32.292746 \n",
+       "L 115.908678 32.622401 \n",
+       "L 117.329989 33.193363 \n",
+       "L 118.7513 34.002995 \n",
+       "L 120.274134 35.137008 \n",
+       "L 121.898489 36.646827 \n",
+       "L 123.522845 38.463884 \n",
+       "L 125.248723 40.729272 \n",
+       "L 127.076123 43.498372 \n",
+       "L 129.005045 46.829204 \n",
+       "L 131.137012 50.989448 \n",
+       "L 133.268979 55.638899 \n",
+       "L 135.60399 61.28127 \n",
+       "L 137.939001 67.477462 \n",
+       "L 140.578579 75.125453 \n",
+       "L 143.319679 83.755816 \n",
+       "L 146.263824 93.762817 \n",
+       "L 149.512535 105.630126 \n",
+       "L 152.964291 119.104832 \n",
+       "L 156.720613 134.670736 \n",
+       "L 160.984547 153.315879 \n",
+       "L 165.959136 176.116651 \n",
+       "L 172.253514 206.074624 \n",
+       "L 177.228103 229.874326 \n",
+       "L 187.58337 180.095321 \n",
+       "L 193.06557 154.845345 \n",
+       "L 197.634071 134.825146 \n",
+       "L 201.593437 118.434578 \n",
+       "L 205.248238 104.237796 \n",
+       "L 208.598471 92.111906 \n",
+       "L 211.644138 81.896563 \n",
+       "L 214.48676 73.107966 \n",
+       "L 217.22786 65.358441 \n",
+       "L 219.765916 58.847099 \n",
+       "L 222.100927 53.443398 \n",
+       "L 224.334416 48.819 \n",
+       "L 226.364861 45.087308 \n",
+       "L 228.395305 41.815814 \n",
+       "L 230.222705 39.270879 \n",
+       "L 232.050106 37.110981 \n",
+       "L 233.775983 35.428423 \n",
+       "L 235.298817 34.233754 \n",
+       "L 236.82165 33.313357 \n",
+       "L 238.141439 32.737121 \n",
+       "L 239.56275 32.349447 \n",
+       "L 240.984062 32.20308 \n",
+       "L 242.303851 32.282197 \n",
+       "L 243.725162 32.601303 \n",
+       "L 245.146473 33.160398 \n",
+       "L 246.567784 33.958162 \n",
+       "L 248.19214 35.164699 \n",
+       "L 249.816495 36.679792 \n",
+       "L 251.542373 38.627393 \n",
+       "L 253.369773 41.06552 \n",
+       "L 255.197173 43.883409 \n",
+       "L 257.126096 47.264348 \n",
+       "L 259.258062 51.478655 \n",
+       "L 261.491551 56.419521 \n",
+       "L 263.72504 61.881242 \n",
+       "L 266.263096 68.702459 \n",
+       "L 268.902674 76.459896 \n",
+       "L 271.846819 85.873518 \n",
+       "L 274.892485 96.404011 \n",
+       "L 278.141197 108.459355 \n",
+       "L 281.694475 122.527835 \n",
+       "L 285.552319 138.72549 \n",
+       "L 289.917775 158.03469 \n",
+       "L 295.095409 181.990176 \n",
+       "L 302.100442 215.56143 \n",
+       "L 305.044587 229.856602 \n",
+       "L 305.146109 229.715774 \n",
+       "L 315.501376 179.941833 \n",
+       "L 320.983576 154.698715 \n",
+       "L 325.552077 134.686163 \n",
+       "L 329.511443 118.30377 \n",
+       "L 333.166244 104.11556 \n",
+       "L 336.516477 91.998504 \n",
+       "L 339.562144 81.791073 \n",
+       "L 342.506288 72.71238 \n",
+       "L 345.145866 65.270093 \n",
+       "L 347.785444 58.5214 \n",
+       "L 350.120456 53.150664 \n",
+       "L 352.353945 48.557914 \n",
+       "L 353.775256 45.919357 \n",
+       "L 353.775256 45.919357 \n",
+       "\" style=\"fill:none;stroke:#4878d0;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"line2d_17\">\n",
+       "    <path clip-path=\"url(#peb70614cab)\" d=\"M 49.411619 229.539499 \n",
+       "L 59.665364 180.24894 \n",
+       "L 65.147564 154.992372 \n",
+       "L 69.716065 134.964524 \n",
+       "L 73.675432 118.566044 \n",
+       "L 77.330232 104.360823 \n",
+       "L 80.680465 92.225307 \n",
+       "L 83.726132 82.002052 \n",
+       "L 86.568754 73.205544 \n",
+       "L 89.208332 65.721061 \n",
+       "L 91.746388 59.175436 \n",
+       "L 94.182921 53.515922 \n",
+       "L 96.517933 48.68582 \n",
+       "L 98.649899 44.795893 \n",
+       "L 100.578822 41.712962 \n",
+       "L 102.507744 39.054625 \n",
+       "L 104.335144 36.93033 \n",
+       "L 105.9595 35.369085 \n",
+       "L 107.583855 34.117715 \n",
+       "L 109.005167 33.277755 \n",
+       "L 110.528 32.644817 \n",
+       "L 111.949311 32.303295 \n",
+       "L 113.370622 32.20308 \n",
+       "L 114.791933 32.344172 \n",
+       "L 116.111722 32.68965 \n",
+       "L 117.533034 33.294897 \n",
+       "L 119.055867 34.2087 \n",
+       "L 120.5787 35.396776 \n",
+       "L 122.203056 36.964614 \n",
+       "L 123.827412 38.83969 \n",
+       "L 125.654812 41.31342 \n",
+       "L 127.482212 44.165593 \n",
+       "L 129.512656 47.774654 \n",
+       "L 131.644623 52.052255 \n",
+       "L 133.878112 57.057733 \n",
+       "L 136.213123 62.845153 \n",
+       "L 138.751179 69.759992 \n",
+       "L 141.492279 77.9262 \n",
+       "L 144.436424 87.465092 \n",
+       "L 147.583613 98.484001 \n",
+       "L 150.933846 111.076023 \n",
+       "L 154.487124 125.309067 \n",
+       "L 158.446491 142.103133 \n",
+       "L 162.913469 162.0396 \n",
+       "L 168.294147 187.117627 \n",
+       "L 175.908314 223.778339 \n",
+       "L 177.228103 229.874489 \n",
+       "L 187.58337 180.095584 \n",
+       "L 193.06557 154.845873 \n",
+       "L 197.634071 134.825673 \n",
+       "L 201.593437 118.435237 \n",
+       "L 205.248238 104.238455 \n",
+       "L 208.598471 92.111906 \n",
+       "L 211.644138 81.896563 \n",
+       "L 214.588283 72.808639 \n",
+       "L 217.329383 65.085487 \n",
+       "L 219.867438 58.600518 \n",
+       "L 222.303972 53.00166 \n",
+       "L 224.638983 48.230896 \n",
+       "L 226.77095 44.396352 \n",
+       "L 228.699872 41.366165 \n",
+       "L 230.527272 38.885842 \n",
+       "L 232.25315 36.896046 \n",
+       "L 233.877506 35.341394 \n",
+       "L 235.400339 34.163867 \n",
+       "L 236.923172 33.261931 \n",
+       "L 238.344484 32.668553 \n",
+       "L 239.664273 32.332305 \n",
+       "L 241.085584 32.20308 \n",
+       "L 242.506895 32.313844 \n",
+       "L 243.826684 32.63295 \n",
+       "L 245.349517 33.260613 \n",
+       "L 246.770828 34.092661 \n",
+       "L 248.293662 35.250409 \n",
+       "L 249.918018 36.785282 \n",
+       "L 251.643895 38.75398 \n",
+       "L 253.369773 41.06552 \n",
+       "L 255.298696 44.052192 \n",
+       "L 257.227618 47.45423 \n",
+       "L 259.359585 51.692272 \n",
+       "L 261.694596 56.894224 \n",
+       "L 264.131129 62.929544 \n",
+       "L 266.669185 69.852295 \n",
+       "L 269.308763 77.712584 \n",
+       "L 272.151385 86.891492 \n",
+       "L 275.298574 97.86636 \n",
+       "L 278.648808 110.415131 \n",
+       "L 282.202086 124.607298 \n",
+       "L 286.161453 141.361805 \n",
+       "L 290.628431 161.262274 \n",
+       "L 296.009109 186.309314 \n",
+       "L 303.521753 222.457399 \n",
+       "L 305.044587 229.85628 \n",
+       "L 305.146109 229.716099 \n",
+       "L 315.501376 179.942361 \n",
+       "L 320.983576 154.699374 \n",
+       "L 325.552077 134.686823 \n",
+       "L 329.511443 118.30443 \n",
+       "L 333.166244 104.116219 \n",
+       "L 336.516477 91.998504 \n",
+       "L 339.663666 81.465374 \n",
+       "L 342.506288 72.71238 \n",
+       "L 345.247389 64.998458 \n",
+       "L 347.785444 58.522719 \n",
+       "L 350.018933 53.373511 \n",
+       "L 352.252422 48.755707 \n",
+       "L 353.775256 45.920675 \n",
+       "L 353.775256 45.920675 \n",
+       "\" style=\"fill:none;stroke:#ee854a;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_3\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 34.193438 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_4\">\n",
+       "    <path d=\"M 368.993438 239.758125 \n",
+       "L 368.993438 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_5\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 368.993437 239.758125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_6\">\n",
+       "    <path d=\"M 34.193438 22.318125 \n",
+       "L 368.993437 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"text_16\">\n",
+       "    <!-- Rotation -->\n",
+       "    <defs>\n",
+       "     <path d=\"M 44.390625 34.1875 \n",
+       "Q 47.5625 33.109375 50.5625 29.59375 \n",
+       "Q 53.5625 26.078125 56.59375 19.921875 \n",
+       "L 66.609375 0 \n",
+       "L 56 0 \n",
+       "L 46.6875 18.703125 \n",
+       "Q 43.0625 26.03125 39.671875 28.421875 \n",
+       "Q 36.28125 30.8125 30.421875 30.8125 \n",
+       "L 19.671875 30.8125 \n",
+       "L 19.671875 0 \n",
+       "L 9.8125 0 \n",
+       "L 9.8125 72.90625 \n",
+       "L 32.078125 72.90625 \n",
+       "Q 44.578125 72.90625 50.734375 67.671875 \n",
+       "Q 56.890625 62.453125 56.890625 51.90625 \n",
+       "Q 56.890625 45.015625 53.6875 40.46875 \n",
+       "Q 50.484375 35.9375 44.390625 34.1875 \n",
+       "z\n",
+       "M 19.671875 64.796875 \n",
+       "L 19.671875 38.921875 \n",
+       "L 32.078125 38.921875 \n",
+       "Q 39.203125 38.921875 42.84375 42.21875 \n",
+       "Q 46.484375 45.515625 46.484375 51.90625 \n",
+       "Q 46.484375 58.296875 42.84375 61.546875 \n",
+       "Q 39.203125 64.796875 32.078125 64.796875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-82\"/>\n",
+       "     <path d=\"M 30.609375 48.390625 \n",
+       "Q 23.390625 48.390625 19.1875 42.75 \n",
+       "Q 14.984375 37.109375 14.984375 27.296875 \n",
+       "Q 14.984375 17.484375 19.15625 11.84375 \n",
+       "Q 23.34375 6.203125 30.609375 6.203125 \n",
+       "Q 37.796875 6.203125 41.984375 11.859375 \n",
+       "Q 46.1875 17.53125 46.1875 27.296875 \n",
+       "Q 46.1875 37.015625 41.984375 42.703125 \n",
+       "Q 37.796875 48.390625 30.609375 48.390625 \n",
+       "z\n",
+       "M 30.609375 56 \n",
+       "Q 42.328125 56 49.015625 48.375 \n",
+       "Q 55.71875 40.765625 55.71875 27.296875 \n",
+       "Q 55.71875 13.875 49.015625 6.21875 \n",
+       "Q 42.328125 -1.421875 30.609375 -1.421875 \n",
+       "Q 18.84375 -1.421875 12.171875 6.21875 \n",
+       "Q 5.515625 13.875 5.515625 27.296875 \n",
+       "Q 5.515625 40.765625 12.171875 48.375 \n",
+       "Q 18.84375 56 30.609375 56 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-111\"/>\n",
+       "     <path d=\"M 18.3125 70.21875 \n",
+       "L 18.3125 54.6875 \n",
+       "L 36.8125 54.6875 \n",
+       "L 36.8125 47.703125 \n",
+       "L 18.3125 47.703125 \n",
+       "L 18.3125 18.015625 \n",
+       "Q 18.3125 11.328125 20.140625 9.421875 \n",
+       "Q 21.96875 7.515625 27.59375 7.515625 \n",
+       "L 36.8125 7.515625 \n",
+       "L 36.8125 0 \n",
+       "L 27.59375 0 \n",
+       "Q 17.1875 0 13.234375 3.875 \n",
+       "Q 9.28125 7.765625 9.28125 18.015625 \n",
+       "L 9.28125 47.703125 \n",
+       "L 2.6875 47.703125 \n",
+       "L 2.6875 54.6875 \n",
+       "L 9.28125 54.6875 \n",
+       "L 9.28125 70.21875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-116\"/>\n",
+       "     <path d=\"M 34.28125 27.484375 \n",
+       "Q 23.390625 27.484375 19.1875 25 \n",
+       "Q 14.984375 22.515625 14.984375 16.5 \n",
+       "Q 14.984375 11.71875 18.140625 8.90625 \n",
+       "Q 21.296875 6.109375 26.703125 6.109375 \n",
+       "Q 34.1875 6.109375 38.703125 11.40625 \n",
+       "Q 43.21875 16.703125 43.21875 25.484375 \n",
+       "L 43.21875 27.484375 \n",
+       "z\n",
+       "M 52.203125 31.203125 \n",
+       "L 52.203125 0 \n",
+       "L 43.21875 0 \n",
+       "L 43.21875 8.296875 \n",
+       "Q 40.140625 3.328125 35.546875 0.953125 \n",
+       "Q 30.953125 -1.421875 24.3125 -1.421875 \n",
+       "Q 15.921875 -1.421875 10.953125 3.296875 \n",
+       "Q 6 8.015625 6 15.921875 \n",
+       "Q 6 25.140625 12.171875 29.828125 \n",
+       "Q 18.359375 34.515625 30.609375 34.515625 \n",
+       "L 43.21875 34.515625 \n",
+       "L 43.21875 35.40625 \n",
+       "Q 43.21875 41.609375 39.140625 45 \n",
+       "Q 35.0625 48.390625 27.6875 48.390625 \n",
+       "Q 23 48.390625 18.546875 47.265625 \n",
+       "Q 14.109375 46.140625 10.015625 43.890625 \n",
+       "L 10.015625 52.203125 \n",
+       "Q 14.9375 54.109375 19.578125 55.046875 \n",
+       "Q 24.21875 56 28.609375 56 \n",
+       "Q 40.484375 56 46.34375 49.84375 \n",
+       "Q 52.203125 43.703125 52.203125 31.203125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-97\"/>\n",
+       "     <path d=\"M 9.421875 54.6875 \n",
+       "L 18.40625 54.6875 \n",
+       "L 18.40625 0 \n",
+       "L 9.421875 0 \n",
+       "z\n",
+       "M 9.421875 75.984375 \n",
+       "L 18.40625 75.984375 \n",
+       "L 18.40625 64.59375 \n",
+       "L 9.421875 64.59375 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-105\"/>\n",
+       "     <path d=\"M 54.890625 33.015625 \n",
+       "L 54.890625 0 \n",
+       "L 45.90625 0 \n",
+       "L 45.90625 32.71875 \n",
+       "Q 45.90625 40.484375 42.875 44.328125 \n",
+       "Q 39.84375 48.1875 33.796875 48.1875 \n",
+       "Q 26.515625 48.1875 22.3125 43.546875 \n",
+       "Q 18.109375 38.921875 18.109375 30.90625 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 21.34375 51.125 25.703125 53.5625 \n",
+       "Q 30.078125 56 35.796875 56 \n",
+       "Q 45.21875 56 50.046875 50.171875 \n",
+       "Q 54.890625 44.34375 54.890625 33.015625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-110\"/>\n",
+       "    </defs>\n",
+       "    <g style=\"fill:#262626;\" transform=\"translate(176.235 16.318125)scale(0.12 -0.12)\">\n",
+       "     <use xlink:href=\"#DejaVuSans-82\"/>\n",
+       "     <use x=\"69.419922\" xlink:href=\"#DejaVuSans-111\"/>\n",
+       "     <use x=\"130.601562\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "     <use x=\"169.810547\" xlink:href=\"#DejaVuSans-97\"/>\n",
+       "     <use x=\"231.089844\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "     <use x=\"270.298828\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "     <use x=\"298.082031\" xlink:href=\"#DejaVuSans-111\"/>\n",
+       "     <use x=\"359.263672\" xlink:href=\"#DejaVuSans-110\"/>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"legend_1\">\n",
+       "    <g id=\"patch_7\">\n",
+       "     <path d=\"M 41.893438 234.258125 \n",
+       "L 122.145313 234.258125 \n",
+       "Q 124.345313 234.258125 124.345313 232.058125 \n",
+       "L 124.345313 200.86625 \n",
+       "Q 124.345313 198.66625 122.145313 198.66625 \n",
+       "L 41.893438 198.66625 \n",
+       "Q 39.693438 198.66625 39.693438 200.86625 \n",
+       "L 39.693438 232.058125 \n",
+       "Q 39.693438 234.258125 41.893438 234.258125 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_18\">\n",
+       "     <path d=\"M 44.093438 207.574531 \n",
+       "L 66.093437 207.574531 \n",
+       "\" style=\"fill:none;stroke:#4878d0;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_19\"/>\n",
+       "    <g id=\"text_17\">\n",
+       "     <!-- numeric -->\n",
+       "     <defs>\n",
+       "      <path d=\"M 8.5 21.578125 \n",
+       "L 8.5 54.6875 \n",
+       "L 17.484375 54.6875 \n",
+       "L 17.484375 21.921875 \n",
+       "Q 17.484375 14.15625 20.5 10.265625 \n",
+       "Q 23.53125 6.390625 29.59375 6.390625 \n",
+       "Q 36.859375 6.390625 41.078125 11.03125 \n",
+       "Q 45.3125 15.671875 45.3125 23.6875 \n",
+       "L 45.3125 54.6875 \n",
+       "L 54.296875 54.6875 \n",
+       "L 54.296875 0 \n",
+       "L 45.3125 0 \n",
+       "L 45.3125 8.40625 \n",
+       "Q 42.046875 3.421875 37.71875 1 \n",
+       "Q 33.40625 -1.421875 27.6875 -1.421875 \n",
+       "Q 18.265625 -1.421875 13.375 4.4375 \n",
+       "Q 8.5 10.296875 8.5 21.578125 \n",
+       "z\n",
+       "M 31.109375 56 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-117\"/>\n",
+       "      <path d=\"M 52 44.1875 \n",
+       "Q 55.375 50.25 60.0625 53.125 \n",
+       "Q 64.75 56 71.09375 56 \n",
+       "Q 79.640625 56 84.28125 50.015625 \n",
+       "Q 88.921875 44.046875 88.921875 33.015625 \n",
+       "L 88.921875 0 \n",
+       "L 79.890625 0 \n",
+       "L 79.890625 32.71875 \n",
+       "Q 79.890625 40.578125 77.09375 44.375 \n",
+       "Q 74.3125 48.1875 68.609375 48.1875 \n",
+       "Q 61.625 48.1875 57.5625 43.546875 \n",
+       "Q 53.515625 38.921875 53.515625 30.90625 \n",
+       "L 53.515625 0 \n",
+       "L 44.484375 0 \n",
+       "L 44.484375 32.71875 \n",
+       "Q 44.484375 40.625 41.703125 44.40625 \n",
+       "Q 38.921875 48.1875 33.109375 48.1875 \n",
+       "Q 26.21875 48.1875 22.15625 43.53125 \n",
+       "Q 18.109375 38.875 18.109375 30.90625 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 21.1875 51.21875 25.484375 53.609375 \n",
+       "Q 29.78125 56 35.6875 56 \n",
+       "Q 41.65625 56 45.828125 52.96875 \n",
+       "Q 50 49.953125 52 44.1875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-109\"/>\n",
+       "      <path d=\"M 56.203125 29.59375 \n",
+       "L 56.203125 25.203125 \n",
+       "L 14.890625 25.203125 \n",
+       "Q 15.484375 15.921875 20.484375 11.0625 \n",
+       "Q 25.484375 6.203125 34.421875 6.203125 \n",
+       "Q 39.59375 6.203125 44.453125 7.46875 \n",
+       "Q 49.3125 8.734375 54.109375 11.28125 \n",
+       "L 54.109375 2.78125 \n",
+       "Q 49.265625 0.734375 44.1875 -0.34375 \n",
+       "Q 39.109375 -1.421875 33.890625 -1.421875 \n",
+       "Q 20.796875 -1.421875 13.15625 6.1875 \n",
+       "Q 5.515625 13.8125 5.515625 26.8125 \n",
+       "Q 5.515625 40.234375 12.765625 48.109375 \n",
+       "Q 20.015625 56 32.328125 56 \n",
+       "Q 43.359375 56 49.78125 48.890625 \n",
+       "Q 56.203125 41.796875 56.203125 29.59375 \n",
+       "z\n",
+       "M 47.21875 32.234375 \n",
+       "Q 47.125 39.59375 43.09375 43.984375 \n",
+       "Q 39.0625 48.390625 32.421875 48.390625 \n",
+       "Q 24.90625 48.390625 20.390625 44.140625 \n",
+       "Q 15.875 39.890625 15.1875 32.171875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-101\"/>\n",
+       "      <path d=\"M 41.109375 46.296875 \n",
+       "Q 39.59375 47.171875 37.8125 47.578125 \n",
+       "Q 36.03125 48 33.890625 48 \n",
+       "Q 26.265625 48 22.1875 43.046875 \n",
+       "Q 18.109375 38.09375 18.109375 28.8125 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 20.953125 51.171875 25.484375 53.578125 \n",
+       "Q 30.03125 56 36.53125 56 \n",
+       "Q 37.453125 56 38.578125 55.875 \n",
+       "Q 39.703125 55.765625 41.0625 55.515625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-114\"/>\n",
+       "      <path d=\"M 48.78125 52.59375 \n",
+       "L 48.78125 44.1875 \n",
+       "Q 44.96875 46.296875 41.140625 47.34375 \n",
+       "Q 37.3125 48.390625 33.40625 48.390625 \n",
+       "Q 24.65625 48.390625 19.8125 42.84375 \n",
+       "Q 14.984375 37.3125 14.984375 27.296875 \n",
+       "Q 14.984375 17.28125 19.8125 11.734375 \n",
+       "Q 24.65625 6.203125 33.40625 6.203125 \n",
+       "Q 37.3125 6.203125 41.140625 7.25 \n",
+       "Q 44.96875 8.296875 48.78125 10.40625 \n",
+       "L 48.78125 2.09375 \n",
+       "Q 45.015625 0.34375 40.984375 -0.53125 \n",
+       "Q 36.96875 -1.421875 32.421875 -1.421875 \n",
+       "Q 20.0625 -1.421875 12.78125 6.34375 \n",
+       "Q 5.515625 14.109375 5.515625 27.296875 \n",
+       "Q 5.515625 40.671875 12.859375 48.328125 \n",
+       "Q 20.21875 56 33.015625 56 \n",
+       "Q 37.15625 56 41.109375 55.140625 \n",
+       "Q 45.0625 54.296875 48.78125 52.59375 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-99\"/>\n",
+       "     </defs>\n",
+       "     <g style=\"fill:#262626;\" transform=\"translate(74.893438 211.424531)scale(0.11 -0.11)\">\n",
+       "      <use xlink:href=\"#DejaVuSans-110\"/>\n",
+       "      <use x=\"63.378906\" xlink:href=\"#DejaVuSans-117\"/>\n",
+       "      <use x=\"126.757812\" xlink:href=\"#DejaVuSans-109\"/>\n",
+       "      <use x=\"224.169922\" xlink:href=\"#DejaVuSans-101\"/>\n",
+       "      <use x=\"285.693359\" xlink:href=\"#DejaVuSans-114\"/>\n",
+       "      <use x=\"326.806641\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "      <use x=\"354.589844\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_20\">\n",
+       "     <path d=\"M 44.093438 223.720469 \n",
+       "L 66.093437 223.720469 \n",
+       "\" style=\"fill:none;stroke:#ee854a;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_21\"/>\n",
+       "    <g id=\"text_18\">\n",
+       "     <!-- analytic -->\n",
+       "     <defs>\n",
+       "      <path d=\"M 9.421875 75.984375 \n",
+       "L 18.40625 75.984375 \n",
+       "L 18.40625 0 \n",
+       "L 9.421875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-108\"/>\n",
+       "      <path d=\"M 32.171875 -5.078125 \n",
+       "Q 28.375 -14.84375 24.75 -17.8125 \n",
+       "Q 21.140625 -20.796875 15.09375 -20.796875 \n",
+       "L 7.90625 -20.796875 \n",
+       "L 7.90625 -13.28125 \n",
+       "L 13.1875 -13.28125 \n",
+       "Q 16.890625 -13.28125 18.9375 -11.515625 \n",
+       "Q 21 -9.765625 23.484375 -3.21875 \n",
+       "L 25.09375 0.875 \n",
+       "L 2.984375 54.6875 \n",
+       "L 12.5 54.6875 \n",
+       "L 29.59375 11.921875 \n",
+       "L 46.6875 54.6875 \n",
+       "L 56.203125 54.6875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-121\"/>\n",
+       "     </defs>\n",
+       "     <g style=\"fill:#262626;\" transform=\"translate(74.893438 227.570469)scale(0.11 -0.11)\">\n",
+       "      <use xlink:href=\"#DejaVuSans-97\"/>\n",
+       "      <use x=\"61.279297\" xlink:href=\"#DejaVuSans-110\"/>\n",
+       "      <use x=\"124.658203\" xlink:href=\"#DejaVuSans-97\"/>\n",
+       "      <use x=\"185.9375\" xlink:href=\"#DejaVuSans-108\"/>\n",
+       "      <use x=\"213.720703\" xlink:href=\"#DejaVuSans-121\"/>\n",
+       "      <use x=\"272.900391\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "      <use x=\"312.109375\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "      <use x=\"339.892578\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "  </g>\n",
+       " </g>\n",
+       " <defs>\n",
+       "  <clipPath id=\"peb70614cab\">\n",
+       "   <rect height=\"217.44\" width=\"334.8\" x=\"34.193438\" y=\"22.318125\"/>\n",
+       "  </clipPath>\n",
+       " </defs>\n",
+       "</svg>\n"
+      ],
+      "text/plain": [
+       "<Figure size 432x288 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "plt.title(\"Rotation\")\n",
+    "plt.plot(x, data[4], label=\"numeric\")\n",
+    "plt.plot(x, data[5], label=\"analytic\")\n",
+    "plt.legend()\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 42,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/svg+xml": [
+       "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n",
+       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
+       "  \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
+       "<!-- Created with matplotlib (https://matplotlib.org/) -->\n",
+       "<svg height=\"267.104062pt\" version=\"1.1\" viewBox=\"0 0 382.193437 267.104062\" width=\"382.193437pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+       " <defs>\n",
+       "  <style type=\"text/css\">\n",
+       "*{stroke-linecap:butt;stroke-linejoin:round;}\n",
+       "  </style>\n",
+       " </defs>\n",
+       " <g id=\"figure_1\">\n",
+       "  <g id=\"patch_1\">\n",
+       "   <path d=\"M 0 267.104062 \n",
+       "L 382.193437 267.104062 \n",
+       "L 382.193437 0 \n",
+       "L 0 0 \n",
+       "z\n",
+       "\" style=\"fill:#ffffff;\"/>\n",
+       "  </g>\n",
+       "  <g id=\"axes_1\">\n",
+       "   <g id=\"patch_2\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 368.993438 239.758125 \n",
+       "L 368.993438 22.318125 \n",
+       "L 34.193438 22.318125 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_1\">\n",
+       "    <g id=\"xtick_1\">\n",
+       "     <g id=\"line2d_1\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 49.411619 239.758125 \n",
+       "L 49.411619 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_1\">\n",
+       "      <!-- 0 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 31.78125 66.40625 \n",
+       "Q 24.171875 66.40625 20.328125 58.90625 \n",
+       "Q 16.5 51.421875 16.5 36.375 \n",
+       "Q 16.5 21.390625 20.328125 13.890625 \n",
+       "Q 24.171875 6.390625 31.78125 6.390625 \n",
+       "Q 39.453125 6.390625 43.28125 13.890625 \n",
+       "Q 47.125 21.390625 47.125 36.375 \n",
+       "Q 47.125 51.421875 43.28125 58.90625 \n",
+       "Q 39.453125 66.40625 31.78125 66.40625 \n",
+       "z\n",
+       "M 31.78125 74.21875 \n",
+       "Q 44.046875 74.21875 50.515625 64.515625 \n",
+       "Q 56.984375 54.828125 56.984375 36.375 \n",
+       "Q 56.984375 17.96875 50.515625 8.265625 \n",
+       "Q 44.046875 -1.421875 31.78125 -1.421875 \n",
+       "Q 19.53125 -1.421875 13.0625 8.265625 \n",
+       "Q 6.59375 17.96875 6.59375 36.375 \n",
+       "Q 6.59375 54.828125 13.0625 64.515625 \n",
+       "Q 19.53125 74.21875 31.78125 74.21875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-48\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(45.912244 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_2\">\n",
+       "     <g id=\"line2d_2\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 100.172733 239.758125 \n",
+       "L 100.172733 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_2\">\n",
+       "      <!-- 500 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 10.796875 72.90625 \n",
+       "L 49.515625 72.90625 \n",
+       "L 49.515625 64.59375 \n",
+       "L 19.828125 64.59375 \n",
+       "L 19.828125 46.734375 \n",
+       "Q 21.96875 47.46875 24.109375 47.828125 \n",
+       "Q 26.265625 48.1875 28.421875 48.1875 \n",
+       "Q 40.625 48.1875 47.75 41.5 \n",
+       "Q 54.890625 34.8125 54.890625 23.390625 \n",
+       "Q 54.890625 11.625 47.5625 5.09375 \n",
+       "Q 40.234375 -1.421875 26.90625 -1.421875 \n",
+       "Q 22.3125 -1.421875 17.546875 -0.640625 \n",
+       "Q 12.796875 0.140625 7.71875 1.703125 \n",
+       "L 7.71875 11.625 \n",
+       "Q 12.109375 9.234375 16.796875 8.0625 \n",
+       "Q 21.484375 6.890625 26.703125 6.890625 \n",
+       "Q 35.15625 6.890625 40.078125 11.328125 \n",
+       "Q 45.015625 15.765625 45.015625 23.390625 \n",
+       "Q 45.015625 31 40.078125 35.4375 \n",
+       "Q 35.15625 39.890625 26.703125 39.890625 \n",
+       "Q 22.75 39.890625 18.8125 39.015625 \n",
+       "Q 14.890625 38.140625 10.796875 36.28125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-53\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(89.674608 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_3\">\n",
+       "     <g id=\"line2d_3\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 150.933846 239.758125 \n",
+       "L 150.933846 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_3\">\n",
+       "      <!-- 1000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 12.40625 8.296875 \n",
+       "L 28.515625 8.296875 \n",
+       "L 28.515625 63.921875 \n",
+       "L 10.984375 60.40625 \n",
+       "L 10.984375 69.390625 \n",
+       "L 28.421875 72.90625 \n",
+       "L 38.28125 72.90625 \n",
+       "L 38.28125 8.296875 \n",
+       "L 54.390625 8.296875 \n",
+       "L 54.390625 0 \n",
+       "L 12.40625 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-49\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(136.936346 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_4\">\n",
+       "     <g id=\"line2d_4\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 201.69496 239.758125 \n",
+       "L 201.69496 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_4\">\n",
+       "      <!-- 1500 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(187.69746 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_5\">\n",
+       "     <g id=\"line2d_5\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 252.456073 239.758125 \n",
+       "L 252.456073 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_5\">\n",
+       "      <!-- 2000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 19.1875 8.296875 \n",
+       "L 53.609375 8.296875 \n",
+       "L 53.609375 0 \n",
+       "L 7.328125 0 \n",
+       "L 7.328125 8.296875 \n",
+       "Q 12.9375 14.109375 22.625 23.890625 \n",
+       "Q 32.328125 33.6875 34.8125 36.53125 \n",
+       "Q 39.546875 41.84375 41.421875 45.53125 \n",
+       "Q 43.3125 49.21875 43.3125 52.78125 \n",
+       "Q 43.3125 58.59375 39.234375 62.25 \n",
+       "Q 35.15625 65.921875 28.609375 65.921875 \n",
+       "Q 23.96875 65.921875 18.8125 64.3125 \n",
+       "Q 13.671875 62.703125 7.8125 59.421875 \n",
+       "L 7.8125 69.390625 \n",
+       "Q 13.765625 71.78125 18.9375 73 \n",
+       "Q 24.125 74.21875 28.421875 74.21875 \n",
+       "Q 39.75 74.21875 46.484375 68.546875 \n",
+       "Q 53.21875 62.890625 53.21875 53.421875 \n",
+       "Q 53.21875 48.921875 51.53125 44.890625 \n",
+       "Q 49.859375 40.875 45.40625 35.40625 \n",
+       "Q 44.1875 33.984375 37.640625 27.21875 \n",
+       "Q 31.109375 20.453125 19.1875 8.296875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-50\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(238.458573 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_6\">\n",
+       "     <g id=\"line2d_6\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 303.217187 239.758125 \n",
+       "L 303.217187 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_6\">\n",
+       "      <!-- 2500 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(289.219687 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"xtick_7\">\n",
+       "     <g id=\"line2d_7\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 353.9783 239.758125 \n",
+       "L 353.9783 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_7\">\n",
+       "      <!-- 3000 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 40.578125 39.3125 \n",
+       "Q 47.65625 37.796875 51.625 33 \n",
+       "Q 55.609375 28.21875 55.609375 21.1875 \n",
+       "Q 55.609375 10.40625 48.1875 4.484375 \n",
+       "Q 40.765625 -1.421875 27.09375 -1.421875 \n",
+       "Q 22.515625 -1.421875 17.65625 -0.515625 \n",
+       "Q 12.796875 0.390625 7.625 2.203125 \n",
+       "L 7.625 11.71875 \n",
+       "Q 11.71875 9.328125 16.59375 8.109375 \n",
+       "Q 21.484375 6.890625 26.8125 6.890625 \n",
+       "Q 36.078125 6.890625 40.9375 10.546875 \n",
+       "Q 45.796875 14.203125 45.796875 21.1875 \n",
+       "Q 45.796875 27.640625 41.28125 31.265625 \n",
+       "Q 36.765625 34.90625 28.71875 34.90625 \n",
+       "L 20.21875 34.90625 \n",
+       "L 20.21875 43.015625 \n",
+       "L 29.109375 43.015625 \n",
+       "Q 36.375 43.015625 40.234375 45.921875 \n",
+       "Q 44.09375 48.828125 44.09375 54.296875 \n",
+       "Q 44.09375 59.90625 40.109375 62.90625 \n",
+       "Q 36.140625 65.921875 28.71875 65.921875 \n",
+       "Q 24.65625 65.921875 20.015625 65.03125 \n",
+       "Q 15.375 64.15625 9.8125 62.3125 \n",
+       "L 9.8125 71.09375 \n",
+       "Q 15.4375 72.65625 20.34375 73.4375 \n",
+       "Q 25.25 74.21875 29.59375 74.21875 \n",
+       "Q 40.828125 74.21875 47.359375 69.109375 \n",
+       "Q 53.90625 64.015625 53.90625 55.328125 \n",
+       "Q 53.90625 49.265625 50.4375 45.09375 \n",
+       "Q 46.96875 40.921875 40.578125 39.3125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-51\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(339.9808 257.616406)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-51\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"127.246094\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"190.869141\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"matplotlib.axis_2\">\n",
+       "    <g id=\"ytick_1\">\n",
+       "     <g id=\"line2d_8\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 34.193438 229.953652 \n",
+       "L 368.993438 229.953652 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_8\">\n",
+       "      <!-- 0.0 -->\n",
+       "      <defs>\n",
+       "       <path d=\"M 10.6875 12.40625 \n",
+       "L 21 12.40625 \n",
+       "L 21 0 \n",
+       "L 10.6875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-46\"/>\n",
+       "      </defs>\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 234.132793)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_2\">\n",
+       "     <g id=\"line2d_9\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 34.193438 203.528033 \n",
+       "L 368.993438 203.528033 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_9\">\n",
+       "      <!-- 0.5 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 207.707174)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-48\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_3\">\n",
+       "     <g id=\"line2d_10\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 34.193438 177.102415 \n",
+       "L 368.993438 177.102415 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_10\">\n",
+       "      <!-- 1.0 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 181.281555)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_4\">\n",
+       "     <g id=\"line2d_11\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 34.193438 150.676796 \n",
+       "L 368.993438 150.676796 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_11\">\n",
+       "      <!-- 1.5 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 154.855936)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-49\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_5\">\n",
+       "     <g id=\"line2d_12\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 34.193438 124.251177 \n",
+       "L 368.993438 124.251177 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_12\">\n",
+       "      <!-- 2.0 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 128.430317)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_6\">\n",
+       "     <g id=\"line2d_13\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 34.193438 97.825558 \n",
+       "L 368.993438 97.825558 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_13\">\n",
+       "      <!-- 2.5 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 102.004698)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-50\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_7\">\n",
+       "     <g id=\"line2d_14\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 34.193438 71.399939 \n",
+       "L 368.993438 71.399939 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_14\">\n",
+       "      <!-- 3.0 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 75.57908)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-51\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"ytick_8\">\n",
+       "     <g id=\"line2d_15\">\n",
+       "      <path clip-path=\"url(#p51d8a71626)\" d=\"M 34.193438 44.97432 \n",
+       "L 368.993438 44.97432 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:round;\"/>\n",
+       "     </g>\n",
+       "     <g id=\"text_15\">\n",
+       "      <!-- 3.5 -->\n",
+       "      <g style=\"fill:#262626;\" transform=\"translate(7.2 49.153461)scale(0.11 -0.11)\">\n",
+       "       <use xlink:href=\"#DejaVuSans-51\"/>\n",
+       "       <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
+       "       <use x=\"95.410156\" xlink:href=\"#DejaVuSans-53\"/>\n",
+       "      </g>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"line2d_16\">\n",
+       "    <path clip-path=\"url(#p51d8a71626)\" d=\"M 49.411619 32.20229 \n",
+       "L 50.83293 32.340232 \n",
+       "L 52.254242 32.718646 \n",
+       "L 53.675553 33.338063 \n",
+       "L 55.096864 34.196367 \n",
+       "L 56.619697 35.381292 \n",
+       "L 58.244053 36.94516 \n",
+       "L 59.868409 38.816622 \n",
+       "L 61.594287 41.137849 \n",
+       "L 63.421687 43.96539 \n",
+       "L 65.452131 47.54659 \n",
+       "L 67.584098 51.795301 \n",
+       "L 69.817587 56.769659 \n",
+       "L 72.152598 62.527273 \n",
+       "L 74.690654 69.409561 \n",
+       "L 77.330232 77.228902 \n",
+       "L 80.172854 86.366352 \n",
+       "L 83.218521 96.931843 \n",
+       "L 86.467232 109.020507 \n",
+       "L 90.02051 123.121217 \n",
+       "L 93.979877 139.78627 \n",
+       "L 98.446855 159.60337 \n",
+       "L 103.624489 183.615114 \n",
+       "L 110.832567 218.203342 \n",
+       "L 113.2691 229.874408 \n",
+       "L 123.624367 180.112821 \n",
+       "L 129.106567 154.869484 \n",
+       "L 133.675068 134.853664 \n",
+       "L 137.634435 118.465552 \n",
+       "L 141.289235 104.270238 \n",
+       "L 144.537946 92.498682 \n",
+       "L 147.583613 82.25664 \n",
+       "L 150.426235 73.441054 \n",
+       "L 153.167335 65.662937 \n",
+       "L 155.705391 59.124182 \n",
+       "L 158.040402 53.694246 \n",
+       "L 160.273891 49.042808 \n",
+       "L 162.405858 45.110676 \n",
+       "L 164.33478 41.989282 \n",
+       "L 166.16218 39.42124 \n",
+       "L 167.888058 37.348415 \n",
+       "L 169.512414 35.713726 \n",
+       "L 171.136769 34.388217 \n",
+       "L 172.659603 33.428439 \n",
+       "L 174.080914 32.780482 \n",
+       "L 175.502225 32.372999 \n",
+       "L 176.822014 32.210218 \n",
+       "L 178.141803 32.255141 \n",
+       "L 179.563114 32.53631 \n",
+       "L 180.984425 33.057951 \n",
+       "L 182.405737 33.819538 \n",
+       "L 183.92857 34.900874 \n",
+       "L 185.451403 36.254923 \n",
+       "L 187.075759 37.997957 \n",
+       "L 188.801637 40.184941 \n",
+       "L 190.629037 42.872426 \n",
+       "L 192.557959 46.117492 \n",
+       "L 194.588404 49.979332 \n",
+       "L 196.720371 54.516082 \n",
+       "L 199.055382 60.036394 \n",
+       "L 201.491915 66.392284 \n",
+       "L 204.029971 73.635018 \n",
+       "L 206.771071 82.13931 \n",
+       "L 209.715216 92.018264 \n",
+       "L 212.862405 103.374938 \n",
+       "L 216.31416 116.697678 \n",
+       "L 219.968961 131.688403 \n",
+       "L 224.232894 150.169424 \n",
+       "L 229.105961 172.349503 \n",
+       "L 235.095772 200.707891 \n",
+       "L 241.187106 229.715919 \n",
+       "L 251.542373 179.959447 \n",
+       "L 257.024573 154.723086 \n",
+       "L 261.491551 135.147516 \n",
+       "L 265.450918 118.742492 \n",
+       "L 269.105718 104.52868 \n",
+       "L 272.455952 92.385051 \n",
+       "L 275.501619 82.150938 \n",
+       "L 278.344241 73.344336 \n",
+       "L 280.983819 65.849502 \n",
+       "L 283.521875 59.293306 \n",
+       "L 285.856886 53.846457 \n",
+       "L 288.090375 49.178107 \n",
+       "L 290.222342 45.229591 \n",
+       "L 292.252786 41.94013 \n",
+       "L 294.080186 39.378959 \n",
+       "L 295.806064 37.312476 \n",
+       "L 297.43042 35.684129 \n",
+       "L 298.953253 34.437897 \n",
+       "L 300.476087 33.465434 \n",
+       "L 301.897398 32.80638 \n",
+       "L 303.318709 32.387269 \n",
+       "L 304.638498 32.213389 \n",
+       "L 305.958287 32.247742 \n",
+       "L 307.278076 32.489801 \n",
+       "L 308.699387 32.982374 \n",
+       "L 310.120698 33.715421 \n",
+       "L 311.542009 34.687355 \n",
+       "L 313.064843 35.993309 \n",
+       "L 314.689198 37.685077 \n",
+       "L 316.415076 39.817625 \n",
+       "L 318.242476 42.448559 \n",
+       "L 320.171399 45.63496 \n",
+       "L 322.201843 49.436022 \n",
+       "L 324.33381 53.910407 \n",
+       "L 326.567299 59.115197 \n",
+       "L 329.003832 65.379126 \n",
+       "L 331.64341 72.829565 \n",
+       "L 334.38451 81.265151 \n",
+       "L 337.328655 91.074869 \n",
+       "L 340.475844 102.362837 \n",
+       "L 343.9276 115.61687 \n",
+       "L 347.683922 130.970683 \n",
+       "L 351.846333 148.96283 \n",
+       "L 353.775256 157.598722 \n",
+       "L 353.775256 157.598722 \n",
+       "\" style=\"fill:none;stroke:#4878d0;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"line2d_17\">\n",
+       "    <path clip-path=\"url(#p51d8a71626)\" d=\"M 49.411619 32.202818 \n",
+       "L 50.83293 32.34076 \n",
+       "L 52.254242 32.719175 \n",
+       "L 53.675553 33.338591 \n",
+       "L 55.096864 34.197424 \n",
+       "L 56.619697 35.38182 \n",
+       "L 58.244053 36.945688 \n",
+       "L 59.969931 38.943994 \n",
+       "L 61.797331 41.433816 \n",
+       "L 63.624731 44.303638 \n",
+       "L 65.553653 47.73844 \n",
+       "L 67.68562 52.010405 \n",
+       "L 69.919109 57.009075 \n",
+       "L 72.25412 62.791001 \n",
+       "L 74.690654 69.41009 \n",
+       "L 77.330232 77.22943 \n",
+       "L 80.071332 86.028105 \n",
+       "L 83.116999 96.567698 \n",
+       "L 86.36571 108.630993 \n",
+       "L 89.918988 122.706864 \n",
+       "L 93.776832 138.909996 \n",
+       "L 98.142288 158.222367 \n",
+       "L 103.319922 182.178142 \n",
+       "L 110.324955 215.742747 \n",
+       "L 113.2691 229.874489 \n",
+       "L 123.624367 180.113032 \n",
+       "L 129.106567 154.870013 \n",
+       "L 133.675068 134.854192 \n",
+       "L 137.634435 118.46608 \n",
+       "L 141.289235 104.270766 \n",
+       "L 144.537946 92.49921 \n",
+       "L 147.583613 82.257169 \n",
+       "L 150.426235 73.442111 \n",
+       "L 153.065813 65.938821 \n",
+       "L 155.603869 59.37364 \n",
+       "L 157.93888 53.918863 \n",
+       "L 160.172369 49.243114 \n",
+       "L 162.304336 45.286671 \n",
+       "L 164.33478 41.989811 \n",
+       "L 166.16218 39.421769 \n",
+       "L 167.888058 37.348943 \n",
+       "L 169.613936 35.622293 \n",
+       "L 171.238292 34.316339 \n",
+       "L 172.761125 33.37453 \n",
+       "L 174.182436 32.744015 \n",
+       "L 175.603747 32.353444 \n",
+       "L 177.025059 32.204404 \n",
+       "L 178.344848 32.281038 \n",
+       "L 179.766159 32.59656 \n",
+       "L 181.18747 33.152555 \n",
+       "L 182.608781 33.948495 \n",
+       "L 184.131614 35.066298 \n",
+       "L 185.654448 36.456815 \n",
+       "L 187.278804 38.237901 \n",
+       "L 189.004681 40.465581 \n",
+       "L 190.832081 43.194819 \n",
+       "L 192.761004 46.483751 \n",
+       "L 194.892971 50.59822 \n",
+       "L 197.024937 55.204206 \n",
+       "L 199.359949 60.798509 \n",
+       "L 201.796482 67.228391 \n",
+       "L 204.43606 74.851653 \n",
+       "L 207.17716 83.45742 \n",
+       "L 210.121305 93.438905 \n",
+       "L 213.268494 104.897053 \n",
+       "L 216.720249 118.320211 \n",
+       "L 220.476572 133.836277 \n",
+       "L 224.740505 152.431985 \n",
+       "L 229.715094 175.186557 \n",
+       "L 236.009472 205.104586 \n",
+       "L 241.187106 229.716162 \n",
+       "L 251.542373 179.95987 \n",
+       "L 257.024573 154.723615 \n",
+       "L 261.491551 135.148045 \n",
+       "L 265.450918 118.743021 \n",
+       "L 269.105718 104.529737 \n",
+       "L 272.455952 92.38558 \n",
+       "L 275.501619 82.151466 \n",
+       "L 278.344241 73.345393 \n",
+       "L 280.983819 65.85003 \n",
+       "L 283.521875 59.293834 \n",
+       "L 285.958408 53.623425 \n",
+       "L 288.191897 48.979387 \n",
+       "L 290.323864 45.054654 \n",
+       "L 292.354308 41.788447 \n",
+       "L 294.181708 39.248417 \n",
+       "L 295.907586 37.202545 \n",
+       "L 297.531942 35.593225 \n",
+       "L 299.156298 34.293085 \n",
+       "L 300.679131 33.357618 \n",
+       "L 302.100442 32.732388 \n",
+       "L 303.521753 32.347631 \n",
+       "L 304.841542 32.205989 \n",
+       "L 306.161331 32.272054 \n",
+       "L 307.582642 32.575948 \n",
+       "L 309.003954 33.119787 \n",
+       "L 310.425265 33.9041 \n",
+       "L 311.948098 35.009748 \n",
+       "L 313.572454 36.489582 \n",
+       "L 315.196809 38.277011 \n",
+       "L 316.922687 40.511033 \n",
+       "L 318.750087 43.247142 \n",
+       "L 320.67901 46.542945 \n",
+       "L 322.709454 50.457636 \n",
+       "L 324.841421 55.047766 \n",
+       "L 327.07491 60.370943 \n",
+       "L 329.511443 66.7596 \n",
+       "L 332.151021 74.339525 \n",
+       "L 334.892121 82.902482 \n",
+       "L 337.836266 92.841158 \n",
+       "L 340.983455 104.257025 \n",
+       "L 344.435211 117.637901 \n",
+       "L 348.191533 133.113272 \n",
+       "L 352.353945 151.218521 \n",
+       "L 353.775256 157.598722 \n",
+       "L 353.775256 157.598722 \n",
+       "\" style=\"fill:none;stroke:#ee854a;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_3\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 34.193438 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_4\">\n",
+       "    <path d=\"M 368.993438 239.758125 \n",
+       "L 368.993438 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_5\">\n",
+       "    <path d=\"M 34.193438 239.758125 \n",
+       "L 368.993437 239.758125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"patch_6\">\n",
+       "    <path d=\"M 34.193438 22.318125 \n",
+       "L 368.993437 22.318125 \n",
+       "\" style=\"fill:none;stroke:#ffffff;stroke-linecap:square;stroke-linejoin:miter;stroke-width:1.25;\"/>\n",
+       "   </g>\n",
+       "   <g id=\"text_16\">\n",
+       "    <!-- Angular Velocity -->\n",
+       "    <defs>\n",
+       "     <path d=\"M 34.1875 63.1875 \n",
+       "L 20.796875 26.90625 \n",
+       "L 47.609375 26.90625 \n",
+       "z\n",
+       "M 28.609375 72.90625 \n",
+       "L 39.796875 72.90625 \n",
+       "L 67.578125 0 \n",
+       "L 57.328125 0 \n",
+       "L 50.6875 18.703125 \n",
+       "L 17.828125 18.703125 \n",
+       "L 11.1875 0 \n",
+       "L 0.78125 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-65\"/>\n",
+       "     <path d=\"M 54.890625 33.015625 \n",
+       "L 54.890625 0 \n",
+       "L 45.90625 0 \n",
+       "L 45.90625 32.71875 \n",
+       "Q 45.90625 40.484375 42.875 44.328125 \n",
+       "Q 39.84375 48.1875 33.796875 48.1875 \n",
+       "Q 26.515625 48.1875 22.3125 43.546875 \n",
+       "Q 18.109375 38.921875 18.109375 30.90625 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 21.34375 51.125 25.703125 53.5625 \n",
+       "Q 30.078125 56 35.796875 56 \n",
+       "Q 45.21875 56 50.046875 50.171875 \n",
+       "Q 54.890625 44.34375 54.890625 33.015625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-110\"/>\n",
+       "     <path d=\"M 45.40625 27.984375 \n",
+       "Q 45.40625 37.75 41.375 43.109375 \n",
+       "Q 37.359375 48.484375 30.078125 48.484375 \n",
+       "Q 22.859375 48.484375 18.828125 43.109375 \n",
+       "Q 14.796875 37.75 14.796875 27.984375 \n",
+       "Q 14.796875 18.265625 18.828125 12.890625 \n",
+       "Q 22.859375 7.515625 30.078125 7.515625 \n",
+       "Q 37.359375 7.515625 41.375 12.890625 \n",
+       "Q 45.40625 18.265625 45.40625 27.984375 \n",
+       "z\n",
+       "M 54.390625 6.78125 \n",
+       "Q 54.390625 -7.171875 48.1875 -13.984375 \n",
+       "Q 42 -20.796875 29.203125 -20.796875 \n",
+       "Q 24.46875 -20.796875 20.265625 -20.09375 \n",
+       "Q 16.0625 -19.390625 12.109375 -17.921875 \n",
+       "L 12.109375 -9.1875 \n",
+       "Q 16.0625 -11.328125 19.921875 -12.34375 \n",
+       "Q 23.78125 -13.375 27.78125 -13.375 \n",
+       "Q 36.625 -13.375 41.015625 -8.765625 \n",
+       "Q 45.40625 -4.15625 45.40625 5.171875 \n",
+       "L 45.40625 9.625 \n",
+       "Q 42.625 4.78125 38.28125 2.390625 \n",
+       "Q 33.9375 0 27.875 0 \n",
+       "Q 17.828125 0 11.671875 7.65625 \n",
+       "Q 5.515625 15.328125 5.515625 27.984375 \n",
+       "Q 5.515625 40.671875 11.671875 48.328125 \n",
+       "Q 17.828125 56 27.875 56 \n",
+       "Q 33.9375 56 38.28125 53.609375 \n",
+       "Q 42.625 51.21875 45.40625 46.390625 \n",
+       "L 45.40625 54.6875 \n",
+       "L 54.390625 54.6875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-103\"/>\n",
+       "     <path d=\"M 8.5 21.578125 \n",
+       "L 8.5 54.6875 \n",
+       "L 17.484375 54.6875 \n",
+       "L 17.484375 21.921875 \n",
+       "Q 17.484375 14.15625 20.5 10.265625 \n",
+       "Q 23.53125 6.390625 29.59375 6.390625 \n",
+       "Q 36.859375 6.390625 41.078125 11.03125 \n",
+       "Q 45.3125 15.671875 45.3125 23.6875 \n",
+       "L 45.3125 54.6875 \n",
+       "L 54.296875 54.6875 \n",
+       "L 54.296875 0 \n",
+       "L 45.3125 0 \n",
+       "L 45.3125 8.40625 \n",
+       "Q 42.046875 3.421875 37.71875 1 \n",
+       "Q 33.40625 -1.421875 27.6875 -1.421875 \n",
+       "Q 18.265625 -1.421875 13.375 4.4375 \n",
+       "Q 8.5 10.296875 8.5 21.578125 \n",
+       "z\n",
+       "M 31.109375 56 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-117\"/>\n",
+       "     <path d=\"M 9.421875 75.984375 \n",
+       "L 18.40625 75.984375 \n",
+       "L 18.40625 0 \n",
+       "L 9.421875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-108\"/>\n",
+       "     <path d=\"M 34.28125 27.484375 \n",
+       "Q 23.390625 27.484375 19.1875 25 \n",
+       "Q 14.984375 22.515625 14.984375 16.5 \n",
+       "Q 14.984375 11.71875 18.140625 8.90625 \n",
+       "Q 21.296875 6.109375 26.703125 6.109375 \n",
+       "Q 34.1875 6.109375 38.703125 11.40625 \n",
+       "Q 43.21875 16.703125 43.21875 25.484375 \n",
+       "L 43.21875 27.484375 \n",
+       "z\n",
+       "M 52.203125 31.203125 \n",
+       "L 52.203125 0 \n",
+       "L 43.21875 0 \n",
+       "L 43.21875 8.296875 \n",
+       "Q 40.140625 3.328125 35.546875 0.953125 \n",
+       "Q 30.953125 -1.421875 24.3125 -1.421875 \n",
+       "Q 15.921875 -1.421875 10.953125 3.296875 \n",
+       "Q 6 8.015625 6 15.921875 \n",
+       "Q 6 25.140625 12.171875 29.828125 \n",
+       "Q 18.359375 34.515625 30.609375 34.515625 \n",
+       "L 43.21875 34.515625 \n",
+       "L 43.21875 35.40625 \n",
+       "Q 43.21875 41.609375 39.140625 45 \n",
+       "Q 35.0625 48.390625 27.6875 48.390625 \n",
+       "Q 23 48.390625 18.546875 47.265625 \n",
+       "Q 14.109375 46.140625 10.015625 43.890625 \n",
+       "L 10.015625 52.203125 \n",
+       "Q 14.9375 54.109375 19.578125 55.046875 \n",
+       "Q 24.21875 56 28.609375 56 \n",
+       "Q 40.484375 56 46.34375 49.84375 \n",
+       "Q 52.203125 43.703125 52.203125 31.203125 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-97\"/>\n",
+       "     <path d=\"M 41.109375 46.296875 \n",
+       "Q 39.59375 47.171875 37.8125 47.578125 \n",
+       "Q 36.03125 48 33.890625 48 \n",
+       "Q 26.265625 48 22.1875 43.046875 \n",
+       "Q 18.109375 38.09375 18.109375 28.8125 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 20.953125 51.171875 25.484375 53.578125 \n",
+       "Q 30.03125 56 36.53125 56 \n",
+       "Q 37.453125 56 38.578125 55.875 \n",
+       "Q 39.703125 55.765625 41.0625 55.515625 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-114\"/>\n",
+       "     <path id=\"DejaVuSans-32\"/>\n",
+       "     <path d=\"M 28.609375 0 \n",
+       "L 0.78125 72.90625 \n",
+       "L 11.078125 72.90625 \n",
+       "L 34.1875 11.53125 \n",
+       "L 57.328125 72.90625 \n",
+       "L 67.578125 72.90625 \n",
+       "L 39.796875 0 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-86\"/>\n",
+       "     <path d=\"M 56.203125 29.59375 \n",
+       "L 56.203125 25.203125 \n",
+       "L 14.890625 25.203125 \n",
+       "Q 15.484375 15.921875 20.484375 11.0625 \n",
+       "Q 25.484375 6.203125 34.421875 6.203125 \n",
+       "Q 39.59375 6.203125 44.453125 7.46875 \n",
+       "Q 49.3125 8.734375 54.109375 11.28125 \n",
+       "L 54.109375 2.78125 \n",
+       "Q 49.265625 0.734375 44.1875 -0.34375 \n",
+       "Q 39.109375 -1.421875 33.890625 -1.421875 \n",
+       "Q 20.796875 -1.421875 13.15625 6.1875 \n",
+       "Q 5.515625 13.8125 5.515625 26.8125 \n",
+       "Q 5.515625 40.234375 12.765625 48.109375 \n",
+       "Q 20.015625 56 32.328125 56 \n",
+       "Q 43.359375 56 49.78125 48.890625 \n",
+       "Q 56.203125 41.796875 56.203125 29.59375 \n",
+       "z\n",
+       "M 47.21875 32.234375 \n",
+       "Q 47.125 39.59375 43.09375 43.984375 \n",
+       "Q 39.0625 48.390625 32.421875 48.390625 \n",
+       "Q 24.90625 48.390625 20.390625 44.140625 \n",
+       "Q 15.875 39.890625 15.1875 32.171875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-101\"/>\n",
+       "     <path d=\"M 30.609375 48.390625 \n",
+       "Q 23.390625 48.390625 19.1875 42.75 \n",
+       "Q 14.984375 37.109375 14.984375 27.296875 \n",
+       "Q 14.984375 17.484375 19.15625 11.84375 \n",
+       "Q 23.34375 6.203125 30.609375 6.203125 \n",
+       "Q 37.796875 6.203125 41.984375 11.859375 \n",
+       "Q 46.1875 17.53125 46.1875 27.296875 \n",
+       "Q 46.1875 37.015625 41.984375 42.703125 \n",
+       "Q 37.796875 48.390625 30.609375 48.390625 \n",
+       "z\n",
+       "M 30.609375 56 \n",
+       "Q 42.328125 56 49.015625 48.375 \n",
+       "Q 55.71875 40.765625 55.71875 27.296875 \n",
+       "Q 55.71875 13.875 49.015625 6.21875 \n",
+       "Q 42.328125 -1.421875 30.609375 -1.421875 \n",
+       "Q 18.84375 -1.421875 12.171875 6.21875 \n",
+       "Q 5.515625 13.875 5.515625 27.296875 \n",
+       "Q 5.515625 40.765625 12.171875 48.375 \n",
+       "Q 18.84375 56 30.609375 56 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-111\"/>\n",
+       "     <path d=\"M 48.78125 52.59375 \n",
+       "L 48.78125 44.1875 \n",
+       "Q 44.96875 46.296875 41.140625 47.34375 \n",
+       "Q 37.3125 48.390625 33.40625 48.390625 \n",
+       "Q 24.65625 48.390625 19.8125 42.84375 \n",
+       "Q 14.984375 37.3125 14.984375 27.296875 \n",
+       "Q 14.984375 17.28125 19.8125 11.734375 \n",
+       "Q 24.65625 6.203125 33.40625 6.203125 \n",
+       "Q 37.3125 6.203125 41.140625 7.25 \n",
+       "Q 44.96875 8.296875 48.78125 10.40625 \n",
+       "L 48.78125 2.09375 \n",
+       "Q 45.015625 0.34375 40.984375 -0.53125 \n",
+       "Q 36.96875 -1.421875 32.421875 -1.421875 \n",
+       "Q 20.0625 -1.421875 12.78125 6.34375 \n",
+       "Q 5.515625 14.109375 5.515625 27.296875 \n",
+       "Q 5.515625 40.671875 12.859375 48.328125 \n",
+       "Q 20.21875 56 33.015625 56 \n",
+       "Q 37.15625 56 41.109375 55.140625 \n",
+       "Q 45.0625 54.296875 48.78125 52.59375 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-99\"/>\n",
+       "     <path d=\"M 9.421875 54.6875 \n",
+       "L 18.40625 54.6875 \n",
+       "L 18.40625 0 \n",
+       "L 9.421875 0 \n",
+       "z\n",
+       "M 9.421875 75.984375 \n",
+       "L 18.40625 75.984375 \n",
+       "L 18.40625 64.59375 \n",
+       "L 9.421875 64.59375 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-105\"/>\n",
+       "     <path d=\"M 18.3125 70.21875 \n",
+       "L 18.3125 54.6875 \n",
+       "L 36.8125 54.6875 \n",
+       "L 36.8125 47.703125 \n",
+       "L 18.3125 47.703125 \n",
+       "L 18.3125 18.015625 \n",
+       "Q 18.3125 11.328125 20.140625 9.421875 \n",
+       "Q 21.96875 7.515625 27.59375 7.515625 \n",
+       "L 36.8125 7.515625 \n",
+       "L 36.8125 0 \n",
+       "L 27.59375 0 \n",
+       "Q 17.1875 0 13.234375 3.875 \n",
+       "Q 9.28125 7.765625 9.28125 18.015625 \n",
+       "L 9.28125 47.703125 \n",
+       "L 2.6875 47.703125 \n",
+       "L 2.6875 54.6875 \n",
+       "L 9.28125 54.6875 \n",
+       "L 9.28125 70.21875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-116\"/>\n",
+       "     <path d=\"M 32.171875 -5.078125 \n",
+       "Q 28.375 -14.84375 24.75 -17.8125 \n",
+       "Q 21.140625 -20.796875 15.09375 -20.796875 \n",
+       "L 7.90625 -20.796875 \n",
+       "L 7.90625 -13.28125 \n",
+       "L 13.1875 -13.28125 \n",
+       "Q 16.890625 -13.28125 18.9375 -11.515625 \n",
+       "Q 21 -9.765625 23.484375 -3.21875 \n",
+       "L 25.09375 0.875 \n",
+       "L 2.984375 54.6875 \n",
+       "L 12.5 54.6875 \n",
+       "L 29.59375 11.921875 \n",
+       "L 46.6875 54.6875 \n",
+       "L 56.203125 54.6875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-121\"/>\n",
+       "    </defs>\n",
+       "    <g style=\"fill:#262626;\" transform=\"translate(152.360625 16.318125)scale(0.12 -0.12)\">\n",
+       "     <use xlink:href=\"#DejaVuSans-65\"/>\n",
+       "     <use x=\"68.408203\" xlink:href=\"#DejaVuSans-110\"/>\n",
+       "     <use x=\"131.787109\" xlink:href=\"#DejaVuSans-103\"/>\n",
+       "     <use x=\"195.263672\" xlink:href=\"#DejaVuSans-117\"/>\n",
+       "     <use x=\"258.642578\" xlink:href=\"#DejaVuSans-108\"/>\n",
+       "     <use x=\"286.425781\" xlink:href=\"#DejaVuSans-97\"/>\n",
+       "     <use x=\"347.705078\" xlink:href=\"#DejaVuSans-114\"/>\n",
+       "     <use x=\"388.818359\" xlink:href=\"#DejaVuSans-32\"/>\n",
+       "     <use x=\"420.605469\" xlink:href=\"#DejaVuSans-86\"/>\n",
+       "     <use x=\"488.904297\" xlink:href=\"#DejaVuSans-101\"/>\n",
+       "     <use x=\"550.427734\" xlink:href=\"#DejaVuSans-108\"/>\n",
+       "     <use x=\"578.210938\" xlink:href=\"#DejaVuSans-111\"/>\n",
+       "     <use x=\"639.392578\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     <use x=\"694.373047\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "     <use x=\"722.15625\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "     <use x=\"761.365234\" xlink:href=\"#DejaVuSans-121\"/>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "   <g id=\"legend_1\">\n",
+       "    <g id=\"patch_7\">\n",
+       "     <path d=\"M 281.041562 234.258125 \n",
+       "L 361.293438 234.258125 \n",
+       "Q 363.493438 234.258125 363.493438 232.058125 \n",
+       "L 363.493438 200.86625 \n",
+       "Q 363.493438 198.66625 361.293438 198.66625 \n",
+       "L 281.041562 198.66625 \n",
+       "Q 278.841563 198.66625 278.841563 200.86625 \n",
+       "L 278.841563 232.058125 \n",
+       "Q 278.841563 234.258125 281.041562 234.258125 \n",
+       "z\n",
+       "\" style=\"fill:#eaeaf2;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_18\">\n",
+       "     <path d=\"M 283.241562 207.574531 \n",
+       "L 305.241562 207.574531 \n",
+       "\" style=\"fill:none;stroke:#4878d0;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_19\"/>\n",
+       "    <g id=\"text_17\">\n",
+       "     <!-- numeric -->\n",
+       "     <defs>\n",
+       "      <path d=\"M 52 44.1875 \n",
+       "Q 55.375 50.25 60.0625 53.125 \n",
+       "Q 64.75 56 71.09375 56 \n",
+       "Q 79.640625 56 84.28125 50.015625 \n",
+       "Q 88.921875 44.046875 88.921875 33.015625 \n",
+       "L 88.921875 0 \n",
+       "L 79.890625 0 \n",
+       "L 79.890625 32.71875 \n",
+       "Q 79.890625 40.578125 77.09375 44.375 \n",
+       "Q 74.3125 48.1875 68.609375 48.1875 \n",
+       "Q 61.625 48.1875 57.5625 43.546875 \n",
+       "Q 53.515625 38.921875 53.515625 30.90625 \n",
+       "L 53.515625 0 \n",
+       "L 44.484375 0 \n",
+       "L 44.484375 32.71875 \n",
+       "Q 44.484375 40.625 41.703125 44.40625 \n",
+       "Q 38.921875 48.1875 33.109375 48.1875 \n",
+       "Q 26.21875 48.1875 22.15625 43.53125 \n",
+       "Q 18.109375 38.875 18.109375 30.90625 \n",
+       "L 18.109375 0 \n",
+       "L 9.078125 0 \n",
+       "L 9.078125 54.6875 \n",
+       "L 18.109375 54.6875 \n",
+       "L 18.109375 46.1875 \n",
+       "Q 21.1875 51.21875 25.484375 53.609375 \n",
+       "Q 29.78125 56 35.6875 56 \n",
+       "Q 41.65625 56 45.828125 52.96875 \n",
+       "Q 50 49.953125 52 44.1875 \n",
+       "z\n",
+       "\" id=\"DejaVuSans-109\"/>\n",
+       "     </defs>\n",
+       "     <g style=\"fill:#262626;\" transform=\"translate(314.041562 211.424531)scale(0.11 -0.11)\">\n",
+       "      <use xlink:href=\"#DejaVuSans-110\"/>\n",
+       "      <use x=\"63.378906\" xlink:href=\"#DejaVuSans-117\"/>\n",
+       "      <use x=\"126.757812\" xlink:href=\"#DejaVuSans-109\"/>\n",
+       "      <use x=\"224.169922\" xlink:href=\"#DejaVuSans-101\"/>\n",
+       "      <use x=\"285.693359\" xlink:href=\"#DejaVuSans-114\"/>\n",
+       "      <use x=\"326.806641\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "      <use x=\"354.589844\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_20\">\n",
+       "     <path d=\"M 283.241562 223.720469 \n",
+       "L 305.241562 223.720469 \n",
+       "\" style=\"fill:none;stroke:#ee854a;stroke-linecap:round;stroke-width:1.5;\"/>\n",
+       "    </g>\n",
+       "    <g id=\"line2d_21\"/>\n",
+       "    <g id=\"text_18\">\n",
+       "     <!-- analytic -->\n",
+       "     <g style=\"fill:#262626;\" transform=\"translate(314.041562 227.570469)scale(0.11 -0.11)\">\n",
+       "      <use xlink:href=\"#DejaVuSans-97\"/>\n",
+       "      <use x=\"61.279297\" xlink:href=\"#DejaVuSans-110\"/>\n",
+       "      <use x=\"124.658203\" xlink:href=\"#DejaVuSans-97\"/>\n",
+       "      <use x=\"185.9375\" xlink:href=\"#DejaVuSans-108\"/>\n",
+       "      <use x=\"213.720703\" xlink:href=\"#DejaVuSans-121\"/>\n",
+       "      <use x=\"272.900391\" xlink:href=\"#DejaVuSans-116\"/>\n",
+       "      <use x=\"312.109375\" xlink:href=\"#DejaVuSans-105\"/>\n",
+       "      <use x=\"339.892578\" xlink:href=\"#DejaVuSans-99\"/>\n",
+       "     </g>\n",
+       "    </g>\n",
+       "   </g>\n",
+       "  </g>\n",
+       " </g>\n",
+       " <defs>\n",
+       "  <clipPath id=\"p51d8a71626\">\n",
+       "   <rect height=\"217.44\" width=\"334.8\" x=\"34.193438\" y=\"22.318125\"/>\n",
+       "  </clipPath>\n",
+       " </defs>\n",
+       "</svg>\n"
+      ],
+      "text/plain": [
+       "<Figure size 432x288 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "plt.title(\"Angular Velocity\")\n",
+    "plt.plot(x, data[6], label=\"numeric\")\n",
+    "plt.plot(x, data[7], label=\"analytic\")\n",
+    "plt.legend()\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.7.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/mesa_pd/collision_detection/AnalyticContactDetection.cpp b/tests/mesa_pd/collision_detection/AnalyticContactDetection.cpp
new file mode 100644
index 000000000..bca221069
--- /dev/null
+++ b/tests/mesa_pd/collision_detection/AnalyticContactDetection.cpp
@@ -0,0 +1,192 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   AnalyticContactDetection.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/collision_detection/AnalyticContactDetection.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+#include <mesa_pd/kernel/DoubleCast.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <cmath>
+#include <iostream>
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+      : ParticleAccessor(ps)
+      , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+void checkRotation( const Vec3& from, const Vec3& to )
+{
+   WALBERLA_LOG_DEVEL_VAR(from);
+   WALBERLA_LOG_DEVEL_VAR(to);
+   real_t cos = dot(from, to) / from.length() / to.length();
+   real_t angle = std::acos(cos);
+   WALBERLA_LOG_DEVEL_VAR(angle);
+   Vec3 axis = cross(from, to);
+   Rot3 rot;
+   rot.rotate(axis, angle);
+   WALBERLA_LOG_DEVEL_VAR( rot );
+
+   auto res = rot.getMatrix() * from;
+
+   WALBERLA_LOG_DEVEL_VAR(res);
+
+   WALBERLA_CHECK_FLOAT_EQUAL( res, to );
+}
+
+void checkSphereSphereCollision( )
+{
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+   ParticleAccessorWithShape ac(ps, ss);
+
+   //initialize particles
+   const real_t radius  = real_t(0.5);
+   auto smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+
+   data::Particle&& sp0        = *ps->create();
+   sp0.setPosition( Vec3(real_t(0), real_t(0), real_t(0)) );
+   sp0.setShapeID(  smallSphere );
+
+   auto dir = Vec3(real_t(1), real_t(2), real_t(3)).getNormalized();
+   real_t shift = real_c(0.75);
+   data::Particle&& sp1        = *ps->create();
+   sp1.setPosition( dir * shift );
+   sp1.setShapeID(  smallSphere );
+
+   collision_detection::AnalyticContactDetection         acd;
+   kernel::DoubleCast               double_cast;
+
+   bool isInContact = false;
+   isInContact = double_cast(0, 1, ac, acd, ac );
+
+   //check two spheres in contact
+   WALBERLA_CHECK( isInContact );
+   WALBERLA_CHECK_EQUAL( acd.getIdx1(), 0);
+   WALBERLA_CHECK_EQUAL( acd.getIdx2(), 1);
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getContactPoint(), dir * shift * real_t(0.5));
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getContactNormal(), -dir );
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getPenetrationDepth(), shift - real_t(1) );
+
+   //check order invariance
+   collision_detection::AnalyticContactDetection         acd2;
+   WALBERLA_CHECK( double_cast(1, 0, ac, acd2, ac ) );
+   WALBERLA_CHECK_EQUAL( acd.getIdx1(), acd2.getIdx1());
+   WALBERLA_CHECK_EQUAL( acd.getIdx2(), acd2.getIdx2());
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getContactPoint(), acd2.getContactPoint() );
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getContactNormal(), acd2.getContactNormal() );
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getPenetrationDepth(), acd2.getPenetrationDepth() );
+
+   //no collision
+   sp1.setPosition( dir * real_t(1.1) );
+   WALBERLA_CHECK( !double_cast(1, 0, ac, acd, ac ) );
+}
+
+void checkSphereHalfSpaceCollision( )
+{
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+   ParticleAccessorWithShape ac(ps, ss);
+
+   //initialize particles
+   const real_t radius  = real_t(1.0);
+   auto smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+
+   data::Particle&& sp0        = *ps->create();
+   sp0.setPosition( Vec3(real_t(0), real_t(0), real_t(0)) );
+   sp0.setShapeID(  smallSphere );
+
+   auto dir = Vec3(real_t(1), real_t(2), real_t(3)).getNormalized();
+   real_t shift = real_c(0.75);
+   auto p1              = ps->create(true);
+   p1->setPosition( dir * shift );
+   p1->setShapeID(  ss->create<data::HalfSpace>( -dir ) );
+
+   collision_detection::AnalyticContactDetection         acd;
+   kernel::DoubleCast               double_cast;
+
+   bool isInContact = false;
+   isInContact = double_cast(0, 1, ac, acd, ac );
+
+   //check sphere - half space contact
+   WALBERLA_CHECK( isInContact );
+   WALBERLA_CHECK_EQUAL( acd.getIdx1(), 0);
+   WALBERLA_CHECK_EQUAL( acd.getIdx2(), 1);
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getContactPoint(), dir * shift * real_t(1.0));
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getContactNormal(), -dir );
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getPenetrationDepth(), shift - real_t(1) );
+
+   auto pos = Vec3(shift, real_t(0), real_t(0));
+   p1->setPosition(pos);
+   p1->setShapeID(  ss->create<data::HalfSpace>( -Vec3(real_t(1), real_t(0), real_t(0)) ) );
+   isInContact = double_cast(0, 1, ac, acd, ac );
+   WALBERLA_CHECK( isInContact );
+   WALBERLA_CHECK_EQUAL( acd.getIdx1(), 0);
+   WALBERLA_CHECK_EQUAL( acd.getIdx2(), 1);
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getContactPoint(), pos);
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getContactNormal(), -Vec3(real_t(1), real_t(0), real_t(0)) );
+   WALBERLA_CHECK_FLOAT_EQUAL( acd.getPenetrationDepth(), pos[0] - real_t(1) );
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   walberla::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   using namespace walberla;
+   using namespace walberla::mesa_pd;
+   walberla::mesa_pd::checkRotation( Vec3(real_t(1), real_t(0), real_t(0)),
+                                     Vec3(real_t(0), real_t(1), real_t(0)) );
+   walberla::mesa_pd::checkSphereSphereCollision();
+   walberla::mesa_pd::checkSphereHalfSpaceCollision();
+
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/common/IntersectionRatio.cpp b/tests/mesa_pd/common/IntersectionRatio.cpp
new file mode 100644
index 000000000..0451362c5
--- /dev/null
+++ b/tests/mesa_pd/common/IntersectionRatio.cpp
@@ -0,0 +1,201 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file IntersectionRatio.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/math/all.h"
+
+#include "mesa_pd/common/RayParticleIntersection.h"
+#include "mesa_pd/data/ParticleAccessor.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/kernel/SingleCast.h"
+
+namespace intersection_ratio_test
+{
+using namespace walberla;
+
+
+class ParticleAccessorWithShape : public mesa_pd::data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<mesa_pd::data::ParticleStorage>& ps, std::shared_ptr<mesa_pd::data::ShapeStorage>& ss)
+         : ParticleAccessor(ps)
+         , ss_(ss)
+   {}
+
+   mesa_pd::data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<mesa_pd::data::ShapeStorage> ss_;
+};
+
+
+/*!\brief Tests the ray-particle intersection ratio functionality of the RPDUtility.h in the lbm_rpd_coupling module
+ *
+ * Currently the following shapes are tested:
+ *  - sphere
+ *  - halfspace ( default and rotated )
+ *
+ * Additionally, the default variant with the bisection line search is tested with the help of a sphere.
+ */
+
+//////////
+// MAIN //
+//////////
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto shapeStorage = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor = ParticleAccessorWithShape;
+   ParticleAccessor accessor(ps, shapeStorage);
+
+   const real_t epsilon( real_t(1e-4) );
+
+   mesa_pd::kernel::SingleCast singleCast;
+   mesa_pd::RayParticleIntersectionRatioFunctor intersectionRatioFctr;
+
+   ////////////
+   // SPHERE //
+   ////////////
+   {
+      real_t sphereRadius = real_t(1);
+      auto sphereShape = shapeStorage->create<mesa_pd::data::Sphere>( sphereRadius );
+
+      Vector3<real_t> position(real_t(1), real_t(0), real_t(0));
+
+      mesa_pd::data::Particle&& p = *ps->create();
+      p.setPosition(position);
+      p.setShapeID(sphereShape);
+      auto idx = p.getIdx();
+
+      Vector3<real_t> pos1(real_t(-0.5), real_t(0), real_t(0));
+      Vector3<real_t> dir1(real_t(1), real_t(0), real_t(0));
+      real_t delta1 = singleCast(idx, accessor, intersectionRatioFctr, accessor, pos1, dir1, epsilon );
+      WALBERLA_CHECK_FLOAT_EQUAL(delta1, real_t(0.5), "Intersection ratio 1 with sphere wrong!");
+
+      Vector3<real_t> pos2(real_t(1), real_t(1), real_t(1));
+      Vector3<real_t> dir2(real_t(0), -real_t(1), -real_t(1));
+      real_t delta2 = singleCast(idx, accessor, intersectionRatioFctr, accessor, pos2, dir2, epsilon );
+      WALBERLA_CHECK_FLOAT_EQUAL(delta2, (std::sqrt(2) - real_t(1)) / std::sqrt(2), "Intersection ratio 2 with sphere wrong!");
+   }
+
+   ///////////////
+   // HALFSPACE //
+   ///////////////
+   {
+      Vector3<real_t> position(real_t(1), real_t(0), real_t(0));
+      Vector3<real_t> normal(real_t(0), real_t(1), real_t(1));
+
+      auto planeShape = shapeStorage->create<mesa_pd::data::HalfSpace>( normal.getNormalized() );
+
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(position);
+      p.setShapeID(planeShape);
+      auto idx = p.getIdx();
+
+      Vector3<real_t> pos1(real_t(1), real_t(0.5), real_t(0.5));
+      Vector3<real_t> dir1(real_t(0), -real_t(1), -real_t(1));
+      real_t delta1 = singleCast(idx, accessor, intersectionRatioFctr, accessor, pos1, dir1, epsilon );
+      WALBERLA_CHECK_FLOAT_EQUAL(delta1, real_t(0.5), "Intersection ratio 1 with half space wrong!");
+
+      Vector3<real_t> dir2(real_t(0), real_t(0), -real_t(2));
+      real_t delta2 = singleCast(idx, accessor, intersectionRatioFctr, accessor, pos1, dir2, epsilon );
+      WALBERLA_CHECK_FLOAT_EQUAL(delta2, real_t(0.5), "Intersection ratio 2 with half space wrong!");
+
+      Vector3<real_t> dir3(real_t(0), -real_t(3), real_t(0));
+      real_t delta3 = singleCast(idx, accessor, intersectionRatioFctr, accessor, pos1, dir3, epsilon );
+      WALBERLA_CHECK_FLOAT_EQUAL(delta3, real_t(1)/real_t(3), "Intersection ratio 3 with half space wrong!");
+   }
+
+   /////////////////////////
+   // HALFSPACE (rotated) //
+   /////////////////////////
+   {
+      Vector3<real_t> position(real_t(1), real_t(0), real_t(0));
+      Vector3<real_t> normal(real_t(0), real_t(0), real_t(1));
+
+      auto planeShape = shapeStorage->create<mesa_pd::data::HalfSpace>( normal.getNormalized() );
+
+      // rotate to same position as half space before
+      Vector3<real_t> rotationAngles( -math::M_PI / real_t(4), real_t(0), real_t(0));
+      Quaternion<real_t> quat( rotationAngles );
+
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(position);
+      p.setShapeID(planeShape);
+      p.setRotation(quat);
+      auto idx = p.getIdx();
+
+      Vector3<real_t> pos1(real_t(1), real_t(0.5), real_t(0.5));
+      Vector3<real_t> dir1(real_t(0), -real_t(1), -real_t(1));
+      real_t delta1 = singleCast(idx, accessor, intersectionRatioFctr, accessor, pos1, dir1, epsilon );
+      WALBERLA_CHECK_FLOAT_EQUAL(delta1, real_t(0.5), "Intersection ratio 1 with rotated half space wrong!");
+
+      Vector3<real_t> dir2(real_t(0), real_t(0), -real_t(2));
+      real_t delta2 = singleCast(idx, accessor, intersectionRatioFctr, accessor, pos1, dir2, epsilon );
+      WALBERLA_CHECK_FLOAT_EQUAL(delta2, real_t(0.5), "Intersection ratio 2 with rotated half space wrong!");
+
+      Vector3<real_t> dir3(real_t(0), -real_t(3), real_t(0));
+      real_t delta3 = singleCast(idx, accessor, intersectionRatioFctr, accessor, pos1, dir3, epsilon );
+      WALBERLA_CHECK_FLOAT_EQUAL(delta3, real_t(1)/real_t(3), "Intersection ratio 3 with rotated half space wrong!");
+   }
+
+   ///////////////////////////
+   // Bisection Line Search //
+   ///////////////////////////
+   {
+      // note: here tested with a sphere and therefore called explicitly because otherwise the sphere specialization would be selected
+
+      real_t sphereRadius = real_t(1);
+      auto sphereShape = shapeStorage->create<mesa_pd::data::Sphere>( sphereRadius );
+
+      Vector3<real_t> position(real_t(1), real_t(0), real_t(0));
+
+      mesa_pd::data::Particle&& p = *ps->create();
+      p.setPosition(position);
+      p.setShapeID(sphereShape);
+      auto idx = p.getIdx();
+
+      Vector3<real_t> pos1(real_t(-0.5), real_t(0), real_t(0));
+      Vector3<real_t> dir1(real_t(1), real_t(0), real_t(0));
+      real_t delta1 = mesa_pd::intersectionRatioBisection(idx, accessor, pos1, dir1, epsilon);
+      WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(delta1, real_t(0.5), epsilon, "Intersection ratio 1 with bisection line search for sphere wrong!");
+
+      Vector3<real_t> pos2(real_t(1), real_t(1), real_t(1));
+      Vector3<real_t> dir2(real_t(0), -real_t(1), -real_t(1));
+      real_t delta2 = mesa_pd::intersectionRatioBisection(idx, accessor, pos2, dir2, epsilon);
+      WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(delta2, (std::sqrt(2) - real_t(1)) / std::sqrt(2), epsilon, "Intersection ratio 2 with bisection line search for sphere wrong!");
+   }
+
+   return 0;
+
+}
+
+} //namespace intersection_ratio_test
+
+int main( int argc, char **argv ){
+   intersection_ratio_test::main(argc, argv);
+}
diff --git a/tests/mesa_pd/data/Flags.cpp b/tests/mesa_pd/data/Flags.cpp
new file mode 100644
index 000000000..e54cdcd85
--- /dev/null
+++ b/tests/mesa_pd/data/Flags.cpp
@@ -0,0 +1,57 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   Flags.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/Flags.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   using namespace walberla::mesa_pd::data::particle_flags;
+   FlagT flag;
+
+   WALBERLA_CHECK( !isSet(flag, INFINITE) );
+   set(flag, INFINITE);
+
+   WALBERLA_CHECK( isSet(flag, INFINITE) );
+   unset(flag, INFINITE);
+
+   WALBERLA_CHECK( !isSet(flag, INFINITE) );
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/data/ParticleStorage.cpp b/tests/mesa_pd/data/ParticleStorage.cpp
new file mode 100644
index 000000000..ce3579292
--- /dev/null
+++ b/tests/mesa_pd/data/ParticleStorage.cpp
@@ -0,0 +1,87 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   ParticleStorage.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <algorithm>
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+void basic_test()
+{
+   //init data structures
+   data::ParticleStorage ps(100);
+
+   ps.create();
+   ps.create();
+   ps.create();
+   ps.create();
+   ps.create();
+   ps.create();
+   ps.create();
+
+   WALBERLA_CHECK_EQUAL( ps.size(), 7);
+   for (size_t i = 0; i < ps.size(); ++i)
+   {
+      for (size_t j = 0; j < ps.size(); ++j)
+      {
+         if (i==j)
+         {
+            WALBERLA_CHECK_EQUAL(ps.getUid(i), ps.getUid(j));
+         } else
+         {
+            WALBERLA_CHECK_UNEQUAL(ps.getUid(i), ps.getUid(j));
+         }
+      }
+
+      auto it = ps.find(ps.getUid(i));
+      WALBERLA_CHECK_EQUAL( it->getUid(), ps.getUid(i));
+   }
+
+   auto it  = data::ParticleStorage::iterator(&ps, 3);
+   auto uid = ps.getUid(3);
+   ps.erase(it);
+   WALBERLA_CHECK_EQUAL( ps.size(), 6);
+   WALBERLA_CHECK_EQUAL( ps.find(uid), ps.end());
+}
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   basic_test();
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/domain/BlockForestDomain.cpp b/tests/mesa_pd/domain/BlockForestDomain.cpp
new file mode 100644
index 000000000..33d5a4f72
--- /dev/null
+++ b/tests/mesa_pd/domain/BlockForestDomain.cpp
@@ -0,0 +1,112 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   BlockForestDomain.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/domain/BlockForestDomain.h>
+
+#include <blockforest/loadbalancing/StaticCurve.h>
+#include <blockforest/Initialization.h>
+#include <blockforest/SetupBlockForest.h>
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+void main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+   auto rank = mpi::MPIManager::instance()->rank();
+
+   //   logging::Logging::instance()->setStreamLogLevel(logging::Logging::DETAIL);
+   //   logging::Logging::instance()->includeLoggingToFile("MESA_PD_Kernel_SyncNextNeighbor");
+   //   logging::Logging::instance()->setFileLogLevel(logging::Logging::DETAIL);
+
+   //init domain partitioning
+   auto sforest = std::make_unique<blockforest::SetupBlockForest>( );
+   sforest->addWorkloadMemorySUIDAssignmentFunction( blockforest::uniformWorkloadAndMemoryAssignment );
+   sforest->init( math::AABB(0,0,0,10,10,10), 2, 1, 1 , true, true, true);
+
+   WALBERLA_LOG_INFO_ON_ROOT( "Balancing " << sforest->getNumberOfBlocks() << " blocks for " << 2 << " processes...");
+
+   sforest->balanceLoad( blockforest::StaticLevelwiseCurveBalance(true), 2, real_t(0), memory_t(0), false, true );
+
+   auto forest = std::make_shared< BlockForest >( uint_c( walberla::MPIManager::instance()->rank() ), *sforest, false );
+
+   domain::BlockForestDomain domain(forest);
+
+   auto neighbors = domain.getNeighborProcesses();
+   WALBERLA_CHECK_EQUAL(neighbors.size(), 1);
+   WALBERLA_CHECK_EQUAL(neighbors[0], rank == 0 ? 1 : 0);
+
+   WALBERLA_CHECK_EQUAL(domain.findContainingProcessRank(Vec3(2,2,2)), 0);
+   WALBERLA_CHECK_EQUAL(domain.findContainingProcessRank(Vec3(7,7,7)), 1);
+
+   WALBERLA_CHECK(domain.isContainedInProcessSubdomain(0, Vec3(2,2,2)));
+   WALBERLA_CHECK(domain.isContainedInProcessSubdomain(1, Vec3(7,7,7)));
+
+   WALBERLA_CHECK_EQUAL(domain.isContainedInProcessSubdomain(Vec3(2,2,2), real_t(1)), rank == 0 ? true : false);
+   WALBERLA_CHECK_EQUAL(domain.isContainedInProcessSubdomain(Vec3(7,7,7), real_t(1)), rank == 0 ? false : true);
+
+   WALBERLA_CHECK_EQUAL(domain.isContainedInProcessSubdomain(Vec3(real_t(4.5),2,2), real_t(1)), rank == 0 ? false : false);
+   WALBERLA_CHECK_EQUAL(domain.isContainedInProcessSubdomain(Vec3(real_t(5.5),7,7), real_t(1)), rank == 0 ? false : false);
+
+   if (rank != 0)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(0, Vec3(real_t(2),2,2), real_t(1)), true);
+   if (rank != 1)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(1, Vec3(real_t(2),2,2), real_t(1)), false);
+
+   if (rank != 0)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(0, Vec3(real_t(7),2,2), real_t(1)), false);
+   if (rank != 1)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(1, Vec3(real_t(7),2,2), real_t(1)), true);
+
+   if (rank != 0)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(0, Vec3(real_t(5.5),2,2), real_t(1)), true);
+   if (rank != 1)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(1, Vec3(real_t(5.5),2,2), real_t(1)), true);
+
+   if (rank != 0)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(0, Vec3(real_t(4.5),2,2), real_t(1)), true);
+   if (rank != 1)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(1, Vec3(real_t(4.5),2,2), real_t(1)), true);
+
+   if (rank != 0)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(0, Vec3(real_t(9.5),2,2), real_t(1)), true);
+   if (rank != 1)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(1, Vec3(real_t(9.5),2,2), real_t(1)), true);
+
+   if (rank != 0)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(0, Vec3(real_t(0.5),2,2), real_t(1)), true);
+   if (rank != 1)
+      WALBERLA_CHECK_EQUAL(domain.intersectsWithProcessSubdomain(1, Vec3(real_t(0.5),2,2), real_t(1)), true);
+}
+
+}
+
+int main( int argc, char ** argv )
+{
+   walberla::main(argc, argv);
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/domain/BlockForestSync.cpp b/tests/mesa_pd/domain/BlockForestSync.cpp
new file mode 100644
index 000000000..b0fe8cf5d
--- /dev/null
+++ b/tests/mesa_pd/domain/BlockForestSync.cpp
@@ -0,0 +1,118 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   BlockForestSync.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/math/Random.h>
+#include <core/mpi/Reduce.h>
+
+#include <iostream>
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+
+void sync()
+{
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,2,2,2), // simulation domain
+                                                 Vector3<uint_t>(2,2,2), // blocks in each direction
+                                                 Vector3<bool>(false, false, false) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+
+   //init data structures
+   data::ParticleStorage ps(100);
+
+   //initialize particles
+   real_t spacing = real_c(1);
+   for (auto& iBlk : *forest)
+   {
+      for (auto pt : grid_generator::SCGrid(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing))
+      {
+         WALBERLA_CHECK(iBlk.getAABB().contains(pt));
+
+         auto p                       = ps.create();
+         p->getPositionRef()          = pt;
+         p->getInteractionRadiusRef() = spacing * real_t(0.5);
+         p->getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+         p->getTypeRef()              = 0;
+         p->getLinearVelocityRef()    = (forest->getDomain().center() - p->getPosition()).getNormalized() * real_t(0.1);
+      }
+   }
+
+   //init kernels
+   mpi::SyncNextNeighbors SNN;
+   SNN(ps, domain);
+
+   //WALBERLA_CHECK_EQUAL(ps.size(), 4);
+
+   for (auto i = 0; i < 100; ++i)
+   {
+      for (auto p : ps)
+      {
+         p.getPositionRef() += p.getLinearVelocity();
+      }
+      SNN(ps, domain);
+
+      for (auto p : ps)
+      {
+         using namespace data::particle_flags;
+         if (isSet(p->getFlags(), GHOST))
+         {
+            WALBERLA_CHECK_UNEQUAL(p.getOwner(), walberla::mpi::MPIManager::instance()->rank(), p);
+         } else
+         {
+            WALBERLA_CHECK_EQUAL(p.getOwner(), walberla::mpi::MPIManager::instance()->rank(), p);
+         }
+      }
+   }
+
+   WALBERLA_CHECK_EQUAL(ps.size(), 0);
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+/**
+ * Particles on a regular grid with fly through the domain
+ *
+ * The domain is a 2x2x2 grid. Particles get initialized to cross the domain.
+ * Correct migration is checked.
+ * Also fewer processes than blocks is checked.
+ */
+int main( int argc, char ** argv )
+{
+   using namespace walberla;
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   walberla::mesa_pd::sync();
+
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/domain/BlockForestSyncPeriodic.cpp b/tests/mesa_pd/domain/BlockForestSyncPeriodic.cpp
new file mode 100644
index 000000000..8523ec9fb
--- /dev/null
+++ b/tests/mesa_pd/domain/BlockForestSyncPeriodic.cpp
@@ -0,0 +1,116 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   BlockForestSync.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/math/Random.h>
+#include <core/mpi/Reduce.h>
+
+#include <iostream>
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+
+void periodicSync()
+{
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,2,2,2), // simulation domain
+                                                 Vector3<uint_t>(2,2,2), // blocks in each direction
+                                                 Vector3<bool>(true, true, true) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+
+   //init data structures
+   data::ParticleStorage ps(100);
+
+   //initialize particles
+   real_t spacing = real_c(1);
+   for (auto& iBlk : *forest)
+   {
+      for (auto pt : grid_generator::SCGrid(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing))
+      {
+         WALBERLA_CHECK(iBlk.getAABB().contains(pt));
+
+         auto p                       = ps.create();
+         p->getPositionRef()          = pt;
+         p->getInteractionRadiusRef() = spacing * real_t(0.5);
+         p->getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+         p->getTypeRef()              = 0;
+         p->getLinearVelocityRef()    = (forest->getDomain().center() - p->getPosition()).getNormalized() * real_t(0.1);
+      }
+   }
+
+   //init kernels
+   mpi::SyncNextNeighbors SNN;
+   SNN(ps, domain);
+
+   for (auto i = 0; i < 100; ++i)
+   {
+      for (auto p : ps)
+      {
+         p.getPositionRef() += p.getLinearVelocity();
+      }
+      SNN(ps, domain);
+
+      for (auto p : ps)
+      {
+         using namespace data::particle_flags;
+         if (isSet(p->getFlags(), GHOST))
+         {
+            WALBERLA_CHECK_UNEQUAL(p.getOwner(), walberla::mpi::MPIManager::instance()->rank(), p);
+         } else
+         {
+            WALBERLA_CHECK_EQUAL(p.getOwner(), walberla::mpi::MPIManager::instance()->rank(), p);
+         }
+      }
+   }
+
+   WALBERLA_CHECK_UNEQUAL(ps.size(), 0);
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+/**
+ * Particles on a regular grid with fly through the domain
+ *
+ * The domain is a 2x2x2 grid. Particles get initialized to cross the domain.
+ * Correct migration is checked.
+ * Also fewer processes than blocks is checked.
+ */
+int main( int argc, char ** argv )
+{
+   using namespace walberla;
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   walberla::mesa_pd::periodicSync();
+
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/domain/DistanceCalculation.cpp b/tests/mesa_pd/domain/DistanceCalculation.cpp
new file mode 100644
index 000000000..ef4fffa00
--- /dev/null
+++ b/tests/mesa_pd/domain/DistanceCalculation.cpp
@@ -0,0 +1,81 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   DistanceCalculation.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/domain/BlockForestDomain.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+void main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   using namespace walberla::mesa_pd;
+
+   WALBERLA_CHECK_FLOAT_EQUAL( sqDistanceLineToPoint(real_t(0.7), real_t(0.9), real_t(1.2)) + real_t(1),
+                               real_t(0.04) + real_t(1) );
+   WALBERLA_CHECK_FLOAT_EQUAL( sqDistanceLineToPoint(real_t(1.0), real_t(0.9), real_t(1.2)) + real_t(1),
+                               real_t(0) + real_t(1) );
+   WALBERLA_CHECK_FLOAT_EQUAL( sqDistanceLineToPoint(real_t(1.5), real_t(0.9), real_t(1.2)) + real_t(1),
+                               real_t(0.09) + real_t(1) );
+
+   WALBERLA_CHECK_FLOAT_EQUAL( sqDistancePointToAABB( Vec3(1,1,1),
+                                                      math::AABB(real_t(2),real_t(2),real_t(2),real_t(3),real_t(3),real_t(3)) ) + real_t(1),
+                               real_t(3) + real_t(1) );
+
+   WALBERLA_CHECK_FLOAT_EQUAL( sqDistancePointToAABB( Vec3(real_t(0.5),real_t(0.5),real_t(0.5)),
+                                                      math::AABB(real_t(2),real_t(2),real_t(2),real_t(3),real_t(3),real_t(3)) ) + real_t(1),
+                               real_t(6.75) + real_t(1) );
+
+   WALBERLA_CHECK_FLOAT_EQUAL( sqDistancePointToAABBPeriodic( Vec3(real_t(0.5),real_t(0.5),real_t(0.5)),
+                                                              math::AABB(real_t(2),real_t(2),real_t(2),real_t(3),real_t(3),real_t(3)),
+                                                              math::AABB(real_t(1),real_t(1),real_t(1),real_t(3),real_t(3),real_t(3)),
+                                                              std::array< bool, 3 >{{false, false, false}}) + real_t(1),
+                               real_t(6.75) + real_t(1) );
+
+   WALBERLA_CHECK_FLOAT_EQUAL( sqDistancePointToAABBPeriodic( Vec3(real_t(0.5),real_t(0.5),real_t(0.5)),
+                                                              math::AABB(real_t(2),real_t(2),real_t(2),real_t(3),real_t(3),real_t(3)),
+                                                              math::AABB(real_t(1),real_t(1),real_t(1),real_t(3),real_t(3),real_t(3)),
+                                                              std::array< bool, 3 >{{true, true, true}}) + real_t(1),
+                               real_t(0) + real_t(1) );
+
+   WALBERLA_CHECK_FLOAT_EQUAL( sqDistancePointToAABBPeriodic( Vec3(real_t(1.1),real_t(1.1),real_t(1.1)),
+                                                              math::AABB(real_t(2),real_t(2),real_t(2),real_t(3),real_t(3),real_t(3)),
+                                                              math::AABB(real_t(1),real_t(1),real_t(1),real_t(3),real_t(3),real_t(3)),
+                                                              std::array< bool, 3 >{{true, true, true}}) + real_t(1),
+                               real_t(0.03) + real_t(1) );
+}
+
+}
+
+int main( int argc, char ** argv )
+{
+   walberla::main(argc, argv);
+
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/domain/DynamicRefinement.cpp b/tests/mesa_pd/domain/DynamicRefinement.cpp
new file mode 100644
index 000000000..1696d2485
--- /dev/null
+++ b/tests/mesa_pd/domain/DynamicRefinement.cpp
@@ -0,0 +1,164 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file DynamicRefinement.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/domain/BlockForestDataHandling.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/domain/InfoCollection.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <blockforest/Initialization.h>
+#include <blockforest/loadbalancing/DynamicCurve.h>
+#include <core/debug/TestSubsystem.h>
+#include <core/logging/Logging.h>
+#include <pe/amr/InfoCollection.h>
+#include <pe/amr/level_determination/MinMaxLevelDetermination.h>
+#include <pe/amr/weight_assignment/WeightAssignmentFunctor.h>
+
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+///switches between level 0 and 1 in for every call
+class ReGrid_Switcher
+{
+public:
+   void operator()( std::vector< std::pair< const Block *, uint_t > > & minTargetLevels,
+                    std::vector< const Block * > &, const BlockForest & forest );
+};
+
+void ReGrid_Switcher::operator()( std::vector< std::pair< const Block *, uint_t > > & minTargetLevels,
+                                  std::vector< const Block * > &, const BlockForest & /*forest*/ )
+{
+   for( auto it = minTargetLevels.begin(); it != minTargetLevels.end(); ++it )
+   {
+      it->second = it->first->getLevel() == 0 ? 1 : 0;
+      WALBERLA_LOG_DETAIL("setting block to level: " << it->second);
+   }
+}
+
+void createSphere(data::ParticleStorage& ps, domain::IDomain& domain, const Vec3& pos)
+{
+   auto owned = domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), pos );
+   if (owned)
+   {
+      data::Particle&& p          = *ps.create();
+      p.getPositionRef()          = pos;
+      p.getInteractionRadiusRef() = real_t(1);
+      p.getOwnerRef()             = mpi::MPIManager::instance()->rank();
+   }
+}
+
+/// This tests starts with one block located on one process.
+/// It then generates 1 particle in every quadrant.
+/// Load balancing is setup such that it will subdivide the block into 8 subblocks.
+/// Correct transmission of all particles is checked
+/// Blocks are coarsened again and particles are checked.
+int main( bool simple )
+{
+   //   logging::Logging::instance()->setStreamLogLevel(logging::Logging::DETAIL);
+
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,10,10,10), // simulation domain
+                                                 Vector3<uint_t>(1,1,1), // blocks in each direction
+                                                 Vector3<bool>(false, false, false) // periodicity
+                                                 );
+
+   forest->recalculateBlockLevelsInRefresh( true );
+   forest->alwaysRebalanceInRefresh( false );
+   forest->reevaluateMinTargetLevelsAfterForcedRefinement( false );
+   forest->allowRefreshChangingDepth( true );
+
+   forest->allowMultipleRefreshCycles( false );
+   forest->checkForEarlyOutInRefresh( true );
+   forest->checkForLateOutInRefresh( true );
+
+   auto infoCollection = make_shared<pe::InfoCollection>();
+
+   if (simple)
+   {
+      // use ReGrid_Switcher
+      forest->setRefreshMinTargetLevelDeterminationFunction( ReGrid_Switcher() );
+      forest->setRefreshPhantomBlockMigrationPreparationFunction(
+               blockforest::DynamicCurveBalance< blockforest::NoPhantomData >( false, true, false ) );
+   } else
+   {
+      forest->setRefreshMinTargetLevelDeterminationFunction( pe::amr::MinMaxLevelDetermination(infoCollection, 2, 5) );
+
+      forest->setRefreshPhantomBlockDataAssignmentFunction( pe::amr::WeightAssignmentFunctor( infoCollection ) );
+      forest->setRefreshPhantomBlockDataPackFunction( pe::amr::WeightAssignmentFunctor::PhantomBlockWeightPackUnpackFunctor() );
+      forest->setRefreshPhantomBlockDataUnpackFunction( pe::amr::WeightAssignmentFunctor::PhantomBlockWeightPackUnpackFunctor() );
+
+      forest->setRefreshPhantomBlockMigrationPreparationFunction(
+               blockforest::DynamicCurveBalance< pe::amr::WeightAssignmentFunctor::PhantomBlockWeight >( false, true, false ) );
+   }
+
+   auto ps = std::make_shared<data::ParticleStorage> (100);
+   data::ParticleAccessor ac(ps);
+   forest->addBlockData(domain::createBlockForestDataHandling(ps), "Storage");
+
+   domain::BlockForestDomain domain(forest);
+   // creating particles
+   createSphere(*ps, domain, Vec3(real_t(2.5), real_t(2.5), real_t(2.5)));
+   createSphere(*ps, domain, Vec3(real_t(2.5), real_t(2.5), real_t(7.5)));
+   createSphere(*ps, domain, Vec3(real_t(2.5), real_t(7.5), real_t(2.5)));
+   createSphere(*ps, domain, Vec3(real_t(2.5), real_t(7.5), real_t(7.5)));
+   createSphere(*ps, domain, Vec3(real_t(7.5), real_t(2.5), real_t(2.5)));
+   createSphere(*ps, domain, Vec3(real_t(7.5), real_t(2.5), real_t(7.5)));
+   createSphere(*ps, domain, Vec3(real_t(7.5), real_t(7.5), real_t(2.5)));
+   createSphere(*ps, domain, Vec3(real_t(7.5), real_t(7.5), real_t(7.5)));
+
+   //***** TESTING *****
+   WALBERLA_CHECK_EQUAL( forest->size(), mpi::MPIManager::instance()->rank() == 0 ? 1 : 0 );
+   WALBERLA_CHECK_EQUAL( ps->size(), mpi::MPIManager::instance()->rank() == 0 ? 8 : 0 );
+
+   domain::createWithNeighborhood(ac, *forest, *infoCollection);
+   forest->refresh();
+   WALBERLA_CHECK_EQUAL( forest->size(), 1 );
+   WALBERLA_CHECK_EQUAL( ps->size(), 1 );
+   WALBERLA_CHECK_FLOAT_EQUAL(ps->getPosition(0), forest->begin()->getAABB().center());
+
+   if (!simple)
+   {
+      forest->setRefreshMinTargetLevelDeterminationFunction( pe::amr::MinMaxLevelDetermination(infoCollection, 2, 9) );
+   }
+   domain::createWithNeighborhood(ac, *forest, *infoCollection);
+   forest->refresh();
+   WALBERLA_CHECK_EQUAL( forest->size(), mpi::MPIManager::instance()->rank() == 0 ? 1 : 0 );
+   WALBERLA_CHECK_EQUAL( ps->size(), mpi::MPIManager::instance()->rank() == 0 ? 8 : 0 );
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace walberla
+
+int main( int argc, char* argv[] )
+{
+   walberla::debug::enterTestMode();
+   walberla::MPIManager::instance()->initializeMPI( &argc, &argv );
+
+   WALBERLA_UNUSED(argc);
+   WALBERLA_UNUSED(argv);
+
+   walberla::main( true );
+   walberla::main( false );
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/domain/SerializeDeserialize.cpp b/tests/mesa_pd/domain/SerializeDeserialize.cpp
new file mode 100644
index 000000000..11d543102
--- /dev/null
+++ b/tests/mesa_pd/domain/SerializeDeserialize.cpp
@@ -0,0 +1,169 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file DynamicRefinement.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/domain/BlockForestDataHandling.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+
+#include <blockforest/Initialization.h>
+#include <core/debug/TestSubsystem.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/mpi/Reduce.h>
+#include <core/logging/Logging.h>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+void createDump()
+{
+   const real_t spacing = real_c(1);
+   const real_t radius  = real_c(0.5);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** MESA_PD ***");
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+
+   auto  smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** BLOCKFOREST ***");
+   // create forest
+   auto forest = blockforest::createBlockForest( math::AABB(real_t(0),
+                                                            real_t(0),
+                                                            real_t(0),
+                                                            real_t(6),
+                                                            real_t(6),
+                                                            real_t(6)),
+                                                 Vector3<uint_t>(2,2,2),
+                                                 Vector3<bool>(true, true, true) );
+   forest->saveToFile("SerializeDeserialize.sbf");
+
+   auto bfDataHandlingID = forest->addBlockData(domain::createBlockForestDataHandling(ps), "BFDataHandling");
+
+   WALBERLA_CHECK_EQUAL(forest->size(), 1, "please run with 8 processes -> 1 process per block");
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - START ***");
+   //add one global particle
+   {
+      auto p = ps->create();
+      p->setPosition( Vec3(real_t(1)) );
+      data::particle_flags::set( p->getFlagsRef(), data::particle_flags::GLOBAL);
+      p->setInteractionRadius( radius );
+      p->setShapeID( smallSphere );
+   }
+
+   for (auto& iBlk : *forest)
+   {
+      for (auto pt : grid_generator::SCGrid(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing))
+      {
+         WALBERLA_CHECK(iBlk.getAABB().contains(pt));
+
+         auto p                       = ps->create();
+         p->setPosition( pt );
+         p->setInteractionRadius( radius );
+         p->setShapeID( smallSphere );
+         p->setOwner( mpi::MPIManager::instance()->rank() );
+      }
+   }
+   int64_t numParticles = int64_c(ps->size());
+   mpi::reduceInplace(numParticles, mpi::SUM);
+   WALBERLA_LOG_INFO_ON_ROOT("#particles created: " << numParticles);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** SETUP - END ***");
+
+   WALBERLA_CHECK_EQUAL( ps->size(), 28 );
+
+   WALBERLA_LOG_DEVEL_ON_ROOT("dumping simulation");
+   forest->saveBlockData("SerializeDeserialize.dump", bfDataHandlingID);
+
+   WALBERLA_CHECK_EQUAL( ps->size(), 28 );
+}
+
+void checkDump()
+{
+   const real_t spacing = real_c(1);
+   const real_t radius  = real_c(0.5);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** MESA_PD ***");
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+
+   auto  smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** BLOCKFOREST ***");
+   auto forest = make_shared< BlockForest >( uint_c( MPIManager::instance()->rank() ), "SerializeDeserialize.sbf", true, false );
+
+   //add one global particle
+   {
+      auto p = ps->create();
+      p->setPosition( Vec3(real_t(1)) );
+      data::particle_flags::set( p->getFlagsRef(), data::particle_flags::GLOBAL);
+      p->setInteractionRadius( radius );
+      p->setShapeID( smallSphere );
+   }
+
+   WALBERLA_CHECK_EQUAL(ps->size(), 1);
+   forest->loadBlockData("SerializeDeserialize.dump", domain::createBlockForestDataHandling(ps), "BFDataHandling");
+   WALBERLA_CHECK_EQUAL(ps->size(), 28);
+
+   WALBERLA_LOG_INFO_ON_ROOT("*** CHECKING ***");
+   auto pIt = ++ps->begin();
+   for (auto& iBlk : *forest)
+   {
+      for (auto pt : grid_generator::SCGrid(iBlk.getAABB(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.5), spacing))
+      {
+         WALBERLA_CHECK(iBlk.getAABB().contains(pt));
+         WALBERLA_CHECK_UNEQUAL(pIt, ps->end()); //still particles left
+         WALBERLA_CHECK_FLOAT_EQUAL(pIt->getPosition(), pt);
+         ++pIt;
+      }
+   }
+   WALBERLA_CHECK_EQUAL(pIt, ps->end()); //all particles have been checked
+}
+
+int main( int argc, char ** argv )
+{
+   walberla::debug::enterTestMode();
+
+   WALBERLA_MPI_SECTION()
+   {
+      walberla::MPIManager::instance()->initializeMPI( &argc, &argv );
+   }
+
+   WALBERLA_LOG_DEVEL_ON_ROOT("*** DUMPING ***");
+   createDump();
+   WALBERLA_MPI_SECTION()
+   {
+      WALBERLA_MPI_BARRIER();
+   }
+   WALBERLA_LOG_DEVEL_ON_ROOT("*** CHECKING ***");
+   checkDump();
+
+   return EXIT_SUCCESS;
+}
+} // namespace walberla
+
+int main( int argc, char* argv[] )
+{
+   return walberla::main( argc, argv );
+}
diff --git a/tests/mesa_pd/kernel/ClearNextNeighborSync.cpp b/tests/mesa_pd/kernel/ClearNextNeighborSync.cpp
new file mode 100644
index 000000000..19154dc7b
--- /dev/null
+++ b/tests/mesa_pd/kernel/ClearNextNeighborSync.cpp
@@ -0,0 +1,111 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   ClearNextNeighborSync.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/mpi/ClearNextNeighborSync.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+#include <core/mpi/Reduce.h>
+
+#include <iostream>
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+
+const real_t radius = real_t(1);
+
+void createSphere(data::ParticleStorage& ps, domain::IDomain& domain, const Vec3& pos)
+{
+   auto owned = domain.isContainedInProcessSubdomain( uint_c(walberla::mpi::MPIManager::instance()->rank()), pos );
+   if (owned)
+   {
+      data::Particle&& p          = *ps.create();
+      p.getPositionRef()          = pos;
+      p.getInteractionRadiusRef() = radius;
+      p.getRotationRef()          = Rot3(Quat());
+      p.getLinearVelocityRef()    = Vec3(1,2,3);
+      p.getAngularVelocityRef()   = Vec3(4,5,6);
+      p.getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+   }
+}
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,10,5,5), // simulation domain
+                                                 Vector3<uint_t>(2,1,1), // blocks in each direction
+                                                 Vector3<bool>(false, false, false) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+   std::array< bool, 3 > periodic;
+   periodic[0] = forest->isPeriodic(0);
+   periodic[1] = forest->isPeriodic(1);
+   periodic[2] = forest->isPeriodic(2);
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage> (100);
+   data::ParticleAccessor ac(ps);
+
+   //initialize particle
+   createSphere(*ps, domain, Vec3(real_t(4.5),3,3));
+   createSphere(*ps, domain, Vec3(real_t(5.5),3,3));
+
+   //init kernels
+   mpi::ClearNextNeighborSync CNNS;
+   mpi::SyncNextNeighbors     SNN;
+
+   //init
+   WALBERLA_CHECK_EQUAL(ps->size(), 1);
+   WALBERLA_CHECK_EQUAL(ps->getGhostOwners(0).size(), 0);
+
+   //sync
+   SNN(*ps, domain);
+
+   WALBERLA_CHECK_EQUAL(ps->size(), 2);
+   WALBERLA_CHECK_EQUAL(ps->getGhostOwners(0).size(), 1);
+   WALBERLA_CHECK(data::particle_flags::isSet(ps->getFlags(1), data::particle_flags::GHOST));
+
+   //clear
+   CNNS(ac);
+
+   WALBERLA_CHECK_EQUAL(ps->size(), 1);
+   WALBERLA_CHECK_EQUAL(ps->getGhostOwners(0).size(), 0);
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::mesa_pd::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/CoefficientOfRestitutionLSD.cpp b/tests/mesa_pd/kernel/CoefficientOfRestitutionLSD.cpp
new file mode 100644
index 000000000..7f9ff9028
--- /dev/null
+++ b/tests/mesa_pd/kernel/CoefficientOfRestitutionLSD.cpp
@@ -0,0 +1,197 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file DEMIntegratorAccuracy.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+
+#include "mesa_pd/data/ParticleAccessor.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/VelocityVerlet.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/LinearSpringDashpot.h"
+
+#include "core/Environment.h"
+#include "core/logging/Logging.h"
+
+#include <string>
+
+namespace dem_integrator_accuracy {
+
+using namespace walberla;
+using namespace walberla::mesa_pd;
+
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+         : ParticleAccessor(ps)
+         , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+
+/*
+ * Tests the integrator accuracy for a DEM simulation by comparing the given coefficient of restitution to the simulated one.
+ * For that, the velocity after a single sphere-wall collision is divided by the initial velocity before the simulation.
+ * The parameters of the DEM are chosen such as to (analytically) yield the desried coefficient of restitution.
+ *
+ * The simulation can be adapted via command line arguments.
+ *
+ * Currently compared integrators:
+ *  - explicit euler (default)
+ *  - velocity verlet (--useVV)
+ */
+int main( int argc, char** argv )
+{
+   mpi::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   // parameters
+   real_t radius = real_t(5);
+   real_t dt = real_t(0.1);
+   real_t restitutionCoeff = real_t(0.83);
+   real_t densitySphere = real_t(1.5);
+   real_t collisionTime = real_t(10);
+   bool useVelocityVerlet = false;
+   real_t uIn = real_t(0.1);
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--useVV" )   == 0 ) { useVelocityVerlet = true; continue; }
+      if( std::strcmp( argv[i], "--dt" )      == 0 ) { dt = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--Tc" )      == 0 ) { collisionTime = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--radius" )  == 0 ) { radius = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--e" )       == 0 ) { restitutionCoeff = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--density" ) == 0 ) { densitySphere = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--uIn" )     == 0 ) { uIn = real_c(std::atof( argv[++i] )); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   //init data structures
+   auto ps = walberla::make_shared<data::ParticleStorage>(2);
+   auto ss = walberla::make_shared<data::ShapeStorage>();
+   using ParticleAccessor_T = ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   auto sphereShape = ss->create<data::Sphere>( radius );
+   ss->shapes[sphereShape]->updateMassAndInertia(densitySphere);
+
+   const real_t particleMass = real_t(1) / ss->shapes[sphereShape]->getInvMass();
+   const real_t Mij = particleMass; // Mij = M for sphere-wall collision
+   const real_t lnDryResCoeff = std::log(restitutionCoeff);
+   const real_t stiffnessN = math::M_PI * math::M_PI * Mij / ( collisionTime * collisionTime * ( real_t(1) - lnDryResCoeff * lnDryResCoeff / ( math::M_PI * math::M_PI + lnDryResCoeff* lnDryResCoeff ))  );
+   const real_t dampingN = - real_t(2) * std::sqrt( Mij * stiffnessN ) * ( lnDryResCoeff / std::sqrt( math::M_PI * math::M_PI + ( lnDryResCoeff * lnDryResCoeff ) ) );
+
+   WALBERLA_LOG_INFO("dt = " << dt << ", Tc = " << collisionTime << ", coefficient of restitution = " << restitutionCoeff);
+   WALBERLA_LOG_INFO(" -> mass " << particleMass << ", collision duration = " << collisionTime / dt << ", stiffness = " << stiffnessN << ", damping = " << dampingN);
+
+   // create sphere
+   auto pos = Vec3(0,0,radius);
+   auto linVel = Vec3(0,0,-uIn);
+   data::Particle&& p = *ps->create();
+   p.setPosition(pos);
+   p.setLinearVelocity(linVel);
+   p.setShapeID(sphereShape);
+   p.setType(0);
+   p.setForce(Vec3(0.));
+   p.setOldForce(Vec3(0.));
+
+   // create plane
+   data::Particle&& p0 = *ps->create(true);
+   p0.setPosition(Vec3(0,0,0));
+   p0.setShapeID(ss->create<data::HalfSpace>( Vector3<real_t>(0,0,1)) );
+   p0.setType(0);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::INFINITE);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::FIXED);
+
+   collision_detection::AnalyticContactDetection acd;
+   kernel::DoubleCast       double_cast;
+
+   kernel::ExplicitEulerWithShape explEuler(dt);
+   kernel::VelocityVerletPreForceUpdate  vvPreForce( dt );
+   kernel::VelocityVerletPostForceUpdate vvPostForce( dt );
+
+   kernel::LinearSpringDashpot dem(1);
+   dem.setStiffnessN(0,0,stiffnessN);
+   dem.setDampingN(0,0,dampingN);
+
+   uint_t steps = 0;
+   real_t maxPenetration = real_t(0);
+   do
+   {
+      if(useVelocityVerlet) vvPreForce(0,*accessor);
+
+      if (double_cast(0, 1, *accessor, acd, *accessor ))
+      {
+         real_t penetration = acd.getPenetrationDepth();
+         maxPenetration = std::max( maxPenetration, std::abs(penetration));
+
+         dem(acd.getIdx1(), acd.getIdx2(), *accessor, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth(), dt);
+      }
+      auto force = accessor->getForce(0);
+
+      if(useVelocityVerlet) vvPostForce(0,*accessor);
+      else explEuler(0, *accessor);
+
+      WALBERLA_LOG_INFO(steps << ": penetration = " << acd.getPenetrationDepth() << " || vel = " << accessor->getLinearVelocity(0)[2] << " || force = " << force[2]);
+
+      ++steps;
+   } while (double_cast(0, 1, *accessor, acd, *accessor ));
+
+   real_t simulatedCoefficientOfRestitution = -accessor->getLinearVelocity(0)[2] / linVel[2];
+   real_t relativeError = ( simulatedCoefficientOfRestitution - restitutionCoeff ) / restitutionCoeff;
+   WALBERLA_LOG_INFO("coefficient of restitution = " << simulatedCoefficientOfRestitution << " -> error = " << relativeError * 100. << "%");
+   WALBERLA_LOG_INFO("collision steps = " << steps);
+   WALBERLA_LOG_INFO("Max penetration = " << maxPenetration << " -> " << maxPenetration / radius * 100. << "% of radius");
+
+   if( useVelocityVerlet )
+   {
+      WALBERLA_CHECK_LESS(relativeError, real_t(0.01), "Error in simulated coefficient of restitution too large: " << simulatedCoefficientOfRestitution << " vs ref " << restitutionCoeff);
+   }
+   else
+   {
+      WALBERLA_CHECK_LESS(relativeError, real_t(0.03), "Error in simulated coefficient of restitution too large: " << simulatedCoefficientOfRestitution << " vs ref " << restitutionCoeff);
+   }
+
+
+   return EXIT_SUCCESS;
+}
+} // namespace dem_integrator_accuracy
+
+int main( int argc, char** argv )
+{
+   return dem_integrator_accuracy::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/CoefficientOfRestitutionNLSD.cpp b/tests/mesa_pd/kernel/CoefficientOfRestitutionNLSD.cpp
new file mode 100644
index 000000000..c6c996a9a
--- /dev/null
+++ b/tests/mesa_pd/kernel/CoefficientOfRestitutionNLSD.cpp
@@ -0,0 +1,201 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file DEMIntegratorAccuracy.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+
+#include "mesa_pd/data/ParticleAccessor.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/VelocityVerlet.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/NonLinearSpringDashpot.h"
+#include "mesa_pd/mpi/ReduceContactHistory.h"
+
+#include "core/Environment.h"
+#include "core/logging/Logging.h"
+
+#include <string>
+
+namespace dem_integrator_accuracy {
+
+using namespace walberla;
+using namespace walberla::mesa_pd;
+
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+         : ParticleAccessor(ps)
+         , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+
+/*
+ * Tests the integrator accuracy for a DEM simulation by comparing the given coefficient of restitution to the simulated one.
+ * For that, the velocity after a single sphere-wall collision is divided by the initial velocity before the simulation.
+ * The parameters of the DEM are chosen such as to (analytically) yield the desried coefficient of restitution.
+ *
+ * The simulation can be adapted via command line arguments.
+ *
+ * Currently compared integrators:
+ *  - explicit euler (default)
+ *  - velocity verlet (--useVV)
+ */
+int main( int argc, char** argv )
+{
+   walberla::mpi::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   // parameters
+   real_t radius = real_t(5);
+   real_t dt = real_t(0.1);
+   real_t restitutionCoeff = real_t(0.83);
+   real_t densitySphere = real_t(1.5);
+   real_t collisionTime = real_t(10);
+   bool useVelocityVerlet = false;
+   real_t uIn = real_t(0.1);
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--useVV" )   == 0 ) { useVelocityVerlet = true; continue; }
+      if( std::strcmp( argv[i], "--dt" )      == 0 ) { dt = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--Tc" )      == 0 ) { collisionTime = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--radius" )  == 0 ) { radius = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--e" )       == 0 ) { restitutionCoeff = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--density" ) == 0 ) { densitySphere = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--uIn" )     == 0 ) { uIn = real_c(std::atof( argv[++i] )); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   //init data structures
+   auto ps = walberla::make_shared<data::ParticleStorage>(2);
+   auto ss = walberla::make_shared<data::ShapeStorage>();
+   using ParticleAccessor_T = ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   auto sphereShape = ss->create<data::Sphere>( radius );
+   ss->shapes[sphereShape]->updateMassAndInertia(densitySphere);
+
+   const real_t particleMass = real_t(1) / ss->shapes[sphereShape]->getInvMass();
+   const real_t Mij = particleMass; // Mij = M for sphere-wall collision
+   const real_t lnDryResCoeff = std::log(restitutionCoeff);
+   const real_t stiffnessN = math::M_PI * math::M_PI * Mij / ( collisionTime * collisionTime * ( real_t(1) - lnDryResCoeff * lnDryResCoeff / ( math::M_PI * math::M_PI + lnDryResCoeff* lnDryResCoeff ))  );
+   const real_t dampingN = - real_t(2) * std::sqrt( Mij * stiffnessN ) * ( lnDryResCoeff / std::sqrt( math::M_PI * math::M_PI + ( lnDryResCoeff * lnDryResCoeff ) ) );
+
+   WALBERLA_LOG_INFO("dt = " << dt << ", Tc = " << collisionTime << ", coefficient of restitution = " << restitutionCoeff);
+   WALBERLA_LOG_INFO(" -> mass " << particleMass << ", collision duration = " << collisionTime / dt << ", stiffness = " << stiffnessN << ", damping = " << dampingN);
+
+   // create sphere
+   auto pos = Vec3(0,0,radius);
+   auto linVel = Vec3(0,0,-uIn);
+   data::Particle&& p = *ps->create();
+   p.setPosition(pos);
+   p.setLinearVelocity(linVel);
+   p.setShapeID(sphereShape);
+   p.setType(0);
+   p.setForce(Vec3(0.));
+   p.setOldForce(Vec3(0.));
+
+   // create plane
+   data::Particle&& p0 = *ps->create(true);
+   p0.setPosition(Vec3(0,0,0));
+   p0.setShapeID(ss->create<data::HalfSpace>( Vector3<real_t>(0,0,1)) );
+   p0.setType(0);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::INFINITE);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::FIXED);
+
+   collision_detection::AnalyticContactDetection acd;
+   kernel::DoubleCast       double_cast;
+
+   kernel::ExplicitEulerWithShape explEuler(dt);
+   kernel::VelocityVerletPreForceUpdate  vvPreForce( dt );
+   kernel::VelocityVerletPostForceUpdate vvPostForce( dt );
+
+   mesa_pd::mpi::ReduceContactHistory rch;
+   kernel::NonLinearSpringDashpot dem(1, collisionTime);
+   dem.setMeff(0,0,Mij);
+   dem.setCOR(0,0,restitutionCoeff);
+
+   uint_t steps = 0;
+   real_t maxPenetration = real_t(0);
+   do
+   {
+      if(useVelocityVerlet) vvPreForce(0,*accessor);
+
+      if (double_cast(0, 1, *accessor, acd, *accessor ))
+      {
+         real_t penetration = acd.getPenetrationDepth();
+         maxPenetration = std::max( maxPenetration, std::abs(penetration));
+
+         dem(acd.getIdx1(), acd.getIdx2(), *accessor, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth(), dt);
+      }
+      auto force = accessor->getForce(0);
+
+      if(useVelocityVerlet) vvPostForce(0,*accessor);
+      else explEuler(0, *accessor);
+
+      WALBERLA_LOG_INFO(steps << ": penetration = " << acd.getPenetrationDepth() << " || vel = " << accessor->getLinearVelocity(0)[2] << " || force = " << force[2]);
+
+      rch(*ps);
+
+      ++steps;
+   } while (double_cast(0, 1, *accessor, acd, *accessor ));
+
+   real_t simulatedCoefficientOfRestitution = -accessor->getLinearVelocity(0)[2] / linVel[2];
+   real_t relativeError = ( simulatedCoefficientOfRestitution - restitutionCoeff ) / restitutionCoeff;
+   WALBERLA_LOG_INFO("coefficient of restitution = " << simulatedCoefficientOfRestitution << " -> error = " << relativeError * 100. << "%");
+   WALBERLA_LOG_INFO("collision steps = " << steps);
+   WALBERLA_LOG_INFO("Max penetration = " << maxPenetration << " -> " << maxPenetration / radius * 100. << "% of radius");
+
+   if( useVelocityVerlet )
+   {
+      WALBERLA_CHECK_LESS(relativeError, real_t(0.01), "Error in simulated coefficient of restitution too large: " << simulatedCoefficientOfRestitution << " vs ref " << restitutionCoeff);
+   }
+   else
+   {
+      WALBERLA_CHECK_LESS(relativeError, real_t(0.03), "Error in simulated coefficient of restitution too large: " << simulatedCoefficientOfRestitution << " vs ref " << restitutionCoeff);
+   }
+
+
+   return EXIT_SUCCESS;
+}
+} // namespace dem_integrator_accuracy
+
+int main( int argc, char** argv )
+{
+   return dem_integrator_accuracy::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/CoefficientOfRestitutionSD.cpp b/tests/mesa_pd/kernel/CoefficientOfRestitutionSD.cpp
new file mode 100644
index 000000000..b72a7eaab
--- /dev/null
+++ b/tests/mesa_pd/kernel/CoefficientOfRestitutionSD.cpp
@@ -0,0 +1,197 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file DEMIntegratorAccuracy.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+
+#include "mesa_pd/data/ParticleAccessor.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/VelocityVerlet.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/SpringDashpot.h"
+
+#include "core/Environment.h"
+#include "core/logging/Logging.h"
+
+#include <string>
+
+namespace dem_integrator_accuracy {
+
+using namespace walberla;
+using namespace walberla::mesa_pd;
+
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+         : ParticleAccessor(ps)
+         , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+
+/*
+ * Tests the integrator accuracy for a DEM simulation by comparing the given coefficient of restitution to the simulated one.
+ * For that, the velocity after a single sphere-wall collision is divided by the initial velocity before the simulation.
+ * The parameters of the DEM are chosen such as to (analytically) yield the desried coefficient of restitution.
+ *
+ * The simulation can be adapted via command line arguments.
+ *
+ * Currently compared integrators:
+ *  - explicit euler (default)
+ *  - velocity verlet (--useVV)
+ */
+int main( int argc, char** argv )
+{
+   mpi::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   // parameters
+   real_t radius = real_t(5);
+   real_t dt = real_t(0.1);
+   real_t restitutionCoeff = real_t(0.83);
+   real_t densitySphere = real_t(1.5);
+   real_t collisionTime = real_t(10);
+   bool useVelocityVerlet = false;
+   real_t uIn = real_t(0.1);
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--useVV" )   == 0 ) { useVelocityVerlet = true; continue; }
+      if( std::strcmp( argv[i], "--dt" )      == 0 ) { dt = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--Tc" )      == 0 ) { collisionTime = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--radius" )  == 0 ) { radius = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--e" )       == 0 ) { restitutionCoeff = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--density" ) == 0 ) { densitySphere = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--uIn" )     == 0 ) { uIn = real_c(std::atof( argv[++i] )); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   //init data structures
+   auto ps = walberla::make_shared<data::ParticleStorage>(2);
+   auto ss = walberla::make_shared<data::ShapeStorage>();
+   using ParticleAccessor_T = ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   auto sphereShape = ss->create<data::Sphere>( radius );
+   ss->shapes[sphereShape]->updateMassAndInertia(densitySphere);
+
+   const real_t particleMass = real_t(1) / ss->shapes[sphereShape]->getInvMass();
+   const real_t Mij = particleMass; // Mij = M for sphere-wall collision
+   const real_t lnDryResCoeff = std::log(restitutionCoeff);
+   const real_t stiffnessN = math::M_PI * math::M_PI * Mij / ( collisionTime * collisionTime * ( real_t(1) - lnDryResCoeff * lnDryResCoeff / ( math::M_PI * math::M_PI + lnDryResCoeff* lnDryResCoeff ))  );
+   const real_t dampingN = - real_t(2) * std::sqrt( Mij * stiffnessN ) * ( lnDryResCoeff / std::sqrt( math::M_PI * math::M_PI + ( lnDryResCoeff * lnDryResCoeff ) ) );
+
+   WALBERLA_LOG_INFO("dt = " << dt << ", Tc = " << collisionTime << ", coefficient of restitution = " << restitutionCoeff);
+   WALBERLA_LOG_INFO(" -> mass " << particleMass << ", collision duration = " << collisionTime / dt << ", stiffness = " << stiffnessN << ", damping = " << dampingN);
+
+   // create sphere
+   auto pos = Vec3(0,0,radius);
+   auto linVel = Vec3(0,0,-uIn);
+   data::Particle&& p = *ps->create();
+   p.setPosition(pos);
+   p.setLinearVelocity(linVel);
+   p.setShapeID(sphereShape);
+   p.setType(0);
+   p.setForce(Vec3(0.));
+   p.setOldForce(Vec3(0.));
+
+   // create plane
+   data::Particle&& p0 = *ps->create(true);
+   p0.setPosition(Vec3(0,0,0));
+   p0.setShapeID(ss->create<data::HalfSpace>( Vector3<real_t>(0,0,1)) );
+   p0.setType(0);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::INFINITE);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::FIXED);
+
+   collision_detection::AnalyticContactDetection acd;
+   kernel::DoubleCast       double_cast;
+
+   kernel::ExplicitEulerWithShape explEuler(dt);
+   kernel::VelocityVerletPreForceUpdate  vvPreForce( dt );
+   kernel::VelocityVerletPostForceUpdate vvPostForce( dt );
+
+   kernel::SpringDashpot dem(1);
+   dem.setStiffness(0,0,stiffnessN);
+   dem.setDampingN(0,0,dampingN);
+
+   uint_t steps = 0;
+   real_t maxPenetration = real_t(0);
+   do
+   {
+      if(useVelocityVerlet) vvPreForce(0,*accessor);
+
+      if (double_cast(0, 1, *accessor, acd, *accessor ))
+      {
+         real_t penetration = acd.getPenetrationDepth();
+         maxPenetration = std::max( maxPenetration, std::abs(penetration));
+
+         dem(acd.getIdx1(), acd.getIdx2(), *accessor, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth());
+      }
+      auto force = accessor->getForce(0);
+
+      if(useVelocityVerlet) vvPostForce(0,*accessor);
+      else explEuler(0, *accessor);
+
+      WALBERLA_LOG_INFO(steps << ": penetration = " << acd.getPenetrationDepth() << " || vel = " << accessor->getLinearVelocity(0)[2] << " || force = " << force[2]);
+
+      ++steps;
+   } while (double_cast(0, 1, *accessor, acd, *accessor ));
+
+   real_t simulatedCoefficientOfRestitution = -accessor->getLinearVelocity(0)[2] / linVel[2];
+   real_t relativeError = ( simulatedCoefficientOfRestitution - restitutionCoeff ) / restitutionCoeff;
+   WALBERLA_LOG_INFO("coefficient of restitution = " << simulatedCoefficientOfRestitution << " -> error = " << relativeError * 100. << "%");
+   WALBERLA_LOG_INFO("collision steps = " << steps);
+   WALBERLA_LOG_INFO("Max penetration = " << maxPenetration << " -> " << maxPenetration / radius * 100. << "% of radius");
+
+   if( useVelocityVerlet )
+   {
+      WALBERLA_CHECK_LESS(relativeError, real_t(0.01), "Error in simulated coefficient of restitution too large: " << simulatedCoefficientOfRestitution << " vs ref " << restitutionCoeff);
+   }
+   else
+   {
+      WALBERLA_CHECK_LESS(relativeError, real_t(0.03), "Error in simulated coefficient of restitution too large: " << simulatedCoefficientOfRestitution << " vs ref " << restitutionCoeff);
+   }
+
+
+   return EXIT_SUCCESS;
+}
+} // namespace dem_integrator_accuracy
+
+int main( int argc, char** argv )
+{
+   return dem_integrator_accuracy::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/DoubleCast.cpp b/tests/mesa_pd/kernel/DoubleCast.cpp
new file mode 100644
index 000000000..ec429e4ed
--- /dev/null
+++ b/tests/mesa_pd/kernel/DoubleCast.cpp
@@ -0,0 +1,102 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   DoubleCast.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/DataTypes.h>
+
+#include <mesa_pd/kernel/DoubleCast.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+class SingleParticleAccessor : public data::IAccessor
+{
+public:
+   data::BaseShape* getShape(const size_t p_idx)
+   {
+      return p_idx == 0 ?
+               static_cast<data::BaseShape*>(&halfspace_) :
+               static_cast<data::BaseShape*>(&sphere_);
+   }
+
+   data::HalfSpace halfspace_ = data::HalfSpace(Vec3(1,0,0));
+   data::Sphere    sphere_    = data::Sphere(real_t(0.5));
+};
+
+class ShapeTester
+{
+public:
+   void operator()(const size_t idx0, const size_t idx1, data::HalfSpace& /*hs*/, data::HalfSpace& /*hs*/)
+   {
+      WALBERLA_CHECK_EQUAL(idx0, 0);
+      WALBERLA_CHECK_EQUAL(idx1, 0);
+   }
+
+   void operator()(const size_t idx0, const size_t idx1, data::Sphere& /*hs*/, data::HalfSpace& /*hs*/)
+   {
+      WALBERLA_CHECK_EQUAL(idx0, 1);
+      WALBERLA_CHECK_EQUAL(idx1, 0);
+   }
+
+   void operator()(const size_t idx0, const size_t idx1, data::HalfSpace& /*hs*/, data::Sphere& /*hs*/)
+   {
+      WALBERLA_CHECK_EQUAL(idx0, 0);
+      WALBERLA_CHECK_EQUAL(idx1, 1);
+   }
+
+   void operator()(const size_t idx0, const size_t idx1, data::Sphere& /*hs*/, data::Sphere& /*hs*/)
+   {
+      WALBERLA_CHECK_EQUAL(idx0, 1);
+      WALBERLA_CHECK_EQUAL(idx1, 1);
+   }
+};
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   //init data structures
+   SingleParticleAccessor accessor;
+
+   //init kernels
+   kernel::DoubleCast doublecast;
+
+   ShapeTester tester;
+   doublecast(0, 0, accessor, tester);
+   doublecast(1, 0, accessor, tester);
+   doublecast(0, 1, accessor, tester);
+   doublecast(1, 1, accessor, tester);
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/ExplicitEuler.cpp b/tests/mesa_pd/kernel/ExplicitEuler.cpp
new file mode 100644
index 000000000..34ac15885
--- /dev/null
+++ b/tests/mesa_pd/kernel/ExplicitEuler.cpp
@@ -0,0 +1,116 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   ExplicitEuler.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+
+#include <mesa_pd/kernel/ExplicitEuler.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+class SingleParticleAccessor : public data::SingleParticleAccessor
+{
+public:
+   const walberla::real_t& getInvMass(const size_t /*p_idx*/) const {return invMass_;}
+   void setInvMass(const size_t /*p_idx*/, const walberla::real_t& v) { invMass_ = v;}
+   const walberla::mesa_pd::Mat3& getInvInertiaBF(const size_t /*p_idx*/) const {return invInertiaBF_;}
+   void setInvInertiaBF(const size_t /*p_idx*/, const walberla::mesa_pd::Mat3& v) { invInertiaBF_ = v;}
+
+   walberla::real_t        invMass_;
+   walberla::mesa_pd::Mat3 invInertiaBF_;
+};
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   //init data structures
+   SingleParticleAccessor accessor;
+
+   //initialize particle
+   const auto linVel = Vec3(1,2,3);
+   const auto angVel = Vec3(1,2,3);
+
+   const auto force  = Vec3(1,2,3);
+   const auto torque = Vec3(1,2,3);
+
+   accessor.setPosition(        0, Vec3(0,0,0));
+   accessor.setRotation(        0, Rot3(Quat()));
+   accessor.setLinearVelocity(  0, linVel);
+   accessor.setAngularVelocity( 0, angVel);
+   accessor.setForce(           0, force);
+   accessor.setTorque(          0, torque);
+   accessor.setInvMass(         0, real_t(1.23456));
+   accessor.setInvInertiaBF(    0, Mat3(real_t(1.23456), real_t(0), real_t(0), real_t(0), real_t(1.23456), real_t(0), real_t(0), real_t(0), real_t(1.23456)));
+
+   //init kernels
+   const real_t dt = real_t(1);
+   kernel::ExplicitEuler integrator( dt );
+
+   integrator(0, accessor);
+
+   //check force
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getForce(0), Vec3(0));
+
+   //check velocity
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getLinearVelocity(0), force * accessor.getInvMass(0) * dt + linVel);
+
+   //check position
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getPosition(0), linVel * dt + force * accessor.getInvMass(0) * dt * dt);
+
+   accessor.setPosition(        0, Vec3(0,0,0));
+   accessor.setRotation(        0, Rot3(Quat()));
+   accessor.setLinearVelocity(  0, linVel);
+   accessor.setAngularVelocity( 0, angVel);
+   accessor.setForce(           0, force);
+   accessor.setTorque(          0, torque);
+   accessor.setInvMass(         0, real_t(1.23456));
+   accessor.setInvInertiaBF(    0, Mat3(real_t(1.23456), real_t(0), real_t(0), real_t(0), real_t(1.23456), real_t(0), real_t(0), real_t(0), real_t(1.23456)));
+   data::particle_flags::set( accessor.getFlagsRef(0), data::particle_flags::FIXED );
+
+   integrator(0, accessor);
+
+   //check force
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getForce(0), Vec3(0));
+
+   //check velocity
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getLinearVelocity(0), linVel);
+
+   //check position
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getPosition(0), Vec3(0,0,0));
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/ExplicitEulerWithShape.cpp b/tests/mesa_pd/kernel/ExplicitEulerWithShape.cpp
new file mode 100644
index 000000000..b04e420b0
--- /dev/null
+++ b/tests/mesa_pd/kernel/ExplicitEulerWithShape.cpp
@@ -0,0 +1,125 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   ExplicitEulerWithShape.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+
+#include <mesa_pd/kernel/ExplicitEulerWithShape.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+class SingleParticleAccessor : public data::SingleParticleAccessor
+{
+public:
+   const walberla::real_t& getInvMass(const size_t /*p_idx*/) const {return invMass_;}
+   void setInvMass(const size_t /*p_idx*/, const walberla::real_t& v) { invMass_ = v;}
+   const walberla::mesa_pd::Mat3& getInvInertiaBF(const size_t /*p_idx*/) const {return invInertiaBF_;}
+   void setInvInertiaBF(const size_t /*p_idx*/, const walberla::mesa_pd::Mat3& v) { invInertiaBF_ = v;}
+
+   walberla::real_t        invMass_;
+   walberla::mesa_pd::Mat3 invInertiaBF_;
+};
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   //init data structures
+   SingleParticleAccessor accessor;
+
+   //initialize particle
+   const auto linVel = Vec3(1,2,3);
+   const auto angVel = Vec3(1,2,3);
+
+   const auto force  = Vec3(1,2,3);
+   const auto torque = Vec3(1,2,3);
+
+   accessor.setPosition(        0, Vec3(0,0,0));
+   accessor.setRotation(        0, Rot3(Quat()));
+   accessor.setLinearVelocity(  0, linVel);
+   accessor.setAngularVelocity( 0, angVel);
+   accessor.setForce(           0, force);
+   accessor.setTorque(          0, torque);
+   accessor.setInvMass(         0, real_t(1.23456));
+   accessor.setInvInertiaBF(    0, Mat3(real_t(1.23456), real_t(0), real_t(0), real_t(0), real_t(1.23456), real_t(0), real_t(0), real_t(0), real_t(1.23456)));
+
+   //init kernels
+   const real_t dt = real_t(1);
+   kernel::ExplicitEulerWithShape integrator( dt );
+
+   integrator(0, accessor);
+
+   const auto& R = accessor.getRotation(0).getMatrix();
+   const auto wdot = R * accessor.getInvInertiaBF(0) * R.getTranspose() * torque;
+
+   //check force
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getForce(0), Vec3(0));
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getTorque(0), Vec3(0));
+
+   //check velocity
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getLinearVelocity(0), force * accessor.getInvMass(0) * dt + linVel);
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getAngularVelocity(0), wdot * dt + angVel);
+
+   //check position
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getPosition(0), linVel * dt + force * accessor.getInvMass(0) * dt * dt);
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getRotation(0).getQuaternion(), Quat( (wdot * dt + angVel).getNormalized(), (wdot * dt + angVel).length() * dt ));
+
+   accessor.setPosition(        0, Vec3(0,0,0));
+   accessor.setRotation(        0, Rot3(Quat()));
+   accessor.setLinearVelocity(  0, linVel);
+   accessor.setAngularVelocity( 0, angVel);
+   accessor.setForce(           0, force);
+   accessor.setTorque(          0, torque);
+   accessor.setInvMass(         0, real_t(1.23456));
+   accessor.setInvInertiaBF(    0, Mat3(real_t(1.23456), real_t(0), real_t(0), real_t(0), real_t(1.23456), real_t(0), real_t(0), real_t(0), real_t(1.23456)));
+   data::particle_flags::set( accessor.getFlagsRef(0), data::particle_flags::FIXED );
+
+   integrator(0, accessor);
+
+   //check force
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getForce(0), Vec3(0));
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getTorque(0), Vec3(0));
+
+   //check velocity
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getLinearVelocity(0), linVel);
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getAngularVelocity(0), angVel);
+
+   //check position
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getPosition(0), Vec3(0,0,0));
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getRotation(0).getQuaternion(), Quat());
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/ForceLJ.cpp b/tests/mesa_pd/kernel/ForceLJ.cpp
new file mode 100644
index 000000000..6e15f7774
--- /dev/null
+++ b/tests/mesa_pd/kernel/ForceLJ.cpp
@@ -0,0 +1,128 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   ForceLJ.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <mesa_pd/kernel/ForceLJ.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   if (std::is_same<real_t, float>::value)
+   {
+      WALBERLA_LOG_WARNING("waLBerla build in sp mode: skipping test due to low precision");
+      return EXIT_SUCCESS;
+   }
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+
+   data::Particle&& p1        = *ps->create();
+   p1.getPositionRef()        = Vec3(0,0,0);
+   p1.getForceRef()           = Vec3(0,0,0);
+   p1.getTypeRef()            = 0;
+
+   data::Particle&& p2        = *ps->create();
+   p2.getPositionRef()        = Vec3(0,0,0);
+   p2.getForceRef()           = Vec3(0,0,0);
+   p2.getTypeRef()            = 0;
+
+   data::ParticleAccessor accessor(ps);
+
+   //init kernels
+   kernel::ForceLJ lj(1);
+   lj.setEpsilon(0, 0, real_t(0.121));
+   lj.setSigma  (0, 0, real_t(0.212));
+
+   //check equilibrium distance
+   const real_t eq_dist = std::pow(real_t(2), real_t(1.0)/real_t(6.0));
+   accessor.setPosition(1, Vec3( eq_dist * lj.getSigma(0, 0), 0, 0));
+   lj(0, 1, accessor);
+   WALBERLA_CHECK_FLOAT_EQUAL( p1.getForce(), Vec3(0,0,0) );
+   WALBERLA_CHECK_FLOAT_EQUAL( p2.getForce(), Vec3(0,0,0) );
+
+   //force is zero for large distances
+   accessor.setForce(0, Vec3(0,0,0));
+   accessor.setForce(1, Vec3(0,0,0));
+   accessor.setPosition(1, Vec3( eq_dist * lj.getSigma(0, 0) * real_t(100), 0, 0));
+   lj(0, 1, accessor);
+   WALBERLA_CHECK_FLOAT_EQUAL( accessor.getForce(0), Vec3(0,0,0) );
+   WALBERLA_CHECK_FLOAT_EQUAL( accessor.getForce(1), Vec3(0,0,0) );
+
+   //attractive
+   p1.getForceRef()           = Vec3(0,0,0);
+   p2.getForceRef()           = Vec3(0,0,0);
+   p2.getPositionRef()        = Vec3( real_t(2) * lj.getSigma(0, 0), 0, 0);
+   lj(0, 1, accessor);
+   WALBERLA_CHECK_GREATER( p1.getForce()[0], real_t(0), p1 << p2 );
+   WALBERLA_CHECK_LESS   ( p2.getForce()[0], real_t(0), p1 << p2 );
+
+   //repulsive
+   p1.getForceRef()           = Vec3(0,0,0);
+   p2.getForceRef()           = Vec3(0,0,0);
+   p2.getPositionRef()        = Vec3( lj.getSigma(0, 0), 0, 0);
+   lj(0, 1, accessor);
+   WALBERLA_CHECK_LESS   ( p1.getForce()[0], real_t(0), p1 << p2 );
+   WALBERLA_CHECK_GREATER( p2.getForce()[0], real_t(0), p1 << p2 );
+
+   //action = reactio
+   p1.getForceRef()           = Vec3(0,0,0);
+   p2.getForceRef()           = Vec3(0,0,0);
+   p2.getPositionRef()        = Vec3( 1, 2, 3) * real_t(0.1);
+   lj(0, 1, accessor);
+   WALBERLA_CHECK_FLOAT_EQUAL( p1.getForce(), -p2.getForce() );
+
+   // thread safety test
+   auto singleForce = p1.getForce();
+#ifdef _OPENMP
+#pragma omp parallel for schedule(static)
+#endif
+   for (int i = 0; i < 100; ++i)
+      lj(0, 1, accessor);
+
+   WALBERLA_LOG_DEVEL(p1);
+   WALBERLA_LOG_DEVEL(p2);
+   WALBERLA_CHECK_FLOAT_EQUAL( p1.getForce(), -p2.getForce() );
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON( p1.getForce(),
+                                       real_t(101) * singleForce,
+                                       real_t(1e-13) );
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/GenerateAnalyticContacts.cpp b/tests/mesa_pd/kernel/GenerateAnalyticContacts.cpp
new file mode 100644
index 000000000..f85423e4e
--- /dev/null
+++ b/tests/mesa_pd/kernel/GenerateAnalyticContacts.cpp
@@ -0,0 +1,176 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   GenerateAnalyticContacts.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/collision_detection/AnalyticContactDetection.h>
+#include <mesa_pd/data/LinkedCells.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/kernel/DoubleCast.h>
+#include <mesa_pd/kernel/InsertParticleIntoLinkedCells.h>
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/mpi/ContactFilter.h>
+#include <mesa_pd/mpi/notifications/ForceTorqueNotification.h>
+#include <mesa_pd/mpi/ReduceProperty.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/mpi/Reduce.h>
+
+#include <iostream>
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+      : ParticleAccessor(ps)
+      , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,3,3,3), // simulation domain
+                                                 Vector3<uint_t>(3,3,3), // blocks in each direction
+                                                 Vector3<bool>(true, true, true) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+   ParticleAccessorWithShape accessor(ps, ss);
+   data::LinkedCells      lc(math::AABB(-1,-1,-1,4,4,4), real_t(1));
+
+   //initialize particles
+   const real_t radius  = real_t(0.6);
+   const real_t spacing = real_t(1.0);
+   auto smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+
+   WALBERLA_CHECK_EQUAL(forest->size(), 1);
+   const Block& blk = *static_cast<blockforest::Block*>(&*forest->begin());
+
+   for (auto pt : grid_generator::SCGrid(blk.getAABB(), Vec3(spacing, spacing, spacing) * real_c(0.5), spacing))
+   {
+      data::Particle&& p          = *ps->create();
+      p.getPositionRef()          = pt;
+      p.getInteractionRadiusRef() = radius;
+      p.getShapeIDRef()           = smallSphere;
+      p.getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+   }
+
+   //init kernels
+   kernel::InsertParticleIntoLinkedCells  insert_particle_into_linked_cells;
+   mpi::ReduceProperty                    RP;
+   mpi::SyncNextNeighbors                 SNN;
+
+
+   SNN(*ps, domain);
+   ps->forEachParticlePairHalf(false,
+                               kernel::ExcludeInfiniteInfinite(),
+                               accessor,
+                               [&](const size_t idx1, const size_t idx2, auto& ac)
+   {
+      collision_detection::AnalyticContactDetection         acd;
+      kernel::DoubleCast               double_cast;
+      mpi::ContactFilter               contact_filter;
+      if (double_cast(idx1, idx2, ac, acd, ac ))
+      {
+         if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), domain))
+         {
+            ac.getForceRef(acd.getIdx1()) += acd.getContactNormal() + Vec3(1,0,0);
+            ac.getForceRef(acd.getIdx2()) -= acd.getContactNormal() - Vec3(1,0,0);
+         }
+      }
+   },
+   accessor );
+
+   RP.operator()<ForceTorqueNotification>(*ps);
+
+   WALBERLA_CHECK_FLOAT_EQUAL(ps->getForce(0), Vec3(6,0,0));
+
+   //TEST WITH LINKED CELLS
+   ps->forEachParticle(false, kernel::SelectAll(), accessor, [](size_t idx, auto& ac) {ac.setForce(idx, Vec3(0,0,0));}, accessor);
+   lc.clear();
+   ps->forEachParticle(false, kernel::SelectAll(), accessor, insert_particle_into_linked_cells, accessor, lc);
+
+   lc.forEachParticlePairHalf(false,
+                              kernel::ExcludeInfiniteInfinite(),
+                              accessor,
+                              [&domain](const size_t idx1, const size_t idx2, auto& ac)
+   {
+      collision_detection::AnalyticContactDetection         acd;
+      kernel::DoubleCast               double_cast;
+      mpi::ContactFilter               contact_filter;
+      if (double_cast(idx1, idx2, ac, acd, ac ))
+      {
+         if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), domain))
+         {
+            ac.getForceRef(acd.getIdx1()) += acd.getContactNormal() + Vec3(1,0,0);
+            ac.getForceRef(acd.getIdx2()) -= acd.getContactNormal() - Vec3(1,0,0);
+         }
+      }
+   },
+   accessor );
+
+   RP.operator()<ForceTorqueNotification>(*ps);
+
+   WALBERLA_CHECK_FLOAT_EQUAL(ps->getForce(0), Vec3(6,0,0));
+
+   //ps->forEachParticle(false, [](data::ParticleStorage& ps, size_t idx) {WALBERLA_LOG_DEVEL_ON_ROOT(*ps[idx]);});
+   //cs.forEachContact(false, [](data::ContactStorage& cs, size_t idx) {WALBERLA_LOG_DEVEL_ON_ROOT(*cs[idx]);});
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::mesa_pd::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/GenerateLinkedCells.cpp b/tests/mesa_pd/kernel/GenerateLinkedCells.cpp
new file mode 100644
index 000000000..ef4724b5e
--- /dev/null
+++ b/tests/mesa_pd/kernel/GenerateLinkedCells.cpp
@@ -0,0 +1,111 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   GenerateLinkedCells.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/kernel/ExplicitEuler.h>
+#include <mesa_pd/kernel/ForceLJ.h>
+#include <mesa_pd/kernel/InsertParticleIntoLinkedCells.h>
+#include <mesa_pd/kernel/ParticleSelector.h>
+
+#include <mesa_pd/data/LinkedCells.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   //domain setup
+   const real_t spacing = real_t(1.0);
+   math::AABB domain( Vec3(real_t(-0.5), real_t(-0.5), real_t(-0.5)),
+                      Vec3(real_t(+0.5), real_t(+0.5), real_t(+0.5)));
+   domain.scale(real_t(10));
+
+   //init data structures
+   auto storage = std::make_shared<data::ParticleStorage>(100);
+   data::LinkedCells     linkedCells(domain.getScaled(2), real_t(1));
+
+   data::ParticleAccessor accessor(storage);
+
+   //initialize particles
+   for (auto it = grid_generator::SCIterator(domain, Vec3(spacing, spacing, spacing) * real_c(0.5), spacing);
+        it != grid_generator::SCIterator();
+        ++it)
+   {
+      data::Particle&& p    = *storage->create();
+      p.getPositionRef()    = (*it);
+   }
+
+   //init kernels
+   kernel::InsertParticleIntoLinkedCells ipilc;
+   kernel::ForceLJ lj(1);
+   kernel::ExplicitEuler integrator( real_t(0.01) );
+
+   //timeloop
+   for (auto timestep = 0; timestep < 100; ++timestep)
+   {
+      linkedCells.clear();
+      storage->forEachParticle(true, kernel::SelectAll(), accessor, ipilc, accessor, linkedCells);
+
+      int particleCounter = 0;
+      for (int x = 0; x < linkedCells.numCellsPerDim_[0]; ++x)
+         for (int y = 0; y < linkedCells.numCellsPerDim_[1]; ++y)
+            for (int z = 0; z < linkedCells.numCellsPerDim_[2]; ++z)
+            {
+               const int cell_idx = getCellIdx(linkedCells, x, y, z);
+               auto aabb = getCellAABB(linkedCells, x, y, z);
+               int p_idx = linkedCells.cells_[uint_c(cell_idx)];
+               while (p_idx != -1)
+               {
+                  ++particleCounter;
+                  WALBERLA_CHECK( aabb.contains( storage->getPosition(uint_c(p_idx)) ),
+                                  "Particle(" << p_idx << ") with position (" <<
+                                  storage->getPosition(uint_c(p_idx)) <<
+                                  ") not contained in cell(" << x << ", " << y << ", " << z <<
+                                  ") with aabb: " << aabb << ".");
+                  p_idx = storage->getNextParticle(uint_c(p_idx));
+               }
+            }
+      WALBERLA_CHECK_EQUAL(particleCounter, storage->size());
+      linkedCells.forEachParticlePairHalf(true, kernel::SelectAll(), accessor, lj, accessor);
+      std::for_each(storage->begin(), storage->end(), [](data::Particle&& p){ p.getForceRef() = -p.getPosition() + p.getForce(); });
+      storage->forEachParticle(true, kernel::SelectAll(), accessor, integrator, accessor);
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/HeatConduction.cpp b/tests/mesa_pd/kernel/HeatConduction.cpp
new file mode 100644
index 000000000..466ae1f58
--- /dev/null
+++ b/tests/mesa_pd/kernel/HeatConduction.cpp
@@ -0,0 +1,79 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   HeatConduction.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/kernel/HeatConduction.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   if (std::is_same<real_t, float>::value)
+   {
+      WALBERLA_LOG_WARNING("waLBerla build in sp mode: skipping test due to low precision");
+      return EXIT_SUCCESS;
+   }
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+
+   data::ParticleAccessor ac(ps);
+
+   data::Particle&& p1 = *ps->create();
+   p1.setTemperature(10);
+   p1.setType( 0 );
+
+   data::Particle&& p2 = *ps->create();
+   p2.setTemperature(20);
+   p2.setType( 0 );
+
+   // Init kernels
+   kernel::HeatConduction heatConduction(1);
+   heatConduction.setConductance(0, 0, real_t(0.2));
+
+   // single contact test
+   heatConduction(0,
+                  1,
+                  ac);
+
+   WALBERLA_CHECK_FLOAT_EQUAL( ps->getHeatFlux(0), -ps->getHeatFlux(1) );
+   WALBERLA_CHECK_FLOAT_EQUAL( ps->getHeatFlux(0), real_t(2) );
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/IntegratorAccuracy.cpp b/tests/mesa_pd/kernel/IntegratorAccuracy.cpp
new file mode 100644
index 000000000..48f623466
--- /dev/null
+++ b/tests/mesa_pd/kernel/IntegratorAccuracy.cpp
@@ -0,0 +1,180 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   IntegratorAccuracy.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "mesa_pd/data/ParticleAccessor.h"
+#include "mesa_pd/data/ParticleStorage.h"
+
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/kernel/VelocityVerlet.h"
+#include "mesa_pd/kernel/ExplicitEuler.h"
+
+#include "core/Environment.h"
+#include "core/logging/Logging.h"
+#include "core/math/all.h"
+
+#include <iostream>
+
+namespace integrator_accuracy
+{
+
+using namespace walberla;
+
+using namespace walberla::mesa_pd;
+
+
+Vec3 getForce(const Vec3& pos, real_t k)
+{
+   return -k * pos;
+}
+
+real_t analyticalTrajectory(real_t amplitude, real_t timeStep, real_t omega, real_t phase)
+{
+   return amplitude * std::cos(omega * timeStep + phase);
+}
+
+real_t analyticalVelocity(real_t amplitude, real_t timeStep, real_t omega, real_t phase)
+{
+   return -amplitude * omega * std::sin(omega * timeStep + phase);
+}
+
+
+/*
+ * Simulates a harmonic oscillator to test the accuracy of the integrators.
+ * The error of the maximum position and the maximum velocity is compared against the analytical values.
+ * Via command line arguments, the simulation can be adapted.
+ * Currently tested integrators:
+ *  - explicit Euler (default)
+ *  - velocity verlet (-useVV)
+ */
+int main( int argc, char ** argv )
+{
+   mpi::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   real_t amplitude       = real_t(1.5);
+   real_t k               = real_t(0.1);
+   real_t mass            = real_t(0.9);
+   real_t dt              = real_t(0.2);
+   bool useVelocityVerlet = false;
+   real_t phaseFraction   = real_t(0);
+   real_t periods         = real_t(1.1);
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--useVV" )         == 0 ) { useVelocityVerlet = true; continue; }
+      if( std::strcmp( argv[i], "--dt" )            == 0 ) { dt = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--amplitude" )     == 0 ) { amplitude = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--k" )             == 0 ) { k = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--mass" )          == 0 ) { mass = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--phaseFraction" ) == 0 ) { phaseFraction = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--periods" )       == 0 ) { periods = real_c(std::atof( argv[++i] )); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+
+   real_t phase = phaseFraction * math::M_PI;
+   real_t omega = std::sqrt(k / mass);
+   real_t durationOnePeriod = real_t(2) * math::M_PI / omega;
+   uint_t timeSteps = uint_c(periods * durationOnePeriod / dt);
+
+   WALBERLA_LOG_INFO("omega = " << omega << ", T = " << real_t(2) * math::M_PI / omega << ", time steps = " << timeSteps << ", phase = " << phase << ", periods = " << periods);
+
+   //initialize particle
+   const auto pos = Vec3(0,0,analyticalTrajectory(amplitude, real_t(0), omega, phase));
+   const auto linVel = Vec3(0,0,analyticalVelocity(amplitude, real_t(0), omega, phase));
+
+   WALBERLA_LOG_INFO("Initial pos = " << pos << ", vel = " << linVel);
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(1);
+   data::ParticleAccessor accessor(ps);
+   
+   data::Particle&& p = *ps->create();
+   p.setPosition(pos);
+   p.setLinearVelocity(linVel);
+   p.setForce(getForce(pos, k));
+   p.setOldForce(getForce(pos, k));
+   p.setInvMass(real_t(1) / mass);
+
+   // velocity verlet
+   kernel::VelocityVerletPreForceUpdate  preForce( dt );
+   kernel::VelocityVerletPostForceUpdate postForce( dt );
+
+   // explicit euler
+   kernel::ExplicitEuler explEuler( dt );
+
+   real_t maxVel = 0.;
+   real_t maxRefVel = 0.;
+   real_t maxPos = 0.;
+   real_t maxRefPos = 0.;
+   
+   for (auto i = uint_t(1); i <= timeSteps; ++i)
+   {
+
+      if( useVelocityVerlet ) ps->forEachParticle(false, kernel::SelectAll(), accessor, preForce, accessor);
+      p.setForce( getForce(p.getPosition(), k) );
+      auto force = p.getForce();
+
+      if( useVelocityVerlet ) ps->forEachParticle(false, kernel::SelectAll(), accessor, postForce, accessor);
+      else  ps->forEachParticle(false, kernel::SelectAll(), accessor, explEuler, accessor);
+
+      real_t refPos = analyticalTrajectory(amplitude, real_c(i) * dt, omega, phase);
+      real_t refVel = analyticalVelocity(amplitude, real_c(i) * dt, omega, phase);
+
+      WALBERLA_LOG_INFO(i << ": pos = " << p.getPosition()[2] << " " << refPos
+                          << " || vel = " << p.getLinearVelocity()[2] << " " << refVel
+                          << " || force = " << force[2] );
+
+      maxPos = std::max(maxPos, std::abs(p.getPosition()[2]));
+      maxRefPos = std::max(maxRefPos, std::abs(refPos));
+
+      maxVel = std::max(maxVel, std::abs(p.getLinearVelocity()[2]));
+      maxRefVel = std::max(maxRefVel, std::abs(refVel));
+   }
+
+   real_t relativePositionError = ( maxPos - maxRefPos ) / maxRefPos;
+   WALBERLA_LOG_INFO("error in position = " << relativePositionError * 100. << "%");
+
+   real_t relativeVelocityError = ( maxVel - maxRefVel ) / maxRefVel;
+   WALBERLA_LOG_INFO("error in velocity = " << relativeVelocityError * 100. << "%");
+
+   if( useVelocityVerlet )
+   {
+      WALBERLA_CHECK_LESS(relativePositionError, real_t(0.01), "Error in position too large!");
+      WALBERLA_CHECK_LESS(relativeVelocityError, real_t(0.01), "Error in velocity too large!");
+   }
+   else
+   {
+      WALBERLA_CHECK_LESS(relativePositionError, real_t(0.11), "Error in position too large!");
+      WALBERLA_CHECK_LESS(relativeVelocityError, real_t(0.10), "Error in velocity too large!");
+   }
+
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace integrator_accuracy
+
+int main( int argc, char ** argv )
+{
+   return integrator_accuracy::main(argc, argv);
+}
+
diff --git a/tests/mesa_pd/kernel/Interfaces.cpp b/tests/mesa_pd/kernel/Interfaces.cpp
new file mode 100644
index 000000000..25fabf624
--- /dev/null
+++ b/tests/mesa_pd/kernel/Interfaces.cpp
@@ -0,0 +1,26 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   Interfaces.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <cstdlib>
+
+int main( int /*argc*/, char ** /*argv*/ )
+{
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/kernel/LinearSpringDashpot.cpp b/tests/mesa_pd/kernel/LinearSpringDashpot.cpp
new file mode 100644
index 000000000..9fcce832d
--- /dev/null
+++ b/tests/mesa_pd/kernel/LinearSpringDashpot.cpp
@@ -0,0 +1,224 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   DEMTangentialCollision.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/common/ParticleFunctions.h"
+
+#include "mesa_pd/data/ParticleAccessor.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/VelocityVerletWithShape.h"
+#include "mesa_pd/kernel/LinearSpringDashpot.h"
+#include "mesa_pd/mpi/ReduceContactHistory.h"
+
+#include "core/Environment.h"
+#include "core/logging/Logging.h"
+
+#include <iostream>
+
+namespace walberla {
+namespace mesa_pd {
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+         : ParticleAccessor(ps)
+         , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+
+/*
+ * Simulates oblique sphere-wall collision and checks rebound angle, i.e. the tangential part of the collision model.
+ *
+ */
+int main( int argc, char ** argv )
+{
+   walberla::mpi::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   real_t impactAngle = real_t(0);
+   real_t dt         = real_c(2e-7);
+   real_t frictionCoeff_s = real_t(0.8);
+   real_t frictionCoeff_d = real_t(0.125);
+   std::string filename = "TangentialCollision.txt";
+   real_t collisionDuration = real_t(10);
+   real_t nu = real_t(0.22); //Poissons ratio
+   bool useVelocityVerlet = false;
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--impactAngle" ) == 0 ) { impactAngle = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--dt" ) == 0 ) { dt = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--staticFriction" ) == 0 ) { frictionCoeff_s = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--dynamicFriction" ) == 0 ) { frictionCoeff_d = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--collisionDuration" ) == 0 ) { collisionDuration = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--nu" ) == 0 ) { nu = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--filename" ) == 0 ) { filename = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--useVV" )   == 0 ) { useVelocityVerlet = true; continue; }
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("******************************************************");
+   WALBERLA_LOG_INFO_ON_ROOT("**                  NEW SIMULATION                  **");
+   WALBERLA_LOG_INFO_ON_ROOT("******************************************************");
+   WALBERLA_LOG_INFO_ON_ROOT("impactAngle = " << impactAngle);
+   WALBERLA_LOG_INFO_ON_ROOT("dt = " << dt);
+   WALBERLA_LOG_INFO_ON_ROOT("frictionCoeff_s = " << frictionCoeff_s);
+   WALBERLA_LOG_INFO_ON_ROOT("frictionCoeff_d = " << frictionCoeff_d);
+   WALBERLA_LOG_INFO_ON_ROOT("collisionDuration = " << collisionDuration);
+   WALBERLA_LOG_INFO_ON_ROOT("nu = " << nu);
+
+   real_t radius     = real_c(0.00159);
+   real_t density    = real_c(2500);
+   real_t restitutionCoeff = real_t(0.83);
+   real_t collisionTime =  collisionDuration * dt;
+
+   //init data structures
+   auto ps = walberla::make_shared<data::ParticleStorage>(2);
+   auto ss = walberla::make_shared<data::ShapeStorage>();
+   using ParticleAccessor_T = ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   auto sphereShape = ss->create<data::Sphere>( radius );
+   ss->shapes[sphereShape]->updateMassAndInertia(density);
+
+   const real_t particleMass =  real_t(1) / ss->shapes[sphereShape]->getInvMass();
+   const real_t Mij = particleMass; // * particleMass / ( real_t(2) * particleMass ); // Mij = M for sphere-wall collision
+   const real_t lnDryResCoeff = std::log(restitutionCoeff);
+
+   // normal material aprameters
+   const real_t stiffnessN = math::M_PI * math::M_PI * Mij / ( collisionTime * collisionTime * ( real_t(1) - lnDryResCoeff * lnDryResCoeff / ( math::M_PI * math::M_PI + lnDryResCoeff* lnDryResCoeff ))  );
+   const real_t dampingN = - real_t(2) * std::sqrt( Mij * stiffnessN ) *
+   ( lnDryResCoeff / std::sqrt( math::M_PI * math::M_PI + ( lnDryResCoeff * lnDryResCoeff ) ) );
+
+   WALBERLA_LOG_INFO_ON_ROOT("normal: stiffness = " << stiffnessN << ", damping = " << dampingN);
+
+   const real_t lnDryResCoeffTangential = lnDryResCoeff; // std::log(0.31); //TODO: was same as in normal direction
+   const real_t kappa = real_t(2) * ( real_t(1) - nu ) / ( real_t(2) - nu ) ;
+   const real_t stiffnessT = kappa * Mij * math::M_PI * math::M_PI / ( collisionTime *  collisionTime );
+   const real_t dampingT = real_t(2) * std::sqrt(Mij * stiffnessT) * ( - lnDryResCoeffTangential ) / ( std::sqrt( math::M_PI * math::M_PI + lnDryResCoeffTangential * lnDryResCoeffTangential ));
+
+   WALBERLA_LOG_INFO_ON_ROOT("tangential: kappa = " << kappa << ", stiffness T = " << stiffnessT << ", damping T = " << dampingT);
+
+   real_t uNin = real_t(1);
+   real_t uTin = uNin * impactAngle;
+
+   // create sphere
+   data::Particle&& p = *ps->create();
+   p.setPosition(Vec3(0,0,2*radius));
+   p.setLinearVelocity(Vec3(uTin, 0., -uNin));
+   p.setType(0);
+
+   // create plane
+   data::Particle&& p0 = *ps->create(true);
+   p0.setPosition(Vec3(0,0,0));
+   p0.setShapeID(ss->create<data::HalfSpace>(Vector3<real_t>(0,0,1)));
+   p0.setType(0);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::INFINITE);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::FIXED);
+
+   // velocity verlet
+   kernel::VelocityVerletWithShapePreForceUpdate  vvPreForce( dt );
+   kernel::VelocityVerletWithShapePostForceUpdate vvPostForce( dt );
+
+   // explicit euler
+   kernel::ExplicitEulerWithShape explEuler( dt );
+
+   // collision response
+   collision_detection::AnalyticContactDetection     acd;
+   kernel::DoubleCast           double_cast;
+   kernel::LinearSpringDashpot  dem(1);
+   mpi::ReduceContactHistory    rch;
+   dem.setStiffnessN(0,0,stiffnessN);
+   dem.setStiffnessT(0,0,stiffnessT);
+   dem.setDampingN(0,0,dampingN);
+   dem.setDampingT(0,0,dampingT);
+   dem.setFrictionCoefficientStatic(0,0,frictionCoeff_s);
+   dem.setFrictionCoefficientDynamic(0,0,frictionCoeff_d);
+
+   WALBERLA_LOG_DEVEL("begin: vel = " << p.getLinearVelocity() << ", contact vel: " << getVelocityAtWFPoint(0,*accessor,p.getPosition() + Vec3(0,0,-radius)) );
+
+   uint_t steps = 0;
+   real_t maxPenetration = real_t(0);
+   do
+   {
+      if(useVelocityVerlet) vvPreForce(0,*accessor);
+
+      real_t penetration;
+
+      if (double_cast(0, 1, *accessor, acd, *accessor ))
+      {
+         penetration = acd.getPenetrationDepth();
+         maxPenetration = std::max( maxPenetration, std::abs(penetration));
+
+         dem(acd.getIdx1(), acd.getIdx2(), *accessor, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth(), dt);
+         //auto force = accessor->getForce(0);
+         //WALBERLA_LOG_INFO(steps << ": penetration = " << penetration << " || vel = " << accessor->getLinearVelocity(0) << " || force = " << force);
+      }
+      rch(*ps);
+
+      if(useVelocityVerlet) vvPostForce(0,*accessor);
+      else explEuler(0, *accessor);
+
+      ++steps;
+   } while (double_cast(0, 1, *accessor, acd, *accessor ) || p.getLinearVelocity()[2] < 0);
+
+   real_t uTout = p.getLinearVelocity()[0] - radius * p.getAngularVelocity()[1];
+   WALBERLA_LOG_DEVEL("end: linear vel = " << p.getLinearVelocity() << ", angular vel = " << p.getAngularVelocity());
+
+   real_t reboundAngle = uTout / uNin;
+   WALBERLA_LOG_INFO_ON_ROOT("gamma_in = " << impactAngle);
+   WALBERLA_LOG_INFO_ON_ROOT("gamma_out = " << reboundAngle);
+
+   WALBERLA_LOG_INFO_ON_ROOT("Thornton: sliding should occur if " << real_t(2) * impactAngle / ( frictionCoeff_d * ( real_t(1) + restitutionCoeff)) << " >= " << real_t(7) - real_t(1) / kappa );
+   WALBERLA_LOG_INFO_ON_ROOT("Max penetration = " << maxPenetration << " -> " << maxPenetration / radius * 100. << "% of radius");
+
+   std::ofstream file;
+   file.open( filename.c_str(), std::ios::out | std::ios::app );
+   file << impactAngle << " " << reboundAngle << "\n";
+   file.close();
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::mesa_pd::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/LinkedCellsVsBruteForce.cpp b/tests/mesa_pd/kernel/LinkedCellsVsBruteForce.cpp
new file mode 100644
index 000000000..0340e428b
--- /dev/null
+++ b/tests/mesa_pd/kernel/LinkedCellsVsBruteForce.cpp
@@ -0,0 +1,192 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   LinkedCellsVsBruteForce.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/collision_detection/AnalyticContactDetection.h>
+#include <mesa_pd/data/LinkedCells.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/kernel/DoubleCast.h>
+#include <mesa_pd/kernel/InsertParticleIntoLinkedCells.h>
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/math/Random.h>
+#include <core/mpi/Reduce.h>
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <numeric>
+
+namespace walberla {
+namespace mesa_pd {
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+      : ParticleAccessor(ps)
+      , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+class comp
+{
+public:
+   comp(std::vector<collision_detection::AnalyticContactDetection>& cs) : cs_(cs) {}
+   bool operator()(const size_t& c1, const size_t& c2)
+   {
+      if (cs_[c1].getIdx1() == cs_[c2].getIdx1()) return cs_[c1].getIdx2() < cs_[c2].getIdx2();
+      return cs_[c1].getIdx1() < cs_[c2].getIdx1();
+   }
+   std::vector<collision_detection::AnalyticContactDetection>& cs_;
+};
+
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   math::seedRandomGenerator( numeric_cast<std::mt19937::result_type>( 42 * walberla::mpi::MPIManager::instance()->rank() ) );
+
+   //logging::Logging::instance()->setStreamLogLevel(logging::Logging::DETAIL);
+   //logging::Logging::instance()->includeLoggingToFile("MESA_PD_Kernel_SyncNextNeighbor");
+   //logging::Logging::instance()->setFileLogLevel(logging::Logging::DETAIL);
+
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,30,30,30), // simulation domain
+                                                 Vector3<uint_t>(3,3,3), // blocks in each direction
+                                                 Vector3<bool>(true, true, true) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+
+   WALBERLA_CHECK_EQUAL(forest->size(), 1);
+   const Block& blk = *static_cast<blockforest::Block*>(&*forest->begin());
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+   data::LinkedCells     lc(blk.getAABB(), real_t(1));
+   std::vector<collision_detection::AnalyticContactDetection> cs1(100);
+   std::vector<collision_detection::AnalyticContactDetection> cs2(100);
+
+   ParticleAccessorWithShape accessor(ps, ss);
+
+   //initialize particles
+   const real_t radius  = real_t(0.5);
+   auto smallSphere = ss->create<data::Sphere>( radius );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2707));
+
+   for (int i = 0; i < 1000; ++i)
+   {
+      data::Particle&& p          = *ps->create();
+      p.getPositionRef()          = Vec3( math::realRandom(blk.getAABB().xMin(), blk.getAABB().xMax()),
+                                       math::realRandom(blk.getAABB().yMin(), blk.getAABB().yMax()),
+                                       math::realRandom(blk.getAABB().zMin(), blk.getAABB().zMax()) );
+      p.getInteractionRadiusRef() = radius;
+      p.getShapeIDRef()           = smallSphere;
+      p.getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+   }
+
+   //init kernels
+   kernel::InsertParticleIntoLinkedCells  ipilc;
+   mpi::SyncNextNeighbors                 SNN;
+
+   SNN(*ps, domain);
+
+   lc.clear();
+   ps->forEachParticle(true, kernel::SelectAll(), accessor, ipilc, accessor, lc);
+
+   ps->forEachParticlePairHalf(false,
+                               kernel::SelectAll(),
+                               accessor,
+                               [&cs1](const size_t idx1, const size_t idx2, auto& ac)
+   {
+      collision_detection::AnalyticContactDetection         acd;
+      kernel::DoubleCast               double_cast;
+      if (double_cast(idx1, idx2, ac, acd, ac ))
+      {
+         cs1.push_back(acd);
+      }
+   },
+   accessor );
+
+   lc.forEachParticlePairHalf(false,
+                              kernel::SelectAll(),
+                              accessor,
+                              [&cs2](const size_t idx1, const size_t idx2, auto& ac)
+   {
+      collision_detection::AnalyticContactDetection         acd;
+      kernel::DoubleCast               double_cast;
+      if (double_cast(idx1, idx2, ac, acd, ac ))
+      {
+         cs2.push_back(acd);
+      }
+   },
+   accessor );
+
+   WALBERLA_CHECK_EQUAL(cs1.size(), cs2.size());
+   WALBERLA_LOG_DEVEL(cs1.size() << " contacts detected");
+
+   std::vector<size_t> cs1_idx(cs1.size());
+   std::vector<size_t> cs2_idx(cs2.size());
+   std::iota(cs1_idx.begin(), cs1_idx.end(), 0);
+   std::iota(cs2_idx.begin(), cs2_idx.end(), 0);
+   std::sort(cs1_idx.begin(), cs1_idx.end(), comp(cs1));
+   std::sort(cs2_idx.begin(), cs2_idx.end(), comp(cs2));
+
+
+   for (size_t i = 0; i < cs1.size(); ++i)
+   {
+      WALBERLA_CHECK_EQUAL(cs1[cs1_idx[i]].getIdx1(), cs2[cs2_idx[i]].getIdx1());
+      WALBERLA_CHECK_EQUAL(cs1[cs1_idx[i]].getIdx2(), cs2[cs2_idx[i]].getIdx2());
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::mesa_pd::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/SingleCast.cpp b/tests/mesa_pd/kernel/SingleCast.cpp
new file mode 100644
index 000000000..c8ea556da
--- /dev/null
+++ b/tests/mesa_pd/kernel/SingleCast.cpp
@@ -0,0 +1,86 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   SingleCast.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/DataTypes.h>
+
+#include <mesa_pd/kernel/SingleCast.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+class SingleParticleAccessor : public data::IAccessor
+{
+public:
+   data::BaseShape* getShape(const size_t p_idx)
+   {
+      return p_idx == 0 ?
+               static_cast<data::BaseShape*>(&halfspace_) :
+               static_cast<data::BaseShape*>(&sphere_);
+   }
+
+   data::HalfSpace halfspace_ = data::HalfSpace(Vec3(1,0,0));
+   data::Sphere    sphere_    = data::Sphere(real_t(0.5));
+};
+
+class ShapeTester
+{
+public:
+   void operator()(const size_t idx, data::HalfSpace& /*hs*/)
+   {
+      WALBERLA_CHECK_EQUAL(idx, 0);
+   }
+
+   void operator()(const size_t idx, data::Sphere& /*s*/)
+   {
+      WALBERLA_CHECK_EQUAL(idx, 1);
+   }
+};
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   //init data structures
+   SingleParticleAccessor accessor;
+
+   //init kernels
+   kernel::SingleCast singlecast;
+
+   ShapeTester tester;
+   singlecast(0, accessor, tester);
+   singlecast(1, accessor, tester);
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/SpringDashpot.cpp b/tests/mesa_pd/kernel/SpringDashpot.cpp
new file mode 100644
index 000000000..746b9b1fe
--- /dev/null
+++ b/tests/mesa_pd/kernel/SpringDashpot.cpp
@@ -0,0 +1,139 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   SpringDashpot.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/collision_detection/AnalyticContactDetection.h>
+
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+
+#include <mesa_pd/kernel/DoubleCast.h>
+#include <mesa_pd/kernel/SpringDashpot.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps, std::shared_ptr<data::ShapeStorage>& ss)
+      : ParticleAccessor(ps)
+      , ss_(ss)
+   {}
+
+   const walberla::real_t& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass();}
+   void setInvMass(const size_t p_idx, const walberla::real_t& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t p_idx) {return ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF();}
+   void setInvInertiaBF(const size_t p_idx, const Mat3& v) { ss_->shapes[ps_->getShapeID(p_idx)]->getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeID(p_idx)].get();}
+private:
+   std::shared_ptr<data::ShapeStorage> ss_;
+};
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   if (std::is_same<real_t, float>::value)
+   {
+      WALBERLA_LOG_WARNING("waLBerla build in sp mode: skipping test due to low precision");
+      return EXIT_SUCCESS;
+   }
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   auto ss = std::make_shared<data::ShapeStorage>();
+
+   auto smallSphere = ss->create<data::Sphere>( real_t(2) );
+   ss->shapes[smallSphere]->updateMassAndInertia(real_t(2500));
+
+   ParticleAccessorWithShape ac(ps, ss);
+
+   data::Particle&& p1 = *ps->create();
+   p1.getPositionRef() = Vec3(0,0,0);
+   //p1.getAngularVelocityRef() = Vec3(1,1,-1).getNormalized();
+   p1.getShapeIDRef()  = smallSphere;
+   p1.getTypeRef()     = 0;
+
+   data::Particle&& p2 = *ps->create();
+   p2.getPositionRef() = Vec3(2,2,2);
+   p2.getShapeIDRef()  = smallSphere;
+   p2.getTypeRef()     = 0;
+
+   // Init kernels
+   kernel::SpringDashpot sd(1);
+   sd.setStiffness(0, 0, real_t(8.11e6));
+   sd.setDampingN (0, 0, real_t(6.86e1));
+   sd.setDampingT (0, 0, real_t(6.86e1));
+   sd.setFriction (0, 0, real_t(1.2));
+
+   collision_detection::AnalyticContactDetection contact;
+   kernel::DoubleCast       double_cast;
+   WALBERLA_CHECK(double_cast(0, 1, ac, contact, ac ));
+
+   // single contact test
+   sd(contact.getIdx1(),
+      contact.getIdx2(),
+      ac,
+      contact.getContactPoint(),
+      contact.getContactNormal(),
+      contact.getPenetrationDepth());
+   std::for_each(ps->begin(), ps->end(), [](data::Particle&& p){ WALBERLA_LOG_DEVEL(p); });
+   WALBERLA_CHECK_FLOAT_EQUAL( ps->getForce(0), -ps->getForce(1) );
+   WALBERLA_CHECK_FLOAT_EQUAL( ps->getForce(0), Vec3(1,1,1).getNormalized() * ((std::sqrt(real_t(12)) - 4) * sd.getStiffness(0, 0)) );
+
+   // thread safety test
+#ifdef _OPENMP
+#pragma omp parallel for schedule(static)
+#endif
+   for (int i = 0; i < 100; ++i)
+      sd(contact.getIdx1(),
+         contact.getIdx2(),
+         ac,
+         contact.getContactPoint(),
+         contact.getContactNormal(),
+         contact.getPenetrationDepth());
+
+   WALBERLA_CHECK_FLOAT_EQUAL( ps->getForce(0), -ps->getForce(1) );
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON( ps->getForce(0),
+                                       real_t(101) * Vec3(1,1,1).getNormalized() * ((std::sqrt(real_t(12)) - 4) * sd.getStiffness(0, 0)),
+                                       real_t(1e-6) );
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/SyncNextNeighbors.cpp b/tests/mesa_pd/kernel/SyncNextNeighbors.cpp
new file mode 100644
index 000000000..373dffbc3
--- /dev/null
+++ b/tests/mesa_pd/kernel/SyncNextNeighbors.cpp
@@ -0,0 +1,145 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   SyncNextNeighbors.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+#include <core/mpi/Reduce.h>
+
+#include <iostream>
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+
+const real_t radius = real_t(1);
+
+walberla::id_t createSphere(data::ParticleStorage& ps, domain::IDomain& domain)
+{
+   walberla::id_t uid = 0;
+   auto owned = domain.isContainedInProcessSubdomain( uint_c(walberla::mpi::MPIManager::instance()->rank()), Vec3(0,0,0) );
+   if (owned)
+   {
+      data::Particle&& p          = *ps.create();
+      p.getPositionRef()          = Vec3(0,0,0);
+      p.getInteractionRadiusRef() = radius;
+      p.getRotationRef()          = Rot3(Quat());
+      p.getLinearVelocityRef()    = Vec3(1,2,3);
+      p.getAngularVelocityRef()   = Vec3(4,5,6);
+      p.getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+      uid = p.getUid();
+   }
+
+   walberla::mpi::allReduceInplace(uid, walberla::mpi::SUM);
+   return uid;
+}
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+//   logging::Logging::instance()->setStreamLogLevel(logging::Logging::DETAIL);
+//   logging::Logging::instance()->includeLoggingToFile("MESA_PD_Kernel_SyncNextNeighbor");
+//   logging::Logging::instance()->setFileLogLevel(logging::Logging::DETAIL);
+
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(-15,-15,-15,15,15,15), // simulation domain
+                                                 Vector3<uint_t>(3,3,3), // blocks in each direction
+                                                 Vector3<bool>(true, true, true) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+   std::array< bool, 3 > periodic;
+   periodic[0] = forest->isPeriodic(0);
+   periodic[1] = forest->isPeriodic(1);
+   periodic[2] = forest->isPeriodic(2);
+
+   //init data structures
+   data::ParticleStorage ps(100);
+
+   //initialize particle
+   auto uid = createSphere(ps, domain);
+   WALBERLA_LOG_DEVEL_ON_ROOT("uid: " << uid);
+
+   //init kernels
+   mpi::SyncNextNeighbors SNN;
+
+   std::vector<real_t> deltas { real_t(0),
+            real_t(4.9),
+            real_t(5.1),
+            real_t(10),
+            real_t(14.9),
+            real_t(-14.9),
+            real_t(-10),
+            real_t(-5.1),
+            real_t(-4.9),
+            real_t(0)};
+
+   for (auto delta : deltas)
+   {
+      auto pos = Vec3(1,-1,1) * delta;
+      WALBERLA_LOG_DETAIL("checking position: " << pos);
+      // owner moves particle to new position
+      auto pIt = ps.find(uid);
+      if (pIt != ps.end())
+      {
+         if (!data::particle_flags::isSet(pIt->getFlags(), data::particle_flags::GHOST))
+         {
+            pIt->setPosition(pos);
+         }
+      }
+
+      //sync
+      SNN(ps, domain);
+
+      //check
+      if (sqDistancePointToAABBPeriodic(pos, forest->begin()->getAABB(), forest->getDomain(), periodic) <= radius * radius)
+      {
+         WALBERLA_CHECK_EQUAL(ps.size(), 1);
+         if (forest->begin()->getAABB().contains(pos))
+         {
+            WALBERLA_CHECK(!data::particle_flags::isSet(ps.begin()->getFlags(), data::particle_flags::GHOST));
+         } else
+         {
+            WALBERLA_CHECK(data::particle_flags::isSet(ps.begin()->getFlags(), data::particle_flags::GHOST));
+         }
+      } else
+      {
+         WALBERLA_CHECK_EQUAL(ps.size(), 0);
+      }
+   }
+
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::mesa_pd::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/TemperatureIntegration.cpp b/tests/mesa_pd/kernel/TemperatureIntegration.cpp
new file mode 100644
index 000000000..fefa30cad
--- /dev/null
+++ b/tests/mesa_pd/kernel/TemperatureIntegration.cpp
@@ -0,0 +1,70 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   TemperatureIntegration.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+
+#include <mesa_pd/kernel/TemperatureIntegration.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   //init data structures
+   data::SingleParticleAccessor accessor;
+
+   //initialize particle
+   accessor.setType(        0, 0 );
+   accessor.setHeatFlux(    0, real_t(8) );
+   accessor.setTemperature( 0, real_t(5) );
+
+   //init kernels
+   const real_t dt = real_t(1);
+   kernel::TemperatureIntegration integrator( dt, 1 );
+   integrator.setInvHeatCapacity( 0, real_t(2) );
+
+   integrator(0, accessor);
+
+   //check force
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getHeatFlux(0) + real_t(1), real_t(1));
+
+   //check velocity
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getTemperature(0), real_t(21));
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/VelocityVerlet.cpp b/tests/mesa_pd/kernel/VelocityVerlet.cpp
new file mode 100644
index 000000000..e322d78c1
--- /dev/null
+++ b/tests/mesa_pd/kernel/VelocityVerlet.cpp
@@ -0,0 +1,118 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   VelocityVerlet.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/kernel/VelocityVerlet.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   const real_t k = real_t(0.1);
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   data::ParticleAccessor accessor(ps);
+
+   const Vec3 startingVelocity(real_t(1),real_t(2),real_t(3));
+
+   //initialize particle
+   data::Particle&& p        = *ps->create();
+   p.getPositionRef()        = Vec3(0,0,0);
+   p.getInvMassRef()         = real_t(1.1);
+   p.getLinearVelocityRef()  = startingVelocity;
+   p.getForceRef()           = Vec3(0,0,0);
+   p.getOldForceRef()        = Vec3(0,0,0);
+
+   const real_t dt = real_t(0.1);
+   const real_t w = std::sqrt(k*accessor.getInvMass(0));
+   const Vec3 A = accessor.getLinearVelocity(0) / w;
+   WALBERLA_LOG_DEVEL_VAR(w);
+   WALBERLA_LOG_DEVEL_VAR(A);
+
+   auto analyticPosition = [A, w](const real_t t){return A * std::sin(w*t);};
+   auto analyticVelocity = [A, w](const real_t t){return A * std::cos(w*t) * w;};
+
+   //init kernels
+   kernel::VelocityVerletPreForceUpdate  preForce( dt );
+   kernel::VelocityVerletPostForceUpdate postForce( dt );
+
+   p.getPositionRef()        = analyticPosition(-dt);
+   p.getLinearVelocityRef()  = analyticVelocity(-dt);
+   p.getOldForceRef()        = - k * p.getPosition();
+   ps->forEachParticle(false, kernel::SelectAll(), accessor, preForce, accessor);
+   p.getLinearVelocityRef()  = analyticVelocity(real_t(0));
+   p.getForceRef()           = - k * p.getPosition();
+   ps->forEachParticle(false, kernel::SelectAll(), accessor, postForce, accessor);
+
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getPosition(), Vec3(0), real_t(1e-2));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getLinearVelocity(), startingVelocity, real_t(1e-2));
+
+   for (auto i = 1; i < 500; ++i)
+   {
+      ps->forEachParticle(false, kernel::SelectAll(), accessor, preForce, accessor);
+      p.getForceRef()           = - k * p.getPosition();
+      ps->forEachParticle(false, kernel::SelectAll(), accessor, postForce, accessor);
+
+      //check force
+      WALBERLA_CHECK_FLOAT_EQUAL(p.getForce(), Vec3(0), p);
+
+      //check velocity
+      WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getLinearVelocity(),
+                                         analyticVelocity(real_c(i) * dt),
+                                         real_t(1e-2),
+                                         "iteration: " << i << "\n" <<
+                                         "t: " << real_c(i)*dt << "\n" <<
+                                         p);
+
+      //check position
+      WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getPosition(),
+                                         analyticPosition(real_c(i) * dt),
+                                         real_t(1e-2),
+                                         "iteration: " << i << "\n" <<
+                                         "t: " << real_c(i)*dt << "\n" <<
+                                         p);
+//      WALBERLA_LOG_DEVEL_VAR(p.getPosition());
+//      WALBERLA_LOG_DEVEL_VAR(analyticPosition(real_c(i) * dt));
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/kernel/VelocityVerletWithShape.cpp b/tests/mesa_pd/kernel/VelocityVerletWithShape.cpp
new file mode 100644
index 000000000..fc3567c1f
--- /dev/null
+++ b/tests/mesa_pd/kernel/VelocityVerletWithShape.cpp
@@ -0,0 +1,201 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   VelocityVerletWithShape.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/shape/Sphere.h>
+
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/kernel/VelocityVerletWithShape.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <cmath>
+#include <fstream>
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps)
+      : ParticleAccessor(ps)
+   {
+      sp.updateMassAndInertia(real_t(1234));
+   }
+
+   const walberla::real_t& getInvMass(const size_t /*p_idx*/) const {return sp.getInvMass();}
+   walberla::real_t& getInvMassRef(const size_t /*p_idx*/) {return sp.getInvMass();}
+   void setInvMass(const size_t /*p_idx*/, const walberla::real_t& v) { sp.getInvMass() = v;}
+
+   const auto& getInvInertiaBF(const size_t /*p_idx*/) const {return sp.getInvInertiaBF();}
+   auto& getInvInertiaBFRef(const size_t /*p_idx*/) {return sp.getInvInertiaBF();}
+   void setInvInertiaBF(const size_t /*p_idx*/, const Mat3& v) { sp.getInvInertiaBF() = v;}
+
+   data::BaseShape* getShape(const size_t /*p_idx*/) {return &sp;}
+private:
+   data::Sphere sp = data::Sphere(real_t(0.6));
+};
+
+int main()
+{
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   ParticleAccessorWithShape accessor(ps);
+
+   const Vec3 startingVelocity(real_t(1),real_t(2),real_t(3));
+
+   //initialize particle
+   data::Particle&& p        = *ps->create();
+   p.getPositionRef()        = Vec3(0,0,0);
+   p.getLinearVelocityRef()  = startingVelocity;
+   p.getAngularVelocityRef() = startingVelocity;
+   p.getForceRef()           = Vec3(0,0,0);
+   p.getOldForceRef()        = Vec3(0,0,0);
+
+   const real_t dt = real_t(0.001);
+
+   const real_t k = real_t(10000);
+   const real_t w = std::sqrt(k*accessor.getInvMass(0));
+   const Vec3   A = accessor.getLinearVelocity(0) / w;
+   WALBERLA_LOG_DEVEL_VAR(w);
+   WALBERLA_LOG_DEVEL_VAR(A);
+
+   auto analyticPosition = [A, w](const real_t t){return A * std::sin(w*t);};
+   auto analyticVelocity = [A, w](const real_t t){return A * std::cos(w*t) * w;};
+
+   const real_t kappa = real_t(1000);
+   const real_t omega = std::sqrt(kappa*accessor.getInvInertiaBF(0)[0]);
+   const Vec3   Alpha = accessor.getAngularVelocity(0) / omega;
+   WALBERLA_LOG_DEVEL_VAR(omega);
+   WALBERLA_LOG_DEVEL_VAR(Alpha);
+
+   auto analyticRotation = [Alpha, omega](const real_t t){return Alpha * std::sin(omega*t);};
+   auto analyticAngVel   = [Alpha, omega](const real_t t){return Alpha * std::cos(omega*t) * omega;};
+
+   //init kernels
+   kernel::VelocityVerletWithShapePreForceUpdate  preForce( dt );
+   kernel::VelocityVerletWithShapePostForceUpdate postForce( dt );
+
+   p.getPositionRef()        = analyticPosition(-dt);
+   p.getLinearVelocityRef()  = analyticVelocity(-dt);
+   p.getOldForceRef()        = - k * p.getPosition();
+
+   p.getRotationRef()        = Rot3(Quat(analyticRotation(-dt), analyticRotation(-dt).length()));
+   p.getAngularVelocityRef() = analyticAngVel(-dt);
+   p.getOldTorqueRef()       = - kappa * p.getRotation().getQuaternion().getAxis() * p.getRotation().getQuaternion().getAngle();
+
+   ps->forEachParticle(false, kernel::SelectAll(), accessor, preForce, accessor);
+
+   p.getLinearVelocityRef()  = analyticVelocity(real_t(0));
+   p.getForceRef()           = - k * p.getPosition();
+
+   p.getAngularVelocityRef() = analyticAngVel(real_t(0));
+   p.getTorqueRef()          = - kappa * p.getRotation().getQuaternion().getAxis() * p.getRotation().getQuaternion().getAngle();
+   ps->forEachParticle(false, kernel::SelectAll(), accessor, postForce, accessor);
+
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getPosition(), Vec3(0), real_t(1e-2));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getLinearVelocity(), startingVelocity, real_t(1e-2));
+
+   std::fstream fout("VelocityVerletWithShape.txt", std::ofstream::out);
+   for (auto i = 1; i < 3000; ++i)
+   {
+      ps->forEachParticle(false, kernel::SelectAll(), accessor, preForce, accessor);
+      p.getForceRef()           = - k * p.getPosition();
+      p.getTorqueRef()          = - kappa * p.getRotation().getQuaternion().getAxis() * p.getRotation().getQuaternion().getAngle();
+      ps->forEachParticle(false, kernel::SelectAll(), accessor, postForce, accessor);
+
+      //check force&torque
+      WALBERLA_CHECK_FLOAT_EQUAL(p.getForce(), Vec3(0), p);
+      WALBERLA_CHECK_FLOAT_EQUAL(p.getTorque(), Vec3(0), p);
+
+      //check velocity
+      WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getLinearVelocity(),
+                                         analyticVelocity(real_c(i) * dt),
+                                         real_t(1e-4),
+                                         "iteration: " << i << "\n" <<
+                                         "t: " << real_c(i)*dt << "\n" <<
+                                         p);
+
+      //check angular velocity
+      WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getAngularVelocity(),
+                                         analyticAngVel(real_c(i) * dt),
+                                         real_t(1e-4),
+                                         "iteration: " << i << "\n" <<
+                                         "t: " << real_c(i)*dt << "\n" <<
+                                         p);
+      //            WALBERLA_LOG_DEVEL_VAR(p.getAngularVelocity());
+      //            WALBERLA_LOG_DEVEL_VAR(analyticAngVel(real_c(i) * dt));
+
+      //check position
+      WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getPosition(),
+                                         analyticPosition(real_c(i) * dt),
+                                         real_t(1e-4),
+                                         "iteration: " << i << "\n" <<
+                                         "t: " << real_c(i)*dt << "\n" <<
+                                         p);
+
+      //check rotation
+      auto angSol = Rot3(Quat(analyticRotation(real_c(i) * dt), analyticRotation(real_c(i) * dt).length()));
+      WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(p.getRotation().getQuaternion().getAxis() * p.getRotation().getQuaternion().getAngle(),
+                                         angSol.getQuaternion().getAxis() * angSol.getQuaternion().getAngle(),
+                                         real_t(1e-4),
+                                         "iteration: " << i << "\n" <<
+                                         "t: " << real_c(i)*dt << "\n" <<
+                                         p);
+      //      WALBERLA_LOG_DEVEL_VAR(p.getPosition());
+      //      WALBERLA_LOG_DEVEL_VAR(analyticPosition(real_c(i) * dt));
+
+      //            WALBERLA_LOG_DEVEL_VAR(/*p.getRotation().getQuaternion().getAxis() * */p.getRotation().getQuaternion().getAngle());
+      //            WALBERLA_LOG_DEVEL_VAR(/*angSol.getQuaternion().getAxis() * */angSol.getQuaternion().getAngle());
+      fout << p.getPosition().length() << " " <<
+              analyticPosition(real_c(i) * dt).length() << " " <<
+              p.getLinearVelocity().length() << " " <<
+              analyticVelocity(real_c(i) * dt).length() << " " <<
+              p.getRotation().getQuaternion().getAngle() << " " <<
+              angSol.getQuaternion().getAngle() << " " <<
+              p.getAngularVelocity().length() << " " <<
+              analyticAngVel(real_c(i) * dt).length() << std::endl;
+   }
+   fout.close();
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   walberla::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   if (std::is_same<walberla::real_t, float>::value)
+   {
+      WALBERLA_LOG_WARNING("waLBerla build in sp mode: skipping test due to low precision");
+      return EXIT_SUCCESS;
+   }
+
+   return walberla::main();
+}
diff --git a/tests/mesa_pd/kernel/interfaces/ExplicitEulerInterfaceCheck.cpp b/tests/mesa_pd/kernel/interfaces/ExplicitEulerInterfaceCheck.cpp
new file mode 100644
index 000000000..792965081
--- /dev/null
+++ b/tests/mesa_pd/kernel/interfaces/ExplicitEulerInterfaceCheck.cpp
@@ -0,0 +1,75 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ExplicitEulerInterfaceCheck.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/kernel/ExplicitEuler.h>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+   const walberla::mesa_pd::Vec3& getPosition(const size_t /*p_idx*/) const {return position_;}
+   void setPosition(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { position_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t /*p_idx*/) const {return linearVelocity_;}
+   void setLinearVelocity(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { linearVelocity_ = v;}
+   
+   const walberla::real_t& getInvMass(const size_t /*p_idx*/) const {return invMass_;}
+   
+   const walberla::mesa_pd::Vec3& getForce(const size_t /*p_idx*/) const {return force_;}
+   void setForce(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { force_ = v;}
+   
+   const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t /*p_idx*/) const {return flags_;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   walberla::mesa_pd::Vec3 position_;
+   walberla::mesa_pd::Vec3 linearVelocity_;
+   walberla::real_t invMass_;
+   walberla::mesa_pd::Vec3 force_;
+   walberla::mesa_pd::data::particle_flags::FlagT flags_;
+};
+
+template void kernel::ExplicitEuler::operator()(const size_t p_idx1, Accessor& ac) const;
+
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/tests/mesa_pd/kernel/interfaces/ExplicitEulerWithShapeInterfaceCheck.cpp b/tests/mesa_pd/kernel/interfaces/ExplicitEulerWithShapeInterfaceCheck.cpp
new file mode 100644
index 000000000..3737bd292
--- /dev/null
+++ b/tests/mesa_pd/kernel/interfaces/ExplicitEulerWithShapeInterfaceCheck.cpp
@@ -0,0 +1,90 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ExplicitEulerWithShapeInterfaceCheck.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/kernel/ExplicitEulerWithShape.h>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+   const walberla::mesa_pd::Vec3& getPosition(const size_t /*p_idx*/) const {return position_;}
+   void setPosition(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { position_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t /*p_idx*/) const {return linearVelocity_;}
+   void setLinearVelocity(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { linearVelocity_ = v;}
+   
+   const walberla::real_t& getInvMass(const size_t /*p_idx*/) const {return invMass_;}
+   
+   const walberla::mesa_pd::Vec3& getForce(const size_t /*p_idx*/) const {return force_;}
+   void setForce(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { force_ = v;}
+   
+   const walberla::mesa_pd::Rot3& getRotation(const size_t /*p_idx*/) const {return rotation_;}
+   void setRotation(const size_t /*p_idx*/, const walberla::mesa_pd::Rot3& v) { rotation_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t /*p_idx*/) const {return angularVelocity_;}
+   void setAngularVelocity(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { angularVelocity_ = v;}
+   
+   const walberla::mesa_pd::Mat3& getInvInertiaBF(const size_t /*p_idx*/) const {return invInertiaBF_;}
+   
+   const walberla::mesa_pd::Vec3& getTorque(const size_t /*p_idx*/) const {return torque_;}
+   void setTorque(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { torque_ = v;}
+   
+   const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t /*p_idx*/) const {return flags_;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   walberla::mesa_pd::Vec3 position_;
+   walberla::mesa_pd::Vec3 linearVelocity_;
+   walberla::real_t invMass_;
+   walberla::mesa_pd::Vec3 force_;
+   walberla::mesa_pd::Rot3 rotation_;
+   walberla::mesa_pd::Vec3 angularVelocity_;
+   walberla::mesa_pd::Mat3 invInertiaBF_;
+   walberla::mesa_pd::Vec3 torque_;
+   walberla::mesa_pd::data::particle_flags::FlagT flags_;
+};
+
+template void kernel::ExplicitEulerWithShape::operator()(const size_t p_idx1, Accessor& ac) const;
+
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/tests/mesa_pd/kernel/interfaces/ForceLJInterfaceCheck.cpp b/tests/mesa_pd/kernel/interfaces/ForceLJInterfaceCheck.cpp
new file mode 100644
index 000000000..b742e0d22
--- /dev/null
+++ b/tests/mesa_pd/kernel/interfaces/ForceLJInterfaceCheck.cpp
@@ -0,0 +1,66 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ForceLJInterfaceCheck.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/kernel/ForceLJ.h>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+   const walberla::mesa_pd::Vec3& getPosition(const size_t /*p_idx*/) const {return position_;}
+   
+   walberla::mesa_pd::Vec3& getForceRef(const size_t /*p_idx*/) {return force_;}
+   
+   const uint_t& getType(const size_t /*p_idx*/) const {return type_;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   walberla::mesa_pd::Vec3 position_;
+   walberla::mesa_pd::Vec3 force_;
+   uint_t type_;
+};
+
+template void kernel::ForceLJ::operator()(const size_t p_idx1, const size_t p_idx2, Accessor& ac) const;
+
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/tests/mesa_pd/kernel/interfaces/HeatConductionInterfaceCheck.cpp b/tests/mesa_pd/kernel/interfaces/HeatConductionInterfaceCheck.cpp
new file mode 100644
index 000000000..4e30eba2e
--- /dev/null
+++ b/tests/mesa_pd/kernel/interfaces/HeatConductionInterfaceCheck.cpp
@@ -0,0 +1,68 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file HeatConductionInterfaceCheck.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/kernel/HeatConduction.h>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+   const walberla::real_t& getTemperature(const size_t /*p_idx*/) const {return temperature_;}
+   
+   const walberla::real_t& getHeatFlux(const size_t /*p_idx*/) const {return heatFlux_;}
+   void setHeatFlux(const size_t /*p_idx*/, const walberla::real_t& v) { heatFlux_ = v;}
+   walberla::real_t& getHeatFluxRef(const size_t /*p_idx*/) {return heatFlux_;}
+   
+   const uint_t& getType(const size_t /*p_idx*/) const {return type_;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   walberla::real_t temperature_;
+   walberla::real_t heatFlux_;
+   uint_t type_;
+};
+
+template void kernel::HeatConduction::operator()(const size_t p_idx1, const size_t p_idx2, Accessor& ac) const;
+
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/tests/mesa_pd/kernel/interfaces/SpringDashpotInterfaceCheck.cpp b/tests/mesa_pd/kernel/interfaces/SpringDashpotInterfaceCheck.cpp
new file mode 100644
index 000000000..b76620428
--- /dev/null
+++ b/tests/mesa_pd/kernel/interfaces/SpringDashpotInterfaceCheck.cpp
@@ -0,0 +1,79 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file SpringDashpotInterfaceCheck.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/kernel/SpringDashpot.h>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+   const walberla::mesa_pd::Vec3& getPosition(const size_t /*p_idx*/) const {return position_;}
+   
+   const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t /*p_idx*/) const {return linearVelocity_;}
+   
+   walberla::mesa_pd::Vec3& getForceRef(const size_t /*p_idx*/) {return force_;}
+   
+   const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t /*p_idx*/) const {return angularVelocity_;}
+   
+   walberla::mesa_pd::Vec3& getTorqueRef(const size_t /*p_idx*/) {return torque_;}
+   
+   const uint_t& getType(const size_t /*p_idx*/) const {return type_;}
+   
+   const std::map<walberla::id_t, walberla::mesa_pd::Vec3>& getContactHistory(const size_t /*p_idx*/) const {return contactHistory_;}
+   void setContactHistory(const size_t /*p_idx*/, const std::map<walberla::id_t, walberla::mesa_pd::Vec3>& v) { contactHistory_ = v;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   walberla::mesa_pd::Vec3 position_;
+   walberla::mesa_pd::Vec3 linearVelocity_;
+   walberla::mesa_pd::Vec3 force_;
+   walberla::mesa_pd::Vec3 angularVelocity_;
+   walberla::mesa_pd::Vec3 torque_;
+   uint_t type_;
+   std::map<walberla::id_t, walberla::mesa_pd::Vec3> contactHistory_;
+};
+
+template void kernel::SpringDashpot::operator()(const size_t p_idx1, const size_t p_idx2, Accessor& ac, const Vec3& contactPoint, const Vec3& contactNormal, const real_t& penetrationDepth) const;
+
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/tests/mesa_pd/kernel/interfaces/TemperatureIntegrationInterfaceCheck.cpp b/tests/mesa_pd/kernel/interfaces/TemperatureIntegrationInterfaceCheck.cpp
new file mode 100644
index 000000000..e764dffa1
--- /dev/null
+++ b/tests/mesa_pd/kernel/interfaces/TemperatureIntegrationInterfaceCheck.cpp
@@ -0,0 +1,68 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file TemperatureIntegrationInterfaceCheck.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/kernel/TemperatureIntegration.h>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+   const walberla::real_t& getTemperature(const size_t /*p_idx*/) const {return temperature_;}
+   void setTemperature(const size_t /*p_idx*/, const walberla::real_t& v) { temperature_ = v;}
+   
+   const walberla::real_t& getHeatFlux(const size_t /*p_idx*/) const {return heatFlux_;}
+   void setHeatFlux(const size_t /*p_idx*/, const walberla::real_t& v) { heatFlux_ = v;}
+   
+   const uint_t& getType(const size_t /*p_idx*/) const {return type_;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   walberla::real_t temperature_;
+   walberla::real_t heatFlux_;
+   uint_t type_;
+};
+
+template void kernel::TemperatureIntegration::operator()(const size_t p_idx1, Accessor& ac) const;
+
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/tests/mesa_pd/kernel/interfaces/VelocityVerletInterfaceCheck.cpp b/tests/mesa_pd/kernel/interfaces/VelocityVerletInterfaceCheck.cpp
new file mode 100644
index 000000000..e0cca71cc
--- /dev/null
+++ b/tests/mesa_pd/kernel/interfaces/VelocityVerletInterfaceCheck.cpp
@@ -0,0 +1,80 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file VelocityVerletInterfaceCheck.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/kernel/VelocityVerlet.h>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+   const walberla::mesa_pd::Vec3& getPosition(const size_t /*p_idx*/) const {return position_;}
+   void setPosition(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { position_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t /*p_idx*/) const {return linearVelocity_;}
+   void setLinearVelocity(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { linearVelocity_ = v;}
+   
+   const walberla::real_t& getInvMass(const size_t /*p_idx*/) const {return invMass_;}
+   
+   const walberla::mesa_pd::Vec3& getForce(const size_t /*p_idx*/) const {return force_;}
+   void setForce(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { force_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getOldForce(const size_t /*p_idx*/) const {return oldForce_;}
+   void setOldForce(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { oldForce_ = v;}
+   
+   const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t /*p_idx*/) const {return flags_;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   walberla::mesa_pd::Vec3 position_;
+   walberla::mesa_pd::Vec3 linearVelocity_;
+   walberla::real_t invMass_;
+   walberla::mesa_pd::Vec3 force_;
+   walberla::mesa_pd::Vec3 oldForce_;
+   walberla::mesa_pd::data::particle_flags::FlagT flags_;
+};
+
+template void kernel::VelocityVerletPreForceUpdate::operator()(const size_t p_idx1, Accessor& ac) const;
+template void kernel::VelocityVerletPostForceUpdate::operator()(const size_t p_idx1, Accessor& ac) const;
+
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/tests/mesa_pd/kernel/interfaces/VelocityVerletWithShapeInterfaceCheck.cpp b/tests/mesa_pd/kernel/interfaces/VelocityVerletWithShapeInterfaceCheck.cpp
new file mode 100644
index 000000000..d000cb66e
--- /dev/null
+++ b/tests/mesa_pd/kernel/interfaces/VelocityVerletWithShapeInterfaceCheck.cpp
@@ -0,0 +1,99 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file VelocityVerletWithShapeInterfaceCheck.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+//======================================================================================================================
+//
+//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/IAccessor.h>
+#include <mesa_pd/kernel/VelocityVerletWithShape.h>
+
+#include <core/UniqueID.h>
+
+#include <map>
+
+namespace walberla {
+namespace mesa_pd {
+
+class Accessor : public data::IAccessor
+{
+public:
+   virtual ~Accessor() = default;
+   const walberla::mesa_pd::Vec3& getPosition(const size_t /*p_idx*/) const {return position_;}
+   void setPosition(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { position_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getLinearVelocity(const size_t /*p_idx*/) const {return linearVelocity_;}
+   void setLinearVelocity(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { linearVelocity_ = v;}
+   
+   const walberla::real_t& getInvMass(const size_t /*p_idx*/) const {return invMass_;}
+   
+   const walberla::mesa_pd::Vec3& getForce(const size_t /*p_idx*/) const {return force_;}
+   void setForce(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { force_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getOldForce(const size_t /*p_idx*/) const {return oldForce_;}
+   void setOldForce(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { oldForce_ = v;}
+   
+   const walberla::mesa_pd::Rot3& getRotation(const size_t /*p_idx*/) const {return rotation_;}
+   void setRotation(const size_t /*p_idx*/, const walberla::mesa_pd::Rot3& v) { rotation_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getAngularVelocity(const size_t /*p_idx*/) const {return angularVelocity_;}
+   void setAngularVelocity(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { angularVelocity_ = v;}
+   
+   const walberla::mesa_pd::Mat3& getInvInertiaBF(const size_t /*p_idx*/) const {return invInertiaBF_;}
+   
+   const walberla::mesa_pd::Vec3& getTorque(const size_t /*p_idx*/) const {return torque_;}
+   void setTorque(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { torque_ = v;}
+   
+   const walberla::mesa_pd::Vec3& getOldTorque(const size_t /*p_idx*/) const {return oldTorque_;}
+   void setOldTorque(const size_t /*p_idx*/, const walberla::mesa_pd::Vec3& v) { oldTorque_ = v;}
+   
+   const walberla::mesa_pd::data::particle_flags::FlagT& getFlags(const size_t /*p_idx*/) const {return flags_;}
+   
+
+   id_t getInvalidUid() const {return UniqueID<int>::invalidID();}
+   size_t getInvalidIdx() const {return std::numeric_limits<size_t>::max();}
+   /**
+   * @brief Returns the index of particle specified by uid.
+   * @param uid unique id of the particle to be looked up
+   * @return the index of the particle or std::numeric_limits<size_t>::max() if the particle is not found
+   */
+   size_t uidToIdx(const id_t& /*uid*/) const {return 0;}
+   size_t size() const { return 1; }
+private:
+   walberla::mesa_pd::Vec3 position_;
+   walberla::mesa_pd::Vec3 linearVelocity_;
+   walberla::real_t invMass_;
+   walberla::mesa_pd::Vec3 force_;
+   walberla::mesa_pd::Vec3 oldForce_;
+   walberla::mesa_pd::Rot3 rotation_;
+   walberla::mesa_pd::Vec3 angularVelocity_;
+   walberla::mesa_pd::Mat3 invInertiaBF_;
+   walberla::mesa_pd::Vec3 torque_;
+   walberla::mesa_pd::Vec3 oldTorque_;
+   walberla::mesa_pd::data::particle_flags::FlagT flags_;
+};
+
+template void kernel::VelocityVerletWithShapePreForceUpdate::operator()(const size_t p_idx1, Accessor& ac) const;
+template void kernel::VelocityVerletWithShapePostForceUpdate::operator()(const size_t p_idx1, Accessor& ac) const;
+
+} //namespace mesa_pd
+} //namespace walberla
\ No newline at end of file
diff --git a/tests/mesa_pd/mpi/BroadcastProperty.cpp b/tests/mesa_pd/mpi/BroadcastProperty.cpp
new file mode 100644
index 000000000..de7a0a2d7
--- /dev/null
+++ b/tests/mesa_pd/mpi/BroadcastProperty.cpp
@@ -0,0 +1,110 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   BroadcastProperty.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/mpi/BroadcastProperty.h>
+#include <mesa_pd/mpi/notifications/ForceTorqueNotification.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+#include <core/mpi/Reduce.h>
+
+#include <iostream>
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+
+const real_t radius = real_t(3);
+
+walberla::id_t createSphere(data::ParticleStorage& ps, domain::IDomain& domain)
+{
+   walberla::id_t uid = 0;
+   auto owned = domain.isContainedInProcessSubdomain( uint_c(walberla::mpi::MPIManager::instance()->rank()), Vec3(9,9,9) );
+   if (owned)
+   {
+      data::Particle&& p          = *ps.create();
+      p.getPositionRef()          = Vec3(9,9,9);
+      p.getInteractionRadiusRef() = radius;
+      p.getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+      p.setForce( Vec3(1) );
+      p.setTorque( Vec3(1) );
+      uid = p.getUid();
+   }
+
+   walberla::mpi::allReduceInplace(uid, walberla::mpi::SUM);
+   return uid;
+}
+
+void main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,20,20,20), // simulation domain
+                                                 Vector3<uint_t>(2,2,2), // blocks in each direction
+                                                 Vector3<bool>(false, false, false) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+
+   //init data structures
+   data::ParticleStorage ps(100);
+
+   //initialize particle
+   auto uid = createSphere(ps, domain);
+   WALBERLA_LOG_DEVEL_ON_ROOT("uid: " << uid);
+
+   //init kernels
+   mpi::BroadcastProperty BP;
+   mpi::SyncNextNeighbors SNN;
+
+   //sync
+   SNN(ps, domain);
+
+   auto pIt = ps.find(uid);
+   if (pIt != ps.end())
+   {
+      if (!data::particle_flags::isSet( pIt->getFlags(), data::particle_flags::GHOST))
+      {
+         pIt->setForce( Vec3(real_c(42)) );
+         pIt->setTorque( Vec3(real_c(43)) );
+      }
+   }
+
+   BP.operator()<ForceTorqueNotification>(ps);
+
+   WALBERLA_CHECK_FLOAT_EQUAL( pIt->getForce(), Vec3(real_c(42)) );
+   WALBERLA_CHECK_FLOAT_EQUAL( pIt->getTorque(), Vec3(real_c(43)) );
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   walberla::mesa_pd::main(argc, argv);
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/mpi/Notifications.cpp b/tests/mesa_pd/mpi/Notifications.cpp
new file mode 100644
index 000000000..e6eeacca6
--- /dev/null
+++ b/tests/mesa_pd/mpi/Notifications.cpp
@@ -0,0 +1,68 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   Notifications.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+
+   //init data structures
+   data::ParticleStorage ps(100);
+
+   //initialize particle
+   const auto linVel = Vec3(1,2,3);
+   const auto angVel = Vec3(1,2,3);
+
+   const auto force  = Vec3(1,2,3);
+   const auto torque = Vec3(1,2,3);
+
+   data::Particle&& p        = *ps.create();
+   p.getPositionRef()        = Vec3(0,0,0);
+   p.getRotationRef()        = Rot3(Quat());
+   p.getLinearVelocityRef()  = linVel;
+   p.getAngularVelocityRef() = angVel;
+   p.getForceRef()           = force;
+   p.getTorqueRef()          = torque;
+
+   //init kernels
+   WALBERLA_LOG_DEVEL(p);
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
diff --git a/tests/mesa_pd/mpi/ReduceContactHistory.cpp b/tests/mesa_pd/mpi/ReduceContactHistory.cpp
new file mode 100644
index 000000000..1135e1d93
--- /dev/null
+++ b/tests/mesa_pd/mpi/ReduceContactHistory.cpp
@@ -0,0 +1,182 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   ReduceContactHistory.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/collision_detection/AnalyticContactDetection.h>
+#include <mesa_pd/data/ParticleAccessor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/kernel/DoubleCast.h>
+#include <mesa_pd/kernel/InsertParticleIntoLinkedCells.h>
+#include <mesa_pd/kernel/ParticleSelector.h>
+#include <mesa_pd/mpi/ContactFilter.h>
+#include <mesa_pd/mpi/ReduceContactHistory.h>
+#include <mesa_pd/mpi/ReduceProperty.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <mesa_pd/mpi/notifications/ContactHistoryNotification.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+
+#include <iostream>
+
+namespace walberla {
+namespace mesa_pd {
+
+const real_t radius = real_t(0.7);
+const real_t spacing = real_t(1.0);
+
+class ParticleAccessorWithShape : public data::ParticleAccessor
+{
+public:
+   ParticleAccessorWithShape(std::shared_ptr<data::ParticleStorage>& ps)
+      : ParticleAccessor(ps)
+   {}
+
+   data::BaseShape* getShape(const size_t /*p_idx*/) {return &sp;}
+private:
+   data::Sphere sp = data::Sphere(radius);
+};
+
+class IncreaseContactHistory
+{
+public:
+   IncreaseContactHistory() = default;
+
+   template <typename Accessor>
+   void operator()(const size_t p_idx1,
+                   const size_t p_idx2,
+                   Accessor& ac,
+                   const Vec3& /*contactPoint*/,
+                   const Vec3& /*contactNormal*/,
+                   const real_t& /*penetrationDepth*/) const
+   {
+      auto& oldCH1 = ac.getOldContactHistoryRef(p_idx1)[ac.getUid(p_idx2)];
+      auto& oldCH2 = ac.getOldContactHistoryRef(p_idx2)[ac.getUid(p_idx1)];
+      auto& newCH1 = ac.getNewContactHistoryRef(p_idx1)[ac.getUid(p_idx2)];
+      auto& newCH2 = ac.getNewContactHistoryRef(p_idx2)[ac.getUid(p_idx1)];
+      newCH1.setTangentialSpringDisplacement(oldCH1.getTangentialSpringDisplacement() + Vec3(ac.getUid(p_idx2)));
+      newCH2.setTangentialSpringDisplacement(oldCH2.getTangentialSpringDisplacement() + Vec3(ac.getUid(p_idx1)));
+   }
+
+};
+
+void createSphere(data::ParticleStorage& ps, domain::IDomain& domain, const Vec3& pos)
+{
+   static uint64_t uid = 0;
+
+   auto owned = domain.isContainedInProcessSubdomain( uint_c(walberla::mpi::MPIManager::instance()->rank()), pos );
+   if (owned)
+   {
+      data::Particle&& p          = *ps.create(uid); // DO NOT DO THIS IF YOU DONT KNOW WHAT YOU ARE DOING
+      p.getPositionRef()          = pos;
+      p.getInteractionRadiusRef() = radius;
+      p.getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+      p.getTypeRef()              = 0;
+   }
+   ++uid;
+}
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,4,4,4), // simulation domain
+                                                 Vector3<uint_t>(2,2,2), // blocks in each direction
+                                                 Vector3<bool>(true, true, true) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+   ParticleAccessorWithShape accessor(ps);
+
+   for (auto pt : grid_generator::SCGrid(forest->getDomain(), Vector3<real_t>(spacing, spacing, spacing) * real_c(0.4), spacing))
+   {
+      createSphere(*ps, domain, pt);
+   }
+
+   IncreaseContactHistory                INC;
+   mpi::ReduceContactHistory             ReduceContactHistory;
+   mpi::ReduceProperty                   RP;
+   mpi::SyncNextNeighbors                SNN;
+
+   SNN(*ps, domain);
+
+   const int simulationSteps = 10;
+   for (int i=0; i < simulationSteps; ++i)
+   {
+      //if (i % visSpacing == 0)
+      //{
+      //   vtkWriter->write();
+      //}
+
+
+      ps->forEachParticlePairHalf(true,
+                                  kernel::SelectAll(),
+                                  accessor,
+                                  [&](const size_t idx1, const size_t idx2, auto& ac)
+      {
+         collision_detection::AnalyticContactDetection acd;
+         kernel::DoubleCast double_cast;
+         const mpi::ContactFilter contact_filter;
+         if (double_cast(idx1, idx2, ac, acd, ac ))
+         {
+            if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), domain))
+            {
+               INC(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth());
+            }
+         }
+      },
+      accessor );
+
+      ReduceContactHistory(*ps);
+
+      SNN(*ps, domain);
+   }
+
+
+   for (data::Particle&& p : *ps)
+   {
+      WALBERLA_CHECK_EQUAL(p.getOldContactHistory().size(), 6, p);
+      WALBERLA_CHECK_EQUAL(p.getNewContactHistory().size(), 0, p);
+      for (auto& entry : p.getOldContactHistoryRef())
+      {
+         WALBERLA_CHECK_EQUAL(entry.second.getTangentialSpringDisplacement(), Vec3(real_c(entry.first * simulationSteps)), p);
+      }
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::mesa_pd::main(argc, argv);
+}
diff --git a/tests/mesa_pd/mpi/ReduceProperty.cpp b/tests/mesa_pd/mpi/ReduceProperty.cpp
new file mode 100644
index 000000000..300fc5dc1
--- /dev/null
+++ b/tests/mesa_pd/mpi/ReduceProperty.cpp
@@ -0,0 +1,109 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   ReduceProperty.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/domain/BlockForestDomain.h>
+#include <mesa_pd/mpi/notifications/ForceTorqueNotification.h>
+#include <mesa_pd/mpi/ReduceProperty.h>
+#include <mesa_pd/mpi/SyncNextNeighbors.h>
+
+#include <blockforest/BlockForest.h>
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+#include <core/mpi/Reduce.h>
+
+#include <iostream>
+#include <memory>
+
+namespace walberla {
+namespace mesa_pd {
+
+const real_t radius = real_t(3);
+
+walberla::id_t createSphere(data::ParticleStorage& ps, domain::IDomain& domain)
+{
+   walberla::id_t uid = 0;
+   auto owned = domain.isContainedInProcessSubdomain( uint_c(walberla::mpi::MPIManager::instance()->rank()), Vec3(9,9,9) );
+   if (owned)
+   {
+      data::Particle&& p          = *ps.create();
+      p.getPositionRef()          = Vec3(9,9,9);
+      p.getInteractionRadiusRef() = radius;
+      p.getOwnerRef()             = walberla::mpi::MPIManager::instance()->rank();
+      uid = p.getUid();
+   }
+
+   walberla::mpi::allReduceInplace(uid, walberla::mpi::SUM);
+   return uid;
+}
+
+void main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   //init domain partitioning
+   auto forest = blockforest::createBlockForest( AABB(0,0,0,20,20,20), // simulation domain
+                                                 Vector3<uint_t>(2,2,2), // blocks in each direction
+                                                 Vector3<bool>(false, false, false) // periodicity
+                                                 );
+   domain::BlockForestDomain domain(forest);
+
+   //init data structures
+   data::ParticleStorage ps(100);
+
+   //initialize particle
+   auto uid = createSphere(ps, domain);
+   WALBERLA_LOG_DEVEL_ON_ROOT("uid: " << uid);
+
+   //init kernels
+   mpi::ReduceProperty    RP;
+   mpi::SyncNextNeighbors SNN;
+
+   //sync
+   SNN(ps, domain);
+
+   auto pIt = ps.find(uid);
+   if (pIt != ps.end())
+   {
+      pIt->getForceRef() += Vec3(real_c(walberla::mpi::MPIManager::instance()->rank()));
+   }
+
+   RP.operator()<ForceTorqueNotification>(ps);
+
+   if (walberla::mpi::MPIManager::instance()->rank() == 0)
+   {
+      WALBERLA_CHECK_FLOAT_EQUAL( pIt->getForce(), Vec3(real_t(28)) );
+   } else
+   {
+      WALBERLA_CHECK_FLOAT_EQUAL( pIt->getForce(), Vec3(real_t(walberla::mpi::MPIManager::instance()->rank())) );
+   }
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   walberla::mesa_pd::main(argc, argv);
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/vtk/VTKOutputs.cpp b/tests/mesa_pd/vtk/VTKOutputs.cpp
new file mode 100644
index 000000000..7cd96b260
--- /dev/null
+++ b/tests/mesa_pd/vtk/VTKOutputs.cpp
@@ -0,0 +1,86 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   VTKOutputs.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/vtk/ParticleVtkOutput.h>
+
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <blockforest/Initialization.h>
+#include <core/Environment.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+#include <core/math/Random.h>
+#include <vtk/VTKOutput.h>
+
+#include <iostream>
+
+namespace walberla {
+
+using namespace walberla::mesa_pd;
+
+int main( int argc, char ** argv )
+{
+   Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   mpi::MPIManager::instance()->useWorldComm();
+   WALBERLA_CHECK_EQUAL(mpi::MPIManager::instance()->numProcesses(), 8, "please run with 8 processes");
+
+   //init data structures
+   auto ps = std::make_shared<data::ParticleStorage> (100);
+
+   const math::AABB domain(real_t(0), real_t(0), real_t(0), real_t(4), real_t(4), real_t(4));
+   auto forest = blockforest::createBlockForest( domain,
+                                                 Vector3<uint_t>(2,2,2),
+                                                 Vector3<bool>(false, false, false) );
+
+   //initialize particles
+   const Vec3 shift(real_t(0.5), real_t(0.5), real_t(0.5));
+   for (auto& iBlk : *forest)
+   {
+      for (auto pt : grid_generator::SCGrid(iBlk.getAABB(), shift, real_t(1)))
+      {
+         WALBERLA_CHECK(iBlk.getAABB().contains(pt));
+
+         auto p = ps->create();
+         p->setPosition( pt );
+         p->setOwner( mpi::MPIManager::instance()->rank() );
+      }
+   }
+
+   auto vtkOutput       = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+   vtkOutput->addOutput<data::SelectParticleOwner>("owner");
+   vtkOutput->addOutput<data::SelectParticleLinearVelocity>("velocity");
+   auto rank = mpi::MPIManager::instance()->rank();
+   vtkOutput->setParticleSelector( [rank](const data::ParticleStorage::iterator& pIt) {return pIt->getIdx() < uint_c(rank);} );
+   auto vtkWriter       = walberla::vtk::createVTKOutput_PointData(vtkOutput, "Bodies", 1, "vtk", "simulation_step", false, false);
+
+   vtkWriter->write();
+
+   WALBERLA_CHECK_EQUAL(vtkOutput->getParticlesWritten(), rank);
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::main(argc, argv);
+}
-- 
GitLab