From 4249d29ccf9fe60bdc6051541c4839669e0073b5 Mon Sep 17 00:00:00 2001
From: Lukas Werner <lks.werner@fau.de>
Date: Thu, 26 Aug 2021 08:56:21 +0000
Subject: [PATCH] Add virtual mass functionality to lbm-mesapd coupling

---
 apps/CMakeLists.txt                           |    2 +-
 apps/showcases/CMakeLists.txt                 |    1 +
 .../CMakeLists.txt                            |    3 +
 .../LightRisingParticleInFluidAMR.cpp         | 1410 +++++++++++++++++
 python/mesa_pd.py                             |   16 +
 .../ParticlePresenceLevelDetermination.h      |   57 +-
 .../virtualmass/AddVirtualForceTorqueKernel.h |   88 +
 .../virtualmass/InitializeVirtualMassKernel.h |   64 +
 ...ticleAccessorWithShapeVirtualMassWrapper.h |   58 +
 src/mesa_pd/data/ParticleAccessor.h           |   63 +
 src/mesa_pd/data/ParticleAccessorWithShape.h  |    3 +
 src/mesa_pd/data/ParticleStorage.h            |  203 +++
 src/mesa_pd/mpi/notifications/ParseMessage.h  |    7 +
 .../notifications/ParticleCopyNotification.h  |   28 +
 .../ParticleMigrationNotification.h           |   21 +
 tests/lbm_mesapd_coupling/CMakeLists.txt      |    3 +
 .../utility/VirtualMass.cpp                   |  117 ++
 17 files changed, 2118 insertions(+), 26 deletions(-)
 create mode 100644 apps/showcases/LightRisingParticleInFluidAMR/CMakeLists.txt
 create mode 100644 apps/showcases/LightRisingParticleInFluidAMR/LightRisingParticleInFluidAMR.cpp
 create mode 100644 src/lbm_mesapd_coupling/utility/virtualmass/AddVirtualForceTorqueKernel.h
 create mode 100644 src/lbm_mesapd_coupling/utility/virtualmass/InitializeVirtualMassKernel.h
 create mode 100644 src/lbm_mesapd_coupling/utility/virtualmass/ParticleAccessorWithShapeVirtualMassWrapper.h
 create mode 100644 tests/lbm_mesapd_coupling/utility/VirtualMass.cpp

diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index 178380b57..965675d4d 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -32,4 +32,4 @@ endif()
 # Python module
 if ( WALBERLA_BUILD_WITH_PYTHON )
     add_subdirectory( pythonmodule )
-endif()
\ No newline at end of file
+endif()
diff --git a/apps/showcases/CMakeLists.txt b/apps/showcases/CMakeLists.txt
index 1b23d4954..6b34ee252 100644
--- a/apps/showcases/CMakeLists.txt
+++ b/apps/showcases/CMakeLists.txt
@@ -2,6 +2,7 @@ add_subdirectory( BidisperseFluidizedBed )
 add_subdirectory( CombinedResolvedUnresolved )
 add_subdirectory( FluidizedBed )
 add_subdirectory( HeatConduction )
+add_subdirectory( LightRisingParticleInFluidAMR )
 add_subdirectory( Mixer )
 add_subdirectory( PegIntoSphereBed )
 if ( WALBERLA_BUILD_WITH_CODEGEN AND WALBERLA_BUILD_WITH_PYTHON )
diff --git a/apps/showcases/LightRisingParticleInFluidAMR/CMakeLists.txt b/apps/showcases/LightRisingParticleInFluidAMR/CMakeLists.txt
new file mode 100644
index 000000000..96fd54bdb
--- /dev/null
+++ b/apps/showcases/LightRisingParticleInFluidAMR/CMakeLists.txt
@@ -0,0 +1,3 @@
+waLBerla_add_executable ( NAME LightRisingParticleInFluidAMR
+        FILES LightRisingParticleInFluidAMR.cpp
+        DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field vtk geometry postprocessing )
diff --git a/apps/showcases/LightRisingParticleInFluidAMR/LightRisingParticleInFluidAMR.cpp b/apps/showcases/LightRisingParticleInFluidAMR/LightRisingParticleInFluidAMR.cpp
new file mode 100644
index 000000000..6dcafd552
--- /dev/null
+++ b/apps/showcases/LightRisingParticleInFluidAMR/LightRisingParticleInFluidAMR.cpp
@@ -0,0 +1,1410 @@
+//======================================================================================================================
+//
+//  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 LightRisingParticleInFluidAMR.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//! \author Lukas Werner <lks.werner@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/SetupBlockForest.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+#include "blockforest/communication/UniformToNonUniformPackInfoAdapter.h"
+#include "blockforest/loadbalancing/DynamicCurve.h"
+#include "blockforest/loadbalancing/StaticCurve.h"
+
+#include "boundary/all.h"
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/SharedFunctor.h"
+#include "core/debug/Debug.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/logging/all.h"
+#include "core/math/Random.h"
+#include "core/math/all.h"
+#include "core/mpi/Broadcast.h"
+#include "core/timing/RemainingTimeLogger.h"
+
+#include "domain_decomposition/BlockSweepWrapper.h"
+
+#include "field/AddToStorage.h"
+#include "field/StabilityChecker.h"
+#include "field/adaptors/AdaptorCreators.h"
+#include "field/communication/PackInfo.h"
+#include "field/vtk/all.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/field/QCriterionFieldWriter.h"
+#include "lbm/field/VelocityFieldWriter.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/refinement/all.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/vtk/all.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+
+#include <functional>
+
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/amr/InfoCollection.h"
+#include "lbm_mesapd_coupling/amr/level_determination/ParticlePresenceLevelDetermination.h"
+#include "lbm_mesapd_coupling/mapping/ParticleMapping.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/ExtrapolationDirectionFinder.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/PdfReconstructionManager.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.h"
+#include "lbm_mesapd_coupling/utility/AddForceOnParticlesKernel.h"
+#include "lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h"
+#include "lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/InitializeHydrodynamicForceTorqueForAveragingKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/SubCyclingManager.h"
+#include "lbm_mesapd_coupling/utility/virtualmass/AddVirtualForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/virtualmass/InitializeVirtualMassKernel.h"
+#include "lbm_mesapd_coupling/utility/virtualmass/ParticleAccessorWithShapeVirtualMassWrapper.h"
+
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDataHandling.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/kernel/VelocityVerlet.h"
+#include "mesa_pd/mpi/ClearGhostOwnerSync.h"
+#include "mesa_pd/mpi/ClearNextNeighborSync.h"
+#include "mesa_pd/mpi/ReduceProperty.h"
+#include "mesa_pd/mpi/SyncGhostOwners.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/mpi/notifications/HydrodynamicForceTorqueNotification.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+/**
+ * \brief Showcase for a stabilized fluid - very light particle simulation using virtual mass
+ * A light sphere, situated at the bottom of a cuboid domain, rises.
+ * Depending on the density of the sphere (--sphereDensity) and galileo number (--galileoNumber), the sphere
+ * then undergoes various movement patterns along its way to the top.
+ * Employs adaptive mesh refinement and checkpointing.
+ * Can output VTK files based on the q criterion to visualize vortices behind the rising sphere.
+ * Most default parameters should be sufficient and do not need explicit values.
+ * As used in the master thesis of Lukas Werner, 2020, and follow-up paper at http://doi.org/10.1002/fld.5034
+ */
+
+namespace light_rising_particle_amr {
+using namespace walberla;
+using walberla::uint_t;
+
+#ifdef BUILD_WITH_MRT
+using LatticeModel_T = lbm::D3Q19< lbm::collision_model::D3Q19MRT>;
+#else
+using LatticeModel_T = lbm::D3Q19< lbm::collision_model::TRT, false >;
+#endif
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+using ParticleField_T = lbm_mesapd_coupling::ParticleField_T;
+using VelocityField_T = GhostLayerField<Vector3<real_t>, 1>;
+using QCriterionField_T = GhostLayerField<real_t, 1>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+using ScalarField_T = GhostLayerField<real_t, 1>;
+
+const uint_t FieldGhostLayers = 4;
+
+using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+
+using NoSlip_T = lbm::NoSlip<LatticeModel_T, flag_t> ;
+using MO_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+using BoundaryHandling_T = BoundaryHandling< FlagField_T, Stencil_T, NoSlip_T, MO_T >;
+
+
+//// flags
+
+const FlagUID Fluid_Flag("fluid");
+const FlagUID NoSlip_Flag("no slip");
+const FlagUID MO_Flag("moving obstacle");
+const FlagUID FormerMO_Flag("former moving obstacle");
+
+
+//// block structure
+
+static void refinementSelection(SetupBlockForest &forest, uint_t levels, AABB refinementBox) {
+   real_t dx = real_t(1); // dx on finest level
+   const uint_t finestLevel = levels - uint_t(1);
+   for (auto block = forest.begin(); block != forest.end(); ++block) {
+      uint_t blockLevel = block->getLevel();
+      uint_t levelScalingFactor = (uint_t(1) << (finestLevel - blockLevel));
+      real_t dxOnLevel = dx * real_c(levelScalingFactor);
+      AABB blockAABB = block->getAABB();
+      // extend block AABB by ghostlayers
+      AABB extendedBlockAABB = blockAABB.getExtended(dxOnLevel * real_c(FieldGhostLayers));
+      if (extendedBlockAABB.intersects(refinementBox))
+         if (blockLevel < finestLevel) // only if the block is not on the finest level
+            block->setMarker(true);
+   }
+}
+
+static void workloadAndMemoryAssignment(SetupBlockForest &forest) {
+   for (auto block = forest.begin(); block != forest.end(); ++block) {
+      block->setWorkload(numeric_cast<workload_t>(uint_t(1) << block->getLevel()));
+      block->setMemory(numeric_cast<memory_t>(1));
+   }
+}
+static shared_ptr<StructuredBlockForest>
+createBlockStructure(const AABB &domainAABB, Vector3<uint_t> blockSizeInCells, uint_t numberOfLevels, real_t diameter,
+                     Vector3<real_t> spherePosition, Vector3<bool> periodicity,
+                     bool readFromCheckPointFile, const std::string & forestCheckPointFileName,
+                     bool keepGlobalBlockInformation = false) {
+   if (readFromCheckPointFile) {
+         WALBERLA_LOG_INFO_ON_ROOT("Reading block forest from file!");
+         return blockforest::createUniformBlockGrid(forestCheckPointFileName,
+                 blockSizeInCells[0], blockSizeInCells[1], blockSizeInCells[2],
+                 false);
+   } else {
+      SetupBlockForest sforest;
+      Vector3<uint_t> numberOfFineBlocksPerDirection(uint_c(domainAABB.size(0)) / blockSizeInCells[0],
+                                                     uint_c(domainAABB.size(1)) / blockSizeInCells[1],
+                                                     uint_c(domainAABB.size(2)) / blockSizeInCells[2]);
+      for (uint_t i = 0; i < 3; ++i) {
+         WALBERLA_CHECK_EQUAL(numberOfFineBlocksPerDirection[i] * blockSizeInCells[i], uint_c(domainAABB.size(i)),
+                              "Domain can not be decomposed in direction " << i << " into fine blocks of size "
+                                                                           << blockSizeInCells[i]);
+      }
+
+      uint_t levelScalingFactor = (uint_t(1) << (numberOfLevels - uint_t(1)));
+      Vector3<uint_t> numberOfCoarseBlocksPerDirection(numberOfFineBlocksPerDirection / levelScalingFactor);
+      for (uint_t i = 0; i < 3; ++i) {
+         WALBERLA_CHECK_EQUAL(numberOfCoarseBlocksPerDirection[i] * levelScalingFactor,
+                              numberOfFineBlocksPerDirection[i],
+                              "Domain can not be refined in direction " << i
+                                                                        << " according to the specified number of levels!");
+      }
+      AABB refinementBox( std::floor(spherePosition[0] - real_t(0.5) * diameter),
+                          std::floor(spherePosition[1] - real_t(0.5) * diameter),
+                          std::floor(spherePosition[2] - real_t(0.5) * diameter),
+                          std::ceil( spherePosition[0] + real_t(0.5) * diameter),
+                          std::ceil( spherePosition[1] + real_t(0.5) * diameter),
+                          std::ceil( spherePosition[2] + real_t(0.5) * diameter) );
+      WALBERLA_LOG_INFO_ON_ROOT(" - refinement box: " << refinementBox);
+
+      sforest.addRefinementSelectionFunction(
+              std::bind(refinementSelection, std::placeholders::_1, numberOfLevels, refinementBox));
+      sforest.addWorkloadMemorySUIDAssignmentFunction(workloadAndMemoryAssignment);
+
+      sforest.init(domainAABB, numberOfCoarseBlocksPerDirection[0], numberOfCoarseBlocksPerDirection[1],
+                   numberOfCoarseBlocksPerDirection[2], periodicity[0], periodicity[1], periodicity[2]);
+
+      // calculate process distribution
+      const memory_t memoryLimit = math::Limits<memory_t>::inf();
+      sforest.balanceLoad(blockforest::StaticLevelwiseCurveBalance(true),
+                          uint_c(MPIManager::instance()->numProcesses()), real_t(0), memoryLimit, true);
+
+      WALBERLA_LOG_INFO_ON_ROOT(sforest);
+
+      MPIManager::instance()->useWorldComm();
+
+      auto blockForest = make_shared<BlockForest>(uint_c(MPIManager::instance()->rank()), sforest,
+                                                  keepGlobalBlockInformation);
+
+      // create StructuredBlockForest (encapsulates a newly created BlockForest)
+      shared_ptr<StructuredBlockForest> sbf =
+              make_shared<StructuredBlockForest>(
+                      blockForest,
+                      blockSizeInCells[0], blockSizeInCells[1], blockSizeInCells[2]);
+      sbf->createCellBoundingBoxes();
+
+      return sbf;
+   }
+}
+
+//// boundary handling
+
+class MyBoundaryHandling : public blockforest::AlwaysInitializeBlockDataHandling< BoundaryHandling_T > {
+public:
+   MyBoundaryHandling(const weak_ptr< StructuredBlockStorage > & storage, const BlockDataID& flagFieldID,
+         const BlockDataID& pdfFieldID, const BlockDataID& particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         storage_(storage), flagFieldID_(flagFieldID), pdfFieldID_(pdfFieldID), particleFieldID_(particleFieldID),
+         ac_(ac) {
+
+   }
+
+   BoundaryHandling_T * initialize( IBlock * const block ) override;
+
+private:
+   weak_ptr< StructuredBlockStorage > storage_;
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+
+BoundaryHandling_T * MyBoundaryHandling::initialize( IBlock * const block )
+{
+   WALBERLA_ASSERT_NOT_NULLPTR( block );
+
+   auto* flagField = block->getData<FlagField_T>(flagFieldID_);
+   auto* pdfField = block->getData<PdfField_T>(pdfFieldID_);
+   auto* particleField = block->getData<lbm_mesapd_coupling::ParticleField_T>(particleFieldID_);
+
+   const auto fluid = flagField->flagExists( Fluid_Flag ) ? flagField->getFlag( Fluid_Flag ) : flagField->registerFlag( Fluid_Flag );
+
+   auto storagePtr = storage_.lock();
+   WALBERLA_CHECK_NOT_NULLPTR( storagePtr );
+
+   BoundaryHandling_T * handling = new BoundaryHandling_T( "moving obstacle boundary handling", flagField, fluid,
+                                                           NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ),
+                                                           MO_T( "MO", MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storagePtr, *block ) );
+
+   handling->fillWithDomain( FieldGhostLayers );
+
+   return handling;
+}
+
+void clearBoundaryHandling( BlockForest & forest, const BlockDataID & boundaryHandlingID ) {
+   for( auto blockIt = forest.begin(); blockIt != forest.end(); ++blockIt )
+   {
+      BoundaryHandling_T * boundaryHandling = blockIt->getData<BoundaryHandling_T>(boundaryHandlingID);
+      boundaryHandling->clear( FieldGhostLayers );
+   }
+}
+
+void recreateBoundaryHandling( BlockForest & forest, const BlockDataID & boundaryHandlingID ) {
+   for( auto blockIt = forest.begin(); blockIt != forest.end(); ++blockIt )
+   {
+      BoundaryHandling_T * boundaryHandling = blockIt->getData<BoundaryHandling_T>(boundaryHandlingID);
+      boundaryHandling->fillWithDomain( FieldGhostLayers );
+   }
+}
+
+void clearParticleField( BlockForest & forest, const BlockDataID & particleFieldID, const ParticleAccessor_T & accessor ) {
+   for (auto blockIt = forest.begin(); blockIt != forest.end(); ++blockIt) {
+      ParticleField_T *particleField = blockIt->getData<ParticleField_T>(particleFieldID);
+      particleField->setWithGhostLayer(accessor.getInvalidUid());
+   }
+}
+
+class SpherePropertyLogger {
+public:
+   SpherePropertyLogger(lbm_mesapd_coupling::SubCyclingManager* mesapdTimeStep, const shared_ptr<ParticleAccessor_T>& ac, walberla::id_t sphereUid,
+                        const std::string& fileName, bool fileIO, bool writeHeader, real_t t_g, real_t u_g, real_t diameter) :
+         mesapdTimeStep_(mesapdTimeStep), ac_(ac), sphereUid_(sphereUid), fileName_(fileName), fileIO_(fileIO), t_g_(t_g),
+         u_g_(u_g), diameter_(diameter), position_(real_t(0)) {
+      if (fileIO_ && writeHeader) {
+         WALBERLA_ROOT_SECTION() {
+            std::ofstream file;
+            file.open(fileName_.c_str());
+            file << "#\t posZ\t uZ\t u\t rotX\t rotY\t rotZ\t hydFZ\t hydT\t tN\t posXN\t posYN\t posZN\t uXN\t uYN\t uZN\t uN\t wX\t wY\t wZ\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()() {
+      const uint_t timestep (mesapdTimeStep_->getCurrentTimeStep());
+      Vector3<real_t> transVel;
+      Vector3<real_t> hydForce;
+      Vector3<real_t> hydTorque;
+      Vector3<real_t> angVel;
+      Vector3<real_t> eulerRotation;
+
+      size_t idx = ac_->uidToIdx(sphereUid_);
+      if (idx != ac_->getInvalidIdx()) {
+         if (!mesa_pd::data::particle_flags::isSet(ac_->getFlags(idx), mesa_pd::data::particle_flags::GHOST)) {
+            transVel = ac_->getLinearVelocity(idx);
+            hydForce = ac_->getHydrodynamicForce(idx);
+            hydTorque = ac_->getHydrodynamicTorque(idx);
+            angVel = ac_->getAngularVelocity(idx);
+            eulerRotation = ac_->getRotation(idx).getMatrix().getEulerAnglesXYZ();
+         }
+      }
+
+      WALBERLA_MPI_SECTION() {
+         mpi::allReduceInplace(transVel[0], mpi::SUM);
+         mpi::allReduceInplace(transVel[1], mpi::SUM);
+         mpi::allReduceInplace(transVel[2], mpi::SUM);
+
+         mpi::allReduceInplace(angVel[0], mpi::SUM);
+         mpi::allReduceInplace(angVel[1], mpi::SUM);
+         mpi::allReduceInplace(angVel[2], mpi::SUM);
+
+         mpi::allReduceInplace(hydForce[0], mpi::SUM);
+         mpi::allReduceInplace(hydForce[1], mpi::SUM);
+         mpi::allReduceInplace(hydForce[2], mpi::SUM);
+
+         mpi::allReduceInplace(hydTorque[0], mpi::SUM);
+         mpi::allReduceInplace(hydTorque[1], mpi::SUM);
+         mpi::allReduceInplace(hydTorque[2], mpi::SUM);
+
+         mpi::allReduceInplace(eulerRotation[0], mpi::SUM);
+         mpi::allReduceInplace(eulerRotation[1], mpi::SUM);
+         mpi::allReduceInplace(eulerRotation[2], mpi::SUM);
+      }
+
+      syncPosition();
+
+      if (fileIO_) {
+         writeToFile(timestep, position_, eulerRotation, transVel, angVel, hydForce, hydTorque);
+      }
+   }
+
+   Vector3<real_t> getPosition() const {
+      return position_;
+   }
+
+   void syncPosition() {
+      Vector3<real_t> pos(real_t(0));
+
+      size_t idx = ac_->uidToIdx(sphereUid_);
+      if (idx != ac_->getInvalidIdx()) {
+         if (!mesa_pd::data::particle_flags::isSet(ac_->getFlags(idx), mesa_pd::data::particle_flags::GHOST)) {
+            pos = ac_->getPosition(idx);
+         }
+      }
+
+      WALBERLA_MPI_SECTION() {
+         mpi::allReduceInplace(pos[0], mpi::SUM);
+         mpi::allReduceInplace(pos[1], mpi::SUM);
+         mpi::allReduceInplace(pos[2], mpi::SUM);
+      }
+
+      position_ = pos;
+   }
+
+private:
+   void writeToFile(const uint_t timestep, const Vector3<real_t>& position, const Vector3<real_t>& eulerRotation,
+         const Vector3<real_t>& velocity, const Vector3<real_t>& angularVelocity,
+         const Vector3<real_t>& hydForce, const Vector3<real_t>& hydTorque) {
+      WALBERLA_ROOT_SECTION() {
+         std::ofstream file;
+         file.open(fileName_.c_str(), std::ofstream::app);
+
+         file << timestep << "\t"
+              << position[2] << "\t"
+              << velocity[2] << "\t"
+              << velocity.length() << "\t"
+              << eulerRotation[0] << "\t"
+              << eulerRotation[1] << "\t"
+              << eulerRotation[2] << "\t"
+              << hydForce[2] << "\t"
+              << hydTorque.length() << "\t"
+
+              << real_t(timestep) / t_g_ << "\t"
+              << position[0] / diameter_ << "\t"
+              << position[1] / diameter_ << "\t"
+              << position[2] / diameter_ << "\t"
+              << velocity[0] / u_g_ << "\t"
+              << velocity[1] / u_g_ << "\t"
+              << velocity[2] / u_g_ << "\t"
+              << velocity.length() / u_g_ << "\t"
+              << angularVelocity[0] * diameter_ / u_g_ << "\t"
+              << angularVelocity[1] * diameter_ / u_g_ << "\t"
+              << angularVelocity[2] * diameter_ / u_g_ << "\n";
+
+         file.close();
+      }
+   }
+
+   lbm_mesapd_coupling::SubCyclingManager* mesapdTimeStep_;
+   shared_ptr<ParticleAccessor_T> ac_;
+   const walberla::id_t sphereUid_;
+   std::string fileName_;
+   bool fileIO_;
+
+   real_t t_g_;
+   real_t u_g_;
+   real_t diameter_;
+
+   Vector3<real_t> position_;
+};
+
+void createCheckpointFiles(const std::string& checkPointFileName, std::vector<std::string>& oldCheckpointFiles,
+        uint_t lbmTimeStep, uint_t mesaTimeStep, const shared_ptr<BlockForest>& forest, const BlockDataID pdfFieldID,
+        const BlockDataID particleStorageID) {
+   WALBERLA_LOG_INFO_ON_ROOT("Writing checkpointing files in lbm time step " << lbmTimeStep << " (mesapd: " << mesaTimeStep << ").");
+
+   const std::string timeStepTag = std::to_string(lbmTimeStep) + "_" + std::to_string(mesaTimeStep);
+
+   const std::string lbmFileName = checkPointFileName + "_" + timeStepTag + "_lbm.txt";
+   const std::string mesaFileName = checkPointFileName + "_" + timeStepTag + "_mesa.txt";
+   const std::string forestFileName = checkPointFileName + "_" + timeStepTag + "_forest.txt";
+
+   forest->saveBlockData(lbmFileName, pdfFieldID);
+   forest->saveBlockData(mesaFileName, particleStorageID);
+   forest->saveToFile(forestFileName);
+
+   WALBERLA_ROOT_SECTION() {
+      if (!oldCheckpointFiles.empty()) {
+         for (const std::string& file: oldCheckpointFiles) {
+            filesystem::path path(file);
+            if (filesystem::exists(path)) {
+               filesystem::remove(path);
+            }
+         }
+         oldCheckpointFiles.clear();
+         WALBERLA_LOG_INFO("Deleted old checkpoint files.");
+      }
+
+      oldCheckpointFiles.push_back(lbmFileName);
+      oldCheckpointFiles.push_back(mesaFileName);
+      oldCheckpointFiles.push_back(forestFileName);
+   }
+}
+
+// main
+
+int main(int argc, char** argv) {
+   debug::enterTestMode();
+
+   mpi::Environment env(argc, argv);
+
+   logging::Logging::instance()->setStreamLogLevel(logging::Logging::INFO);
+   logging::Logging::instance()->setFileLogLevel(logging::Logging::INFO);
+
+   math::seedRandomGenerator( static_cast<unsigned int>(1337 * mpi::MPIManager::instance()->worldRank()) );
+   
+   // general params
+   bool fileIO = true;
+   uint_t vtkIOFreq = 0;
+   uint_t fullVtkIOFreq = 0;
+   uint_t qCriterionVtkIOFreq = 0;
+   std::string vtkIOSelection = "sliced"; // "none", "full", "thicksliced" (half the sphere still in), "trackingsliced" (sphere is followed in y direction)
+   std::string baseFolder = "vtk_out_UnsettlingSphereDR";
+   auto vtkLowerVelocityLimit = real_t(0.003);
+   auto vtkLowerQCriterionLimit = real_t(1e-10);
+   auto propertyLoggingFreq = uint_t(1);
+
+   // numerical params
+   auto blockSize = uint_t(32);
+   auto XBlocks = uint_t(32);
+   auto YBlocks = uint_t(32);
+   auto ZBlocks = uint_t(64);
+   auto refinementCheckFrequency = uint_t(0);
+   auto initialZ = real_t(-1);
+   auto diameter = real_t(16);
+   bool offsetInitialPosition = true;
+   bool averageForceTorqueOverTwoTimeSteps = true;
+   auto numMESAPDSubCycles = uint_t(10);
+   auto u_ref = real_t(0.01);
+   auto densitySphere = real_t(0.1);
+   auto timesteps = uint_t(2000);
+   auto galileoNumber = real_t(300);
+   auto Lambda_Bulk = real_t(100); // if = 1, normal TRT, else not. Up to 100.
+   bool conserveMomentum = false;
+
+   // virtual mass
+   bool useVirtualMass = true;
+   auto C_v = real_t(0.5);
+   auto C_v_omega = real_t(0.5);
+
+   // dynamic refinement
+   auto numberOfLevels = uint_t(3);
+
+   bool useVorticityCriterion = false;
+   auto lowerFluidRefinementLimit = real_t(0);
+   auto upperFluidRefinementLimit = std::numeric_limits<real_t>::infinity();
+
+   bool useCurlCriterion = true;
+   auto upperCurlLimit = real_t(0.001);
+   auto curlCoarsenFactor = real_t(0.2);
+   auto curlLengthScaleWeight = real_t(2);
+
+   // checkpointing
+   bool writeCheckPointFile = false;
+   bool readFromCheckPointFile = false;
+   uint_t checkPointingFreq = 0;
+   uint_t checkPointLBMTimeStep = 0;
+   uint_t checkPointMESAPDTimeStep = 0;
+
+   for (int i = 1; i < argc; i++) {
+      if (std::strcmp(argv[i], "--shortRun") == 0) { fileIO = false; densitySphere = real_t(0.02); galileoNumber = 100; timesteps = 2; continue; }
+
+      if (std::strcmp(argv[i], "--noLogging")                == 0) { fileIO = false; continue; }
+      if (std::strcmp(argv[i], "--vtkIOFreq")                == 0) { vtkIOFreq = uint_c( std::stod( argv[++i] ) ); continue; }
+      if (std::strcmp(argv[i], "--fullVtkIOFreq")            == 0) { fullVtkIOFreq = uint_c( std::stod( argv[++i] ) ); continue; }
+      if (std::strcmp(argv[i], "--qCriterionVtkIOFreq")      == 0) { qCriterionVtkIOFreq = uint_c( std::stod( argv[++i] ) ); continue; }
+      if (std::strcmp(argv[i], "--vtkIOSelection")           == 0) { vtkIOSelection = argv[++i]; continue; }
+      if (std::strcmp(argv[i], "--numMESAPDSubCycles")       == 0) { numMESAPDSubCycles = uint_c( std::stod( argv[++i] ) ); continue; }
+      if (std::strcmp(argv[i], "--noForceAveraging")         == 0) { averageForceTorqueOverTwoTimeSteps = false; continue; }
+      if (std::strcmp(argv[i], "--baseFolder")               == 0) { baseFolder = argv[++i]; continue; }
+      if (std::strcmp(argv[i], "--vtkLowerVelocityLimit")    == 0) { vtkLowerVelocityLimit = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--vtkLowerQCriterionLimit")  == 0) { vtkLowerQCriterionLimit = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--propertyLoggingFreq")      == 0) { propertyLoggingFreq = uint_c( std::stod( argv[++i] ) ); continue; }
+
+      if (std::strcmp(argv[i], "--dontOffsetInitialPos")     == 0) { offsetInitialPosition = false; continue; }
+      if (std::strcmp(argv[i], "--sphereDensity")            == 0) { densitySphere = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--u_ref")                    == 0) { u_ref = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--timesteps")                == 0) { timesteps = uint_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--blockSize")                == 0) { blockSize = uint_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--XBlocks")                  == 0) { XBlocks = uint_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--YBlocks")                  == 0) { YBlocks = uint_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--ZBlocks")                  == 0) { ZBlocks = uint_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--refinementCheckFrequency") == 0) { refinementCheckFrequency = uint_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--initialZ")                 == 0) { initialZ = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--diameter")                 == 0) { diameter = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--galileoNumber")            == 0) { galileoNumber = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--lambdaBulk")               == 0) { Lambda_Bulk = real_c(std::stod(argv[++i])); continue; }
+
+      if (std::strcmp(argv[i], "--noVirtualMass")            == 0) { useVirtualMass = false; continue; }
+      if (std::strcmp(argv[i], "--c_v")                      == 0) { C_v = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--c_v_omega")                == 0) { C_v_omega = real_c(std::stod(argv[++i])); continue; }
+
+      // dynamic refinement
+      if (std::strcmp(argv[i], "--numberOfLevels")           == 0) { numberOfLevels = uint_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--useVorticityCriterion")    == 0) { useVorticityCriterion = true; useCurlCriterion = false; continue; }
+      if (std::strcmp(argv[i], "--lowerLimit")               == 0) { lowerFluidRefinementLimit = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--upperLimit")               == 0) { upperFluidRefinementLimit = real_c(std::stod(argv[++i])); continue; }
+
+      if (std::strcmp(argv[i], "--upperCurlLimit")           == 0) { upperCurlLimit = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--curlCoarsenFactor")        == 0) { curlCoarsenFactor = real_c(std::stod(argv[++i])); continue; }
+      if (std::strcmp(argv[i], "--curlLengthScaleWeight")    == 0) { curlLengthScaleWeight = real_c(std::stod(argv[++i])); continue; }
+
+      if (std::strcmp(argv[i], "--writeCheckPointFile")      == 0) { writeCheckPointFile = true; continue; }
+      if (std::strcmp(argv[i], "--readFromCheckPointFile")   == 0) { readFromCheckPointFile = true; continue; }
+      if (std::strcmp(argv[i], "--checkPointingFreq")        == 0) { checkPointingFreq = uint_c(std::stod( argv[++i]) ); continue; }
+      if (std::strcmp(argv[i], "--checkPointLBMTimeStep")    == 0) { checkPointLBMTimeStep = uint_c(std::stod( argv[++i]) ); continue; }
+      if (std::strcmp(argv[i], "--checkPointMESAPDTimeStep") == 0) { checkPointMESAPDTimeStep = uint_c(std::stod( argv[++i]) ); continue; }
+
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   WALBERLA_CHECK(!realIsEqual(u_ref, real_t(0)), "u_ref has to be non-zero.")
+   WALBERLA_CHECK(useCurlCriterion || useVorticityCriterion,
+         "Use curl or vorticity (legacy) criterion for refinement.");
+   WALBERLA_CHECK(!(useCurlCriterion && useVorticityCriterion),
+         "Using curl and vorticity criterion together makes no sense.");
+
+   // create base dir if it doesnt already exist
+   filesystem::path bpath(baseFolder);
+   if (!filesystem::exists(bpath)) {
+      filesystem::create_directory(bpath);
+   }
+   filesystem::path qpath(baseFolder + "/q_criterion_mesh");
+   if (filesystem::exists(qpath)) {
+      filesystem::remove_all(qpath);
+   }
+
+   //// numerical parameters
+
+   const auto densityFluid = real_t(1);
+
+   // calculate relaxation time from u_ref
+   real_t viscosity = u_ref * diameter / galileoNumber;
+   real_t omega = lbm::collision_model::omegaFromViscosity(viscosity);
+   real_t relaxationTime = real_t(1) / omega;
+
+   const auto d3 = diameter*diameter*diameter;
+   const auto G2 = galileoNumber*galileoNumber;
+   const auto v2 = viscosity*viscosity;
+   const auto gravitationalAcceleration = G2*v2 / (std::fabs(densitySphere-real_t(1))*d3);
+
+   const auto dx = real_t(1);
+   const auto overlap = real_t(4.5) * dx;
+
+   const Vector3<uint_t> domainSize( XBlocks * blockSize, YBlocks * blockSize, ZBlocks * blockSize );
+   if (realIsIdentical(initialZ, real_t(-1))) {
+      initialZ = real_c(blockSize) + real_t(0.5) * diameter;
+   }
+   Vector3<real_t> initialPosition(real_t(0.5) * real_c(domainSize[0]),
+                                   real_t(0.5) * real_c(domainSize[1]),
+                                   initialZ);
+   if (offsetInitialPosition) {
+      // offset sphere's initial position half a cell to introduce a slight disturbance.
+      initialPosition += Vector3<real_t>(real_t(0.5), real_t(0.5), real_t(0));
+   }
+
+   if (useVorticityCriterion && floatIsEqual(lowerFluidRefinementLimit, real_t(0)) && std::isinf(upperFluidRefinementLimit)) {
+      // use computed criterion instead of user input
+      lowerFluidRefinementLimit = real_t(0.05) * u_ref;
+      upperFluidRefinementLimit = real_t(0.1) * u_ref;
+   }
+
+   //const auto dx = real_t(1);
+
+   const auto u_g = std::sqrt(std::fabs(densitySphere - real_t(1)) * gravitationalAcceleration * diameter);
+   const auto t_g = std::sqrt(diameter / (std::fabs(densitySphere - real_t(1)) * gravitationalAcceleration));
+
+   const bool rising = densitySphere < real_t(1);
+
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in simulation, i.e. lattice, units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - number of processes = " << mpi::MPIManager::instance()->numProcesses());
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize);
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size / D = " << (Vector3<real_t>(domainSize) / diameter));
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter << ", density = " << densitySphere << ", initial position: " << initialPosition);
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid << ", relaxation time (tau) = " << relaxationTime << ", kin. visc = " << viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - lambda bulk = " << Lambda_Bulk );
+   WALBERLA_LOG_INFO_ON_ROOT(" - galileo number = " << galileoNumber );
+   WALBERLA_LOG_INFO_ON_ROOT(" - gravitational velocity (u_g) = " << u_g);
+   WALBERLA_LOG_INFO_ON_ROOT(" - gravitational acceleration = " << gravitationalAcceleration );
+#ifdef BUILD_WITH_MRT
+   WALBERLA_LOG_INFO_ON_ROOT(" - lattice model: MRT");
+#else
+   WALBERLA_LOG_INFO_ON_ROOT(" - lattice model: TRT");
+#endif
+   WALBERLA_LOG_INFO_ON_ROOT(" - integrator = Velocity Verlet" );
+   WALBERLA_LOG_INFO_ON_ROOT(" - use virtual mass = " << useVirtualMass );
+   //WALBERLA_LOG_INFO_ON_ROOT(" - virtual mass acceleration estimation = " << vmAccelerationOption );
+   WALBERLA_LOG_INFO_ON_ROOT(" - C_v = " << C_v );
+   WALBERLA_LOG_INFO_ON_ROOT(" - C_v_omega = " << C_v_omega );
+   WALBERLA_LOG_INFO_ON_ROOT(" - timesteps = " << timesteps);
+   WALBERLA_LOG_INFO_ON_ROOT(" - averageForceTorqueOverTwoTimeSteps = " << averageForceTorqueOverTwoTimeSteps);
+   WALBERLA_LOG_INFO_ON_ROOT(" - conserve momentum = " << conserveMomentum);
+   if (useCurlCriterion) {
+      WALBERLA_LOG_INFO_ON_ROOT(" - using curl criterion with upper limit = " << upperCurlLimit << ", coarsen factor = " << curlCoarsenFactor << " and length scale weight = " << curlLengthScaleWeight);
+   }
+   if (useVorticityCriterion) {
+      WALBERLA_LOG_INFO_ON_ROOT(" - using vorticity criterion with lower limit = " << lowerFluidRefinementLimit << " and upper limit = " << upperFluidRefinementLimit );
+   }
+   if (vtkIOFreq > 0) {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing vtk files to folder \"" << baseFolder << "\" with frequency " << vtkIOFreq);
+   }
+
+   if(refinementCheckFrequency == 0 && numberOfLevels != 1){
+      // determine check frequency automatically based on maximum admissible velocity and block sizes
+      auto uMax = real_t(0.02); // this should never ever be reached
+      const uint_t finestLevel = numberOfLevels - uint_t(1);
+      const uint_t levelScalingFactor = ( uint_t(1) << finestLevel );
+      const uint_t lbmTimeStepsPerTimeLoopIteration = levelScalingFactor;
+      refinementCheckFrequency = uint_c((overlap + (real_c(blockSize) - real_t(2) * real_t(FieldGhostLayers)) * dx) / uMax) / lbmTimeStepsPerTimeLoopIteration;
+   }
+   WALBERLA_LOG_INFO_ON_ROOT(" - refinement / load balancing check frequency (coarse time steps): " << refinementCheckFrequency);
+
+   //// checkpointing setup
+
+   WALBERLA_LOG_INFO_ON_ROOT(" - read from checkpoint file = " << readFromCheckPointFile);
+   WALBERLA_LOG_INFO_ON_ROOT(" - write checkpoint file = " << writeCheckPointFile);
+
+   std::string checkPointFileName = "checkpointing";
+   if (readFromCheckPointFile || writeCheckPointFile) {
+      WALBERLA_LOG_INFO_ON_ROOT(" - checkPointingFileName = " << checkPointFileName);
+   }
+
+   std::string readCheckPointFileName = checkPointFileName + "_" + std::to_string(checkPointLBMTimeStep) + "_" +
+                                        std::to_string(checkPointMESAPDTimeStep);
+
+   if (readFromCheckPointFile && writeCheckPointFile) {
+      // decide which option to choose
+      if (filesystem::exists(readCheckPointFileName+"_lbm.txt")) {
+         WALBERLA_LOG_INFO_ON_ROOT("Checkpoint file already exists! Will skip writing check point file and start from this check point!");
+         writeCheckPointFile = false;
+      } else {
+         WALBERLA_LOG_INFO_ON_ROOT("Checkpoint file does not exists yet! Will skip reading check point file and just regularly start the simulation from the beginning!");
+         readFromCheckPointFile = false;
+      }
+   }
+
+   // write parameters to file
+   if (fileIO) {
+      WALBERLA_ROOT_SECTION() {
+         std::string paramsFileName(baseFolder + "/Params.txt");
+         std::ofstream file;
+         file.open(paramsFileName.c_str());
+         file
+               << "D\t "
+               << "Ga\t "
+               << "LBM\t "
+               << "pi_p\t "
+               << "tau\t "
+               << "visc\t "
+               << "u_g\t "
+               << "t_g\t "
+               << "acc_g\t "
+               << "vm\t "
+               << "C_v\t "
+               << "C_vw\t "
+               << "fa\t "
+               << "curlCrit\t "
+               << "curlMax\t "
+               << "curlCoaF\t "
+               << "curlLenS\t "
+               << "refChk\t "
+               << "numLvl\t "
+               << "sX\t "
+               << "sY\t "
+               << "sZ\t "
+               << "nProc\t "
+               << "\n";
+         file
+               << diameter << "\t "
+               << galileoNumber << "\t "
+               #ifdef BUILD_WITH_MRT
+               << "MRT\t "
+               #else
+               << "TRT\t "
+               #endif
+               << densitySphere << "\t "
+               << relaxationTime << "\t "
+               << viscosity << "\t "
+               << u_g << "\t "
+               << t_g << "\t "
+               << gravitationalAcceleration << "\t "
+               << (useVirtualMass ? "lt" : "no") << "\t "
+               << C_v << "\t "
+               << C_v_omega << "\t "
+               << averageForceTorqueOverTwoTimeSteps << "\t "
+               << useCurlCriterion << "\t "
+               << upperCurlLimit << "\t "
+               << curlCoarsenFactor << "\t "
+               << curlLengthScaleWeight << "\t "
+               << refinementCheckFrequency << "\t "
+               << numberOfLevels << "\t "
+               << domainSize[0] << "\t "
+               << domainSize[1] << "\t "
+               << domainSize[2] << "\t "
+               << mpi::MPIManager::instance()->numProcesses() << "\t "
+               << "\n";
+         file.close();
+      }
+   }
+
+   //
+
+   uint_t currentLBMTimeStep = 0;
+   uint_t currentMESAPDTimeStep = 0;
+
+   if (readFromCheckPointFile) {
+      currentLBMTimeStep = checkPointLBMTimeStep;
+      currentMESAPDTimeStep = checkPointMESAPDTimeStep;
+   }
+
+   //// block structure setup
+
+   const uint_t finestLevel = numberOfLevels - uint_t(1);
+
+   Vector3<uint_t> blockSizeInCells(blockSize);
+
+   AABB simulationDomain( real_t(0), real_t(0), real_t(0), real_c(domainSize[0]), real_c(domainSize[1]), real_c(domainSize[2]) );
+   Vector3<bool> periodicity(true, true, true);
+   auto blocks = createBlockStructure(simulationDomain, blockSizeInCells, numberOfLevels, diameter, initialPosition,
+           periodicity, readFromCheckPointFile, readCheckPointFileName+"_forest.txt");
+   auto forest = blocks->getBlockForestPointer();
+
+   if (vtkIOFreq > 0) {
+      vtk::writeDomainDecomposition(blocks, "initial_domain_decomposition", baseFolder);
+   }
+
+   //// dynamic refinement I
+
+   forest->recalculateBlockLevelsInRefresh(true);
+   forest->alwaysRebalanceInRefresh(false);
+   forest->reevaluateMinTargetLevelsAfterForcedRefinement(false);
+   forest->allowRefreshChangingDepth(false);
+
+   //// mesapd coupling
+
+   auto mesapdDomain = std::make_shared<mesa_pd::domain::BlockForestDomain>(blocks->getBlockForestPointer());
+
+   // init data structures
+   auto ps = walberla::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = walberla::make_shared<mesa_pd::data::ShapeStorage>();
+
+   auto accessor = walberla::make_shared<ParticleAccessor_T>(ps, ss);
+
+   BlockDataID particleStorageID;
+   if (readFromCheckPointFile) {
+      WALBERLA_LOG_INFO_ON_ROOT("Initializing particles from checkpointing file.");
+      particleStorageID = forest->loadBlockData( readCheckPointFileName + "_mesa.txt", mesa_pd::domain::createBlockForestDataHandling(ps), "Particle Storage" );
+   } else {
+      particleStorageID = forest->addBlockData(mesa_pd::domain::createBlockForestDataHandling(ps), "Particle Storage");
+   }
+
+
+   // bounding planes can be added here
+
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>(diameter * real_t(0.5));
+   ss->shapes[sphereShape]->updateMassAndInertia(densitySphere);
+
+   walberla::id_t sphereUid = 0;
+   if (readFromCheckPointFile) {
+      for (auto pIt = ps->begin(); pIt != ps->end(); ++pIt) {
+         // find sphere in loaded data structure and store uid for later reference
+         if (pIt->getShapeID() == sphereShape) {
+            sphereUid = pIt->getUid();
+            pIt->getNeighborStateRef().clear();
+            pIt->getGhostOwnersRef().clear();
+         }
+      }
+   } else {
+      if (mesapdDomain->isContainedInProcessSubdomain(uint_c(mpi::MPIManager::instance()->rank()), initialPosition)) {
+         mesa_pd::data::Particle &&p = *ps->create();
+         p.setPosition(initialPosition);
+         p.setInteractionRadius(diameter * real_t(0.5));
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         sphereUid = p.getUid();
+      }
+   }
+   mpi::allReduceInplace(sphereUid, mpi::SUM);
+
+   if(sphereUid == 0) {
+      WALBERLA_ABORT("No sphere present - aborting!");
+   }
+
+   auto ghostOwnerSyncIterations = uint_t(real_t(0.5) * diameter/real_t(blockSize) + real_t(1));
+   WALBERLA_LOG_INFO_ON_ROOT(" - Number of req'd ghost owner sync iterations: " << ghostOwnerSyncIterations << ", thus using " << (ghostOwnerSyncIterations == 1 ? "SNN" : "SGO") << " for synchronization.");
+
+   bool useSyncNextNeighbors = (ghostOwnerSyncIterations == 1);
+
+   std::function<void(void)> syncCall = [ps, mesapdDomain, useSyncNextNeighbors, overlap](){
+      if (useSyncNextNeighbors) {
+         mesa_pd::mpi::SyncNextNeighbors SNN; // alt. SyncGhostOwners
+         SNN(*ps, *mesapdDomain, overlap);
+      } else {
+         // need to use syncghostowners since the particle is bigger than a block and thus, syncnextneighbors doesn't suffice.
+         mesa_pd::mpi::SyncGhostOwners SGO;
+         SGO(*ps, *mesapdDomain, overlap);
+      }
+   };
+
+   std::function<void(void)> initializationSyncCall = [&syncCall, ghostOwnerSyncIterations](){
+      for (uint_t j = 0; j < ghostOwnerSyncIterations; ++j) {
+         syncCall();
+      }
+   };
+
+   //initial sync
+   initializationSyncCall();
+
+   //// add data to blocks
+
+   // create the lattice model
+#ifdef BUILD_WITH_MRT
+   const real_t Lambda_Magic = real_t(3) / real_t(16);
+   const real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega );
+   const real_t lambda_d = lbm::collision_model::TRT::lambda_d( omega, Lambda_Magic );
+   const real_t omegaBulk = real_t(1) / (Lambda_Bulk * ( real_t(1) / omega - real_t(1)/ real_t(2) ) + real_t(1)/ real_t(2) );
+
+   LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::D3Q19MRT( omegaBulk, lambda_e, lambda_d, lambda_e, lambda_e, lambda_d, finestLevel ));
+#else
+   LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::TRT::constructWithMagicNumber( omega, lbm::collision_model::TRT::threeSixteenth, finestLevel ) );
+#endif
+
+   // add PDF field
+   BlockDataID pdfFieldID;
+   if (readFromCheckPointFile) {
+      // add PDF field
+      WALBERLA_LOG_INFO_ON_ROOT( "Initializing PDF Field from checkpointing file!" );
+      shared_ptr< lbm::internal::PdfFieldHandling< LatticeModel_T > > dataHandling =
+              make_shared< lbm::internal::PdfFieldHandling< LatticeModel_T > >(blocks, latticeModel, false,
+                      Vector3<real_t>(real_t(0)), real_t(1),
+                      FieldGhostLayers, field::zyxf );
+
+      pdfFieldID = blocks->loadBlockData( readCheckPointFileName+"_lbm.txt", dataHandling, "pdf field" );
+
+   } else {
+      // add PDF field
+      pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >(blocks, "pdf field (zyxf)", latticeModel,
+              Vector3<real_t>(real_t(0)), real_t(1),
+              FieldGhostLayers, field::zyxf);
+   }
+
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field", FieldGhostLayers );
+
+   using FluidFilter_T = typename field::FlagFieldEvaluationFilter<FlagField_T>;
+   FluidFilter_T fluidFlagFieldEvaluationFilter(flagFieldID, Fluid_Flag);
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<ParticleField_T>(blocks, "particle field",
+         accessor->getInvalidUid(),
+         field::zyxf,
+         FieldGhostLayers);
+
+   // add velocity field and utility
+   BlockDataID velocityFieldID = field::addToStorage<VelocityField_T>( blocks, "velocity field", Vector3<real_t>(real_t(0)), field::zyxf, uint_t(2) );
+
+   typedef lbm::VelocityFieldWriter< PdfField_T, VelocityField_T > VelocityFieldWriter_T;
+   BlockSweepWrapper< VelocityFieldWriter_T > velocityFieldWriter( blocks, VelocityFieldWriter_T( pdfFieldID, velocityFieldID ) );
+
+   shared_ptr<blockforest::communication::NonUniformBufferedScheme<stencil::D3Q27> > velocityCommunicationScheme = make_shared<blockforest::communication::NonUniformBufferedScheme<stencil::D3Q27> >( blocks );
+   velocityCommunicationScheme->addPackInfo( make_shared< field::refinement::PackInfo<VelocityField_T, stencil::D3Q27> >( velocityFieldID ) );
+
+   // add q criterion field (only needed for mesh output)
+   BlockDataID qCriterionFieldID = field::addToStorage<QCriterionField_T>(blocks, "q criterion field", real_t(0), field::zyxf, uint_t(1));
+
+   typedef lbm::QCriterionFieldWriter<VelocityField_T, QCriterionField_T, FluidFilter_T> QCriterionFieldWriter_T;
+   BlockSweepWrapper<QCriterionFieldWriter_T> qCriterionFieldWriter(blocks, QCriterionFieldWriter_T(blocks, velocityFieldID,
+           qCriterionFieldID, fluidFlagFieldEvaluationFilter));
+
+   // add boundary handling
+   BlockDataID boundaryHandlingID = blocks->addBlockData( make_shared< MyBoundaryHandling >( blocks, flagFieldID, pdfFieldID, particleFieldID, accessor ),
+                                                          "boundary handling" );
+
+   // map particles into simulation
+   lbm_mesapd_coupling::RegularParticlesSelector sphereSelector;
+   lbm_mesapd_coupling::ParticleMappingKernel<BoundaryHandling_T> particleMappingKernel(blocks, boundaryHandlingID);
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks,
+                                                                                                    boundaryHandlingID,
+                                                                                                    particleFieldID);
+
+   std::function<void(void)> mappingCall = [ps, sphereSelector, accessor, &movingParticleMappingKernel](){
+      ps->forEachParticle(false, sphereSelector, *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+   };
+
+   mappingCall();
+
+   syncCall();
+
+   //// dynamic refinement II
+
+   blockforest::CombinedMinTargetLevelDeterminationFunctions minTargetLevelDeterminationFunctions;
+
+   auto couplingInfoCollection = walberla::make_shared<lbm_mesapd_coupling::amr::InfoCollection>();
+   lbm_mesapd_coupling::amr::ParticlePresenceLevelDetermination particlePresenceRefinement(couplingInfoCollection, finestLevel);
+
+   minTargetLevelDeterminationFunctions.add(particlePresenceRefinement);
+
+   if (useVorticityCriterion) {
+      // add refinement criterion based on vorticity magnitude
+      // "legacy"
+      lbm::refinement::VorticityBasedLevelDetermination<field::FlagFieldEvaluationFilter<FlagField_T>> vorticityRefinement(
+            velocityFieldID, fluidFlagFieldEvaluationFilter, upperFluidRefinementLimit, lowerFluidRefinementLimit, finestLevel);
+
+      minTargetLevelDeterminationFunctions.add(vorticityRefinement);
+   }
+
+   if (useCurlCriterion) {
+      // add refinement criterion based on curl (=vorticity) magnitude
+      // -> computes the same as vorticity, but has other params and uses correct length scale weighting
+      const real_t lowerCurlLimit = curlCoarsenFactor*upperCurlLimit;
+
+      lbm::refinement::CurlBasedLevelDetermination<field::FlagFieldEvaluationFilter<FlagField_T>> curlRefinement(
+            velocityFieldID, *blocks, fluidFlagFieldEvaluationFilter, finestLevel, upperCurlLimit, lowerCurlLimit,
+            curlLengthScaleWeight);
+
+      minTargetLevelDeterminationFunctions.add(curlRefinement);
+   }
+
+   forest->setRefreshMinTargetLevelDeterminationFunction(minTargetLevelDeterminationFunctions);
+
+   bool curveHilbert = false;
+   bool curveAllGather = true;
+
+   forest->setRefreshPhantomBlockMigrationPreparationFunction( blockforest::DynamicCurveBalance< blockforest::NoPhantomData >( curveHilbert, curveAllGather ) );
+
+
+   //// time loop
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+   std::function< void () > commFunction;
+   blockforest::communication::UniformBufferedScheme< Stencil_T > scheme( blocks );
+   scheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) );
+   commFunction = scheme;
+
+   // create the timeloop
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+   timeloop.setCurrentTimeStep(currentLBMTimeStep);
+
+   shared_ptr<WcTimingPool> timeloopTiming = make_shared<WcTimingPool>();
+
+   auto sweep = lbm::makeCellwiseSweep< LatticeModel_T, FlagField_T >( pdfFieldID, flagFieldID, Fluid_Flag );
+   auto refinementTimestep = lbm::refinement::makeTimeStep< LatticeModel_T, BoundaryHandling_T >( blocks, sweep, pdfFieldID, boundaryHandlingID );
+
+   //// MESAPD Time Step
+
+   const real_t sphereVolume = math::pi / real_t(6) * diameter * diameter * diameter;
+   Vector3<real_t> gravitationalForce(real_t(0), real_t(0),
+                                      -(densitySphere - densityFluid) * gravitationalAcceleration * sphereVolume);
+
+   lbm_mesapd_coupling::SubCyclingManager mesapdTimeStep(numMESAPDSubCycles, timeloopTiming);
+   mesapdTimeStep.setCurrentTimeStep(currentMESAPDTimeStep);
+   bool useOpenMP = false;
+
+   // before subcycling
+   mesa_pd::mpi::ReduceProperty reduceProperty;
+   lbm_mesapd_coupling::InitializeHydrodynamicForceTorqueForAveragingKernel initializeHydrodynamicForceTorqueAveraging;
+   lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque;
+
+   mesapdTimeStep.addFuncBeforeSubCycles([ps, accessor, useOpenMP, &reduceProperty, averageForceTorqueOverTwoTimeSteps,
+                                          &initializeHydrodynamicForceTorqueAveraging, &averageHydrodynamicForceTorque,
+                                          &mesapdTimeStep](){
+      reduceProperty.operator()<mesa_pd::HydrodynamicForceTorqueNotification>(*ps);
+      if (averageForceTorqueOverTwoTimeSteps) {
+         if (mesapdTimeStep.getCurrentTimeStep() == 0) {
+            ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, initializeHydrodynamicForceTorqueAveraging, *accessor);
+         } else {
+            ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, averageHydrodynamicForceTorque, *accessor);
+         }
+      }
+   }, "RPD Before Subcycling");
+
+   // during subcycling
+   using VirtualMass_ParticleAccessor_T = lbm_mesapd_coupling::ParticleAccessorWithShapeVirtualMassWrapper<ParticleAccessor_T>;
+   auto virtualMassAccessor = walberla::make_shared<VirtualMass_ParticleAccessor_T>(ps, ss);
+
+   mesa_pd::kernel::VelocityVerletPreForceUpdate vvIntegratorPreForce(real_t(1) / real_t(numMESAPDSubCycles));
+   mesa_pd::kernel::VelocityVerletPostForceUpdate vvIntegratorPostForce(real_t(1) / real_t(numMESAPDSubCycles));
+   lbm_mesapd_coupling::AddForceOnParticlesKernel addGravitationalForce(gravitationalForce);
+   lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction;
+
+   mesapdTimeStep.addFuncDuringSubCycles([ps, accessor, virtualMassAccessor, useOpenMP, syncCall,
+                                          useVirtualMass,
+                                          vvIntegratorPreForce, addHydrodynamicInteraction, addGravitationalForce,
+                                          vvIntegratorPostForce, mesapdDomain,
+                                          C_v, C_v_omega, densityFluid](){
+      if (useVirtualMass) {
+         lbm_mesapd_coupling::InitializeVirtualMassKernel virtualMass;
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, virtualMass, *accessor, C_v, C_v_omega, densityFluid);
+
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *virtualMassAccessor, vvIntegratorPreForce, *virtualMassAccessor);
+      } else {
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPreForce, *accessor);
+      }
+      syncCall();
+
+      // Hydrodynamic force and torque got reduced onto particle local process before subcycling,
+      // thus only add them there.
+      ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addHydrodynamicInteraction, *accessor);
+      ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addGravitationalForce, *accessor);
+
+      if (useVirtualMass) {
+         lbm_mesapd_coupling::InitializeVirtualMassKernel virtualMass;
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, virtualMass, *accessor, C_v, C_v_omega, densityFluid);
+
+         lbm_mesapd_coupling::AddVirtualForceTorqueKernel addVirtualForceAndTorque(ps);
+
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addVirtualForceAndTorque, *accessor);
+      }
+
+      if (useVirtualMass) {
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *virtualMassAccessor, vvIntegratorPostForce, *virtualMassAccessor);
+      } else {
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPostForce, *accessor);
+      }
+
+      syncCall();
+   }, "RPD Subcycling");
+
+   // after subcycling
+   // evaluation
+   std::string loggingFileName(baseFolder + "/LoggingUnsettlingSphereDynamicRefinement");
+   if (useVirtualMass) {
+      loggingFileName += "_vm";
+   }
+   loggingFileName += ".txt";
+   if (fileIO) {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing logging output to file \"" << loggingFileName << "\"");
+   }
+   SpherePropertyLogger logger(&mesapdTimeStep, accessor, sphereUid, loggingFileName, fileIO,
+           !readFromCheckPointFile, t_g, u_g, diameter);
+
+   auto periodicLogger = [&mesapdTimeStep, &logger, propertyLoggingFreq](){
+      if (propertyLoggingFreq != 0 && mesapdTimeStep.getCurrentTimeStep() % propertyLoggingFreq == 0) {
+         logger();
+      }
+   };
+
+   mesapdTimeStep.addFuncAfterSubCycles(periodicLogger, "Sphere property logger" );
+
+   lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+   mesapdTimeStep.addFuncAfterSubCycles([ps, accessor, useOpenMP, resetHydrodynamicForceTorque](){
+      ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor);
+   }, "RPD After Subcycling");
+
+   // mesapdTimeStep seems to get copied or something by the functorwrapper
+   auto wrapper = lbm::refinement::FunctorWrapper([&mesapdTimeStep](){
+      mesapdTimeStep();
+   });
+   refinementTimestep->addPostStreamVoidFunction(wrapper, "rpd Time Step", finestLevel);
+
+
+   //// Particle Mapping
+
+   // sweep for updating the particle mapping into the LBM simulation
+   auto movingParticleMapping = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks,
+                                                                                                               pdfFieldID,
+                                                                                                               boundaryHandlingID,
+                                                                                                               particleFieldID,
+                                                                                                               accessor,
+                                                                                                               MO_Flag,
+                                                                                                               FormerMO_Flag,
+                                                                                                               sphereSelector,
+                                                                                                               conserveMomentum);
+   refinementTimestep->addPostStreamVoidFunction(lbm::refinement::SweepAsFunctorWrapper(movingParticleMapping, blocks),
+                                                 "Particle Mapping", finestLevel);
+
+   //// PDF Restore
+
+   blockforest::communication::NonUniformBufferedScheme<Stencil_T> fullPDFCommunicationScheme(blocks);
+   auto uniformPackInfo = make_shared<field::communication::PackInfo<PdfField_T>>(pdfFieldID);
+   auto packInfoAdapter = make_shared<blockforest::communication::UniformToNonUniformPackInfoAdapter>(uniformPackInfo);
+   // using the pack info adapter is required, else the ghost layers are not correctly synced, resulting in invalid reconstructed cells (with NaN as value).
+   fullPDFCommunicationScheme.addPackInfo(packInfoAdapter);
+   refinementTimestep->addPostStreamVoidFunction(lbm::refinement::FunctorWrapper(fullPDFCommunicationScheme),
+                                                 "PDF Communication", finestLevel);
+
+   // add sweep for restoring PDFs in cells previously occupied by particles
+   auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(
+           blocks);
+   auto extrapolationSphereNormalReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T, lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder, true>(
+           blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder);
+   //auto equilibriumRecon = lbm_mesapd_coupling::makeEquilibriumReconstructor<BoundaryHandling_T>(
+   //        blocks, boundaryHandlingID);
+   auto reconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T, BoundaryHandling_T>(
+           blocks,
+           pdfFieldID,
+           boundaryHandlingID,
+           particleFieldID,
+           accessor,
+           FormerMO_Flag,
+           Fluid_Flag,
+           extrapolationSphereNormalReconstructor,
+           conserveMomentum);
+   refinementTimestep->addPostStreamVoidFunction(lbm::refinement::SweepAsFunctorWrapper(*reconstructionManager, blocks),
+                                                 "PDF Restore", finestLevel);
+
+   // add LBM sweep with refinement
+   timeloop.addFuncBeforeTimeStep( makeSharedFunctor( refinementTimestep ), "LBM refinement time step" );
+
+
+   //// VTK output
+
+   auto vtkInitialExecutionCount = uint_t(0);
+   std::string vtkFileTag = "";
+   if (readFromCheckPointFile) {
+      vtkInitialExecutionCount = checkPointLBMTimeStep;
+      vtkFileTag = "_" + std::to_string(vtkInitialExecutionCount);
+   }
+
+   // sphere(s)
+   auto particleVtkOutput = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+   particleVtkOutput->addOutput<mesa_pd::data::SelectParticleOwner>("owner");
+   particleVtkOutput->addOutput<mesa_pd::data::SelectParticleLinearVelocity>("velocity");
+   particleVtkOutput->addOutput<mesa_pd::data::SelectParticleInteractionRadius>("interactionRadius");
+   auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "Particles"+vtkFileTag, vtkIOFreq,
+         baseFolder, "simulation_step", false, true, true, true,
+         vtkInitialExecutionCount);
+
+   // flag field left out
+
+   // pdf field
+   auto pdfFieldVTK = vtk::createVTKOutput_BlockData(blocks, "fluid_field"+vtkFileTag, vtkIOFreq, 0,
+         false, baseFolder, "simulation_step", false, true, true, true,
+         vtkInitialExecutionCount);
+
+   vtk::ChainedFilter combinedFilter;
+   if (vtkIOSelection == "sliced") {
+      AABB sliceAABB( real_t(0), real_c(domainSize[1])*real_t(0.5), real_t(0),
+                      real_c(domainSize[0]), real_c(domainSize[1])*real_t(0.5)+real_t(1), real_c(domainSize[2]) );
+      vtk::AABBCellFilter aabbSliceFilter( sliceAABB );
+      combinedFilter.addFilter( aabbSliceFilter );
+   } else if (vtkIOSelection == "thicksliced") {
+      AABB sliceAABB( real_t(0), real_c(domainSize[1])*real_t(0.5)-real_t(diameter)*real_t(0.5)-real_t(1), real_t(0),
+                      real_c(domainSize[0]), real_c(domainSize[1])*real_t(0.5), real_c(domainSize[2]) );
+      vtk::AABBCellFilter aabbSliceFilter( sliceAABB );
+      combinedFilter.addFilter( aabbSliceFilter );
+   } else if (vtkIOSelection == "trackingsliced") {
+      auto filter = [logger, domainSize](CellSet& filteredCells, const IBlock& block, const StructuredBlockStorage& storage, const uint_t ghostLayers = uint_t(0)){
+         auto yPosition = logger.getPosition()[1];
+         real_t minY = std::floor(yPosition);
+         real_t maxY = std::floor(yPosition)+real_t(1);
+         AABB sliceAABB( real_t(0), minY, real_t(0),
+                         real_c(domainSize[0]), maxY, real_c(domainSize[2]) );
+         vtk::AABBCellFilter aabbSliceFilter( sliceAABB );
+         aabbSliceFilter(filteredCells, block, storage, ghostLayers);
+      };
+      combinedFilter.addFilter( filter );
+      logger.syncPosition(); // synchronize the position once to correctly supply the vtk output before the first timestep
+   } else if (vtkIOSelection == "velocity") {
+      field::VelocityCellFilter<PdfField_T, field::FlagFieldEvaluationFilter<FlagField_T>> filter(pdfFieldID,
+            fluidFlagFieldEvaluationFilter, vtkLowerVelocityLimit);
+      combinedFilter.addFilter(filter);
+   } else if (vtkIOSelection != "none" && vtkIOSelection != "full") {
+      WALBERLA_ABORT("Invalid vtk output selection.");
+   }
+
+   field::FlagFieldCellFilter< FlagField_T > fluidFilter(flagFieldID);
+   fluidFilter.addFlag(Fluid_Flag);
+   combinedFilter.addFilter(fluidFilter);
+
+   pdfFieldVTK->addCellInclusionFilter(combinedFilter);
+
+   pdfFieldVTK->addBeforeFunction(velocityFieldWriter);
+   pdfFieldVTK->addBeforeFunction(*velocityCommunicationScheme);
+
+   pdfFieldVTK->addCellDataWriter(make_shared<lbm::VelocityVTKWriter<LatticeModel_T, float>>(pdfFieldID, "VelocityFromPDF"));
+   pdfFieldVTK->addCellDataWriter(make_shared<lbm::DensityVTKWriter<LatticeModel_T, float>>(pdfFieldID, "DensityFromPDF"));
+   pdfFieldVTK->addCellDataWriter(make_shared<lbm::QCriterionVTKWriter<VelocityField_T, FluidFilter_T, float>>(blocks,
+         fluidFlagFieldEvaluationFilter, velocityFieldID, "QCriterionFromPDF"));
+   pdfFieldVTK->addCellDataWriter(make_shared<lbm::CurlMagnitudeVTKWriter<VelocityField_T, FluidFilter_T, float>>(blocks,
+         fluidFlagFieldEvaluationFilter, velocityFieldID, "CurlSensor", real_t(2)));
+
+   // full size vtk
+   auto fullPdfFieldVTK = vtk::createVTKOutput_BlockData(blocks, "fluid_field_full"+vtkFileTag, fullVtkIOFreq,
+         0, false, baseFolder, "simulation_step", false, true, true, true,
+         vtkInitialExecutionCount);
+   fullPdfFieldVTK->addBeforeFunction(velocityFieldWriter);
+   fullPdfFieldVTK->addBeforeFunction(*velocityCommunicationScheme);
+   fullPdfFieldVTK->addCellInclusionFilter(fluidFilter);
+   fullPdfFieldVTK->addCellDataWriter(make_shared<lbm::VelocityVTKWriter<LatticeModel_T, float>>(pdfFieldID, "VelocityFromPDF"));
+   fullPdfFieldVTK->addCellDataWriter(make_shared<lbm::QCriterionVTKWriter<VelocityField_T , FluidFilter_T, float>>(blocks,
+         fluidFlagFieldEvaluationFilter, velocityFieldID, "QCriterionFromPDF"));
+   fullPdfFieldVTK->addCellDataWriter(make_shared<lbm::CurlMagnitudeVTKWriter<VelocityField_T, FluidFilter_T, float>>(blocks,
+         fluidFlagFieldEvaluationFilter, velocityFieldID, "CurlSensor", curlLengthScaleWeight));
+
+   // q value vtk
+   auto qCriterionVTK = vtk::createVTKOutput_BlockData(blocks, "q_value"+vtkFileTag, qCriterionVtkIOFreq, 0,
+         false, baseFolder, "simulation_step", false, true, true, true,
+         vtkInitialExecutionCount);
+
+   field::QCriterionCellFilter<VelocityField_T, field::FlagFieldEvaluationFilter<FlagField_T>> qFilter(velocityFieldID,
+         fluidFlagFieldEvaluationFilter, vtkLowerQCriterionLimit);
+   qCriterionVTK->addCellInclusionFilter(qFilter);
+   qCriterionVTK->addBeforeFunction(velocityFieldWriter);
+   qCriterionVTK->addBeforeFunction(*velocityCommunicationScheme);
+   qCriterionVTK->addCellDataWriter(make_shared<lbm::QCriterionVTKWriter<VelocityField_T, FluidFilter_T, float>>(blocks,
+         fluidFlagFieldEvaluationFilter, velocityFieldID, "QCriterionFromPDF"));
+   qCriterionVTK->addCellDataWriter(make_shared<lbm::VorticityComponentVTKWriter<VelocityField_T, FluidFilter_T>>(blocks,
+         fluidFlagFieldEvaluationFilter, velocityFieldID, uint_t(2), "VorticityZFromPDF", u_g/diameter));
+
+   // domain decomposition
+   auto domainDecompVTK = vtk::createVTKOutput_DomainDecomposition(blocks, "domain_decomposition"+vtkFileTag,
+         vtkIOFreq, baseFolder, "simulation_step", false, true, true, true,
+         vtkInitialExecutionCount);
+
+   timeloop.addFuncBeforeTimeStep(vtk::writeFiles(particleVtkWriter), "VTK (sparse data)");
+   if (vtkIOSelection != "none") {
+      timeloop.addFuncBeforeTimeStep(vtk::writeFiles(pdfFieldVTK), "VTK (fluid field data)");
+   }
+   timeloop.addFuncAfterTimeStep(vtk::writeFiles(domainDecompVTK), "VTK (domain decomposition)");
+
+   if (fullVtkIOFreq != uint_t(0)) {
+      timeloop.addFuncBeforeTimeStep(vtk::writeFiles(fullPdfFieldVTK), "VTK (full fluid field data)");
+   }
+
+   timeloop.addFuncBeforeTimeStep(vtk::writeFiles(qCriterionVTK), "VTK (q value data)");
+
+   timeloop.addFuncAfterTimeStep( RemainingTimeLogger(timeloop.getNrOfTimeSteps()), "Remaining Time Logger" );
+
+   real_t terminationPosition;
+   // when the sphere is only a diameter away from the initial position again
+   if (rising) {
+      terminationPosition = real_t(initialPosition[2]) - diameter;
+   } else {
+      terminationPosition = real_t(initialPosition[2]) + diameter;
+   }
+   if (terminationPosition < real_t(0)) {
+      terminationPosition = real_t(domainSize[2]) + terminationPosition;
+   } else if (terminationPosition > real_t(domainSize[2])) {
+      terminationPosition = terminationPosition - real_t(domainSize[2]);
+   }
+   WALBERLA_LOG_INFO_ON_ROOT(" - termination position = " << terminationPosition );
+
+   std::vector<std::string> oldCheckpointFiles;
+
+   //// execute simulation
+   bool hasCrossedBoundary = false;
+
+   for (uint_t i = 0; i < timesteps; ++i) {
+      if (refinementCheckFrequency != 0 && i % refinementCheckFrequency == 0) {
+         WALBERLA_LOG_INFO_ON_ROOT("Refinement: Refreshing...");
+
+         lbm_mesapd_coupling::amr::updateAndSyncInfoCollection<BoundaryHandling_T, ParticleAccessor_T>(*forest, boundaryHandlingID,
+               *accessor, numMESAPDSubCycles, *couplingInfoCollection);
+
+         // for the fluid property based check
+         if (useCurlCriterion || useVorticityCriterion) {
+            velocityFieldWriter();
+            (*velocityCommunicationScheme)();
+         }
+
+         // clear mesapd
+         if (useSyncNextNeighbors) {
+            mesa_pd::mpi::ClearNextNeighborSync CNNS;
+            CNNS(*accessor);
+         } else {
+            mesa_pd::mpi::ClearGhostOwnerSync CGOS;
+            CGOS(*accessor);
+         }
+
+         forest->refresh();
+         mesapdDomain->refresh();
+         initializationSyncCall();
+
+         clearBoundaryHandling(*forest, boundaryHandlingID);
+         clearParticleField(*forest, particleFieldID, *accessor);
+         recreateBoundaryHandling(*forest, boundaryHandlingID);
+
+         mappingCall();
+
+         std::vector<uint_t> blocksPerLevel(numberOfLevels);
+         for (uint_t l = 0; l < numberOfLevels; l++) {
+            blocksPerLevel[l] = blocks->getNumberOfBlocks(l);
+         }
+         mpi::reduceInplace(blocksPerLevel, mpi::SUM);
+         WALBERLA_ROOT_SECTION() {
+            WALBERLA_LOG_INFO("Refinement: Number of blocks per level: ");
+            for (uint_t l = 0; l < numberOfLevels; l++) {
+               WALBERLA_LOG_INFO(" - Level " << l << ": " << blocksPerLevel[l] << " blocks");
+            }
+         }
+      }
+
+      // perform a single simulation step
+      timeloop.singleStep( *timeloopTiming );
+
+      // check for termination
+      // check if the sphere crossed a boundary
+      auto zPosition = logger.getPosition()[2];
+      if (!hasCrossedBoundary && ((rising && zPosition < terminationPosition) ||
+                                  (!rising && zPosition > terminationPosition))) {
+         hasCrossedBoundary = true;
+      }
+      if (hasCrossedBoundary && ((rising && zPosition > terminationPosition)
+                                 || (!rising && zPosition < terminationPosition))) {
+         // end of simulation reached after crossing a boundary and then passing the termination position
+         WALBERLA_LOG_INFO_ON_ROOT(
+                 "Sphere reached terminal position " << logger.getPosition() << " after " << i << " timesteps!");
+         break;
+      }
+      if (checkPointingFreq != 0 && i % checkPointingFreq == 0) {
+         createCheckpointFiles(checkPointFileName, oldCheckpointFiles, timeloop.getCurrentTimeStep(),
+                 mesapdTimeStep.getCurrentTimeStep(), forest, pdfFieldID, particleStorageID);
+      }
+   }
+
+   size_t particleIdx = accessor->uidToIdx(sphereUid);
+   if (particleIdx != accessor->getInvalidIdx()) {
+      if (!mesa_pd::data::particle_flags::isSet(accessor->getFlags(particleIdx), mesa_pd::data::particle_flags::GHOST)) {
+         Vector3<real_t> terminalVelocity = accessor->getLinearVelocity(particleIdx);
+         real_t reynoldsNumber = terminalVelocity[2] * diameter / viscosity;
+         WALBERLA_LOG_INFO("Terminal velocity: " << terminalVelocity);
+         WALBERLA_LOG_INFO("Terminal Reynolds number: " << reynoldsNumber);
+      }
+   }
+
+   timeloopTiming->logResultOnRoot();
+
+   return EXIT_SUCCESS;
+}
+
+}
+
+int main(int argc, char **argv) {
+   light_rising_particle_amr::main(argc, argv);
+}
\ No newline at end of file
diff --git a/python/mesa_pd.py b/python/mesa_pd.py
index 4e748a1d7..3161a1a27 100755
--- a/python/mesa_pd.py
+++ b/python/mesa_pd.py
@@ -63,6 +63,22 @@ if __name__ == '__main__':
     ps.add_property("oldHydrodynamicTorque", "walberla::mesa_pd::Vec3", defValue="real_t(0)",
                     syncMode="ON_OWNERSHIP_CHANGE")
 
+    # Properties for virtual mass:
+    ps.add_property("virtualMass",                  "walberla::real_t",        defValue="real_t(0)",
+                    syncMode="ON_OWNERSHIP_CHANGE")
+    ps.add_property("invMassIncludingVirtual",      "walberla::real_t",        defValue="real_t(0)",
+                    syncMode="ON_OWNERSHIP_CHANGE")
+    ps.add_property("oldLinearAcceleration",        "walberla::mesa_pd::Vec3", defValue="real_t(0)",
+                    syncMode="ON_OWNERSHIP_CHANGE")
+    ps.add_property("invInertiaBF",                 "walberla::mesa_pd::Mat3", defValue="real_t(0)",
+                    syncMode="ON_OWNERSHIP_CHANGE")
+    ps.add_property("virtualInertiaBF",             "walberla::mesa_pd::Mat3", defValue="real_t(0)",
+                    syncMode="ON_OWNERSHIP_CHANGE")
+    ps.add_property("invInertiaBFIncludingVirtual", "walberla::mesa_pd::Mat3", defValue="real_t(0)",
+                    syncMode="ON_OWNERSHIP_CHANGE")
+    ps.add_property("oldAngularAcceleration",       "walberla::mesa_pd::Vec3", defValue="real_t(0)",
+                    syncMode="ON_OWNERSHIP_CHANGE")
+
     # properties for VBond model
     ps.add_property("clusterID", "int64_t", defValue="-1", syncMode="ON_GHOST_CREATION")
     ps.add_property("segmentID", "int64_t", defValue="-1", syncMode="ON_GHOST_CREATION")
diff --git a/src/lbm_mesapd_coupling/amr/level_determination/ParticlePresenceLevelDetermination.h b/src/lbm_mesapd_coupling/amr/level_determination/ParticlePresenceLevelDetermination.h
index 9ad15259e..deb9b2a7d 100644
--- a/src/lbm_mesapd_coupling/amr/level_determination/ParticlePresenceLevelDetermination.h
+++ b/src/lbm_mesapd_coupling/amr/level_determination/ParticlePresenceLevelDetermination.h
@@ -25,7 +25,8 @@
 
 namespace walberla {
 namespace lbm_mesapd_coupling {
-
+namespace amr
+{
 /*
  * Class to determine the minimum level a block can be.
  * For coupled LBM-PE simulations the following rules apply:
@@ -39,21 +40,24 @@ namespace lbm_mesapd_coupling {
  */
 class ParticlePresenceLevelDetermination
 {
-public:
-
-   ParticlePresenceLevelDetermination( const shared_ptr<lbm_mesapd_coupling::InfoCollection> & infoCollection, uint_t finestLevel) :
-         infoCollection_( infoCollection ), finestLevel_( finestLevel)
+ public:
+   ParticlePresenceLevelDetermination(const shared_ptr< InfoCollection > infoCollection,
+                                      uint_t finestLevel)
+      : infoCollection_(infoCollection), finestLevel_(finestLevel)
    {}
 
-   void operator()( std::vector< std::pair< const Block *, uint_t > > & minTargetLevels,
-                    std::vector< const Block * > &, const BlockForest & /*forest*/ ) {
-      for (auto &minTargetLevel : minTargetLevels) {
+   void operator()(std::vector< std::pair< const Block*, uint_t > >& minTargetLevels, std::vector< const Block* >&,
+                   const BlockForest& /*forest*/)
+   {
+      for (auto& minTargetLevel : minTargetLevels)
+      {
          uint_t currentLevelOfBlock = minTargetLevel.first->getLevel();
 
-         const uint_t numberOfParticlesInDirectNeighborhood = getNumberOfLocalAndShadowParticlesInNeighborhood(minTargetLevel.first);
+         const uint_t numberOfParticlesInDirectNeighborhood =
+            getNumberOfLocalAndShadowParticlesInNeighborhood(minTargetLevel.first);
 
-         uint_t targetLevelOfBlock = currentLevelOfBlock; //keep everything as it is
-         if ( numberOfParticlesInDirectNeighborhood > uint_t(0) )
+         uint_t targetLevelOfBlock = currentLevelOfBlock; // keep everything as it is
+         if (numberOfParticlesInDirectNeighborhood > uint_t(0))
          {
             // set block to finest level if there are particles nearby
             targetLevelOfBlock = finestLevel_;
@@ -61,41 +65,44 @@ public:
          else
          {
             // block could coarsen since there are no particles nearby
-            if( currentLevelOfBlock > uint_t(0) )
-               targetLevelOfBlock = currentLevelOfBlock - uint_t(1);
+            if (currentLevelOfBlock > uint_t(0)) targetLevelOfBlock = currentLevelOfBlock - uint_t(1);
          }
 
-         WALBERLA_CHECK_LESS_EQUAL(std::abs(int_c(targetLevelOfBlock) - int_c(currentLevelOfBlock)), uint_t(1), "Only level difference of maximum 1 allowed!");
+         WALBERLA_CHECK_LESS_EQUAL(std::abs(int_c(targetLevelOfBlock) - int_c(currentLevelOfBlock)), uint_t(1),
+                                   "Only level difference of maximum 1 allowed!");
          minTargetLevel.second = targetLevelOfBlock;
       }
    }
 
-private:
-
-   uint_t getNumberOfLocalAndShadowParticlesInNeighborhood(const Block * block) {
+ private:
+   uint_t getNumberOfLocalAndShadowParticlesInNeighborhood(const Block* block)
+   {
       auto numParticles = uint_t(0);
 
       // add particles of current block
       const auto infoIt = infoCollection_->find(block->getId());
-      WALBERLA_CHECK_UNEQUAL(infoIt, infoCollection_->end(), "Block with ID " << block->getId() << " not found in info collection!");
+      WALBERLA_CHECK_UNEQUAL(infoIt, infoCollection_->end(),
+                             "Block with ID " << block->getId() << " not found in info collection!");
 
-      numParticles += infoIt->second.numberOfLocalParticles + infoIt->second.numberOfShadowParticles;
+      numParticles += infoIt->second.numberOfLocalParticles + infoIt->second.numberOfGhostParticles;
 
       // add particles of all neighboring blocks
-      for(uint_t i = 0; i < block->getNeighborhoodSize(); ++i)
+      for (uint_t i = 0; i < block->getNeighborhoodSize(); ++i)
       {
-         const BlockID &neighborBlockID = block->getNeighborId(i);
-         const auto infoItNeighbor = infoCollection_->find(neighborBlockID);
-         WALBERLA_CHECK_UNEQUAL(infoItNeighbor, infoCollection_->end(), "Neighbor block with ID " << neighborBlockID << " not found in info collection!");
+         const BlockID& neighborBlockID = block->getNeighborId(i);
+         const auto infoItNeighbor      = infoCollection_->find(neighborBlockID);
+         WALBERLA_CHECK_UNEQUAL(infoItNeighbor, infoCollection_->end(),
+                                "Neighbor block with ID " << neighborBlockID << " not found in info collection!");
 
-         numParticles += infoItNeighbor->second.numberOfLocalParticles + infoItNeighbor->second.numberOfShadowParticles;
+         numParticles += infoItNeighbor->second.numberOfLocalParticles + infoItNeighbor->second.numberOfGhostParticles;
       }
       return numParticles;
    }
 
-   shared_ptr<lbm_mesapd_coupling::InfoCollection> infoCollection_;
+   shared_ptr< InfoCollection > infoCollection_;
    uint_t finestLevel_;
 };
 
+} // namespace amr
 } // namespace lbm_mesapd_coupling
 } // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/virtualmass/AddVirtualForceTorqueKernel.h b/src/lbm_mesapd_coupling/utility/virtualmass/AddVirtualForceTorqueKernel.h
new file mode 100644
index 000000000..6f4a7ea35
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/virtualmass/AddVirtualForceTorqueKernel.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 AddVirtualForceTorqueKernel.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Lukas Werner <lks.werner@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <utility>
+
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/IAccessor.h"
+#include "mesa_pd/common/ParticleFunctions.h"
+#include "mesa_pd/data/ParticleStorage.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/**
+ * Kernel that sets a virtual force and torque on particles. It accesses a virtual mass and inertia, which have to be
+ * set beforehand using InitializeVirtualMassKernel.
+ * During the calculation of virtual force and torque linear and angular accelerations are used, for which acceleration
+ * estimators need to be supplied.
+ *
+ * This kernel requires the following particle attributes:
+ *  ps.addProperty("virtualMass",       "walberla::real_t",        defValue="real_t(0)", syncMode="ON_OWNERSHIP_CHANGE")
+ *  ps.addProperty("virtualInertiaBF",  "walberla::mesa_pd::Mat3", defValue="real_t(0)", syncMode="ON_OWNERSHIP_CHANGE")
+ *  ps.add_property("oldLinearAcceleration",  "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ON_OWNERSHIP_CHANGE")
+ *  ps.add_property("oldAngularAcceleration", "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="ON_OWNERSHIP_CHANGE")
+ */
+class AddVirtualForceTorqueKernel
+{
+public:
+   explicit AddVirtualForceTorqueKernel(shared_ptr<mesa_pd::data::ParticleStorage> ps) : ps_(std::move(ps)){};
+
+   template <typename Accessor_T>
+   void operator()(const size_t idx, Accessor_T& ac) {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, Accessor_T>::value, "please provide a valid accessor");
+
+      WALBERLA_CHECK_FLOAT_UNEQUAL(ac.getVirtualMass(idx), real_t(0.),
+                                   "No virtual mass set on body. Was the virtualMass kernel not called before?");
+
+      //// force
+      // obtain the acceleration estimation
+      Vector3<real_t> approximatedCurrentLinearAcceleration = ac.getOldLinearAcceleration(idx);
+
+      // prepare the next acceleration estimation for the following time step
+      const Vector3<real_t> virtualForce = ac.getVirtualMass(idx) * approximatedCurrentLinearAcceleration;
+      const Vector3<real_t> currentLinearAcceleration = (ac.getForce(idx) + virtualForce) /
+                                                          (ac.getVirtualMass(idx) + ac.getMass(idx));
+      ac.setOldLinearAcceleration(idx, currentLinearAcceleration);
+
+      mesa_pd::addForceAtomic(idx, ac, virtualForce);
+
+      //// torque
+      // obtain the acceleration estimation
+      Vector3<real_t> approximatedCurrentAngularAcceleration = ac.getOldAngularAcceleration(idx);
+
+      // prepare the next acceleration estimation for the following time step
+      const Vector3<real_t> virtualTorque = ac.getVirtualInertiaBF(idx) * approximatedCurrentAngularAcceleration;
+      const Vector3<real_t> angularAcceleration = math::transformMatrixRART(ac.getRotation(idx).getMatrix(),
+        ac.getInvInertiaBFIncludingVirtual(idx)) * (ac.getTorque(idx) + virtualTorque);
+      ac.setOldAngularAcceleration(idx, angularAcceleration);
+
+      mesa_pd::addTorqueAtomic(idx, ac, virtualTorque);
+   }
+
+private:
+   const shared_ptr<mesa_pd::data::ParticleStorage> ps_;
+};
+
+}
+}
\ No newline at end of file
diff --git a/src/lbm_mesapd_coupling/utility/virtualmass/InitializeVirtualMassKernel.h b/src/lbm_mesapd_coupling/utility/virtualmass/InitializeVirtualMassKernel.h
new file mode 100644
index 000000000..f03cf4274
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/virtualmass/InitializeVirtualMassKernel.h
@@ -0,0 +1,64 @@
+//======================================================================================================================
+//
+//  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 VirtualMass.h
+//! \author Lukas Werner <lks.werner@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/IAccessor.h>
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/**
+ * This kernel calculates virtual mass and inertia and sets them as attributes on a particle.
+ *
+ * It requires the following particle attributes:
+ *  ps.addProperty("virtualMass",                  "walberla::real_t",        defValue="real_t(0)", syncMode="ON_OWNERSHIP_CHANGE")
+ *  ps.addProperty("invMassIncludingVirtual",      "walberla::real_t",        defValue="real_t(0)", syncMode="ON_OWNERSHIP_CHANGE")
+ *  ps.addProperty("virtualInertiaBF",             "walberla::mesa_pd::Mat3", defValue="real_t(0)", syncMode="ON_OWNERSHIP_CHANGE")
+ *  ps.addProperty("invInertiaBFIncludingVirtual", "walberla::mesa_pd::Mat3", defValue="real_t(0)", syncMode="ON_OWNERSHIP_CHANGE")
+ */
+class InitializeVirtualMassKernel {
+public:
+   InitializeVirtualMassKernel() = default;
+
+   template <typename Accessor>
+   void operator()(size_t i, Accessor& ac, real_t C_v, real_t C_v_omega, real_t fluidDensity) const;
+};
+
+
+template <typename Accessor>
+inline void InitializeVirtualMassKernel::operator()(const size_t i, Accessor& ac, const real_t C_v, const real_t C_v_omega,
+        const real_t fluidDensity) const {
+   static_assert(std::is_base_of<mesa_pd::data::IAccessor, Accessor>::value, "please provide a valid accessor");
+
+   const real_t virtualMass = C_v * fluidDensity * ac.getVolume(i);
+   ac.setVirtualMass(i, virtualMass);
+   ac.setInvMassIncludingVirtual(i, real_t(1.) / (ac.getMass(i) + virtualMass));
+
+   const real_t angularVirtualMass = C_v_omega * fluidDensity * ac.getVolume(i);
+   const mesa_pd::Mat3 virtualInertiaBF = ac.getInertiaBF(i) * ac.getInvMass(i) * angularVirtualMass;
+   const mesa_pd::Mat3 inertiaBFIncludingVirtual = ac.getInertiaBF(i) + virtualInertiaBF;
+   ac.setVirtualInertiaBF(i, virtualInertiaBF);
+   ac.setInvInertiaBFIncludingVirtual(i, inertiaBFIncludingVirtual.getInverse());
+}
+
+} //namespace lbm_mesapd_coupling
+} //namespace walberla
\ No newline at end of file
diff --git a/src/lbm_mesapd_coupling/utility/virtualmass/ParticleAccessorWithShapeVirtualMassWrapper.h b/src/lbm_mesapd_coupling/utility/virtualmass/ParticleAccessorWithShapeVirtualMassWrapper.h
new file mode 100644
index 000000000..744a614fb
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/virtualmass/ParticleAccessorWithShapeVirtualMassWrapper.h
@@ -0,0 +1,58 @@
+//======================================================================================================================
+//
+//  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 ParticleAccessorWithShapeAndVirtualMassWrapper.h
+//! \author Lukas Werner <lks.werner@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/ParticleStorage.h>
+#include <mesa_pd/data/ShapeStorage.h>
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/**
+ * !\brief Wraps a ParticleAccessor to change its getInvMass and getInvInertiaBF methods to include virtual mass.
+ * @tparam T A ParticleAccessor providing functions getInvMass and getInvInertiaBF, and their respective getInvMassIncludingVirtual and getInvInertiaBFIncludingVirtual.
+ */
+template <typename T>
+class ParticleAccessorWithShapeVirtualMassWrapper : public T {
+public:
+  ParticleAccessorWithShapeVirtualMassWrapper(std::shared_ptr<mesa_pd::data::ParticleStorage>& ps, std::shared_ptr<mesa_pd::data::ShapeStorage>& ss)
+      : T(ps, ss) {
+   }
+
+   auto getInvMass(const size_t p_idx) const {
+      return T::getInvMassIncludingVirtual(p_idx);
+   }
+
+   const auto getMass(const size_t p_idx) const {
+      return T::getMass(p_idx) + T::getVirtualMass(p_idx);
+   }
+
+   auto getInvInertiaBF(const size_t p_idx) const {
+      return T::getInvInertiaBFIncludingVirtual(p_idx);
+   }
+
+   const auto getInertiaBF(const size_t p_idx) const {
+      return T::getInertiaBF(p_idx) + T::getVirtualInertiaBF(p_idx);
+   }
+};
+
+}
+}
\ No newline at end of file
diff --git a/src/mesa_pd/data/ParticleAccessor.h b/src/mesa_pd/data/ParticleAccessor.h
index 62c61f6f7..14ed03dc2 100644
--- a/src/mesa_pd/data/ParticleAccessor.h
+++ b/src/mesa_pd/data/ParticleAccessor.h
@@ -164,6 +164,34 @@ public:
    walberla::mesa_pd::Vec3& getOldHydrodynamicTorqueRef(const size_t p_idx) {return ps_->getOldHydrodynamicTorqueRef(p_idx);}
    void setOldHydrodynamicTorque(const size_t p_idx, walberla::mesa_pd::Vec3 const & v) { ps_->setOldHydrodynamicTorque(p_idx, v);}
    
+   walberla::real_t const & getVirtualMass(const size_t p_idx) const {return ps_->getVirtualMass(p_idx);}
+   walberla::real_t& getVirtualMassRef(const size_t p_idx) {return ps_->getVirtualMassRef(p_idx);}
+   void setVirtualMass(const size_t p_idx, walberla::real_t const & v) { ps_->setVirtualMass(p_idx, v);}
+   
+   walberla::real_t const & getInvMassIncludingVirtual(const size_t p_idx) const {return ps_->getInvMassIncludingVirtual(p_idx);}
+   walberla::real_t& getInvMassIncludingVirtualRef(const size_t p_idx) {return ps_->getInvMassIncludingVirtualRef(p_idx);}
+   void setInvMassIncludingVirtual(const size_t p_idx, walberla::real_t const & v) { ps_->setInvMassIncludingVirtual(p_idx, v);}
+   
+   walberla::mesa_pd::Vec3 const & getOldLinearAcceleration(const size_t p_idx) const {return ps_->getOldLinearAcceleration(p_idx);}
+   walberla::mesa_pd::Vec3& getOldLinearAccelerationRef(const size_t p_idx) {return ps_->getOldLinearAccelerationRef(p_idx);}
+   void setOldLinearAcceleration(const size_t p_idx, walberla::mesa_pd::Vec3 const & v) { ps_->setOldLinearAcceleration(p_idx, v);}
+   
+   walberla::mesa_pd::Mat3 const & getInvInertiaBF(const size_t p_idx) const {return ps_->getInvInertiaBF(p_idx);}
+   walberla::mesa_pd::Mat3& getInvInertiaBFRef(const size_t p_idx) {return ps_->getInvInertiaBFRef(p_idx);}
+   void setInvInertiaBF(const size_t p_idx, walberla::mesa_pd::Mat3 const & v) { ps_->setInvInertiaBF(p_idx, v);}
+   
+   walberla::mesa_pd::Mat3 const & getVirtualInertiaBF(const size_t p_idx) const {return ps_->getVirtualInertiaBF(p_idx);}
+   walberla::mesa_pd::Mat3& getVirtualInertiaBFRef(const size_t p_idx) {return ps_->getVirtualInertiaBFRef(p_idx);}
+   void setVirtualInertiaBF(const size_t p_idx, walberla::mesa_pd::Mat3 const & v) { ps_->setVirtualInertiaBF(p_idx, v);}
+   
+   walberla::mesa_pd::Mat3 const & getInvInertiaBFIncludingVirtual(const size_t p_idx) const {return ps_->getInvInertiaBFIncludingVirtual(p_idx);}
+   walberla::mesa_pd::Mat3& getInvInertiaBFIncludingVirtualRef(const size_t p_idx) {return ps_->getInvInertiaBFIncludingVirtualRef(p_idx);}
+   void setInvInertiaBFIncludingVirtual(const size_t p_idx, walberla::mesa_pd::Mat3 const & v) { ps_->setInvInertiaBFIncludingVirtual(p_idx, v);}
+   
+   walberla::mesa_pd::Vec3 const & getOldAngularAcceleration(const size_t p_idx) const {return ps_->getOldAngularAcceleration(p_idx);}
+   walberla::mesa_pd::Vec3& getOldAngularAccelerationRef(const size_t p_idx) {return ps_->getOldAngularAccelerationRef(p_idx);}
+   void setOldAngularAcceleration(const size_t p_idx, walberla::mesa_pd::Vec3 const & v) { ps_->setOldAngularAcceleration(p_idx, v);}
+   
    int64_t const & getClusterID(const size_t p_idx) const {return ps_->getClusterID(p_idx);}
    int64_t& getClusterIDRef(const size_t p_idx) {return ps_->getClusterIDRef(p_idx);}
    void setClusterID(const size_t p_idx, int64_t const & v) { ps_->setClusterID(p_idx, v);}
@@ -337,6 +365,34 @@ public:
    void setOldHydrodynamicTorque(const size_t /*p_idx*/, walberla::mesa_pd::Vec3 const & v) { oldHydrodynamicTorque_ = v;}
    walberla::mesa_pd::Vec3& getOldHydrodynamicTorqueRef(const size_t /*p_idx*/) {return oldHydrodynamicTorque_;}
    
+   walberla::real_t const & getVirtualMass(const size_t /*p_idx*/) const {return virtualMass_;}
+   void setVirtualMass(const size_t /*p_idx*/, walberla::real_t const & v) { virtualMass_ = v;}
+   walberla::real_t& getVirtualMassRef(const size_t /*p_idx*/) {return virtualMass_;}
+   
+   walberla::real_t const & getInvMassIncludingVirtual(const size_t /*p_idx*/) const {return invMassIncludingVirtual_;}
+   void setInvMassIncludingVirtual(const size_t /*p_idx*/, walberla::real_t const & v) { invMassIncludingVirtual_ = v;}
+   walberla::real_t& getInvMassIncludingVirtualRef(const size_t /*p_idx*/) {return invMassIncludingVirtual_;}
+   
+   walberla::mesa_pd::Vec3 const & getOldLinearAcceleration(const size_t /*p_idx*/) const {return oldLinearAcceleration_;}
+   void setOldLinearAcceleration(const size_t /*p_idx*/, walberla::mesa_pd::Vec3 const & v) { oldLinearAcceleration_ = v;}
+   walberla::mesa_pd::Vec3& getOldLinearAccelerationRef(const size_t /*p_idx*/) {return oldLinearAcceleration_;}
+   
+   walberla::mesa_pd::Mat3 const & getInvInertiaBF(const size_t /*p_idx*/) const {return invInertiaBF_;}
+   void setInvInertiaBF(const size_t /*p_idx*/, walberla::mesa_pd::Mat3 const & v) { invInertiaBF_ = v;}
+   walberla::mesa_pd::Mat3& getInvInertiaBFRef(const size_t /*p_idx*/) {return invInertiaBF_;}
+   
+   walberla::mesa_pd::Mat3 const & getVirtualInertiaBF(const size_t /*p_idx*/) const {return virtualInertiaBF_;}
+   void setVirtualInertiaBF(const size_t /*p_idx*/, walberla::mesa_pd::Mat3 const & v) { virtualInertiaBF_ = v;}
+   walberla::mesa_pd::Mat3& getVirtualInertiaBFRef(const size_t /*p_idx*/) {return virtualInertiaBF_;}
+   
+   walberla::mesa_pd::Mat3 const & getInvInertiaBFIncludingVirtual(const size_t /*p_idx*/) const {return invInertiaBFIncludingVirtual_;}
+   void setInvInertiaBFIncludingVirtual(const size_t /*p_idx*/, walberla::mesa_pd::Mat3 const & v) { invInertiaBFIncludingVirtual_ = v;}
+   walberla::mesa_pd::Mat3& getInvInertiaBFIncludingVirtualRef(const size_t /*p_idx*/) {return invInertiaBFIncludingVirtual_;}
+   
+   walberla::mesa_pd::Vec3 const & getOldAngularAcceleration(const size_t /*p_idx*/) const {return oldAngularAcceleration_;}
+   void setOldAngularAcceleration(const size_t /*p_idx*/, walberla::mesa_pd::Vec3 const & v) { oldAngularAcceleration_ = v;}
+   walberla::mesa_pd::Vec3& getOldAngularAccelerationRef(const size_t /*p_idx*/) {return oldAngularAcceleration_;}
+   
    int64_t const & getClusterID(const size_t /*p_idx*/) const {return clusterID_;}
    void setClusterID(const size_t /*p_idx*/, int64_t const & v) { clusterID_ = v;}
    int64_t& getClusterIDRef(const size_t /*p_idx*/) {return clusterID_;}
@@ -389,6 +445,13 @@ private:
    walberla::mesa_pd::Vec3 hydrodynamicTorque_;
    walberla::mesa_pd::Vec3 oldHydrodynamicForce_;
    walberla::mesa_pd::Vec3 oldHydrodynamicTorque_;
+   walberla::real_t virtualMass_;
+   walberla::real_t invMassIncludingVirtual_;
+   walberla::mesa_pd::Vec3 oldLinearAcceleration_;
+   walberla::mesa_pd::Mat3 invInertiaBF_;
+   walberla::mesa_pd::Mat3 virtualInertiaBF_;
+   walberla::mesa_pd::Mat3 invInertiaBFIncludingVirtual_;
+   walberla::mesa_pd::Vec3 oldAngularAcceleration_;
    int64_t clusterID_;
    int64_t segmentID_;
    std::unordered_set<walberla::mpi::MPIRank> neighborState_;
diff --git a/src/mesa_pd/data/ParticleAccessorWithShape.h b/src/mesa_pd/data/ParticleAccessorWithShape.h
index 7f3413c12..8c884d370 100644
--- a/src/mesa_pd/data/ParticleAccessorWithShape.h
+++ b/src/mesa_pd/data/ParticleAccessorWithShape.h
@@ -37,7 +37,10 @@ public:
    {}
 
    const auto& getInvMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvMass();}
+   const auto& getMass(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getMass();}
    const auto& getInvInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInvInertiaBF();}
+   const auto& getInertiaBF(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getInertiaBF();}
+   const auto getVolume(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)]->getVolume();}
 
    data::BaseShape* getShape(const size_t p_idx) const {return ss_->shapes[ps_->getShapeIDRef(p_idx)].get();}
 private:
diff --git a/src/mesa_pd/data/ParticleStorage.h b/src/mesa_pd/data/ParticleStorage.h
index 81d628681..4d64d3405 100644
--- a/src/mesa_pd/data/ParticleStorage.h
+++ b/src/mesa_pd/data/ParticleStorage.h
@@ -100,6 +100,13 @@ public:
       using hydrodynamicTorque_type = walberla::mesa_pd::Vec3;
       using oldHydrodynamicForce_type = walberla::mesa_pd::Vec3;
       using oldHydrodynamicTorque_type = walberla::mesa_pd::Vec3;
+      using virtualMass_type = walberla::real_t;
+      using invMassIncludingVirtual_type = walberla::real_t;
+      using oldLinearAcceleration_type = walberla::mesa_pd::Vec3;
+      using invInertiaBF_type = walberla::mesa_pd::Mat3;
+      using virtualInertiaBF_type = walberla::mesa_pd::Mat3;
+      using invInertiaBFIncludingVirtual_type = walberla::mesa_pd::Mat3;
+      using oldAngularAcceleration_type = walberla::mesa_pd::Vec3;
       using clusterID_type = int64_t;
       using segmentID_type = int64_t;
       using neighborState_type = std::unordered_set<walberla::mpi::MPIRank>;
@@ -221,6 +228,34 @@ public:
       oldHydrodynamicTorque_type& getOldHydrodynamicTorqueRef() {return storage_.getOldHydrodynamicTorqueRef(i_);}
       void setOldHydrodynamicTorque(oldHydrodynamicTorque_type const & v) { storage_.setOldHydrodynamicTorque(i_, v);}
       
+      virtualMass_type const & getVirtualMass() const {return storage_.getVirtualMass(i_);}
+      virtualMass_type& getVirtualMassRef() {return storage_.getVirtualMassRef(i_);}
+      void setVirtualMass(virtualMass_type const & v) { storage_.setVirtualMass(i_, v);}
+      
+      invMassIncludingVirtual_type const & getInvMassIncludingVirtual() const {return storage_.getInvMassIncludingVirtual(i_);}
+      invMassIncludingVirtual_type& getInvMassIncludingVirtualRef() {return storage_.getInvMassIncludingVirtualRef(i_);}
+      void setInvMassIncludingVirtual(invMassIncludingVirtual_type const & v) { storage_.setInvMassIncludingVirtual(i_, v);}
+      
+      oldLinearAcceleration_type const & getOldLinearAcceleration() const {return storage_.getOldLinearAcceleration(i_);}
+      oldLinearAcceleration_type& getOldLinearAccelerationRef() {return storage_.getOldLinearAccelerationRef(i_);}
+      void setOldLinearAcceleration(oldLinearAcceleration_type const & v) { storage_.setOldLinearAcceleration(i_, v);}
+      
+      invInertiaBF_type const & getInvInertiaBF() const {return storage_.getInvInertiaBF(i_);}
+      invInertiaBF_type& getInvInertiaBFRef() {return storage_.getInvInertiaBFRef(i_);}
+      void setInvInertiaBF(invInertiaBF_type const & v) { storage_.setInvInertiaBF(i_, v);}
+      
+      virtualInertiaBF_type const & getVirtualInertiaBF() const {return storage_.getVirtualInertiaBF(i_);}
+      virtualInertiaBF_type& getVirtualInertiaBFRef() {return storage_.getVirtualInertiaBFRef(i_);}
+      void setVirtualInertiaBF(virtualInertiaBF_type const & v) { storage_.setVirtualInertiaBF(i_, v);}
+      
+      invInertiaBFIncludingVirtual_type const & getInvInertiaBFIncludingVirtual() const {return storage_.getInvInertiaBFIncludingVirtual(i_);}
+      invInertiaBFIncludingVirtual_type& getInvInertiaBFIncludingVirtualRef() {return storage_.getInvInertiaBFIncludingVirtualRef(i_);}
+      void setInvInertiaBFIncludingVirtual(invInertiaBFIncludingVirtual_type const & v) { storage_.setInvInertiaBFIncludingVirtual(i_, v);}
+      
+      oldAngularAcceleration_type const & getOldAngularAcceleration() const {return storage_.getOldAngularAcceleration(i_);}
+      oldAngularAcceleration_type& getOldAngularAccelerationRef() {return storage_.getOldAngularAccelerationRef(i_);}
+      void setOldAngularAcceleration(oldAngularAcceleration_type const & v) { storage_.setOldAngularAcceleration(i_, v);}
+      
       clusterID_type const & getClusterID() const {return storage_.getClusterID(i_);}
       clusterID_type& getClusterIDRef() {return storage_.getClusterIDRef(i_);}
       void setClusterID(clusterID_type const & v) { storage_.setClusterID(i_, v);}
@@ -322,6 +357,13 @@ public:
    using hydrodynamicTorque_type = walberla::mesa_pd::Vec3;
    using oldHydrodynamicForce_type = walberla::mesa_pd::Vec3;
    using oldHydrodynamicTorque_type = walberla::mesa_pd::Vec3;
+   using virtualMass_type = walberla::real_t;
+   using invMassIncludingVirtual_type = walberla::real_t;
+   using oldLinearAcceleration_type = walberla::mesa_pd::Vec3;
+   using invInertiaBF_type = walberla::mesa_pd::Mat3;
+   using virtualInertiaBF_type = walberla::mesa_pd::Mat3;
+   using invInertiaBFIncludingVirtual_type = walberla::mesa_pd::Mat3;
+   using oldAngularAcceleration_type = walberla::mesa_pd::Vec3;
    using clusterID_type = int64_t;
    using segmentID_type = int64_t;
    using neighborState_type = std::unordered_set<walberla::mpi::MPIRank>;
@@ -443,6 +485,34 @@ public:
    oldHydrodynamicTorque_type& getOldHydrodynamicTorqueRef(const size_t idx) {return oldHydrodynamicTorque_[idx];}
    void setOldHydrodynamicTorque(const size_t idx, oldHydrodynamicTorque_type const & v) { oldHydrodynamicTorque_[idx] = v; }
    
+   virtualMass_type const & getVirtualMass(const size_t idx) const {return virtualMass_[idx];}
+   virtualMass_type& getVirtualMassRef(const size_t idx) {return virtualMass_[idx];}
+   void setVirtualMass(const size_t idx, virtualMass_type const & v) { virtualMass_[idx] = v; }
+   
+   invMassIncludingVirtual_type const & getInvMassIncludingVirtual(const size_t idx) const {return invMassIncludingVirtual_[idx];}
+   invMassIncludingVirtual_type& getInvMassIncludingVirtualRef(const size_t idx) {return invMassIncludingVirtual_[idx];}
+   void setInvMassIncludingVirtual(const size_t idx, invMassIncludingVirtual_type const & v) { invMassIncludingVirtual_[idx] = v; }
+   
+   oldLinearAcceleration_type const & getOldLinearAcceleration(const size_t idx) const {return oldLinearAcceleration_[idx];}
+   oldLinearAcceleration_type& getOldLinearAccelerationRef(const size_t idx) {return oldLinearAcceleration_[idx];}
+   void setOldLinearAcceleration(const size_t idx, oldLinearAcceleration_type const & v) { oldLinearAcceleration_[idx] = v; }
+   
+   invInertiaBF_type const & getInvInertiaBF(const size_t idx) const {return invInertiaBF_[idx];}
+   invInertiaBF_type& getInvInertiaBFRef(const size_t idx) {return invInertiaBF_[idx];}
+   void setInvInertiaBF(const size_t idx, invInertiaBF_type const & v) { invInertiaBF_[idx] = v; }
+   
+   virtualInertiaBF_type const & getVirtualInertiaBF(const size_t idx) const {return virtualInertiaBF_[idx];}
+   virtualInertiaBF_type& getVirtualInertiaBFRef(const size_t idx) {return virtualInertiaBF_[idx];}
+   void setVirtualInertiaBF(const size_t idx, virtualInertiaBF_type const & v) { virtualInertiaBF_[idx] = v; }
+   
+   invInertiaBFIncludingVirtual_type const & getInvInertiaBFIncludingVirtual(const size_t idx) const {return invInertiaBFIncludingVirtual_[idx];}
+   invInertiaBFIncludingVirtual_type& getInvInertiaBFIncludingVirtualRef(const size_t idx) {return invInertiaBFIncludingVirtual_[idx];}
+   void setInvInertiaBFIncludingVirtual(const size_t idx, invInertiaBFIncludingVirtual_type const & v) { invInertiaBFIncludingVirtual_[idx] = v; }
+   
+   oldAngularAcceleration_type const & getOldAngularAcceleration(const size_t idx) const {return oldAngularAcceleration_[idx];}
+   oldAngularAcceleration_type& getOldAngularAccelerationRef(const size_t idx) {return oldAngularAcceleration_[idx];}
+   void setOldAngularAcceleration(const size_t idx, oldAngularAcceleration_type const & v) { oldAngularAcceleration_[idx] = v; }
+   
    clusterID_type const & getClusterID(const size_t idx) const {return clusterID_[idx];}
    clusterID_type& getClusterIDRef(const size_t idx) {return clusterID_[idx];}
    void setClusterID(const size_t idx, clusterID_type const & v) { clusterID_[idx] = v; }
@@ -575,6 +645,13 @@ public:
    std::vector<hydrodynamicTorque_type> hydrodynamicTorque_ {};
    std::vector<oldHydrodynamicForce_type> oldHydrodynamicForce_ {};
    std::vector<oldHydrodynamicTorque_type> oldHydrodynamicTorque_ {};
+   std::vector<virtualMass_type> virtualMass_ {};
+   std::vector<invMassIncludingVirtual_type> invMassIncludingVirtual_ {};
+   std::vector<oldLinearAcceleration_type> oldLinearAcceleration_ {};
+   std::vector<invInertiaBF_type> invInertiaBF_ {};
+   std::vector<virtualInertiaBF_type> virtualInertiaBF_ {};
+   std::vector<invInertiaBFIncludingVirtual_type> invInertiaBFIncludingVirtual_ {};
+   std::vector<oldAngularAcceleration_type> oldAngularAcceleration_ {};
    std::vector<clusterID_type> clusterID_ {};
    std::vector<segmentID_type> segmentID_ {};
    std::vector<neighborState_type> neighborState_ {};
@@ -616,6 +693,13 @@ ParticleStorage::Particle& ParticleStorage::Particle::operator=(const ParticleSt
    getHydrodynamicTorqueRef() = rhs.getHydrodynamicTorque();
    getOldHydrodynamicForceRef() = rhs.getOldHydrodynamicForce();
    getOldHydrodynamicTorqueRef() = rhs.getOldHydrodynamicTorque();
+   getVirtualMassRef() = rhs.getVirtualMass();
+   getInvMassIncludingVirtualRef() = rhs.getInvMassIncludingVirtual();
+   getOldLinearAccelerationRef() = rhs.getOldLinearAcceleration();
+   getInvInertiaBFRef() = rhs.getInvInertiaBF();
+   getVirtualInertiaBFRef() = rhs.getVirtualInertiaBF();
+   getInvInertiaBFIncludingVirtualRef() = rhs.getInvInertiaBFIncludingVirtual();
+   getOldAngularAccelerationRef() = rhs.getOldAngularAcceleration();
    getClusterIDRef() = rhs.getClusterID();
    getSegmentIDRef() = rhs.getSegmentID();
    getNeighborStateRef() = rhs.getNeighborState();
@@ -654,6 +738,13 @@ ParticleStorage::Particle& ParticleStorage::Particle::operator=(ParticleStorage:
    getHydrodynamicTorqueRef() = std::move(rhs.getHydrodynamicTorqueRef());
    getOldHydrodynamicForceRef() = std::move(rhs.getOldHydrodynamicForceRef());
    getOldHydrodynamicTorqueRef() = std::move(rhs.getOldHydrodynamicTorqueRef());
+   getVirtualMassRef() = std::move(rhs.getVirtualMassRef());
+   getInvMassIncludingVirtualRef() = std::move(rhs.getInvMassIncludingVirtualRef());
+   getOldLinearAccelerationRef() = std::move(rhs.getOldLinearAccelerationRef());
+   getInvInertiaBFRef() = std::move(rhs.getInvInertiaBFRef());
+   getVirtualInertiaBFRef() = std::move(rhs.getVirtualInertiaBFRef());
+   getInvInertiaBFIncludingVirtualRef() = std::move(rhs.getInvInertiaBFIncludingVirtualRef());
+   getOldAngularAccelerationRef() = std::move(rhs.getOldAngularAccelerationRef());
    getClusterIDRef() = std::move(rhs.getClusterIDRef());
    getSegmentIDRef() = std::move(rhs.getSegmentIDRef());
    getNeighborStateRef() = std::move(rhs.getNeighborStateRef());
@@ -693,6 +784,13 @@ void swap(ParticleStorage::Particle lhs, ParticleStorage::Particle rhs)
    std::swap(lhs.getHydrodynamicTorqueRef(), rhs.getHydrodynamicTorqueRef());
    std::swap(lhs.getOldHydrodynamicForceRef(), rhs.getOldHydrodynamicForceRef());
    std::swap(lhs.getOldHydrodynamicTorqueRef(), rhs.getOldHydrodynamicTorqueRef());
+   std::swap(lhs.getVirtualMassRef(), rhs.getVirtualMassRef());
+   std::swap(lhs.getInvMassIncludingVirtualRef(), rhs.getInvMassIncludingVirtualRef());
+   std::swap(lhs.getOldLinearAccelerationRef(), rhs.getOldLinearAccelerationRef());
+   std::swap(lhs.getInvInertiaBFRef(), rhs.getInvInertiaBFRef());
+   std::swap(lhs.getVirtualInertiaBFRef(), rhs.getVirtualInertiaBFRef());
+   std::swap(lhs.getInvInertiaBFIncludingVirtualRef(), rhs.getInvInertiaBFIncludingVirtualRef());
+   std::swap(lhs.getOldAngularAccelerationRef(), rhs.getOldAngularAccelerationRef());
    std::swap(lhs.getClusterIDRef(), rhs.getClusterIDRef());
    std::swap(lhs.getSegmentIDRef(), rhs.getSegmentIDRef());
    std::swap(lhs.getNeighborStateRef(), rhs.getNeighborStateRef());
@@ -732,6 +830,13 @@ std::ostream& operator<<( std::ostream& os, const ParticleStorage::Particle& p )
          "hydrodynamicTorque  : " << p.getHydrodynamicTorque() << "\n" <<
          "oldHydrodynamicForce: " << p.getOldHydrodynamicForce() << "\n" <<
          "oldHydrodynamicTorque: " << p.getOldHydrodynamicTorque() << "\n" <<
+         "virtualMass         : " << p.getVirtualMass() << "\n" <<
+         "invMassIncludingVirtual: " << p.getInvMassIncludingVirtual() << "\n" <<
+         "oldLinearAcceleration: " << p.getOldLinearAcceleration() << "\n" <<
+         "invInertiaBF        : " << p.getInvInertiaBF() << "\n" <<
+         "virtualInertiaBF    : " << p.getVirtualInertiaBF() << "\n" <<
+         "invInertiaBFIncludingVirtual: " << p.getInvInertiaBFIncludingVirtual() << "\n" <<
+         "oldAngularAcceleration: " << p.getOldAngularAcceleration() << "\n" <<
          "clusterID           : " << p.getClusterID() << "\n" <<
          "segmentID           : " << p.getSegmentID() << "\n" <<
          "neighborState       : " << p.getNeighborState() << "\n" <<
@@ -841,6 +946,13 @@ inline ParticleStorage::iterator ParticleStorage::create(const id_t& uid)
    hydrodynamicTorque_.emplace_back(real_t(0));
    oldHydrodynamicForce_.emplace_back(real_t(0));
    oldHydrodynamicTorque_.emplace_back(real_t(0));
+   virtualMass_.emplace_back(real_t(0));
+   invMassIncludingVirtual_.emplace_back(real_t(0));
+   oldLinearAcceleration_.emplace_back(real_t(0));
+   invInertiaBF_.emplace_back(real_t(0));
+   virtualInertiaBF_.emplace_back(real_t(0));
+   invInertiaBFIncludingVirtual_.emplace_back(real_t(0));
+   oldAngularAcceleration_.emplace_back(real_t(0));
    clusterID_.emplace_back(-1);
    segmentID_.emplace_back(-1);
    neighborState_.emplace_back();
@@ -905,6 +1017,13 @@ inline ParticleStorage::iterator ParticleStorage::erase(iterator& it)
    hydrodynamicTorque_.pop_back();
    oldHydrodynamicForce_.pop_back();
    oldHydrodynamicTorque_.pop_back();
+   virtualMass_.pop_back();
+   invMassIncludingVirtual_.pop_back();
+   oldLinearAcceleration_.pop_back();
+   invInertiaBF_.pop_back();
+   virtualInertiaBF_.pop_back();
+   invInertiaBFIncludingVirtual_.pop_back();
+   oldAngularAcceleration_.pop_back();
    clusterID_.pop_back();
    segmentID_.pop_back();
    neighborState_.pop_back();
@@ -956,6 +1075,13 @@ inline void ParticleStorage::reserve(const size_t size)
    hydrodynamicTorque_.reserve(size);
    oldHydrodynamicForce_.reserve(size);
    oldHydrodynamicTorque_.reserve(size);
+   virtualMass_.reserve(size);
+   invMassIncludingVirtual_.reserve(size);
+   oldLinearAcceleration_.reserve(size);
+   invInertiaBF_.reserve(size);
+   virtualInertiaBF_.reserve(size);
+   invInertiaBFIncludingVirtual_.reserve(size);
+   oldAngularAcceleration_.reserve(size);
    clusterID_.reserve(size);
    segmentID_.reserve(size);
    neighborState_.reserve(size);
@@ -992,6 +1118,13 @@ inline void ParticleStorage::clear()
    hydrodynamicTorque_.clear();
    oldHydrodynamicForce_.clear();
    oldHydrodynamicTorque_.clear();
+   virtualMass_.clear();
+   invMassIncludingVirtual_.clear();
+   oldLinearAcceleration_.clear();
+   invInertiaBF_.clear();
+   virtualInertiaBF_.clear();
+   invInertiaBFIncludingVirtual_.clear();
+   oldAngularAcceleration_.clear();
    clusterID_.clear();
    segmentID_.clear();
    neighborState_.clear();
@@ -1029,6 +1162,13 @@ inline size_t ParticleStorage::size() const
    //WALBERLA_ASSERT_EQUAL( uid_.size(), hydrodynamicTorque.size() );
    //WALBERLA_ASSERT_EQUAL( uid_.size(), oldHydrodynamicForce.size() );
    //WALBERLA_ASSERT_EQUAL( uid_.size(), oldHydrodynamicTorque.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), virtualMass.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), invMassIncludingVirtual.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), oldLinearAcceleration.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), invInertiaBF.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), virtualInertiaBF.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), invInertiaBFIncludingVirtual.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), oldAngularAcceleration.size() );
    //WALBERLA_ASSERT_EQUAL( uid_.size(), clusterID.size() );
    //WALBERLA_ASSERT_EQUAL( uid_.size(), segmentID.size() );
    //WALBERLA_ASSERT_EQUAL( uid_.size(), neighborState.size() );
@@ -1459,6 +1599,69 @@ public:
    walberla::mesa_pd::Vec3 const & operator()(const data::Particle& p) const {return p.getOldHydrodynamicTorque();}
 };
 ///Predicate that selects a certain property from a Particle
+class SelectParticleVirtualMass
+{
+public:
+   using return_type = walberla::real_t;
+   walberla::real_t& operator()(data::Particle& p) const {return p.getVirtualMassRef();}
+   walberla::real_t& operator()(data::Particle&& p) const {return p.getVirtualMassRef();}
+   walberla::real_t const & operator()(const data::Particle& p) const {return p.getVirtualMass();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleInvMassIncludingVirtual
+{
+public:
+   using return_type = walberla::real_t;
+   walberla::real_t& operator()(data::Particle& p) const {return p.getInvMassIncludingVirtualRef();}
+   walberla::real_t& operator()(data::Particle&& p) const {return p.getInvMassIncludingVirtualRef();}
+   walberla::real_t const & operator()(const data::Particle& p) const {return p.getInvMassIncludingVirtual();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleOldLinearAcceleration
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getOldLinearAccelerationRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getOldLinearAccelerationRef();}
+   walberla::mesa_pd::Vec3 const & operator()(const data::Particle& p) const {return p.getOldLinearAcceleration();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleInvInertiaBF
+{
+public:
+   using return_type = walberla::mesa_pd::Mat3;
+   walberla::mesa_pd::Mat3& operator()(data::Particle& p) const {return p.getInvInertiaBFRef();}
+   walberla::mesa_pd::Mat3& operator()(data::Particle&& p) const {return p.getInvInertiaBFRef();}
+   walberla::mesa_pd::Mat3 const & operator()(const data::Particle& p) const {return p.getInvInertiaBF();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleVirtualInertiaBF
+{
+public:
+   using return_type = walberla::mesa_pd::Mat3;
+   walberla::mesa_pd::Mat3& operator()(data::Particle& p) const {return p.getVirtualInertiaBFRef();}
+   walberla::mesa_pd::Mat3& operator()(data::Particle&& p) const {return p.getVirtualInertiaBFRef();}
+   walberla::mesa_pd::Mat3 const & operator()(const data::Particle& p) const {return p.getVirtualInertiaBF();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleInvInertiaBFIncludingVirtual
+{
+public:
+   using return_type = walberla::mesa_pd::Mat3;
+   walberla::mesa_pd::Mat3& operator()(data::Particle& p) const {return p.getInvInertiaBFIncludingVirtualRef();}
+   walberla::mesa_pd::Mat3& operator()(data::Particle&& p) const {return p.getInvInertiaBFIncludingVirtualRef();}
+   walberla::mesa_pd::Mat3 const & operator()(const data::Particle& p) const {return p.getInvInertiaBFIncludingVirtual();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleOldAngularAcceleration
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getOldAngularAccelerationRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getOldAngularAccelerationRef();}
+   walberla::mesa_pd::Vec3 const & operator()(const data::Particle& p) const {return p.getOldAngularAcceleration();}
+};
+///Predicate that selects a certain property from a Particle
 class SelectParticleClusterID
 {
 public:
diff --git a/src/mesa_pd/mpi/notifications/ParseMessage.h b/src/mesa_pd/mpi/notifications/ParseMessage.h
index 32ae118a5..ed3124bf2 100644
--- a/src/mesa_pd/mpi/notifications/ParseMessage.h
+++ b/src/mesa_pd/mpi/notifications/ParseMessage.h
@@ -153,6 +153,13 @@ void ParseMessage::operator()(int sender,
       pIt->setHydrodynamicTorque(objparam.hydrodynamicTorque_);
       pIt->setOldHydrodynamicForce(objparam.oldHydrodynamicForce_);
       pIt->setOldHydrodynamicTorque(objparam.oldHydrodynamicTorque_);
+      pIt->setVirtualMass(objparam.virtualMass_);
+      pIt->setInvMassIncludingVirtual(objparam.invMassIncludingVirtual_);
+      pIt->setOldLinearAcceleration(objparam.oldLinearAcceleration_);
+      pIt->setInvInertiaBF(objparam.invInertiaBF_);
+      pIt->setVirtualInertiaBF(objparam.virtualInertiaBF_);
+      pIt->setInvInertiaBFIncludingVirtual(objparam.invInertiaBFIncludingVirtual_);
+      pIt->setOldAngularAcceleration(objparam.oldAngularAcceleration_);
 
       WALBERLA_LOG_DETAIL( "Processed PARTICLE_MIGRATION_NOTIFICATION." );
 
diff --git a/src/mesa_pd/mpi/notifications/ParticleCopyNotification.h b/src/mesa_pd/mpi/notifications/ParticleCopyNotification.h
index f0944ef6d..f0f23574c 100644
--- a/src/mesa_pd/mpi/notifications/ParticleCopyNotification.h
+++ b/src/mesa_pd/mpi/notifications/ParticleCopyNotification.h
@@ -69,6 +69,13 @@ public:
       walberla::mesa_pd::Vec3 hydrodynamicTorque {real_t(0)};
       walberla::mesa_pd::Vec3 oldHydrodynamicForce {real_t(0)};
       walberla::mesa_pd::Vec3 oldHydrodynamicTorque {real_t(0)};
+      walberla::real_t virtualMass {real_t(0)};
+      walberla::real_t invMassIncludingVirtual {real_t(0)};
+      walberla::mesa_pd::Vec3 oldLinearAcceleration {real_t(0)};
+      walberla::mesa_pd::Mat3 invInertiaBF {real_t(0)};
+      walberla::mesa_pd::Mat3 virtualInertiaBF {real_t(0)};
+      walberla::mesa_pd::Mat3 invInertiaBFIncludingVirtual {real_t(0)};
+      walberla::mesa_pd::Vec3 oldAngularAcceleration {real_t(0)};
       int64_t clusterID {-1};
       int64_t segmentID {-1};
    };
@@ -103,6 +110,13 @@ inline data::ParticleStorage::iterator createNewParticle(data::ParticleStorage&
    pIt->setHydrodynamicTorque(data.hydrodynamicTorque);
    pIt->setOldHydrodynamicForce(data.oldHydrodynamicForce);
    pIt->setOldHydrodynamicTorque(data.oldHydrodynamicTorque);
+   pIt->setVirtualMass(data.virtualMass);
+   pIt->setInvMassIncludingVirtual(data.invMassIncludingVirtual);
+   pIt->setOldLinearAcceleration(data.oldLinearAcceleration);
+   pIt->setInvInertiaBF(data.invInertiaBF);
+   pIt->setVirtualInertiaBF(data.virtualInertiaBF);
+   pIt->setInvInertiaBFIncludingVirtual(data.invInertiaBFIncludingVirtual);
+   pIt->setOldAngularAcceleration(data.oldAngularAcceleration);
    pIt->setClusterID(data.clusterID);
    pIt->setSegmentID(data.segmentID);
    return pIt;
@@ -152,6 +166,13 @@ mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, cons
    buf << obj.particle_.getHydrodynamicTorque();
    buf << obj.particle_.getOldHydrodynamicForce();
    buf << obj.particle_.getOldHydrodynamicTorque();
+   buf << obj.particle_.getVirtualMass();
+   buf << obj.particle_.getInvMassIncludingVirtual();
+   buf << obj.particle_.getOldLinearAcceleration();
+   buf << obj.particle_.getInvInertiaBF();
+   buf << obj.particle_.getVirtualInertiaBF();
+   buf << obj.particle_.getInvInertiaBFIncludingVirtual();
+   buf << obj.particle_.getOldAngularAcceleration();
    buf << obj.particle_.getClusterID();
    buf << obj.particle_.getSegmentID();
    return buf;
@@ -182,6 +203,13 @@ mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd:
    buf >> objparam.hydrodynamicTorque;
    buf >> objparam.oldHydrodynamicForce;
    buf >> objparam.oldHydrodynamicTorque;
+   buf >> objparam.virtualMass;
+   buf >> objparam.invMassIncludingVirtual;
+   buf >> objparam.oldLinearAcceleration;
+   buf >> objparam.invInertiaBF;
+   buf >> objparam.virtualInertiaBF;
+   buf >> objparam.invInertiaBFIncludingVirtual;
+   buf >> objparam.oldAngularAcceleration;
    buf >> objparam.clusterID;
    buf >> objparam.segmentID;
    return buf;
diff --git a/src/mesa_pd/mpi/notifications/ParticleMigrationNotification.h b/src/mesa_pd/mpi/notifications/ParticleMigrationNotification.h
index 847368ed4..d7cf2b54d 100644
--- a/src/mesa_pd/mpi/notifications/ParticleMigrationNotification.h
+++ b/src/mesa_pd/mpi/notifications/ParticleMigrationNotification.h
@@ -52,6 +52,13 @@ public:
       walberla::mesa_pd::Vec3 hydrodynamicTorque_ {real_t(0)};
       walberla::mesa_pd::Vec3 oldHydrodynamicForce_ {real_t(0)};
       walberla::mesa_pd::Vec3 oldHydrodynamicTorque_ {real_t(0)};
+      walberla::real_t virtualMass_ {real_t(0)};
+      walberla::real_t invMassIncludingVirtual_ {real_t(0)};
+      walberla::mesa_pd::Vec3 oldLinearAcceleration_ {real_t(0)};
+      walberla::mesa_pd::Mat3 invInertiaBF_ {real_t(0)};
+      walberla::mesa_pd::Mat3 virtualInertiaBF_ {real_t(0)};
+      walberla::mesa_pd::Mat3 invInertiaBFIncludingVirtual_ {real_t(0)};
+      walberla::mesa_pd::Vec3 oldAngularAcceleration_ {real_t(0)};
    };
 
    inline explicit ParticleMigrationNotification( const data::Particle& particle ) : particle_(particle) {}
@@ -89,6 +96,13 @@ mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, cons
    buf << obj.particle_.getHydrodynamicTorque();
    buf << obj.particle_.getOldHydrodynamicForce();
    buf << obj.particle_.getOldHydrodynamicTorque();
+   buf << obj.particle_.getVirtualMass();
+   buf << obj.particle_.getInvMassIncludingVirtual();
+   buf << obj.particle_.getOldLinearAcceleration();
+   buf << obj.particle_.getInvInertiaBF();
+   buf << obj.particle_.getVirtualInertiaBF();
+   buf << obj.particle_.getInvInertiaBFIncludingVirtual();
+   buf << obj.particle_.getOldAngularAcceleration();
    return buf;
 }
 
@@ -104,6 +118,13 @@ mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd:
    buf >> objparam.hydrodynamicTorque_;
    buf >> objparam.oldHydrodynamicForce_;
    buf >> objparam.oldHydrodynamicTorque_;
+   buf >> objparam.virtualMass_;
+   buf >> objparam.invMassIncludingVirtual_;
+   buf >> objparam.oldLinearAcceleration_;
+   buf >> objparam.invInertiaBF_;
+   buf >> objparam.virtualInertiaBF_;
+   buf >> objparam.invInertiaBFIncludingVirtual_;
+   buf >> objparam.oldAngularAcceleration_;
    return buf;
 }
 
diff --git a/tests/lbm_mesapd_coupling/CMakeLists.txt b/tests/lbm_mesapd_coupling/CMakeLists.txt
index 90cf13471..eeb208530 100644
--- a/tests/lbm_mesapd_coupling/CMakeLists.txt
+++ b/tests/lbm_mesapd_coupling/CMakeLists.txt
@@ -35,6 +35,9 @@ waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_UpdateParticleMapping PROCES
 waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_UTIL_LubricationCorrection FILES utility/LubricationCorrection.cpp DEPENDS mesa_pd lbm_mesapd_coupling )
 waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_UTIL_LubricationCorrection PROCESSES 1 )
 
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_UTIL_VirtualMass FILES utility/VirtualMass.cpp DEPENDS mesa_pd lbm_mesapd_coupling )
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_UTIL_VirtualMass PROCESSES 1 )
+
 waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_UTIL_InspectionProbe FILES utility/InspectionProbe.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field )
 waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_UTIL_InspectionProbe PROCESSES 1 )
 
diff --git a/tests/lbm_mesapd_coupling/utility/VirtualMass.cpp b/tests/lbm_mesapd_coupling/utility/VirtualMass.cpp
new file mode 100644
index 000000000..4337d0be7
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/utility/VirtualMass.cpp
@@ -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 VirtualMass.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Lukas Werner <lks.werner@fau.de>
+//! \brief Evaluates the algorithms behind the virtual mass kernels by comparing with reference values.
+//
+//======================================================================================================================
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/math/all.h"
+
+#include "lbm_mesapd_coupling/utility/virtualmass/AddVirtualForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/virtualmass/InitializeVirtualMassKernel.h"
+#include "lbm_mesapd_coupling/utility/virtualmass/ParticleAccessorWithShapeVirtualMassWrapper.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+
+namespace virtual_mass_test
+{
+using namespace walberla;
+
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor = mesa_pd::data::ParticleAccessorWithShape;
+   ParticleAccessor accessor(ps, ss);
+
+   real_t sphereRadius = real_t(1);
+   real_t sphereDensity = real_t(1);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>(sphereRadius);
+   ss->shapes[sphereShape]->updateMassAndInertia(sphereDensity);
+
+   mesa_pd::data::Particle&& p1 = *ps->create();
+   p1.setShapeID(sphereShape);
+   auto idx = p1.getIdx();
+
+   using VirtualMass_ParticleAccessor_T = lbm_mesapd_coupling::ParticleAccessorWithShapeVirtualMassWrapper<ParticleAccessor>;
+   auto virtualMassAccessor = walberla::make_shared<VirtualMass_ParticleAccessor_T>(ps, ss);
+
+   auto C_v = real_t(0.5);
+   auto C_v_omega = real_t(0.5);
+   auto fluidDensity = real_t(0.5);
+
+   lbm_mesapd_coupling::InitializeVirtualMassKernel virtualMassInit;
+   virtualMassInit(idx, accessor, C_v, C_v_omega, fluidDensity);
+
+   const real_t sphereVirtualMass = C_v * fluidDensity * accessor.getVolume(idx);
+
+   WALBERLA_CHECK_FLOAT_EQUAL(sphereVirtualMass, accessor.getVirtualMass(idx));
+   WALBERLA_CHECK_FLOAT_EQUAL(real_t(1.) / (accessor.getMass(idx) + sphereVirtualMass),
+                              virtualMassAccessor->getInvMass(idx));
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getMass(idx) + sphereVirtualMass,
+                              virtualMassAccessor->getMass(idx));
+
+   const real_t sphereAngularVirtualMass = C_v_omega * fluidDensity * accessor.getVolume(idx);
+   const mesa_pd::Mat3 sphereVirtualInertiaBF = accessor.getInertiaBF(idx) * accessor.getInvMass(idx) * sphereAngularVirtualMass;
+
+   WALBERLA_CHECK_FLOAT_EQUAL(sphereVirtualInertiaBF, accessor.getVirtualInertiaBF(idx));
+   WALBERLA_CHECK_FLOAT_EQUAL((accessor.getInertiaBF(idx) + sphereVirtualInertiaBF).getInverse(),
+                              virtualMassAccessor->getInvInertiaBF(idx));
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getInertiaBF(idx) + sphereVirtualInertiaBF,
+                              virtualMassAccessor->getInertiaBF(idx));
+
+   lbm_mesapd_coupling::AddVirtualForceTorqueKernel addVirtualForceTorque(ps);
+
+   accessor.setForce(idx, Vector3(real_t(1)));
+   auto oldForce = accessor.getForce(idx);
+   addVirtualForceTorque(idx, accessor);
+   WALBERLA_CHECK_FLOAT_EQUAL(oldForce, accessor.getForce(idx));
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getOldLinearAcceleration(idx),
+                              Vector3<real_t>(real_t(1) / (accessor.getMass(idx) + accessor.getVirtualMass(idx))));
+
+   Vector3 oldAcceleration(real_t(2));
+   accessor.setOldLinearAcceleration(idx, oldAcceleration);
+   accessor.setOldAngularAcceleration(idx, oldAcceleration);
+   oldForce = Vector3(real_t(2));
+   accessor.setForce(idx, oldForce);
+   Vector3 oldTorque(real_t(2));
+   accessor.setTorque(idx, oldTorque);
+   addVirtualForceTorque(idx, accessor);
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getForce(idx), oldForce + sphereVirtualMass*oldAcceleration);
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getForce(idx),
+                              accessor.getOldLinearAcceleration(idx)*(accessor.getMass(idx)+accessor.getVirtualMass(idx)));
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getTorque(idx), oldTorque + sphereVirtualInertiaBF*oldAcceleration);
+   WALBERLA_CHECK_FLOAT_EQUAL(accessor.getTorque(idx),
+                              (accessor.getInertiaBF(idx)+accessor.getVirtualInertiaBF(idx))*accessor.getOldAngularAcceleration(idx));
+
+   return 0;
+}
+
+} // namespace virtual_mass_test
+
+int main( int argc, char **argv ){
+   virtual_mass_test::main(argc, argv);
+}
-- 
GitLab