diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt
index a59d3bc1a5d23a3f52b827507d756c5b2117a877..66e99fb276c27e6294b172e3abc8f60c699555a7 100644
--- a/apps/benchmarks/CMakeLists.txt
+++ b/apps/benchmarks/CMakeLists.txt
@@ -4,6 +4,7 @@ add_subdirectory( DEM )
 add_subdirectory( FieldCommunication )
 add_subdirectory( MeshDistance )
 add_subdirectory( CouetteFlow )
+add_subdirectory( FluidParticleCoupling )
 add_subdirectory( ForcesOnSphereNearPlaneInShearFlow )
 add_subdirectory( GranularGas )
 add_subdirectory( LennardJones )
diff --git a/apps/benchmarks/FluidParticleCoupling/CMakeLists.txt b/apps/benchmarks/FluidParticleCoupling/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a22871bb3eb9db47c10b47ca3a08f7812d5d5068
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/CMakeLists.txt
@@ -0,0 +1,33 @@
+waLBerla_link_files_to_builddir( "*.dat" )
+
+waLBerla_python_file_generates(GeneratedLBM.py
+      GeneratedLBM.cpp GeneratedLBM.h
+      )
+
+waLBerla_python_file_generates(GeneratedLBMWithForce.py
+      GeneratedLBMWithForce.cpp GeneratedLBMWithForce.h
+      )
+
+waLBerla_add_executable ( NAME SphereWallCollision FILES SphereWallCollision.cpp GeneratedLBM.py
+                          DEPENDS blockforest boundary core domain_decomposition field lbm lbm_mesapd_coupling mesa_pd postprocessing timeloop vtk )
+
+waLBerla_add_executable ( NAME SettlingSphereTenCate FILES SettlingSphereInBox.cpp GeneratedLBM.py
+                          DEPENDS blockforest boundary core domain_decomposition field lbm lbm_mesapd_coupling mesa_pd postprocessing timeloop vtk )
+
+waLBerla_add_executable ( NAME SphereMovingWithPrescribedVelocity FILES SphereMovingWithPrescribedVelocity.cpp GeneratedLBM.py
+                          DEPENDS blockforest boundary core domain_decomposition field lbm mesa_pd lbm_mesapd_coupling postprocessing timeloop vtk )
+
+waLBerla_add_executable ( NAME LubricationForceEvaluation FILES LubricationForceEvaluation.cpp GeneratedLBM.py
+                          DEPENDS blockforest boundary core domain_decomposition field lbm lbm_mesapd_coupling mesa_pd postprocessing timeloop vtk )
+
+waLBerla_add_executable ( NAME DragForceSphere FILES DragForceSphere.cpp GeneratedLBMWithForce.py
+                          DEPENDS blockforest boundary core domain_decomposition field lbm lbm_mesapd_coupling mesa_pd postprocessing timeloop vtk )
+
+waLBerla_add_executable ( NAME ForcesOnSphereNearPlane FILES ForcesOnSphereNearPlane.cpp GeneratedLBM.py
+                          DEPENDS blockforest boundary core domain_decomposition field lbm lbm_mesapd_coupling mesa_pd postprocessing timeloop vtk )
+
+waLBerla_add_executable ( NAME ObliqueDryCollision FILES ObliqueDryCollision.cpp
+                          DEPENDS blockforest core mesa_pd postprocessing )
+
+waLBerla_add_executable ( NAME ObliqueWetCollision FILES ObliqueWetCollision.cpp GeneratedLBM.py
+                          DEPENDS blockforest boundary core domain_decomposition field lbm lbm_mesapd_coupling mesa_pd postprocessing timeloop vtk )
\ No newline at end of file
diff --git a/apps/benchmarks/FluidParticleCoupling/DragForceSphere.cpp b/apps/benchmarks/FluidParticleCoupling/DragForceSphere.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2f269c5d944d021820fe9f63af86ff5c3671798d
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/DragForceSphere.cpp
@@ -0,0 +1,731 @@
+//======================================================================================================================
+//
+//  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 DragForceSphere.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/Debug.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/logging/Logging.h"
+#include "core/math/all.h"
+#include "core/SharedFunctor.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/mpi/MPIManager.h"
+#include "core/mpi/Reduce.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/communication/PackInfo.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/MacroscopicValueCalculation.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/lattice_model/ForceModel.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.h"
+
+#include "lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/utility/OmegaBulkAdaption.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/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+#include "stencil/D3Q27.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+#include "lbm/vtk/all.h"
+
+#include <iomanip>
+#include <iostream>
+
+#include "GeneratedLBMWithForce.h"
+
+#define USE_TRT_LIKE_LATTICE_MODEL
+
+namespace drag_force_sphere_mem
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::GeneratedLBMWithForce;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+using ScalarField_T = GhostLayerField< real_t, 1>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag   ( "fluid" );
+const FlagUID MO_BB_Flag   ( "moving obstacle BB" );
+const FlagUID MO_CLI_Flag  ( "moving obstacle CLI" );
+
+////////////////
+// PARAMETERS //
+////////////////
+
+struct Setup{
+   uint_t checkFrequency;
+   real_t visc;
+   real_t tau;
+   real_t radius;
+   uint_t length;
+   real_t chi;
+   real_t extForce;
+   real_t analyticalDrag;
+};
+
+enum MEMVariant { BB, CLI };
+
+MEMVariant to_MEMVariant( const std::string& s )
+{
+   if( s == "BB"  ) return MEMVariant::BB;
+   if( s == "CLI" ) return MEMVariant::CLI;
+   throw std::runtime_error("invalid conversion from text to MEMVariant");
+}
+
+std::string MEMVariant_to_string ( const MEMVariant& m )
+{
+   if( m == MEMVariant::BB  ) return "BB";
+   if( m == MEMVariant::CLI ) return "CLI";
+   throw std::runtime_error("invalid conversion from MEMVariant to string");
+}
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using SBB_T = lbm_mesapd_coupling::SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using CLI_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, SBB_T, CLI_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  SBB_T("SBB_BB", MO_BB_Flag,  pdfField, flagField, particleField, ac_, fluid, *storage, *block ),
+                                  CLI_T("CLI_BB", MO_CLI_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+
+
+template< typename ParticleAccessor_T>
+class DragForceEvaluator
+{
+public:
+   DragForceEvaluator( SweepTimeloop* timeloop, Setup* setup, const shared_ptr< StructuredBlockStorage > & blocks,
+                       const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const shared_ptr<ParticleAccessor_T>& ac, walberla::id_t sphereID )
+:     timeloop_( timeloop ), setup_( setup ), blocks_( blocks ),
+      flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ),
+      ac_( ac ), sphereID_( sphereID ),
+      normalizedDragOld_( 0.0 ), normalizedDragNew_( 0.0 )
+   {
+      // calculate the analytical drag force value based on the series expansion of chi
+      // see also Sangani - Slow flow through a periodic array of spheres, IJMF 1982. Eq. 60 and Table 1
+      real_t analyticalDrag = real_c(0);
+      real_t tempChiPowS = real_c(1);
+
+      // coefficients to calculate the drag in a series expansion
+      real_t dragCoefficients[31] = {  real_c(1.000000),  real_c(1.418649),  real_c(2.012564),  real_c(2.331523),  real_c(2.564809),   real_c(2.584787),
+                                       real_c(2.873609),  real_c(3.340163),  real_c(3.536763),  real_c(3.504092),  real_c(3.253622),   real_c(2.689757),
+                                       real_c(2.037769),  real_c(1.809341),  real_c(1.877347),  real_c(1.534685), real_c(0.9034708),  real_c(0.2857896),
+                                       real_c(-0.5512626), real_c(-1.278724),  real_c(1.013350),  real_c(5.492491),  real_c(4.615388), real_c(-0.5736023),
+                                       real_c(-2.865924), real_c(-4.709215), real_c(-6.870076), real_c(0.1455304),  real_c(12.51891),   real_c(9.742811),
+                                       real_c(-5.566269)};
+
+      for(uint_t s = 0; s <= uint_t(30); ++s){
+         analyticalDrag += dragCoefficients[s] * tempChiPowS;
+         tempChiPowS *= setup->chi;
+      }
+      setup_->analyticalDrag = analyticalDrag;
+   }
+
+   // evaluate the acting drag force
+   void operator()()
+   {
+      const uint_t timestep (timeloop_->getCurrentTimeStep()+1);
+
+      if( timestep % setup_->checkFrequency != 0) return;
+
+      // get force in x-direction acting on the sphere
+      real_t forceX = computeDragForce();
+      // get average volumetric flowrate in the domain
+      real_t uBar = computeAverageVel();
+
+      averageVel_ = uBar;
+
+      // f_total = f_drag + f_buoyancy
+      real_t totalForce = forceX  + real_c(4.0/3.0) * math::pi * setup_->radius * setup_->radius * setup_->radius * setup_->extForce ;
+
+      real_t normalizedDragForce = totalForce / real_c( 6.0 * math::pi * setup_->visc * setup_->radius * uBar );
+
+      // update drag force values
+      normalizedDragOld_ = normalizedDragNew_;
+      normalizedDragNew_ = normalizedDragForce;
+   }
+
+   // return the relative temporal change in the normalized drag
+   real_t getDragForceDiff() const
+   {
+      return std::fabs( ( normalizedDragNew_ - normalizedDragOld_ ) / normalizedDragNew_ );
+   }
+
+   // return the drag force
+   real_t getDragForce() const
+   {
+      return normalizedDragNew_;
+   }
+
+   real_t getAverageVel()
+   {
+      return averageVel_;
+   }
+
+   void logResultToFile( const std::string & filename ) const
+   {
+      // write to file if desired
+      // format: length tau viscosity simulatedDrag analyticalDrag\n
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( filename.c_str(), std::ofstream::app );
+         file.precision(8);
+         file << setup_->length << " " << setup_->tau << " " << setup_->visc << " " << normalizedDragNew_ << " " << setup_->analyticalDrag << "\n";
+         file.close();
+      }
+   }
+
+private:
+
+   // obtain the drag force acting on the sphere by summing up all the process local parts of fX
+   real_t computeDragForce()
+   {
+      size_t idx = ac_->uidToIdx(sphereID_);
+      real_t force = real_t(0);
+      if( idx!= ac_->getInvalidIdx())
+      {
+         force = ac_->getHydrodynamicForce(idx)[0];
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( force, mpi::SUM );
+      }
+
+      return force;
+   }
+
+   // calculate the average velocity in forcing direction (here: x) inside the domain (assuming dx=1)
+   real_t computeAverageVel()
+   {
+      auto velocity_sum = real_t(0);
+      // iterate all blocks stored locally on this process
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         // retrieve the pdf field and the flag field from the block
+         PdfField_T  *  pdfField = blockIt->getData< PdfField_T >( pdfFieldID_ );
+         FlagField_T * flagField = blockIt->getData< FlagField_T >( flagFieldID_ );
+
+         // get the flag that marks a cell as being fluid
+         auto fluid = flagField->getFlag( Fluid_Flag );
+
+         auto xyzField = pdfField->xyzSize();
+         for (auto cell : xyzField) {
+            if( flagField->isFlagSet( cell.x(), cell.y(), cell.z(), fluid ) )
+            {
+             velocity_sum += pdfField->getVelocity(cell)[0];
+            }
+         }
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( velocity_sum, mpi::SUM );
+      }
+
+      return velocity_sum / real_c( setup_->length * setup_->length * setup_->length );
+   }
+
+   SweepTimeloop* timeloop_;
+
+   Setup* setup_;
+
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+   const walberla::id_t sphereID_;
+
+   // drag coefficient
+   real_t normalizedDragOld_;
+   real_t normalizedDragNew_;
+
+   real_t averageVel_;
+
+};
+
+//////////
+// MAIN //
+//////////
+
+
+//*******************************************************************************************************************
+/*!\brief Testcase that checks the drag force acting on a fixed sphere in the center of a cubic domain in Stokes flow
+ *
+ * The drag force for this problem (often denoted as Simple Cubic setup) is given by a semi-analytical series expansion.
+ * The cubic domain is periodic in all directions, making it a physically infinite periodic array of spheres.
+ *         _______________
+ *      ->|               |->
+ *      ->|      ___      |->
+ *    W ->|     /   \     |-> E
+ *    E ->|    |  x  |    |-> A
+ *    S ->|     \___/     |-> S
+ *    T ->|               |-> T
+ *      ->|_______________|->
+ *
+ * The collision model used for the LBM is TRT with a relaxation parameter tau=1.5 and the magic parameter 3/16.
+ * The Stokes approximation of the equilibrium PDFs is used.
+ * The flow is driven by a constant body force of 1e-5.
+ * The domain is length x length x length, and the sphere has a diameter of chi * length cells
+ * The simulation is run until the relative change in the dragforce between 100 time steps is less than 1e-5.
+ * The RPD is not used since the sphere is kept fixed and the force is explicitly reset after each time step.
+ * To avoid periodicity constrain problems, the sphere is set as global.
+ *
+ */
+//*******************************************************************************************************************
+
+
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   auto processes = MPIManager::instance()->numProcesses();
+
+   if( processes != 1 && processes != 2 && processes != 4 && processes != 8)
+   {
+      std::cerr << "Number of processes must be equal to either 1, 2, 4, or 8!" << std::endl;
+      return EXIT_FAILURE;
+   }
+
+   ///////////////////
+   // Customization //
+   ///////////////////
+
+   bool shortrun  = false;
+   bool funcTest  = false;
+   bool logging   = true;
+   uint_t vtkIOFreq = 0;
+   std::string baseFolder = "vtk_out_DragForceSphere";
+
+   real_t tau     = real_c( 1.5 );
+   real_t externalForcing = real_t(1e-8);
+   uint_t length  = uint_c( 40 );
+
+   MEMVariant method = MEMVariant::CLI;
+   real_t bulkViscRateFactor = real_t(1); // ratio between bulk and shear viscosity related relaxation factors, 1 = TRT, > 1 for stability with MRT
+   // see Khirevich - Coarse- and fine-grid numerical behavior of MRT/TRT lattice-Boltzmann schemes in regular and random sphere packings
+   bool useSRT = false;
+   real_t magicNumber = real_t(3) / real_t(16);
+   bool useOmegaBulkAdaption = false;
+   real_t adaptionLayerSize = real_t(2);
+
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--shortrun" )             == 0 ) { shortrun = true; continue; }
+      if( std::strcmp( argv[i], "--funcTest" )             == 0 ) { funcTest = true; continue; }
+      if( std::strcmp( argv[i], "--noLogging" )            == 0 ) { logging = false; continue; }
+      if( std::strcmp( argv[i], "--vtkIOFreq" )            == 0 ) { vtkIOFreq = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--MEMVariant" )           == 0 ) { method = to_MEMVariant( argv[++i] ); continue; }
+      if( std::strcmp( argv[i], "--tau" )                  == 0 ) { tau = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--extForce" )             == 0 ) { externalForcing = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--length" )               == 0 ) { length = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--bulkViscRateFactor" )   == 0 ) { bulkViscRateFactor = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--useSRT" )               == 0 ) { useSRT = true; continue; }
+      if( std::strcmp( argv[i], "--magicNumber" )          == 0 ) { magicNumber = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--useOmegaBulkAdaption" ) == 0 ) { useOmegaBulkAdaption = true; continue; }
+      if( std::strcmp( argv[i], "--adaptionLayerSize" )    == 0 ) { adaptionLayerSize = real_c(std::atof( argv[++i] )); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   Setup setup;
+
+   setup.length         = length;                            // length of the cubic domain in lattice cells
+   setup.chi            = real_c( 0.5 );                     // porosity parameter: diameter / length
+   setup.tau            = tau;                               // relaxation time
+   setup.extForce       = externalForcing;                   // constant body force in lattice units
+   setup.checkFrequency = uint_t( 100 );                     // evaluate the drag force only every checkFrequency time steps
+   setup.radius         = real_c(0.5) * setup.chi * real_c( setup.length );  // sphere radius
+   setup.visc           = ( setup.tau - real_c(0.5) ) / real_c(3);   // viscosity in lattice units
+   const real_t omega      = real_c(1) / setup.tau;          // relaxation rate
+   const real_t dx         = real_c(1);                      // lattice dx
+   const real_t convergenceLimit = real_t(0.1) * setup.extForce;           // tolerance for relative change in drag force
+   const uint_t timesteps  =  funcTest ? 1 : ( shortrun ? uint_c(150) : uint_c( 100000 ) );  // maximum number of time steps for the whole simulation
+
+   WALBERLA_LOG_INFO_ON_ROOT("tau = " << tau);
+   WALBERLA_LOG_INFO_ON_ROOT("diameter = " << real_t(2) * setup.radius);
+   WALBERLA_LOG_INFO_ON_ROOT("viscosity = " << setup.visc);
+   WALBERLA_LOG_INFO_ON_ROOT("MEM variant = " << MEMVariant_to_string(method));
+   WALBERLA_LOG_INFO_ON_ROOT("external forcing = " << setup.extForce);
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+   const uint_t XBlocks = (processes >= 2) ? uint_t( 2 ) : uint_t( 1 );
+   const uint_t YBlocks = (processes >= 4) ? uint_t( 2 ) : uint_t( 1 );
+   const uint_t ZBlocks = (processes == 8) ? uint_t( 2 ) : uint_t( 1 );
+   const uint_t XCells = setup.length / XBlocks;
+   const uint_t YCells = setup.length / YBlocks;
+   const uint_t ZCells = setup.length / ZBlocks;
+
+   // create fully periodic domain
+   auto blocks = blockforest::createUniformBlockGrid( XBlocks, YBlocks, ZBlocks, XCells, YCells, ZCells, dx, true,
+                                                      true, true, true );
+
+
+   /////////
+   // RPD //
+   /////////
+
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+
+   //init data structures
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = make_shared<ParticleAccessor_T >(ps, ss);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( setup.radius );
+
+   //////////////////
+   // RPD COUPLING //
+   //////////////////
+
+   // connect to pe
+   const real_t overlap = real_t( 1.5 ) * dx;
+
+   if( setup.radius > real_c( setup.length ) * real_t(0.5) - overlap )
+   {
+      std::cerr << "Periodic sphere is too large and would lead to incorrect mapping!" << std::endl;
+      // solution: create the periodic copies explicitly
+      return EXIT_FAILURE;
+   }
+
+   // create the sphere in the middle of the domain
+   // it is global and thus present on all processes
+
+   Vector3<real_t> position (real_c(setup.length) * real_c(0.5));
+   walberla::id_t sphereID;
+   {
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(position);
+      p.setInteractionRadius(setup.radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+      sphereID = p.getUid();
+   }
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+
+   real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega );
+   real_t lambda_d = (useSRT) ? lambda_e : lbm::collision_model::TRT::lambda_d( omega, magicNumber );
+   real_t omegaBulk = (useSRT) ? lambda_e : lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor);
+
+   // add omega bulk field
+   BlockDataID omegaBulkFieldID = field::addToStorage<ScalarField_T>( blocks, "omega bulk field", omegaBulk, field::fzyx, uint_t(0) );
+
+   // create the lattice model
+
+   // generated MRT
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+   WALBERLA_LOG_INFO_ON_ROOT("Using TRT-like lattice model!");
+   WALBERLA_LOG_INFO_ON_ROOT(" - magic number " << magicNumber);
+   WALBERLA_LOG_INFO_ON_ROOT(" - omegaBulk = " << omegaBulk << ", bulk visc. = " << lbm_mesapd_coupling::bulkViscosityFromOmegaBulk(omegaBulk) << " (bvrf " << bulkViscRateFactor << ")");
+   WALBERLA_LOG_INFO_ON_ROOT(" - lambda_e " << lambda_e << ", lambda_d " << lambda_d << ", omegaBulk " << omegaBulk );
+   WALBERLA_LOG_INFO_ON_ROOT(" - use omega bulk adaption = " << useOmegaBulkAdaption << " (adaption layer size = " << adaptionLayerSize << ")");
+
+   LatticeModel_T latticeModel = LatticeModel_T(omegaBulkFieldID, setup.extForce, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(setup.extForce, omegaBulk, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(setup.extForce, lambda_e, lambda_d);
+#else
+   WALBERLA_LOG_INFO_ON_ROOT("Using other lattice model!");
+   // generated KBC
+   LatticeModel_T latticeModel = LatticeModel_T(setup.extForce, omega);
+#endif
+
+   // set s_e and s_eps to omegaBulk
+   // s_e allows to set a specific bulk viscosity that is different from the SRT/TRT default: nu_b = 2/3 nu_shear
+   // Khirevich et al propose to set s_eps in the same way to avoid huge differences in s_e and s_eps (would be omega in SRT/TRT) that could lead to stability problems
+   // to not break TRT properties, it is proposed to set Lambda_bulk = conste * Lambda_shear (Khirevich), where conste sets the ratio between bulk and shear viscosity-terms (1 in SRT/TRT -> nu_b = 2/3 nu_shear)
+   // all in all, this allows to set the bulk viscosity but to retain the "TRT" properties, i.e. independent drag force of tau
+   //LatticeModel_T latticeModel = LatticeModel_T(lbm::collision_model::D3Q19MRT( omegaBulk, omegaBulk, lambda_d, lambda_e, lambda_e, lambda_d ), ForceModel_T( Vector3<real_t> ( setup.extForce, 0, 0 ) ));
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (fzyx)", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         uint_t(1), field::fzyx );
+
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::fzyx, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // create the timeloop
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+   blockforest::communication::UniformBufferedScheme< Stencil_T > optimizedPDFCommunicationScheme( blocks );
+   optimizedPDFCommunicationScheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) ); // optimized sync
+
+   //blockforest::communication::UniformBufferedScheme< stencil::D3Q27 > optimizedPDFCommunicationScheme( blocks );
+   //optimizedPDFCommunicationScheme.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) );
+
+   // initially map particles into the LBM simulation
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+   if( method == MEMVariant::CLI )
+   {
+      // uses a higher order boundary condition (CLI)
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, movingParticleMappingKernel, *accessor, MO_CLI_Flag);
+   }else{
+      // uses standard bounce back boundary conditions
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, movingParticleMappingKernel, *accessor, MO_BB_Flag);
+   }
+
+   // update bulk omega in all cells to adapt to changed particle position
+   if(useOmegaBulkAdaption)
+   {
+      using OmegaBulkAdapter_T = lbm_mesapd_coupling::OmegaBulkAdapter<ParticleAccessor_T, mesa_pd::kernel::SelectAll>;
+      real_t defaultOmegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, real_t(1));
+      shared_ptr<OmegaBulkAdapter_T> omegaBulkAdapter = make_shared<OmegaBulkAdapter_T>(blocks, omegaBulkFieldID, accessor, defaultOmegaBulk, omegaBulk, adaptionLayerSize, mesa_pd::kernel::SelectAll());
+      for (auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) {
+         (*omegaBulkAdapter)(blockIt.get());
+      }
+   }
+
+   if( vtkIOFreq != uint_t(0) )
+   {
+
+      // spheres
+      auto particleVtkOutput = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleLinearVelocity>("velocity");
+      auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "Particles", vtkIOFreq, baseFolder, "simulation_step");
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( particleVtkWriter ), "VTK (sphere data)" );
+
+      // pdf field
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", vtkIOFreq, 0, false, baseFolder );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+      pdfFieldVTK->addCellInclusionFilter( fluidFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+
+      // omega bulk field
+      timeloop.addFuncBeforeTimeStep( field::createVTKOutput<ScalarField_T, float>( omegaBulkFieldID, *blocks, "omega_bulk_field", vtkIOFreq, uint_t(0), false, baseFolder ), "VTK (omega bulk field)" );
+   }
+
+
+   // since external forcing is applied, the evaluation of the velocity has to be carried out directly after the streaming step
+   // however, the default sweep is a  stream - collide step, i.e. after the sweep, the velocity evaluation is not correct
+   // solution: split the sweep explicitly into collide and stream
+
+   using DragForceEval_T = DragForceEvaluator<ParticleAccessor_T>;
+   auto forceEval = make_shared< DragForceEval_T >( &timeloop, &setup, blocks, flagFieldID, pdfFieldID, accessor, sphereID );
+
+   auto lbmSweep = LatticeModel_T::Sweep( pdfFieldID );
+
+
+   // splitting stream and collide is currently buggy in lbmpy with vectorization -> build without optimize for local host and vectorization
+   // collide LBM step
+   timeloop.add() << Sweep([&lbmSweep](IBlock * const block){lbmSweep.collide(block);}, "collide LB sweep" );
+
+   // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+   timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                  << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+   // stream LBM step
+   timeloop.add() << Sweep([&lbmSweep](IBlock * const block){lbmSweep.stream(block);}, "stream LB sweep" )
+                  << AfterFunction( SharedFunctor< DragForceEval_T >( forceEval ), "drag force evaluation" );
+
+/*
+   // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+   timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                  << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+   // stream + collide LBM step
+   timeloop.add() << Sweep( lbmSweep, "LB sweep" )
+                  << AfterFunction( SharedFunctor< DragForceEval_T >( forceEval ), "drag force evaluation" );
+*/
+   // resetting force
+   timeloop.addFuncAfterTimeStep( [ps,accessor](){ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel(),*accessor);}, "reset force on sphere");
+
+   timeloop.addFuncAfterTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+   // time loop
+   for (uint_t i = 0; i < timesteps; ++i )
+   {
+      // perform a single simulation step
+      timeloop.singleStep( timeloopTiming );
+
+      // check if the relative change in the normalized drag force is below the specified convergence criterion
+      if ( i > setup.checkFrequency && forceEval->getDragForceDiff() < convergenceLimit )
+      {
+         // if simulation has converged, terminate simulation
+         break;
+      }
+
+      if( std::isnan(forceEval->getDragForce()) ) WALBERLA_ABORT("Nan found!");
+
+      if(i%1000 == 0)
+      {
+         WALBERLA_LOG_INFO_ON_ROOT("Current drag force: " << forceEval->getDragForce());
+      }
+
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("Final drag force: " << forceEval->getDragForce());
+   WALBERLA_LOG_INFO_ON_ROOT("Re = " << forceEval->getAverageVel() * setup.radius * real_t(2) / setup.visc);
+
+   timeloopTiming.logResultOnRoot();
+
+   if ( !funcTest && !shortrun ){
+      // check the result
+      real_t relErr = std::fabs( ( setup.analyticalDrag - forceEval->getDragForce() ) / setup.analyticalDrag );
+      if ( logging )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::cout << "Analytical drag: " << setup.analyticalDrag << "\n"
+                      << "Simulated drag: " << forceEval->getDragForce() << "\n"
+                      << "Relative error: " << relErr << "\n";
+         }
+
+         std::string fileName( MEMVariant_to_string(method) );
+         fileName += "_bvrf" + std::to_string(uint_c(bulkViscRateFactor));
+         fileName += "_mn" + std::to_string(float(magicNumber));
+         if(useOmegaBulkAdaption ) fileName += "_uOBA" + std::to_string(uint_c(adaptionLayerSize));
+         if(useSRT) fileName += "_SRT";
+
+         forceEval->logResultToFile( "log_DragForceSphereMEM_Generated_"+fileName+".txt" );
+      }
+   }
+
+   return 0;
+
+}
+
+} //namespace drag_force_sphere_mem
+
+int main( int argc, char **argv ){
+   drag_force_sphere_mem::main(argc, argv);
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/ForcesOnSphereNearPlane.cpp b/apps/benchmarks/FluidParticleCoupling/ForcesOnSphereNearPlane.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eae674e9aa141bcee24ae4057b9da15a057a35f7
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/ForcesOnSphereNearPlane.cpp
@@ -0,0 +1,812 @@
+//======================================================================================================================
+//
+//  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 ForcesOnSphereNearPlane.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+#include <blockforest/loadbalancing/StaticCurve.h>
+#include <blockforest/SetupBlockForest.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/math/all.h"
+#include "core/timing/RemainingTimeLogger.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/StabilityChecker.h"
+#include "field/communication/PackInfo.h"
+
+#include "lbm/boundary/FreeSlip.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.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/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/utility/AddForceOnParticlesKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/OmegaBulkAdaption.h"
+
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/mpi/ReduceProperty.h"
+#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+#include "lbm/vtk/all.h"
+
+#include "GeneratedLBM.h"
+#include <functional>
+
+#define USE_TRT_LIKE_LATTICE_MODEL
+
+namespace forces_on_sphere_near_plane
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+//////////////
+// TYPEDEFS //
+//////////////
+
+using LatticeModel_T = lbm::GeneratedLBM;
+//using LatticeModel_T = lbm::D3Q19< lbm::collision_model::D3Q19MRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+using ScalarField_T = GhostLayerField< real_t, 1>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag( "fluid" );
+const FlagUID CLI_Flag( "cli boundary" );
+const FlagUID SBB_Flag( "sbb boundary" );
+
+/////////////////////
+// BLOCK STRUCTURE //
+/////////////////////
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using CLI_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using SBB_T = lbm_mesapd_coupling::SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, CLI_T, SBB_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  CLI_T( "MO CLI", CLI_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ),
+                                  SBB_T( "MO SBB", SBB_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+//*******************************************************************************************************************
+
+
+template< typename ParticleAccessor_T>
+class SpherePropertyLogger
+{
+public:
+   SpherePropertyLogger( const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid,
+                         const std::string & fileName, bool fileIO,
+                         real_t dragNormalizationFactor, real_t liftNormalizationFactor, real_t physicalTimeScale ) :
+      ac_( ac ), sphereUid_( sphereUid ),
+      fileName_( fileName ), fileIO_(fileIO),
+      dragNormalizationFactor_( dragNormalizationFactor ), liftNormalizationFactor_( liftNormalizationFactor ),
+      physicalTimeScale_( physicalTimeScale )
+   {
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str() );
+            file << "#\t t\t Cd\t Cl\t fX\t fY\t fZ\t tX\t tY\t tZ\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()(const uint_t timestep)
+   {
+
+      Vector3<real_t> force(real_t(0));
+      Vector3<real_t> torque(real_t(0));
+
+      size_t idx = ac_->uidToIdx(sphereUid_);
+      if( idx != ac_->getInvalidIdx())
+      {
+         force = ac_->getHydrodynamicForce(idx);
+         torque = ac_->getHydrodynamicTorque(idx);
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( force[0], mpi::SUM );
+         mpi::allReduceInplace( force[1], mpi::SUM );
+         mpi::allReduceInplace( force[2], mpi::SUM );
+
+         mpi::allReduceInplace( torque[0], mpi::SUM );
+         mpi::allReduceInplace( torque[1], mpi::SUM );
+         mpi::allReduceInplace( torque[2], mpi::SUM );
+      }
+
+      if( fileIO_ )
+         writeToFile( timestep, force, torque);
+
+      dragForce_ = force[0];
+      liftForce_ = force[2];
+   }
+
+   real_t getDragForce()
+   {
+      return dragForce_;
+   }
+
+   real_t getLiftForce()
+   {
+      return liftForce_;
+   }
+
+   real_t getDragCoefficient()
+   {
+      return dragForce_ / dragNormalizationFactor_;
+   }
+
+   real_t getLiftCoefficient()
+   {
+      return liftForce_ / liftNormalizationFactor_;
+   }
+
+
+private:
+   void writeToFile( uint_t timestep, const Vector3<real_t> & force, const Vector3<real_t> & torque )
+   {
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( fileName_.c_str(), std::ofstream::app );
+
+         file << timestep << "\t" << real_c(timestep) / physicalTimeScale_
+              << "\t" << force[0] / dragNormalizationFactor_ << "\t" << force[2] / liftNormalizationFactor_
+              << "\t" << force[0] << "\t" << force[1] << "\t" << force[2]
+              << "\t" << torque[0] << "\t" << torque[1] << "\t" << torque[2]
+              << "\n";
+         file.close();
+      }
+   }
+
+   shared_ptr< ParticleAccessor_T > ac_;
+   const walberla::id_t sphereUid_;
+   std::string fileName_;
+   bool fileIO_;
+   real_t dragForce_, liftForce_;
+   real_t dragNormalizationFactor_, liftNormalizationFactor_;
+   real_t physicalTimeScale_;
+};
+
+template <typename BoundaryHandling_T>
+void initializeCouetteProfile( const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & pdfFieldID, const BlockDataID & boundaryHandlingID,
+                               const real_t & domainHeight, const real_t wallVelocity )
+{
+   const real_t rho = real_c(1);
+
+   for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt )
+   {
+      auto pdfField = blockIt->getData< PdfField_T > ( pdfFieldID );
+      auto boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID );
+
+      WALBERLA_FOR_ALL_CELLS_XYZ(pdfField,
+                                 if( !boundaryHandling->isDomain(x,y,z) ) continue;
+
+                                 const Vector3< real_t > coord = blocks->getBlockLocalCellCenter( *blockIt, Cell(x,y,z) );
+
+                                 Vector3< real_t > velocity( real_c(0) );
+
+                                 velocity[0] =  wallVelocity * coord[2] / domainHeight;
+
+                                 pdfField->setToEquilibrium( x, y, z, velocity, rho );
+      )
+   }
+}
+
+void createPlaneSetup(const shared_ptr<mesa_pd::data::ParticleStorage> & ps, const shared_ptr<mesa_pd::data::ShapeStorage> & ss, const math::AABB & simulationDomain, const real_t wallVelocity )
+{
+   // create bounding planes
+   mesa_pd::data::Particle p0 = *ps->create(true);
+   p0.setPosition(simulationDomain.minCorner());
+   p0.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,1) ));
+   p0.setOwner(mpi::MPIManager::instance()->rank());
+   p0.setType(0);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p1 = *ps->create(true);
+   p1.setPosition(simulationDomain.maxCorner());
+   p1.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,-1) ));
+   p1.setOwner(mpi::MPIManager::instance()->rank());
+   p1.setType(0);
+   mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+   p1.setLinearVelocity(Vector3<real_t>(wallVelocity, real_t(0), real_t(0))); //moving wall
+
+}
+
+void logFinalResult( const std::string & fileName, real_t Re, real_t wallDistance, real_t diameter,
+                     uint_t domainLength, uint_t domainWidth, uint_t domainHeight,
+                     real_t dragCoeff, real_t dragCoeffRef,
+                     real_t liftCoeff, real_t liftCoeffRef,
+                     uint_t timestep, real_t nonDimTimestep )
+{
+   WALBERLA_ROOT_SECTION()
+   {
+      std::ofstream file;
+      file.open( fileName.c_str(), std::ofstream::app );
+
+      file << Re << "\t " << wallDistance << "\t " << diameter << "\t "
+           << domainLength << "\t " << domainWidth << "\t " << domainHeight << "\t "
+           << dragCoeff << "\t " << dragCoeffRef << "\t "<< std::abs(dragCoeff-dragCoeffRef) / dragCoeffRef << "\t "
+           << liftCoeff << "\t " << liftCoeffRef << "\t "<< std::abs(liftCoeff-liftCoeffRef) / liftCoeffRef << "\t "
+           << timestep << "\t " << nonDimTimestep
+           << "\n";
+      file.close();
+   }
+}
+
+//////////
+// MAIN //
+//////////
+
+//*******************************************************************************************************************
+/*!\brief Testcase that evaluates the drag and lift force on a sphere that is close to the bottom plane in shear flow
+ *
+ * see overview paper:
+ * Zeng, Najjar, Balachandar, Fischer - "Forces on a finite-sized particle located close to a wall in a linear shear flow", 2009
+ *
+ * contains references to:
+ * Leighton, Acrivos - "The lift on a small sphere touching a plane in the presence of a simple shear flow", 1985
+ * Zeng, Balachandar, Fischer - "Wall-induced forces on a rigid sphere at finite Reynolds number", 2005
+ *
+ * CFD-IBM simulations in:
+ * Lee, Balachandar - "Drag and lift forces on a spherical particle moving on a wall in a shear flow at finite Re", 2010
+ *
+ * Description:
+ *  - Domain size [x, y, z] = [48 x 16 x 8 ] * diameter = [L(ength), W(idth), H(eight)]
+ *  - horizontally periodic, bounded by two planes in z-direction
+ *  - top plane is moving with constant wall velocity -> shear flow
+ *  - sphere is placed in the vicinity of the bottom plane at [ L/2 + xOffset, W/2 + yOffset, wallDistance * diameter]
+ *  - distance of sphere center to the bottom plane is crucial parameter
+ *  - viscosity is adjusted to match specified Reynolds number = shearRate * diameter * wallDistance / viscosity
+ *  - dimensionless drag and lift forces are evaluated and written to logging file
+ */
+//*******************************************************************************************************************
+
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   ///////////////////
+   // Customization //
+   ///////////////////
+
+   // simulation control
+   bool fileIO = true;
+   uint_t vtkIOFreq = 0;
+   std::string baseFolderVTK = "vtk_out_ForcesNearPlane";
+   std::string baseFolderLogging = ".";
+
+   // physical setup
+   real_t diameter = real_t(20); // cells per diameter -> determines overall resolution
+   real_t normalizedWallDistance = real_t(1); // distance of the sphere center to the bottom wall, normalized by the diameter
+   real_t ReynoldsNumberShear = real_t(1); // = shearRate * wallDistance * diameter / viscosity
+
+   //numerical parameters
+   real_t maximumNonDimTimesteps = real_t(100); // maximum number of non-dimensional time steps
+   real_t xOffsetOfSpherePosition = real_t(0); // offset in x-direction of sphere position
+   real_t yOffsetOfSpherePosition = real_t(0); // offset in y-direction of sphere position
+   real_t bulkViscRateFactor = real_t(1);
+   real_t magicNumber = real_t(3)/real_t(16);
+   real_t wallVelocity = real_t(0.1);
+
+   bool initializeVelocityProfile = false;
+   bool useOmegaBulkAdaption = false;
+   real_t adaptionLayerSize = real_t(2);
+
+   real_t relativeChangeConvergenceEps = real_t(1e-5);
+   real_t physicalCheckingFrequency = real_t(0.1);
+
+   // command line arguments
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--noLogging" )                 == 0 ) { fileIO = false; continue; }
+      if( std::strcmp( argv[i], "--vtkIOFreq" )                 == 0 ) { vtkIOFreq = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--diameter" )                  == 0 ) { diameter = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--timesteps" )                 == 0 ) { maximumNonDimTimesteps = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--wallDistance" )              == 0 ) { normalizedWallDistance = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--Re" )                        == 0 ) { ReynoldsNumberShear = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--velocity" )                  == 0 ) { wallVelocity = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--xOffset" )                   == 0 ) { xOffsetOfSpherePosition = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--yOffset" )                   == 0 ) { yOffsetOfSpherePosition = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--bvrf" )                      == 0 ) { bulkViscRateFactor = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--baseFolderVTK" )             == 0 ) { baseFolderVTK = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--baseFolderLogging" )         == 0 ) { baseFolderLogging = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--initializeVelocityProfile" ) == 0 ) { initializeVelocityProfile = true; continue; }
+      if( std::strcmp( argv[i], "--magicNumber" )               == 0 ) { magicNumber = real_c(std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--useOmegaBulkAdaption" )      == 0 ) { useOmegaBulkAdaption = true; continue; }
+      if( std::strcmp( argv[i], "--adaptionLayerSize" )         == 0 ) { adaptionLayerSize = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--convergenceLimit" )         == 0 ) { relativeChangeConvergenceEps = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--checkingFrequency" )         == 0 ) { physicalCheckingFrequency = real_c(std::atof( argv[++i] )); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   WALBERLA_CHECK_GREATER_EQUAL(normalizedWallDistance, real_t(0.5));
+   WALBERLA_CHECK_GREATER_EQUAL(ReynoldsNumberShear, real_t(0));
+   WALBERLA_CHECK_GREATER_EQUAL(diameter, real_t(0));
+
+   //////////////////////////
+   // NUMERICAL PARAMETERS //
+   //////////////////////////
+
+   const real_t domainLength = real_t(48) * diameter; //x
+   const real_t domainWidth  = real_t(16) * diameter; //y
+   const real_t domainHeight = real_t( 8) * diameter; //z
+
+   //const real_t domainLength = real_t(3) * diameter; //x
+   //const real_t domainWidth  = real_t(3) * diameter; //y
+   //const real_t domainHeight = real_t(8) * diameter; //z
+
+   Vector3<uint_t> domainSize( uint_c( std::ceil(domainLength)), uint_c( std::ceil(domainWidth)), uint_c( std::ceil(domainHeight)) );
+
+
+   const real_t wallDistance = diameter * normalizedWallDistance;
+   const real_t shearRate = wallVelocity / domainHeight;
+   const real_t velAtSpherePosition = shearRate * wallDistance;
+   const real_t viscosity = velAtSpherePosition * diameter / ReynoldsNumberShear;
+
+   const real_t relaxationTime = real_t(1) / lbm::collision_model::omegaFromViscosity(viscosity);
+
+   const real_t densityFluid = real_t(1);
+
+   const real_t dx = real_t(1);
+
+   const real_t physicalTimeScale = diameter / velAtSpherePosition;
+   const uint_t timesteps = uint_c(maximumNonDimTimesteps * physicalTimeScale);
+
+   const real_t omega = real_t(1) / relaxationTime;
+   const real_t omegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor);
+
+   Vector3<real_t> initialPosition( domainLength * real_t(0.5) + xOffsetOfSpherePosition,
+                                    domainWidth * real_t(0.5) + yOffsetOfSpherePosition,
+                                    wallDistance );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Setup:");
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize );
+   WALBERLA_LOG_INFO_ON_ROOT(" - normalized wall distance = " << normalizedWallDistance );
+   WALBERLA_LOG_INFO_ON_ROOT(" - shear rate = " << shearRate );
+   WALBERLA_LOG_INFO_ON_ROOT(" - wall velocity = " << wallVelocity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - Reynolds number (shear rate based) = " << ReynoldsNumberShear << ", vel at sphere pos = " << velAtSpherePosition);
+   WALBERLA_LOG_INFO_ON_ROOT(" - density = " << densityFluid  );
+   WALBERLA_LOG_INFO_ON_ROOT(" - viscosity = " << viscosity << " -> omega = " << omega << " , tau = " << relaxationTime);
+   WALBERLA_LOG_INFO_ON_ROOT(" - magic number = " << magicNumber);
+   WALBERLA_LOG_INFO_ON_ROOT(" - omegaBulk = " << omegaBulk << ", bulk visc. = " << lbm_mesapd_coupling::bulkViscosityFromOmegaBulk(omegaBulk) << " (bvrf " << bulkViscRateFactor << ")");
+   WALBERLA_LOG_INFO_ON_ROOT(" - use omega bulk adaption = " << useOmegaBulkAdaption << " (adaption layer size = " << adaptionLayerSize << ")");
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere diameter = " << diameter << ", position = " << initialPosition << " ( xOffset = " << xOffsetOfSpherePosition << ", yOffset = " << yOffsetOfSpherePosition << " )");
+   WALBERLA_LOG_INFO_ON_ROOT(" - base folder VTK = " << baseFolderVTK << ", base folder logging = " << baseFolderLogging );
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+   Vector3<uint_t> blocksPerDirection( 24, 5, 2 );
+   //Vector3<uint_t> blocksPerDirection( 3, 3, 1 );
+
+   WALBERLA_CHECK( domainSize[0] % blocksPerDirection[0] == 0 &&
+                   domainSize[1] % blocksPerDirection[1] == 0 &&
+                   domainSize[2] % blocksPerDirection[2] == 0 );
+   Vector3<uint_t> blockSizeInCells(domainSize[0] / ( blocksPerDirection[0]  ),
+                                    domainSize[1] / ( blocksPerDirection[1]  ),
+                                    domainSize[2] / ( blocksPerDirection[2] ) );
+
+   AABB simulationDomain( real_t(0), real_t(0), real_t(0), real_c(domainSize[0]), real_c(domainSize[1]), real_c(domainSize[2]) );
+   auto blocks = blockforest::createUniformBlockGrid( blocksPerDirection[0], blocksPerDirection[1], blocksPerDirection[2],
+                                                      blockSizeInCells[0], blockSizeInCells[1], blockSizeInCells[2], dx,
+                                                      0, false, false,
+                                                      true, true, false, //periodicity
+                                                      false );
+
+   WALBERLA_LOG_INFO_ON_ROOT(" - blocks = " << blocksPerDirection << ", block size = " << blockSizeInCells );
+
+
+   //write domain decomposition to file
+   if( vtkIOFreq > 0 )
+   {
+      vtk::writeDomainDecomposition( blocks, "initial_domain_decomposition", baseFolderVTK );
+   }
+
+
+   /////////////////
+   // PE COUPLING //
+   /////////////////
+
+   auto rpdDomain = 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>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   // bounding planes
+   createPlaneSetup(ps,ss,blocks->getDomain(), wallVelocity);
+
+   // create sphere and store Uid
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( diameter * real_t(0.5) );
+
+   walberla::id_t sphereUid = 0;
+   if (rpdDomain->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);
+
+   // set up RPD functionality
+   std::function<void(void)> syncCall = [ps,rpdDomain](){
+      const real_t overlap = real_t( 1.5 );
+      mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+      syncNextNeighborFunc(*ps, *rpdDomain, overlap);
+   };
+
+   syncCall();
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // add omega bulk field
+   BlockDataID omegaBulkFieldID = field::addToStorage<ScalarField_T>( blocks, "omega bulk field", omegaBulk, field::fzyx, uint_t(0) );
+
+   // create the lattice model
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+   real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega );
+   real_t lambda_d = lbm::collision_model::TRT::lambda_d( omega, magicNumber );
+   //LatticeModel_T latticeModel = LatticeModel_T(omegaBulk, lambda_d, lambda_e);
+   LatticeModel_T latticeModel = LatticeModel_T(omegaBulkFieldID, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(lbm::collision_model::D3Q19MRT( omegaBulk, omegaBulk, lambda_d, lambda_e, lambda_e, lambda_d ));
+#else
+   // generated KBC
+   LatticeModel_T latticeModel = LatticeModel_T(omega);
+#endif
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (fzyx)", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         FieldGhostLayers, field::fzyx );
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field", FieldGhostLayers );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::fzyx, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // set up coupling functionality
+   lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+   lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque;
+
+   // map sphere into the LBM simulation
+   ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, CLI_Flag);
+
+   // map planes
+   ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, CLI_Flag); //TODO: was SBB_Flag?
+
+   // initialize Couette velocity profile in whole domain
+   if(initializeVelocityProfile)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT("Initializing Couette velocity profile.");
+      initializeCouetteProfile<BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, domainHeight, wallVelocity);
+   }
+
+   // update bulk omega in all cells to adapt to changed particle position
+   if(useOmegaBulkAdaption)
+   {
+      using OmegaBulkAdapter_T = lbm_mesapd_coupling::OmegaBulkAdapter<ParticleAccessor_T, lbm_mesapd_coupling::RegularParticlesSelector>;
+      real_t defaultOmegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, real_t(1));
+      shared_ptr<OmegaBulkAdapter_T> omegaBulkAdapter = make_shared<OmegaBulkAdapter_T>(blocks, omegaBulkFieldID, accessor, defaultOmegaBulk, omegaBulk, adaptionLayerSize, lbm_mesapd_coupling::RegularParticlesSelector());
+      for (auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) {
+         (*omegaBulkAdapter)(blockIt.get());
+      }
+   }
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+   blockforest::communication::UniformBufferedScheme< Stencil_T > optimizedPDFCommunicationScheme( blocks );
+   optimizedPDFCommunicationScheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) ); // optimized sync
+
+   // create the timeloop
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+   timeloop.addFuncBeforeTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+   if( vtkIOFreq != uint_t(0) )
+   {
+      // flag field (written only once in the first time step, ghost layers are also written)
+      //auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field", vtkIOFreq, uint_t(0), false, baseFolderVTK );
+      //flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+      //timeloop.addFuncBeforeTimeStep( vtk::writeFiles( flagFieldVTK ), "VTK (flag field data)" );
+
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", vtkIOFreq, uint_t(0), false, baseFolderVTK );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+      pdfFieldVTK->addCellInclusionFilter( fluidFilter );
+
+      //AABB sliceAABB( real_t(0),             real_c(domainSize[1])*real_t(0.5)-real_t(1), 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 );
+      //pdfFieldVTK->addCellInclusionFilter( aabbSliceFilter );
+
+      //vtk::ChainedFilter combinedSliceFilter;
+      //combinedSliceFilter.addFilter( fluidFilter );
+      //combinedSliceFilter.addFilter( aabbSliceFilter );
+      //pdfFieldVTK->addCellInclusionFilter( combinedSliceFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+   }
+
+   auto bhSweep = BoundaryHandling_T::getBlockSweep( boundaryHandlingID );
+   auto lbmSweep = LatticeModel_T::Sweep( pdfFieldID );
+
+   // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+   timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                  << Sweep(bhSweep, "Boundary Handling" );
+
+   // stream + collide LBM step
+   timeloop.add() << Sweep( lbmSweep, "LB sweep" );
+
+
+   // add force evaluation and logging
+   real_t normalizationFactor = math::pi / real_t(8) * densityFluid * shearRate * shearRate * wallDistance * wallDistance * diameter * diameter ;
+   std::string loggingFileName( baseFolderLogging + "/LoggingForcesNearPlane");
+   loggingFileName += "_D" + std::to_string(uint_c(diameter));
+   loggingFileName += "_Re" + std::to_string(uint_c(ReynoldsNumberShear));
+   loggingFileName += "_WD" + std::to_string(uint_c(normalizedWallDistance * real_t(1000)));
+   loggingFileName += "_bvrf" + std::to_string(uint_c(bulkViscRateFactor));
+   if(useOmegaBulkAdaption) loggingFileName += "_uOBA" + std::to_string(uint_c(adaptionLayerSize));
+   loggingFileName += "_mn" + std::to_string(magicNumber);
+   loggingFileName += ".txt";
+   WALBERLA_LOG_INFO_ON_ROOT(" - writing logging file " << loggingFileName);
+   SpherePropertyLogger<ParticleAccessor_T >logger( accessor, sphereUid, loggingFileName, fileIO, normalizationFactor, normalizationFactor, physicalTimeScale);
+
+   // compute reference values from literature
+
+   const real_t normalizedGapSize = normalizedWallDistance - real_t(0.5);
+
+   // drag correlation for the drag coefficient
+   const real_t standardDragCorrelation = real_t(24) / ReynoldsNumberShear * (real_t(1) + real_t(0.15) * std::pow(ReynoldsNumberShear, real_t(0.687))); // Schiller-Naumann correlation
+   const real_t dragCorrelationWithGapSizeStokes = real_t(24) / ReynoldsNumberShear * (real_t(1) + real_t(0.138) * std::exp(real_t(-2) * normalizedGapSize) + real_t(9)/( real_t(16) * (real_t(1) + real_t(2) * normalizedGapSize) ) ); // Goldman et al. (1967)
+   const real_t alphaDragS = real_t(0.15) - real_t(0.046) * ( real_t(1) - real_t(0.16) * normalizedGapSize * normalizedGapSize ) * std::exp( -real_t(0.7) *  normalizedGapSize);
+   const real_t betaDragS = real_t(0.687) + real_t(0.066)*(real_t(1) - real_t(0.76) * normalizedGapSize * normalizedGapSize) * std::exp( - std::pow( normalizedGapSize, real_t(0.9) ) );
+   const real_t dragCorrelationZeng = dragCorrelationWithGapSizeStokes * ( real_t(1) + alphaDragS * std::pow( ReynoldsNumberShear, betaDragS ) ); // Zeng et al. (2009) - Eqs. (13) and (14)
+
+   // lift correlations for the lift coefficient
+   const real_t liftCorrelationZeroGapStokes = real_t(5.87); // Leighton, Acrivos (1985)
+   const real_t liftCorrelationZeroGap = real_t(3.663) / std::pow( ReynoldsNumberShear * ReynoldsNumberShear + real_t(0.1173), real_t(0.22) ); //  Zeng et al. (2009) - Eq. (19)
+   const real_t alphaLiftS = - std::exp( -real_t(0.3) + real_t(0.025) * ReynoldsNumberShear);
+   const real_t betaLiftS = real_t(0.8) + real_t(0.01) * ReynoldsNumberShear;
+   const real_t lambdaLiftS = ( real_t(1) - std::exp(-normalizedGapSize)) * std::pow( ReynoldsNumberShear / real_t(250), real_t(5) / real_t(2) );
+   const real_t liftCorrelationZeng = liftCorrelationZeroGap * std::exp( - real_t(0.5) * normalizedGapSize * std::pow( ReynoldsNumberShear / real_t(250), real_t(4)/real_t(3))) *
+                                      ( std::exp( alphaLiftS * std::pow( normalizedGapSize, betaLiftS ) ) - lambdaLiftS ); // Zeng et al. (2009) - Eqs. (28) and (29)
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+
+   const uint_t checkingFrequency = uint_c( physicalCheckingFrequency * physicalTimeScale );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Starting simulation with at maximum of " << timesteps << " time steps");
+   WALBERLA_LOG_INFO_ON_ROOT("Convergence checking frequency = " << checkingFrequency << " (physically = " << physicalCheckingFrequency << ") until relative difference of " << relativeChangeConvergenceEps << " is reached.");
+
+   real_t maxDragCurrentCheckingPeriod = -math::Limits<real_t>::inf();
+   real_t minDragCurrentCheckingPeriod = math::Limits<real_t>::inf();
+   real_t maxLiftCurrentCheckingPeriod = -math::Limits<real_t>::inf();
+   real_t minLiftCurrentCheckingPeriod = math::Limits<real_t>::inf();
+   uint_t timestep = 0;
+
+   // time loop
+   while( true )
+   {
+      // perform a single simulation step
+      timeloop.singleStep( timeloopTiming );
+
+      // average force
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, averageHydrodynamicForceTorque, *accessor );
+
+      // evaluation
+      timeloopTiming["Logging"].start();
+      logger(timestep);
+      timeloopTiming["Logging"].end();
+
+      // reset after logging here
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor );
+
+      // check for termination
+      real_t curDrag = logger.getDragCoefficient();
+      real_t curLift = logger.getLiftCoefficient();
+
+      if(std::isinf(curDrag) || std::isnan(curDrag)) WALBERLA_ABORT("Found invalid drag value " << curDrag << " in time step " << timestep);
+
+      maxDragCurrentCheckingPeriod = std::max( maxDragCurrentCheckingPeriod, curDrag);
+      minDragCurrentCheckingPeriod = std::min( minDragCurrentCheckingPeriod, curDrag);
+      maxLiftCurrentCheckingPeriod = std::max( maxLiftCurrentCheckingPeriod, curLift);
+      minLiftCurrentCheckingPeriod = std::min( minLiftCurrentCheckingPeriod, curLift);
+
+      real_t dragDiffCurrentCheckingPeriod = std::fabs(maxDragCurrentCheckingPeriod - minDragCurrentCheckingPeriod)/std::fabs(maxDragCurrentCheckingPeriod);
+      real_t liftDiffCurrentCheckingPeriod = std::fabs(maxLiftCurrentCheckingPeriod - minLiftCurrentCheckingPeriod)/std::fabs(maxLiftCurrentCheckingPeriod);
+
+      // continuous output during simulation
+      if( timestep % (checkingFrequency * uint_t(10)) == 0)
+      {
+         WALBERLA_LOG_INFO_ON_ROOT("Drag: current C_D = " << curDrag );
+         WALBERLA_LOG_INFO_ON_ROOT(" - standard C_D = " <<  standardDragCorrelation );
+         WALBERLA_LOG_INFO_ON_ROOT(" - C_D ( Stokes fit ) = " << dragCorrelationWithGapSizeStokes );
+         WALBERLA_LOG_INFO_ON_ROOT(" - C_D ( Zeng ) = " << dragCorrelationZeng );
+
+         WALBERLA_LOG_INFO_ON_ROOT("Lift: current C_L = " << curLift );
+         WALBERLA_LOG_INFO_ON_ROOT(" - C_L ( Stokes, zero gap ) = " << liftCorrelationZeroGapStokes);
+         WALBERLA_LOG_INFO_ON_ROOT(" - C_L ( zero gap ) = " << liftCorrelationZeroGap);
+         WALBERLA_LOG_INFO_ON_ROOT(" - C_L ( Zeng ) = " << liftCorrelationZeng);
+
+         WALBERLA_LOG_INFO_ON_ROOT( "Drag difference [(max-min)/max] = " << dragDiffCurrentCheckingPeriod << ", lift difference = " << liftDiffCurrentCheckingPeriod);
+      }
+
+      // check for convergence ( = difference between min and max values in current checking period is below limit)
+      if( timestep % checkingFrequency == 0 && timestep > 0 )
+      {
+         if( dragDiffCurrentCheckingPeriod < relativeChangeConvergenceEps &&
+             liftDiffCurrentCheckingPeriod < relativeChangeConvergenceEps )
+         {
+            WALBERLA_LOG_INFO_ON_ROOT("Forces converged with an eps of " << relativeChangeConvergenceEps );
+            WALBERLA_LOG_INFO_ON_ROOT(" - drag min = " << minDragCurrentCheckingPeriod << " , max = " << maxDragCurrentCheckingPeriod);
+            WALBERLA_LOG_INFO_ON_ROOT(" - lift min = " << minLiftCurrentCheckingPeriod << " , max = " << maxLiftCurrentCheckingPeriod);
+            break;
+         }
+
+         //reset min and max values for new checking period
+         maxDragCurrentCheckingPeriod = -math::Limits<real_t>::inf();
+         minDragCurrentCheckingPeriod = math::Limits<real_t>::inf();
+         maxLiftCurrentCheckingPeriod = -math::Limits<real_t>::inf();
+         minLiftCurrentCheckingPeriod = math::Limits<real_t>::inf();
+      }
+      ++timestep;
+
+   }
+
+   timeloopTiming.logResultOnRoot();
+
+   std::string resultFileName( baseFolderLogging + "/ResultForcesNearPlane");
+   resultFileName += "_D" + std::to_string(uint_c(diameter));
+   resultFileName += "_Re" + std::to_string(uint_c(ReynoldsNumberShear));
+   resultFileName += "_WD" + std::to_string(uint_c(normalizedWallDistance * real_t(1000)));
+   resultFileName += "_bvrf" + std::to_string(uint_c(bulkViscRateFactor));
+   if(useOmegaBulkAdaption) resultFileName += "_uOBA" + std::to_string(uint_c(adaptionLayerSize));
+   resultFileName += "_mn" + std::to_string(magicNumber);
+   resultFileName += ".txt";
+
+   WALBERLA_LOG_INFO_ON_ROOT(" - writing final result to file " << resultFileName);
+   logFinalResult(resultFileName, ReynoldsNumberShear, normalizedWallDistance, diameter, domainSize[0], domainSize[1], domainSize[2],
+                  logger.getDragCoefficient(), dragCorrelationZeng, logger.getLiftCoefficient(), liftCorrelationZeng, timestep, real_c(timestep) / physicalTimeScale );
+
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace forces_on_sphere_near_plane
+
+int main( int argc, char **argv ){
+   forces_on_sphere_near_plane::main(argc, argv);
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/GeneratedLBM.py b/apps/benchmarks/FluidParticleCoupling/GeneratedLBM.py
new file mode 100644
index 0000000000000000000000000000000000000000..37d1a35ee5e3c18f79764ec4308cd4f2a93bbbbc
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/GeneratedLBM.py
@@ -0,0 +1,188 @@
+import sympy as sp
+import pystencils as ps
+from lbmpy.creationfunctions import create_lb_method_from_existing, create_lb_ast, create_lb_method
+from lbmpy_walberla import generate_lattice_model
+from pystencils_walberla import CodeGeneration
+
+from lbmpy.creationfunctions import create_lb_collision_rule
+from lbmpy.moments import MOMENT_SYMBOLS, is_even, get_order
+from lbmpy.stencils import get_stencil
+from lbmpy.maxwellian_equilibrium import get_moments_of_discrete_maxwellian_equilibrium
+from lbmpy.forcemodels import *
+
+from collections import OrderedDict
+from lbmpy.methods import create_with_discrete_maxwellian_eq_moments
+
+with CodeGeneration() as ctx:
+
+    # methods: TRTlike, KBC, SRT, cumulant
+    generatedMethod = "TRTlike"
+
+    print("Generating " + generatedMethod + " LBM method")
+
+    #
+    # x,y,z = MOMENT_SYMBOLS
+    #
+    # e_sq = x**2 + y**2 + z**2
+    #
+    # moments = [0] * 19
+    #
+    # moments[0] = sp.sympify(1)
+    # moments[1] = 19 * e_sq - 30
+    # moments[2] = (21 * e_sq**2 - 53 * e_sq + 24) / 2
+    #
+    # moments[3] = x
+    # moments[5] = y
+    # moments[7] = z
+    #
+    # moments[4] = (5*e_sq - 9) * x
+    # moments[6] = (5*e_sq - 9) * y
+    # moments[8] = (5*e_sq - 9) * z
+    #
+    # moments[9] = 3 * x**2 - e_sq
+    # moments[11] = y**2 - z**2
+    #
+    # moments[13] = x * y
+    # moments[14] = y * z
+    # moments[15] = x * z
+    #
+    # moments[10] = (3* e_sq - 5)*(3*x**2 - e_sq)
+    # moments[12] = (3* e_sq - 5)*(y**2 - z**2)
+    # moments[16] = (y**2 - z**2) * x
+    # moments[17] = (z**2 - x**2) * y
+    # moments[18] = (x**2 - y**2) * z
+    #
+    # m_eq = get_moments_of_discrete_maxwellian_equilibrium(stencil, moments, order=2, c_s_sq=sp.Rational(1,3))
+    #
+    # rr_dict = OrderedDict(zip(moments, omega))
+    #
+    # forcing = sp.symbols("forcing_:%d" % 3)
+    # forcing=(sp.symbols("fx"),0,0)
+    # forcemodel=Luo(forcing) #None
+    # method = create_with_discrete_maxwellian_eq_moments(stencil, rr_dict, compressible=False, force_model=forcemodel)
+    # at this stage, we have the MRT model from the LBM book!
+
+     #this is the schiller / walberla MRT
+
+    if generatedMethod == "TRTlike":
+         stencil = get_stencil("D3Q19", 'walberla')
+         omega = sp.symbols("omega_:%d" % len(stencil))
+         method = create_lb_method(stencil=stencil, method='mrt', maxwellian_moments=False)
+
+         def modification_func(moment, eq, rate):
+             omegaVisc = sp.Symbol("omega_visc")
+             omegaBulk = ps.fields("omega_bulk: [3D]", layout='fzyx')# sp.Symbol("omega_bulk")
+             omegaMagic = sp.Symbol("omega_magic")
+             if get_order(moment) <= 1:
+                 return moment, eq, 0
+             elif rate == omega[1]:
+                 return moment, eq, omegaBulk.center_vector
+             elif rate == omega[2]:
+                 return moment, eq, omegaBulk.center_vector
+             elif is_even(moment):
+                 return moment, eq, omegaVisc
+             else:
+                 return moment, eq, omegaMagic
+
+         my_method = create_lb_method_from_existing(method, modification_func)
+
+         # optimizations
+
+         collision_rule = create_lb_collision_rule(lb_method=my_method,
+                             optimization={"cse_global": True,
+                                           "cse_pdfs": False,
+                                           #"split": True
+                                           } )
+
+         generate_lattice_model(ctx, 'GeneratedLBM', collision_rule)
+
+    elif generatedMethod == "KBC":
+        methodName = 'trt-kbc-n4'
+        #omega_field = ps.fields("omegaField(1): [3D]", layout='fzyx')  omega_output_field=omega_field.center,
+        collision_rule = create_lb_collision_rule(method=methodName,entropic=True, stencil=get_stencil("D3Q27", 'walberla'), compressible=True,
+                        optimization={"cse_global": True,
+                                      "cse_pdfs": False,
+                                      "split": True})
+        generate_lattice_model(ctx, 'GeneratedLBM', collision_rule)
+
+    elif generatedMethod == "SRT":
+        methodName = 'srt'
+        collision_rule = create_lb_collision_rule(method=methodName,stencil=get_stencil("D3Q19", 'walberla'),
+                     optimization={"cse_global": True,
+                                   "cse_pdfs": False,
+                                   "split": True})
+        generate_lattice_model(ctx, 'GeneratedLBM', collision_rule)
+
+    elif generatedMethod == "cumulant":
+
+        x,y,z = MOMENT_SYMBOLS
+
+        cumulants = [0] * 27
+
+        cumulants[0] = sp.sympify(1) #000
+
+        cumulants[1] = x #100
+        cumulants[2] = y #010
+        cumulants[3] = z #001
+
+        cumulants[4] = x*y #110
+        cumulants[5] = x*z #101
+        cumulants[6] = y*z #011
+
+        cumulants[7] = x**2 - y**2 #200 - 020
+        cumulants[8] = x**2 - z**2 #200 - 002
+        cumulants[9] = x**2 + y**2 + z**2 #200 + 020 + 002
+
+        cumulants[10] = x*y**2 + x*z**2 #120 + 102
+        cumulants[11] = x**2*y + y*z**2 #210 + 012
+        cumulants[12] = x**2*z + y**2*z #201 + 021
+        cumulants[13] = x*y**2 - x*z**2 #120 - 102
+        cumulants[14] = x**2*y - y*z**2 #210 - 012
+        cumulants[15] = x**2*z - y**2*z #201 - 021
+
+        cumulants[16] = x*y*z #111
+
+        cumulants[17] = x**2*y**2 - 2*x**2*z**2 + y**2*z**2 # 220- 2*202 +022
+        cumulants[18] = x**2*y**2 + x**2*z**2 - 2*y**2*z**2 # 220 + 202 - 2*002
+        cumulants[19] = x**2*y**2 + x**2*z**2 + y**2*z**2 # 220 + 202 + 022
+
+        cumulants[20] = x**2*y*z # 211
+        cumulants[21] = x*y**2*z # 121
+        cumulants[22] = x*y*z**2 # 112
+
+        cumulants[23] = x**2*y**2*z # 221
+        cumulants[24] = x**2*y*z**2 # 212
+        cumulants[25] = x*y**2*z**2 # 122
+
+        cumulants[26] = x**2*y**2*z**2 # 222
+
+
+        from lbmpy.moments import get_order
+        def get_relaxation_rate(cumulant, omega):
+            if get_order(cumulant) <= 1:
+                return 0
+            elif get_order(cumulant) == 2 and cumulant != x**2 + y**2 + z**2:
+                return omega
+            else:
+                return 1
+
+        stencil = get_stencil("D3Q27", 'walberla')
+
+        omega = sp.Symbol("omega")
+        rr_dict = OrderedDict((c, get_relaxation_rate(c, omega))
+                              for c in cumulants)
+
+        from lbmpy.methods import create_with_continuous_maxwellian_eq_moments
+        my_method = create_with_continuous_maxwellian_eq_moments(stencil, rr_dict, cumulant=True, compressible=True)
+
+        collision_rule = create_lb_collision_rule(lb_method=my_method,
+                            optimization={"cse_global": True,
+                                          "cse_pdfs": False})
+
+        generate_lattice_model(ctx, 'GeneratedLBM', collision_rule)
+
+    else:
+        print("Invalid generated method string! " + generatedMethod)
+
+
+
diff --git a/apps/benchmarks/FluidParticleCoupling/GeneratedLBMWithForce.py b/apps/benchmarks/FluidParticleCoupling/GeneratedLBMWithForce.py
new file mode 100644
index 0000000000000000000000000000000000000000..00200a679512ecfcce9dd57113cac2fcf116cbd0
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/GeneratedLBMWithForce.py
@@ -0,0 +1,81 @@
+import sympy as sp
+import pystencils as ps
+from lbmpy.creationfunctions import create_lb_method_from_existing, create_lb_ast, create_lb_method
+from lbmpy_walberla import generate_lattice_model
+from pystencils_walberla import CodeGeneration
+
+from lbmpy.creationfunctions import create_lb_collision_rule
+from lbmpy.moments import MOMENT_SYMBOLS, is_even, get_order
+from lbmpy.stencils import get_stencil
+from lbmpy.maxwellian_equilibrium import get_moments_of_discrete_maxwellian_equilibrium
+from lbmpy.forcemodels import *
+
+from collections import OrderedDict
+from lbmpy.methods import create_with_discrete_maxwellian_eq_moments
+
+with CodeGeneration() as ctx:
+
+    forcing=(sp.symbols("fx"),0,0)
+    forcemodel=Luo(forcing) #None
+
+    # methods: TRTlike, KBC, SRT
+    generatedMethod = "TRTlike"
+
+    if generatedMethod == "TRTlike":
+        stencil = get_stencil("D3Q19", 'walberla')
+        omega = sp.symbols("omega_:%d" % len(stencil))
+
+        methodWithForce = create_lb_method(stencil=stencil, method='mrt', maxwellian_moments=False,force_model=forcemodel)
+
+        def modification_func(moment, eq, rate):
+            omegaVisc = sp.Symbol("omega_visc")
+            omegaBulk = ps.fields("omega_bulk: [3D]", layout='fzyx')# = sp.Symbol("omega_bulk")
+            #omegaBulk = sp.Symbol("omega_bulk")
+            omegaMagic = sp.Symbol("omega_magic")
+            if get_order(moment) <= 1:
+                return moment, eq, 0
+            elif rate == omega[1]:
+                return moment, eq, omegaBulk.center_vector
+            elif rate == omega[2]:
+                return moment, eq, omegaBulk.center_vector
+            elif is_even(moment):
+                return moment, eq, omegaVisc
+            else:
+                return moment, eq, omegaMagic
+
+        my_methodWithForce = create_lb_method_from_existing(methodWithForce, modification_func)
+
+        collision_rule = create_lb_collision_rule(lb_method=my_methodWithForce)
+        #    ,
+        #                     optimization={"cse_global": True,
+        #                                   "cse_pdfs": False,
+        #                                   "split": True,
+        #                                   "vectorization":{'instruction_set': 'avx',
+        #                                                    'nontemporal': True,
+        #                                                    'assume_inner_stride_one': True}})
+
+        generate_lattice_model(ctx, 'GeneratedLBMWithForce', collision_rule)
+
+    elif generatedMethod == "SRT":
+        collision_rule = create_lb_collision_rule(method='srt',stencil=get_stencil("D3Q19", 'walberla'), force_model=forcemodel,
+                            optimization={"cse_global": True,
+                                          "cse_pdfs": False,
+                                          #"split": True
+                                          })
+        generate_lattice_model(ctx, 'GeneratedLBMWithForce', collision_rule)
+
+    elif generatedMethod == "TRT":
+        collision_rule = create_lb_collision_rule(method='trt',stencil=get_stencil("D3Q19", 'walberla'), force_model=forcemodel, maxwellian_moments=False)
+        generate_lattice_model(ctx, 'GeneratedLBMWithForce', collision_rule)
+
+
+    elif generatedMethod == "KBC":
+        collision_rule = create_lb_collision_rule(method='trt-kbc-n4',entropic=True, stencil=get_stencil("D3Q27", 'walberla'), compressible=True, force_model=forcemodel,
+                            optimization={"cse_global": True,
+                                          "cse_pdfs": False,
+                                          #"split": True
+                                          })
+        generate_lattice_model(ctx, 'GeneratedLBMWithForce', collision_rule)
+
+    else:
+        print("Invalid generated method string! " + generatedMethod)
diff --git a/apps/benchmarks/FluidParticleCoupling/LubricationForceEvaluation.cpp b/apps/benchmarks/FluidParticleCoupling/LubricationForceEvaluation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6182af2dcd78f41e8b5da294644560b7502e50e7
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/LubricationForceEvaluation.cpp
@@ -0,0 +1,781 @@
+//======================================================================================================================
+//
+//  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 LubricationForceEvaluation.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.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/math/all.h"
+#include "core/mpi/Broadcast.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/logging/all.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/StabilityChecker.h"
+#include "field/communication/PackInfo.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.h"
+
+#include "lbm_mesapd_coupling/DataTypes.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/Reconstructor.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/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h"
+#include "lbm_mesapd_coupling/utility/OmegaBulkAdaption.h"
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/SpringDashpot.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/mpi/ReduceProperty.h"
+#include "mesa_pd/mpi/ContactFilter.h"
+#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+#include "lbm/vtk/all.h"
+
+#include "GeneratedLBM.h"
+
+#include <functional>
+
+#define USE_TRT_LIKE_LATTICE_MODEL
+
+namespace lubrication_force_evaluation
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::GeneratedLBM;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+using ScalarField_T = GhostLayerField< real_t, 1>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag( "fluid" );
+const FlagUID FreeSlip_Flag( "free slip" );
+const FlagUID MO_Flag( "moving obstacle" );
+const FlagUID FormerMO_Flag( "former moving obstacle" );
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using FreeSlip_T = lbm::FreeSlip< LatticeModel_T, FlagField_T>;
+   using MO_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, FreeSlip_T, MO_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  FreeSlip_T( "FreeSlip", FreeSlip_Flag, pdfField, flagField, fluid ),
+                                  MO_T( "MO", MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      const auto freeslip = flagField->getFlag( FreeSlip_Flag );
+
+      CellInterval domainBB = storage->getDomainCellBB();
+      domainBB.xMin() -= cell_idx_c( FieldGhostLayers );
+      domainBB.xMax() += cell_idx_c( FieldGhostLayers );
+
+      domainBB.yMin() -= cell_idx_c( FieldGhostLayers );
+      domainBB.yMax() += cell_idx_c( FieldGhostLayers );
+
+      // SOUTH
+      CellInterval south( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMin(), domainBB.zMax() );
+      storage->transformGlobalToBlockLocalCellInterval( south, *block );
+      handling->forceBoundary( freeslip, south );
+
+      // NORTH
+      CellInterval north( domainBB.xMin(), domainBB.yMax(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() );
+      storage->transformGlobalToBlockLocalCellInterval( north, *block );
+      handling->forceBoundary( freeslip, north );
+
+      domainBB.zMin() -= cell_idx_c( FieldGhostLayers );
+      domainBB.zMax() += cell_idx_c( FieldGhostLayers );
+
+      // BOTTOM
+      CellInterval bottom( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMin() );
+      storage->transformGlobalToBlockLocalCellInterval( bottom, *block );
+      handling->forceBoundary( freeslip, bottom );
+
+      // TOP
+      CellInterval top( domainBB.xMin(), domainBB.yMin(), domainBB.zMax(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() );
+      storage->transformGlobalToBlockLocalCellInterval( top, *block );
+      handling->forceBoundary( freeslip, top );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+//*******************************************************************************************************************
+
+//////////
+// MAIN //
+//////////
+
+//*******************************************************************************************************************
+/*!\brief Evaluates the hydrodynamic force for the lubrication case for sphere-sphere and sphere-wall case.
+ *
+ * 4 different setups are available that change the relative velocity to investigate the different components individually.
+ * All particles are fixed but have a small velocity which is a valid assumption in Stokes flow.
+ * The simulations are run until steady state is reached.
+ *
+ * The positions are always shifted by a random offset to account for the dependence of the resulting force/torque
+ * on the exact location w.r.t. the underlying grid.
+ * This can be eliminated by averaging over several realizations of the same physical setup.
+ *
+ * see also Rettinger, Ruede 2020 for the details
+ */
+//*******************************************************************************************************************
+
+int main( int argc, char **argv )
+{
+
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   bool sphSphTest     = true;
+   bool fileIO         = true;
+   uint_t vtkIOFreq = 0;
+   std::string fileNameEnding = "";
+   std::string baseFolder = "vtk_out_Lubrication";
+
+   real_t radius = real_t(5);
+   real_t ReynoldsNumber = real_t(1e-2);
+   real_t tau = real_t(1);
+   real_t gapSize = real_t(0);
+   real_t bulkViscRateFactor = real_t(1);
+   real_t magicNumber = real_t(3)/real_t(16);
+   bool useOmegaBulkAdaption = false;
+   real_t adaptionLayerSize = real_t(2);
+
+   // 1: translation in normal direction -> normal Lubrication force
+   // 2: translation in tangential direction -> tangential Lubrication force and torque
+   // 3: rotation around tangential direction -> force & torque
+   // 4: rotation around normal direction -> torque
+   uint_t setup = 1;
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--sphWallTest" )               == 0 ) { sphSphTest = false; continue; }
+      if( std::strcmp( argv[i], "--noLogging" )                 == 0 ) { fileIO = false; continue; }
+      if( std::strcmp( argv[i], "--vtkIOFreq" )                 == 0 ) { vtkIOFreq = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--setup" )                 == 0 ) { setup = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--baseFolder" )                == 0 ) { baseFolder = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--diameter" )                  == 0 ) { radius = real_t(0.5)*real_c(std::atof(argv[++i])); continue; }
+      if( std::strcmp( argv[i], "--gapSize" )              == 0 ) { gapSize = real_c(std::atof(argv[++i])); continue; }
+      if( std::strcmp( argv[i], "--bulkViscRateFactor" )        == 0 ) { bulkViscRateFactor = real_c(std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--tau" )                  == 0 ) { tau = real_c(std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--fileName" )                  == 0 ) { fileNameEnding = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--magicNumber" )               == 0 ) { magicNumber = real_c(std::atof(argv[++i])); continue; }
+      if( std::strcmp( argv[i], "--useOmegaBulkAdaption" )      == 0 ) { useOmegaBulkAdaption = true; continue; }
+      if( std::strcmp( argv[i], "--adaptionLayerSize" )         == 0 ) { adaptionLayerSize = real_c(std::atof( argv[++i] )); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   uint_t xSize = uint_c(real_t(24) * radius);
+   uint_t ySize = uint_c(real_t(24) * radius);
+   uint_t zSize = uint_c(real_t(24) * radius);
+
+   uint_t xBlocks = uint_c(4); // number of blocks in x-direction
+   uint_t yBlocks = uint_c(1); // number of blocks in y-direction
+   uint_t zBlocks = uint_c(1); // number of blocks in z-direction
+
+   uint_t xCells = xSize / xBlocks; // number of cells in x-direction on each block
+   uint_t yCells = ySize / yBlocks; // number of cells in y-direction on each block
+   uint_t zCells = zSize / zBlocks; // number of cells in z-direction on each block
+
+   // Perform missing variable calculations
+   real_t omega = real_t(1) / tau;
+   real_t nu = walberla::lbm::collision_model::viscosityFromOmega(omega);
+   real_t velocity = ReynoldsNumber * nu / (real_t(2) * radius);
+   real_t omegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor);
+
+   uint_t timesteps = uint_c( 10000 );
+
+   real_t fStokes = real_t(6) * math::pi * nu * radius * velocity;
+   real_t tStokes = real_t(8) * math::pi * nu * radius * radius * velocity;
+
+   WALBERLA_LOG_INFO_ON_ROOT_SECTION()
+   {
+      std::stringstream ss;
+
+      if ( sphSphTest )
+      {
+         ss << "-------------------------------------------------------\n"
+            << "   Parameters for the sphere-sphere lubrication test \n"
+            << "-------------------------------------------------------\n";
+      }
+      else
+      {
+         ss << "-------------------------------------------------------\n"
+            << "   Parameters for the sphere-wall lubrication test \n"
+            << "-------------------------------------------------------\n";
+      }
+      ss << " omega        = " << omega    << "\n"
+         << " omegaBulk    = " << omegaBulk << " (bvrf " << bulkViscRateFactor << ")\n"
+         << " use omega bulk adaption = " << useOmegaBulkAdaption << " (adaption layer size = " << adaptionLayerSize << ")\n"
+         << " radius       = " << radius   << "\n"
+         << " velocity     = " << velocity << "\n"
+         << " Re           = " << ReynoldsNumber << "\n"
+         << " gap size     = " << gapSize << "\n"
+         << " time steps   = " << timesteps << "\n"
+         << " fStokes      = " << fStokes << "\n"
+         << " setup        = " << setup << "\n"
+         << "-------------------------------------------------------\n"
+         << " domainSize = " << xSize << " x " << ySize << " x " << zSize  << "\n"
+         << " blocks     = " << xBlocks << " x " << yBlocks << " x " << zBlocks  << "\n"
+         << " blockSize  = " << xCells << " x " << yCells << " x " << zCells  << "\n"
+         << "-------------------------------------------------------\n";
+      WALBERLA_LOG_INFO( ss.str() );
+   }
+
+
+   auto blocks = blockforest::createUniformBlockGrid( xBlocks, yBlocks, zBlocks, xCells, yCells, zCells, real_t(1),
+                                                      0, false, false,
+                                                      sphSphTest, true, true,  //periodicity
+                                                      false );
+
+   //////////////////
+   // RPD COUPLING //
+   //////////////////
+
+   auto rpdDomain = 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>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+
+   uint_t id1 (0);
+   uint_t id2 (0);
+
+   uint_t randomSeed = uint_c(std::chrono::system_clock::now().time_since_epoch().count());
+   mpi::broadcastObject(randomSeed); // root process chooses seed and broadcasts it
+   std::mt19937 randomNumberGenerator(randomSeed);
+
+   Vector3<real_t> domainCenter( real_c(xSize) * real_t(0.5), real_c(ySize) * real_t(0.5), real_c(zSize) * real_t(0.5) );
+   Vector3<real_t> offsetVector(math::realRandom<real_t>(real_t(0), real_t(1), randomNumberGenerator),
+                                math::realRandom<real_t>(real_t(0), real_t(1), randomNumberGenerator),
+                                math::realRandom<real_t>(real_t(0), real_t(1), randomNumberGenerator));
+
+   if ( sphSphTest )
+   {
+
+      Vector3<real_t> pos1 = domainCenter + offsetVector;
+      if (rpdDomain->isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), pos1 ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos1);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         if(setup == 1)
+         {
+            // only normal translational vel
+            p.setLinearVelocity(Vector3<real_t>( velocity, real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+         } else if (setup == 2)
+         {
+            // only tangential translational velocity
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), velocity, real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+         } else if (setup == 3)
+         {
+            // only rotation around axis perpendicular to center line
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), velocity / radius, real_t(0)));
+         } else if (setup == 4)
+         {
+            // only rotation around center line
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( velocity / radius, real_t(0) , real_t(0)));
+         }
+         id1 = p.getUid();
+      }
+
+      Vector3<real_t> pos2 = pos1 + Vector3<real_t>(real_t(2) * radius + gapSize, real_t(0), real_t(0));
+      if (rpdDomain->isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), pos2 ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos2);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         if(setup == 1)
+         {
+            // only normal translational vel
+            p.setLinearVelocity(Vector3<real_t>( -velocity, real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+         } else if (setup == 2)
+         {
+            // only tangential translational velocity
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), -velocity, real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+         } else if (setup == 3)
+         {
+            // only rotation around axis perpendicular to center line
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), velocity / radius, real_t(0)));
+         } else if (setup == 4)
+         {
+            // only rotation around center line
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( -velocity / radius, real_t(0) , real_t(0)));
+         }
+         id2 = p.getUid();
+      }
+
+      mpi::allReduceInplace(id1, mpi::SUM);
+      mpi::allReduceInplace(id2, mpi::SUM);
+
+      WALBERLA_LOG_INFO_ON_ROOT("pos sphere 1 = " << pos1);
+      WALBERLA_LOG_INFO_ON_ROOT("pos sphere 2 = " << pos2);
+   } else
+   {
+      // sphere-wall test
+
+      Vector3<real_t> referenceVector(offsetVector[0], domainCenter[1], domainCenter[2]);
+
+      // create two planes
+      mesa_pd::data::Particle&& p0 = *ps->create(true);
+      p0.setPosition(referenceVector);
+      p0.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(1,0,0) ));
+      p0.setOwner(mpi::MPIManager::instance()->rank());
+      mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+      mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+      id2 = p0.getUid();
+
+      mesa_pd::data::Particle&& p1 = *ps->create(true);
+      p1.setPosition(Vector3<real_t>(real_c(xSize),0,0));
+      p1.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(-1,0,0) ));
+      p1.setOwner(mpi::MPIManager::instance()->rank());
+      mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+      mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+      Vector3<real_t> pos1 = referenceVector + Vector3<real_t>(radius + gapSize, real_t(0), real_t(0));
+      if (rpdDomain->isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), pos1 ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos1);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         if(setup == 1)
+         {
+            // only normal translational vel
+            p.setLinearVelocity(Vector3<real_t>( -velocity, real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+         } else if (setup == 2)
+         {
+            // only tangential translational velocity
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), velocity, real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+         } else if (setup == 3)
+         {
+            // only rotation around axis perpendicular to center line
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( real_t(0), velocity / radius, real_t(0)));
+         } else if (setup == 4)
+         {
+            // only rotation around center line
+            p.setLinearVelocity(Vector3<real_t>( real_t(0), real_t(0), real_t(0)));
+            p.setAngularVelocity(Vector3<real_t>( velocity / radius, real_t(0) , real_t(0)));
+         }
+         id1 = p.getUid();
+      }
+
+      mpi::allReduceInplace(id1, mpi::SUM);
+      // id2 is globally known
+
+      WALBERLA_LOG_INFO_ON_ROOT("pos plane = " << referenceVector);
+      WALBERLA_LOG_INFO_ON_ROOT("pos sphere = " << pos1);
+   }
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // add omega bulk field
+   BlockDataID omegaBulkFieldID = field::addToStorage<ScalarField_T>( blocks, "omega bulk field", omegaBulk, field::fzyx, uint_t(0) );
+
+   // create the lattice model
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+   WALBERLA_LOG_INFO_ON_ROOT("Using TRTlike model!");
+   real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega );
+   real_t lambda_d = lbm::collision_model::TRT::lambda_d( omega, magicNumber );
+   LatticeModel_T latticeModel = LatticeModel_T(omegaBulkFieldID, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(omegaBulk, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(lbm::collision_model::D3Q19MRT( omegaBulk, omegaBulk, lambda_d, lambda_e, lambda_e, lambda_d ));
+#else
+   WALBERLA_LOG_INFO_ON_ROOT("Using KBC model!");
+   // generated KBC
+   LatticeModel_T latticeModel = LatticeModel_T(omega);
+#endif
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         uint_t(1), field::fzyx );
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add body field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::fzyx, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // set up RPD functionality
+   std::function<void(void)> syncCall = [&ps,&rpdDomain](){
+      const real_t overlap = real_t( 1.5 );
+      mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+      syncNextNeighborFunc(*ps, *rpdDomain, overlap);
+   };
+
+   syncCall();
+
+   mesa_pd::mpi::ReduceProperty reduceProperty;
+
+   lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+
+
+   real_t lubricationCutOffDistanceNormal = real_t(2) / real_t(3);
+   real_t lubricationCutOffDistanceTangentialTranslational = real_t(0.5);
+   real_t lubricationCutOffDistanceTangentialRotational = real_t(0.5);
+   lbm_mesapd_coupling::LubricationCorrectionKernel lubricationCorrectionKernel(nu, [](real_t){return real_t(0);}, lubricationCutOffDistanceNormal, lubricationCutOffDistanceTangentialTranslational, lubricationCutOffDistanceTangentialRotational );
+   real_t maximalCutOffDistance = std::max(lubricationCutOffDistanceNormal, std::max(lubricationCutOffDistanceTangentialTranslational,lubricationCutOffDistanceTangentialRotational ));
+
+   lbm_mesapd_coupling::RegularParticlesSelector sphereSelector;
+
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // map particles into the LBM simulation
+   ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+   // create the timeloop
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+   blockforest::communication::UniformBufferedScheme< Stencil_T > optimizedPDFCommunicationScheme( blocks );
+   optimizedPDFCommunicationScheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) ); // optimized sync
+
+   blockforest::communication::UniformBufferedScheme< Stencil_T > fullPDFCommunicationScheme( blocks );
+   fullPDFCommunicationScheme.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) ); // full sync
+
+   timeloop.addFuncBeforeTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+
+   if( vtkIOFreq != uint_t(0) )
+   {
+      // spheres
+      auto particleVtkOutput = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleOwner>("owner");
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleLinearVelocity>("velocity");
+      auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "Particles", vtkIOFreq, baseFolder, "simulation_step");
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( particleVtkWriter ), "VTK (sphere data)" );
+
+      // flag field (written only once in the first time step, ghost layers are also written)
+      auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field", vtkIOFreq, FieldGhostLayers, false, baseFolder );
+      flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+      vtk::writeFiles( flagFieldVTK )();
+
+      // pdf field
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", vtkIOFreq, 0, false, baseFolder );
+
+      pdfFieldVTK->addBeforeFunction( fullPDFCommunicationScheme );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+      pdfFieldVTK->addCellInclusionFilter( fluidFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+
+      // omega bulk field
+      timeloop.addFuncBeforeTimeStep( field::createVTKOutput<ScalarField_T, float>( omegaBulkFieldID, *blocks, "omega_bulk_field", vtkIOFreq, uint_t(0), false, baseFolder ), "VTK (omega bulk field)" );
+
+   }
+
+   // update bulk omega in all cells to adapt to changed particle position
+   if(useOmegaBulkAdaption)
+   {
+      using OmegaBulkAdapter_T = lbm_mesapd_coupling::OmegaBulkAdapter<ParticleAccessor_T, decltype(sphereSelector)>;
+      real_t defaultOmegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, real_t(1));
+      shared_ptr<OmegaBulkAdapter_T> omegaBulkAdapter = make_shared<OmegaBulkAdapter_T>(blocks, omegaBulkFieldID, accessor, defaultOmegaBulk, omegaBulk, adaptionLayerSize, sphereSelector);
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt)
+      {
+         (*omegaBulkAdapter)(&(*blockIt));
+      }
+   }
+
+   // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+
+   timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                  << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+   // stream + collide LBM step
+   timeloop.add() << Sweep( LatticeModel_T::Sweep( pdfFieldID ), "LB stream & collide" );
+
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+   Vector3<real_t> hydForce(0.);
+   Vector3<real_t> lubForce(0.);
+   Vector3<real_t> hydTorque(0.);
+   Vector3<real_t> lubTorque(0.);
+
+   real_t curForceNorm = real_t(0);
+   real_t oldForceNorm = real_t(0);
+   real_t curTorqueNorm = real_t(0);
+   real_t oldTorqueNorm = real_t(0);
+
+   real_t convergenceLimit = real_t(1e-4);
+
+   // time loop
+   for (uint_t i = 1; i <= timesteps; ++i )
+   {
+      // perform a single simulation step -> this contains LBM and setting of the hydrodynamic interactions
+      timeloop.singleStep( timeloopTiming );
+
+      // lubrication correction
+      mesa_pd::collision_detection::AnalyticContactDetection acd;
+      acd.getContactThreshold() = maximalCutOffDistance;
+
+      {
+         auto idx1 = accessor->uidToIdx(id1);
+         if( idx1 != accessor->getInvalidIdx() )
+         {
+            auto idx2 = accessor->uidToIdx(id2);
+            if( idx2 != accessor->getInvalidIdx() )
+            {
+               mesa_pd::kernel::DoubleCast double_cast;
+               mesa_pd::mpi::ContactFilter contact_filter;
+               if (double_cast(idx1, idx2, *accessor, acd, *accessor ))
+               {
+                  if (contact_filter(acd.getIdx1(), acd.getIdx2(), *accessor, acd.getContactPoint(), *rpdDomain))
+                  {
+                     //WALBERLA_LOG_INFO(acd.getIdx1() << " " << acd.getIdx2() << " " << acd.getContactNormal() << " " << acd.getPenetrationDepth());
+                     double_cast(acd.getIdx1(), acd.getIdx2(), *accessor, lubricationCorrectionKernel, *accessor, acd.getContactNormal(), acd.getPenetrationDepth());
+                  }
+               }
+            }
+         }
+      }
+
+      if( i% 100 == 0 && i > 1 )
+      {
+         oldForceNorm = curForceNorm;
+         oldTorqueNorm = curTorqueNorm;
+
+         hydForce.reset();
+         lubForce.reset();
+         hydTorque.reset();
+         lubTorque.reset();
+
+         auto idx1 = accessor->uidToIdx(id1);
+         if( idx1 != accessor->getInvalidIdx() )
+         {
+            hydForce = accessor->getHydrodynamicForce(idx1);
+            lubForce = accessor->getForce(idx1);
+            hydTorque = accessor->getHydrodynamicTorque(idx1);
+            lubTorque= accessor->getTorque(idx1);
+         }
+
+         WALBERLA_MPI_SECTION()
+         {
+            mpi::allReduceInplace( hydForce[0], mpi::SUM );
+            mpi::allReduceInplace( hydForce[1], mpi::SUM );
+            mpi::allReduceInplace( hydForce[2], mpi::SUM );
+            mpi::reduceInplace( lubForce[0], mpi::SUM );
+            mpi::reduceInplace( lubForce[1], mpi::SUM );
+            mpi::reduceInplace( lubForce[2], mpi::SUM );
+            mpi::allReduceInplace( hydTorque[0], mpi::SUM );
+            mpi::allReduceInplace( hydTorque[1], mpi::SUM );
+            mpi::allReduceInplace( hydTorque[2], mpi::SUM );
+            mpi::reduceInplace( lubTorque[0], mpi::SUM );
+            mpi::reduceInplace( lubTorque[1], mpi::SUM );
+            mpi::reduceInplace( lubTorque[2], mpi::SUM );
+         }
+
+         curForceNorm = hydForce.length();
+         curTorqueNorm = hydTorque.length();
+
+         real_t forceDiff = std::fabs((curForceNorm - oldForceNorm) / oldForceNorm);
+         real_t torqueDiff = std::fabs((curTorqueNorm - oldTorqueNorm) / oldTorqueNorm);
+
+         WALBERLA_LOG_INFO_ON_ROOT( "F/Fs = " << hydForce/fStokes << " ( " << forceDiff << " ), T/Ts = " << hydTorque/tStokes << " ( " << torqueDiff << " )");
+
+         if( i == 100 ) {
+            WALBERLA_LOG_INFO_ON_ROOT("Flub = " << lubForce << ", Tlub = " << lubTorque);
+            WALBERLA_LOG_INFO_ON_ROOT("Flub/Fs = " << lubForce/fStokes << ", Tlub/Ts = " << lubTorque/tStokes);
+         }
+
+         if( forceDiff < convergenceLimit && torqueDiff < convergenceLimit )
+         {
+            WALBERLA_LOG_INFO_ON_ROOT("Force and torque norms converged - terminating simulation");
+            break;
+         }
+
+      }
+
+      // reset forces
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor,
+                          [](const size_t idx, auto& ac){
+                             ac.getForceRef(idx) = Vector3<real_t>(real_t(0));
+                             ac.getTorqueRef(idx) = Vector3<real_t>(real_t(0));
+                          }, *accessor );
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor );
+
+
+   }
+
+   if( fileIO )
+   {
+      std::string loggingFileName( baseFolder + "/Logging" );
+      if( sphSphTest ) loggingFileName += "_SphSph";
+      else loggingFileName += "_SphPla";
+      loggingFileName += "_Setup" + std::to_string(setup);
+      loggingFileName += "_gapSize" + std::to_string(uint_c(gapSize*real_t(100)));
+      loggingFileName += "_radius" + std::to_string(uint_c(radius));
+      loggingFileName += "_bvrf" + std::to_string(uint_c(bulkViscRateFactor));
+      loggingFileName += "_mn" + std::to_string(float(magicNumber));
+      if( useOmegaBulkAdaption ) loggingFileName += "_uOBA" + std::to_string(uint_c(adaptionLayerSize));
+      if( !fileNameEnding.empty()) loggingFileName += "_" + fileNameEnding;
+      loggingFileName += ".txt";
+
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file1;
+         file1.open( loggingFileName.c_str(), std::ofstream::app );
+         file1.setf( std::ios::unitbuf );
+         file1.precision(15);
+         file1 << radius << " " << gapSize << " " << fStokes << " "
+               << hydForce[0] << " " << hydForce[1] << " " << hydForce[2] << " "
+               << lubForce[0] << " " << lubForce[1] << " " << lubForce[2] << " "
+               << hydTorque[0] << " " << hydTorque[1] << " " << hydTorque[2] << " "
+               << lubTorque[0] << " " << lubTorque[1] << " " << lubTorque[2] << std::endl;
+         file1.close();
+      }
+   }
+
+   timeloopTiming.logResultOnRoot();
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace lubrication_force_evaluation
+
+int main( int argc, char **argv ){
+   lubrication_force_evaluation::main(argc, argv);
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/ObliqueDryCollision.cpp b/apps/benchmarks/FluidParticleCoupling/ObliqueDryCollision.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fb1e42957ef2de7b349259947d59897fa9d7efee
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/ObliqueDryCollision.cpp
@@ -0,0 +1,216 @@
+//======================================================================================================================
+//
+//  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   ObliqueDryCollision.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/common/ParticleFunctions.h"
+
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/VelocityVerletWithShape.h"
+#include "mesa_pd/kernel/LinearSpringDashpot.h"
+#include "mesa_pd/mpi/ReduceContactHistory.h"
+
+#include "core/Environment.h"
+#include "core/logging/Logging.h"
+
+#include <iostream>
+
+namespace walberla {
+namespace mesa_pd {
+
+/*
+ * Simulates oblique sphere-wall collision and checks rebound angle, i.e. the tangential part of the collision model.
+ *
+ */
+int main( int argc, char ** argv )
+{
+   walberla::mpi::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   real_t uNin_SI = real_t(1.7); // m/s
+   real_t diameter_SI = real_t(0.00318); // m
+   //real_t density_SI = real_t(2500); // kg/m**3, not used
+
+   real_t uNin       = real_t(0.02);
+   real_t diameter   = real_t(20);
+   real_t radius     = real_t(0.5) * diameter;
+   real_t density    = real_c(2.5);
+
+   // these values have actually no influence here and are just computed for completeness
+   real_t dx_SI = diameter_SI / diameter;
+   real_t dt_SI = uNin / uNin_SI * dx_SI;
+
+   real_t impactAngle       = real_t(0);
+   real_t dt                = real_t(0.1); // = (1 / #sub steps)
+   real_t frictionCoeff_s   = real_t(0.8); // no influence
+   real_t frictionCoeff_d   = real_t(0.12); // paper: 0.125+-0.007
+   std::string filename     = "TangentialCollision.txt";
+   real_t collisionTime     = real_t(80);
+   real_t nu                = real_t(0.22); //Poissons ratio
+   bool useVelocityVerlet   = false;
+   real_t restitutionCoeff  = real_t(0.83);
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--impactAngle" ) == 0 ) { impactAngle = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--dt" ) == 0 ) { dt = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--staticFriction" ) == 0 ) { frictionCoeff_s = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--dynamicFriction" ) == 0 ) { frictionCoeff_d = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--collisionTime" ) == 0 ) { collisionTime = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--nu" ) == 0 ) { nu = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--filename" ) == 0 ) { filename = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--useVV" )   == 0 ) { useVelocityVerlet = true; continue; }
+      if( std::strcmp( argv[i], "--coefficientOfRestitution" ) == 0 ) { restitutionCoeff = real_c( std::atof( argv[++i] ) ); continue; }
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("******************************************************");
+   WALBERLA_LOG_INFO_ON_ROOT("**                  NEW SIMULATION                  **");
+   WALBERLA_LOG_INFO_ON_ROOT("******************************************************");
+   WALBERLA_LOG_INFO_ON_ROOT("dt_SI = " << dt_SI);
+   WALBERLA_LOG_INFO_ON_ROOT("dx_SI = " << dx_SI);
+   WALBERLA_LOG_INFO_ON_ROOT("impactAngle = " << impactAngle);
+   WALBERLA_LOG_INFO_ON_ROOT("dt = " << dt);
+   WALBERLA_LOG_INFO_ON_ROOT("frictionCoeff_s = " << frictionCoeff_s);
+   WALBERLA_LOG_INFO_ON_ROOT("frictionCoeff_d = " << frictionCoeff_d);
+   WALBERLA_LOG_INFO_ON_ROOT("collisionTime = " << collisionTime);
+   WALBERLA_LOG_INFO_ON_ROOT("nu = " << nu);
+   WALBERLA_LOG_INFO_ON_ROOT("integrator = " << (useVelocityVerlet ? "Velocity Verlet" : "Explicit Euler"));
+   WALBERLA_LOG_INFO_ON_ROOT("restitutionCoeff = " << restitutionCoeff);
+
+
+   //init data structures
+   auto ps = walberla::make_shared<data::ParticleStorage>(2);
+   auto ss = walberla::make_shared<data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   auto sphereShape = ss->create<data::Sphere>( radius );
+   ss->shapes[sphereShape]->updateMassAndInertia(density);
+
+   const real_t particleMass =  real_t(1) / ss->shapes[sphereShape]->getInvMass();
+   const real_t Mij = particleMass; // * particleMass / ( real_t(2) * particleMass ); // Mij = M for sphere-wall collision
+   const real_t lnDryResCoeff = std::log(restitutionCoeff);
+
+   // normal material parameters
+   const real_t stiffnessN = Mij * ( math::pi * math::pi + lnDryResCoeff * lnDryResCoeff ) / (collisionTime * collisionTime); // Costa et al., Eq. 18
+   const real_t dampingN = - real_t(2) * Mij * lnDryResCoeff / collisionTime; // Costa et al., Eq. 18
+
+   WALBERLA_LOG_INFO_ON_ROOT("normal: stiffness = " << stiffnessN << ", damping = " << dampingN);
+
+   // from Thornton et al
+   const real_t kappa = real_t(2) * ( real_t(1) - nu ) / ( real_t(2) - nu ) ;
+   const real_t stiffnessT = kappa * stiffnessN;
+   const real_t dampingT = std::sqrt(kappa) * dampingN;
+
+   WALBERLA_LOG_INFO_ON_ROOT("tangential: kappa = " << kappa << ", stiffness T = " << stiffnessT << ", damping T = " << dampingT);
+
+   real_t uTin = uNin * impactAngle;
+
+   // create sphere
+   data::Particle&& p = *ps->create();
+   p.setPosition(Vec3(0,0,2*radius));
+   p.setLinearVelocity(Vec3(uTin, 0., -uNin));
+   p.setType(0);
+
+   // create plane
+   data::Particle&& p0 = *ps->create(true);
+   p0.setPosition(Vec3(0,0,0));
+   p0.setShapeID(ss->create<data::HalfSpace>(Vector3<real_t>(0,0,1)));
+   p0.setType(0);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::INFINITE);
+   data::particle_flags::set(p0.getFlagsRef(), data::particle_flags::FIXED);
+
+   // velocity verlet
+   kernel::VelocityVerletWithShapePreForceUpdate  vvPreForce( dt );
+   kernel::VelocityVerletWithShapePostForceUpdate vvPostForce( dt );
+
+   // explicit euler
+   kernel::ExplicitEulerWithShape explEuler( dt );
+
+   // collision response
+   collision_detection::AnalyticContactDetection acd;
+   kernel::DoubleCast           double_cast;
+   kernel::LinearSpringDashpot  dem(1);
+   mpi::ReduceContactHistory    rch;
+   dem.setStiffnessN(0,0,stiffnessN);
+   dem.setStiffnessT(0,0,stiffnessT);
+   dem.setDampingN(0,0,dampingN);
+   dem.setDampingT(0,0,dampingT);
+   dem.setFrictionCoefficientStatic(0,0,frictionCoeff_s);
+   dem.setFrictionCoefficientDynamic(0,0,frictionCoeff_d);
+
+   WALBERLA_LOG_DEVEL("begin: vel = " << p.getLinearVelocity() << ", contact vel: " << getVelocityAtWFPoint(0,*accessor,p.getPosition() + Vec3(0,0,-radius)) );
+
+   uint_t steps = 0;
+   real_t maxPenetration = real_t(0);
+   do
+   {
+      if(useVelocityVerlet) vvPreForce(0,*accessor);
+
+      real_t penetration;
+
+      if (double_cast(0, 1, *accessor, acd, *accessor ))
+      {
+         penetration = acd.getPenetrationDepth();
+         maxPenetration = std::max( maxPenetration, std::abs(penetration));
+
+         dem(acd.getIdx1(), acd.getIdx2(), *accessor, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth(), dt);
+         auto force = accessor->getForce(0);
+         auto torque = accessor->getTorque(0);
+         WALBERLA_LOG_INFO(steps << ": penetration = " << penetration << " || vel = " << accessor->getLinearVelocity(0) << " || force = " << force << ", torque = " << torque);
+      }
+      rch(*ps);
+
+      if(useVelocityVerlet) vvPostForce(0,*accessor);
+      else explEuler(0, *accessor);
+
+      ++steps;
+   } while (double_cast(0, 1, *accessor, acd, *accessor ) || p.getLinearVelocity()[2] < 0);
+
+   real_t uTout = p.getLinearVelocity()[0] - radius * p.getAngularVelocity()[1];
+   WALBERLA_LOG_DEVEL("end: linear vel = " << p.getLinearVelocity() << ", angular vel = " << p.getAngularVelocity());
+
+   real_t reboundAngle = uTout / uNin;
+   WALBERLA_LOG_INFO_ON_ROOT("gamma_in = " << impactAngle);
+   WALBERLA_LOG_INFO_ON_ROOT("gamma_out = " << reboundAngle);
+
+   WALBERLA_LOG_INFO_ON_ROOT("Thornton: sliding should occur if " << real_t(2) * impactAngle / ( frictionCoeff_d * ( real_t(1) + restitutionCoeff)) << " >= " << real_t(7) - real_t(1) / kappa );
+   WALBERLA_LOG_INFO_ON_ROOT("Max penetration = " << maxPenetration << " -> " << maxPenetration / radius * 100. << "% of radius");
+
+   std::ofstream file;
+   file.open( filename.c_str(), std::ios::out | std::ios::app );
+   file << impactAngle << " " << reboundAngle << "\n";
+   file.close();
+
+   return EXIT_SUCCESS;
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   return walberla::mesa_pd::main(argc, argv);
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/ObliqueWetCollision.cpp b/apps/benchmarks/FluidParticleCoupling/ObliqueWetCollision.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c8601416f4e27d421b913ea52dd56dde3abee7e4
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/ObliqueWetCollision.cpp
@@ -0,0 +1,1322 @@
+//======================================================================================================================
+//
+//  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 ObliqueWetCollision.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/waLBerlaBuildInfo.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/math/all.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/logging/all.h"
+#include "core/mpi/Broadcast.h"
+#include "core/mpi/Reduce.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/adaptors/AdaptorCreators.h"
+#include "field/communication/PackInfo.h"
+#include "field/interpolators/FieldInterpolatorCreators.h"
+#include "field/interpolators/NearestNeighborFieldInterpolator.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/Adaptors.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.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/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.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/utility/AddForceOnParticlesKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h"
+#include "lbm_mesapd_coupling/utility/OmegaBulkAdaption.h"
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/domain/BlockForestDataHandling.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/LinearSpringDashpot.h"
+#include "mesa_pd/kernel/NonLinearSpringDashpot.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/kernel/VelocityVerletWithShape.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/mpi/ReduceProperty.h"
+#include "mesa_pd/mpi/ReduceContactHistory.h"
+#include "mesa_pd/mpi/ContactFilter.h"
+#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+#include "lbm/vtk/all.h"
+
+#include "GeneratedLBM.h"
+
+#include <functional>
+
+#define USE_TRT_LIKE_LATTICE_MODEL
+
+namespace oblique_wet_collision
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::GeneratedLBM;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+using ScalarField_T = GhostLayerField< real_t, 1>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// 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" );
+const FlagUID SimplePressure_Flag("simple pressure");
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using NoSlip_T = lbm::NoSlip< LatticeModel_T, flag_t >;
+   using MO_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using SimplePressure_T = lbm::SimplePressure< LatticeModel_T, flag_t >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, NoSlip_T, MO_T, SimplePressure_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac,
+                       bool applyOutflowBCAtTop) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ), applyOutflowBCAtTop_(applyOutflowBCAtTop) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ),
+                                  MO_T( "MO", MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ),
+                                  SimplePressure_T( "SimplePressure", SimplePressure_Flag, pdfField, real_t(1) ) );
+
+      if(applyOutflowBCAtTop_)
+      {
+         const auto simplePressure = flagField->getFlag( SimplePressure_Flag );
+
+         CellInterval domainBB = storage->getDomainCellBB();
+         domainBB.xMin() -= cell_idx_c( FieldGhostLayers );
+         domainBB.xMax() += cell_idx_c( FieldGhostLayers );
+
+         domainBB.yMin() -= cell_idx_c( FieldGhostLayers );
+         domainBB.yMax() += cell_idx_c( FieldGhostLayers );
+
+         domainBB.zMin() -= cell_idx_c( FieldGhostLayers );
+         domainBB.zMax() += cell_idx_c( FieldGhostLayers );
+
+         // TOP
+         CellInterval top( domainBB.xMin(), domainBB.yMin(), domainBB.zMax(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() );
+         storage->transformGlobalToBlockLocalCellInterval( top, *block );
+         handling->forceBoundary( simplePressure, top );
+      }
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+
+   bool applyOutflowBCAtTop_;
+};
+
+
+//*******************************************************************************************************************
+/*!\brief Evaluating the position and velocity of the sphere
+ *
+ */
+//*******************************************************************************************************************
+template< typename ParticleAccessor_T>
+class SpherePropertyLogger
+{
+public:
+   SpherePropertyLogger( const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid,
+                         const std::string fileNameLogging, const std::string fileNameForceLogging, bool fileIO,
+                         real_t diameter, real_t uIn, real_t impactRatio, uint_t numberOfSubSteps, real_t fGravX, real_t fGravZ) :
+      ac_( ac ), sphereUid_( sphereUid ), fileNameLogging_( fileNameLogging ), fileNameForceLogging_( fileNameForceLogging ),
+      fileIO_(fileIO),
+      diameter_( diameter ), uIn_( uIn ), impactRatio_(impactRatio), numberOfSubSteps_(numberOfSubSteps), fGravX_(fGravX), fGravZ_(fGravZ),
+      gap_( real_t(0) ), settlingVelocity_( real_t(0) ), tangentialVelocity_(real_t(0))
+   {
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileNameLogging_.c_str() );
+            file << "#\t D\t uIn\t impactRatio\t positionX\t positionY\t positionZ\t velX\t velY\t velZ\t angX\t angY\t angZ\n";
+            file.close();
+         }
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileNameForceLogging_.c_str() );
+            file << "#\t fGravX\t fGravZ\t fHydX\t fHydZ\t fLubX\t fLubZ\t fColX\t fColZ\t tHydY\t tLubY\t tColY\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()(const uint_t timeStep, const uint_t subStep, Vector3<real_t> fHyd, Vector3<real_t> fLub, Vector3<real_t> fCol, Vector3<real_t> tHyd, Vector3<real_t> tLub, Vector3<real_t> tCol  )
+   {
+      real_t curTimestep = real_c(timeStep) + real_c(subStep) / real_c(numberOfSubSteps_);
+
+      Vector3<real_t> pos(real_t(0));
+      Vector3<real_t> transVel(real_t(0));
+      Vector3<real_t> angVel(real_t(0));
+
+      size_t idx = ac_->uidToIdx(sphereUid_);
+      if( idx != std::numeric_limits<size_t>::max())
+      {
+         if(!mesa_pd::data::particle_flags::isSet( ac_->getFlags(idx), mesa_pd::data::particle_flags::GHOST))
+         {
+            pos = ac_->getPosition(idx);
+            transVel = ac_->getLinearVelocity(idx);
+            angVel = ac_->getAngularVelocity(idx);
+         }
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( pos[0], mpi::SUM );
+         mpi::allReduceInplace( pos[1], mpi::SUM );
+         mpi::allReduceInplace( pos[2], mpi::SUM );
+         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 );
+      }
+
+      position_ = pos;
+      gap_ = pos[2] - real_t(0.5) * diameter_;
+      settlingVelocity_ = transVel[2];
+      tangentialVelocity_ = transVel[0] - diameter_ * real_t(0.5) * angVel[1];
+
+
+      if( fileIO_ /* && gap_ < diameter_*/)
+      {
+         writeToLoggingFile( curTimestep, pos, transVel, angVel);
+         writeToForceLoggingFile( curTimestep, fHyd, fLub, fCol, tHyd, tLub, tCol);
+      }
+   }
+
+   real_t getGapSize() const
+   {
+      return gap_;
+   }
+
+   real_t getSettlingVelocity() const
+   {
+      return settlingVelocity_;
+   }
+
+   real_t getTangentialVelocity() const
+   {
+      return tangentialVelocity_;
+   }
+
+   Vector3<real_t> getPosition() const
+   {
+      return position_;
+   }
+
+
+private:
+   void writeToLoggingFile( real_t timestep, Vector3<real_t> position,  Vector3<real_t> transVel, Vector3<real_t> angVel )
+   {
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( fileNameLogging_.c_str(), std::ofstream::app );
+
+         file << timestep << "\t" << diameter_ << "\t" << uIn_ << "\t" << impactRatio_
+              << "\t" << position[0] << "\t" << position[1] << "\t" << position[2]
+              << "\t" << transVel[0] << "\t" << transVel[1] << "\t" << transVel[2]
+              << "\t" << angVel[0] << "\t" << angVel[1] << "\t" << angVel[2]
+              << "\n";
+         file.close();
+      }
+   }
+
+   void writeToForceLoggingFile( real_t timestep, Vector3<real_t> fHyd, Vector3<real_t> fLub, Vector3<real_t> fCol, Vector3<real_t> tHyd, Vector3<real_t> tLub, Vector3<real_t> tCol  )
+   {
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( fileNameForceLogging_.c_str(), std::ofstream::app );
+
+         file << timestep
+              << "\t" << fGravX_ << "\t" << fGravZ_
+              << "\t" << fHyd[0] << "\t" << fHyd[2]
+              << "\t" << fLub[0] << "\t" << fLub[2]
+              << "\t" << fCol[0] << "\t" << fCol[2]
+              << "\t" << tHyd[1]
+              << "\t" << tLub[1]
+              << "\t" << tCol[1]
+              << "\n";
+         file.close();
+      }
+   }
+
+
+   shared_ptr< ParticleAccessor_T > ac_;
+   const walberla::id_t sphereUid_;
+   std::string fileNameLogging_, fileNameForceLogging_;
+   bool fileIO_;
+   real_t diameter_, uIn_, impactRatio_;
+   uint_t numberOfSubSteps_;
+   real_t fGravX_, fGravZ_;
+
+   real_t gap_, settlingVelocity_, tangentialVelocity_;
+   Vector3<real_t> position_;
+
+};
+
+
+void createPlaneSetup(const shared_ptr<mesa_pd::data::ParticleStorage> & ps, const shared_ptr<mesa_pd::data::ShapeStorage> & ss, const math::AABB & simulationDomain, bool applyOutflowBCAtTop )
+{
+   // create bounding planes
+   mesa_pd::data::Particle&& p0 = *ps->create(true);
+   p0.setPosition(simulationDomain.minCorner());
+   p0.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,1) ));
+   p0.setOwner(mpi::MPIManager::instance()->rank());
+   p0.setType(0);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   if(!applyOutflowBCAtTop)
+   {
+      //only create top plane when no outflow BC should be set there
+      mesa_pd::data::Particle&& p1 = *ps->create(true);
+      p1.setPosition(simulationDomain.maxCorner());
+      p1.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,-1) ));
+      p1.setOwner(mpi::MPIManager::instance()->rank());
+      p1.setType(0);
+      mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+      mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+   }
+
+}
+
+
+
+class ForcesOnSphereLogger
+{
+public:
+   ForcesOnSphereLogger( const std::string & fileName, bool fileIO, real_t weightForce, uint_t numberOfSubSteps ) :
+         fileName_( fileName ), fileIO_( fileIO ), weightForce_( weightForce ), numberOfSubSteps_( numberOfSubSteps )
+   {
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str() );
+            file << "#t\t BuoyAndGravF\t HydInteractionF\t LubricationF\t CollisionForce\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()(const uint_t timeStep, const uint_t subStep, real_t hydForce, real_t lubForce, real_t collisionForce )
+   {
+      real_t curTimestep = real_c(timeStep) + real_c(subStep) / real_c(numberOfSubSteps_);
+
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str(), std::ofstream::app );
+
+            file << std::setprecision(10) << curTimestep << "\t" << weightForce_ << "\t" << hydForce << "\t" << lubForce << "\t" << collisionForce << "\n";
+            file.close();
+         }
+      }
+
+   }
+
+
+private:
+
+   std::string fileName_;
+   bool fileIO_;
+
+   real_t weightForce_;
+   uint_t numberOfSubSteps_;
+};
+
+template<typename ParticleAccessor_T>
+Vector3<real_t> getForce(walberla::id_t uid, ParticleAccessor_T & ac)
+{
+   auto idx = ac.uidToIdx(uid);
+   Vector3<real_t> force(0);
+   if(idx != ac.getInvalidIdx())
+   {
+      force = ac.getForce(idx);
+   }
+   WALBERLA_MPI_SECTION()
+   {
+      mpi::allReduceInplace( force[0], mpi::SUM );
+      mpi::allReduceInplace( force[1], mpi::SUM );
+      mpi::allReduceInplace( force[2], mpi::SUM );
+   }
+   return force;
+}
+
+template<typename ParticleAccessor_T>
+Vector3<real_t> getTorque(walberla::id_t uid, ParticleAccessor_T & ac)
+{
+   auto idx = ac.uidToIdx(uid);
+   Vector3<real_t> torque(0);
+   if(idx != ac.getInvalidIdx())
+   {
+      torque = ac.getTorque(idx);
+   }
+   WALBERLA_MPI_SECTION()
+   {
+      mpi::allReduceInplace( torque[0], mpi::SUM );
+      mpi::allReduceInplace( torque[1], mpi::SUM );
+      mpi::allReduceInplace( torque[2], mpi::SUM );
+   }
+   return torque;
+}
+
+//////////
+// MAIN //
+//////////
+
+//*******************************************************************************************************************
+/*!\brief PHYSICAL Test case of a sphere settling and colliding with a wall submerged in a viscous fluid.
+ *
+ * The collision is oblique, i.e. features normal and tangential contributions.
+ * There are in principle two ways to carry out this simulation:
+ * 1) Use some (artificial) gravitational force such that the x(tangential) and z(normal)- components
+ *    have the same ratio as the desired impact ratio of the velocities.
+ *    To have similar normal collision velocities, the option 'useFullGravityInNormalDirection' always applies the same
+ *    gravitational acceleration in normal direction.
+ *    The main problem, however, is that the sphere begins to rotate for increasing impact angles and will thus generate
+ *    a reposing force in normal direction, that will alter the intended impact ratio to larger values.
+ *    Additionally, a reasonable value for the relaxation time, tau, has to be given to avoid too large settling velocities.
+ *
+ * 2) Specify a (normal or magnitude-wise) Stokes number and artificially accelerate the sphere to this value.
+ *    Thus, the settling velocity is prescribed and no rotational velocity is allowed.
+ *    This is stopped before the collision to allow for a 'natural' collision.
+ *    To avoid a too large damping by the fluid forces, since no gravitational forces are present to balance them,
+ *    an artificial gravity is assumed and taken as the opposite fluid force acting when the artificial
+ *    acceleration is stopped.
+ *
+ * For details see Rettinger, Ruede 2020
+ */
+//*******************************************************************************************************************
+
+
+int main( int argc, char **argv )
+{
+   Environment env( argc, argv );
+
+   if( !env.config() )
+   {
+      WALBERLA_ABORT("Usage: " << argv[0] << " path-to-configuration-file \n");
+   }
+
+   Config::BlockHandle simulationInput = env.config()->getBlock( "Setup" );
+
+
+   const int caseNumber = simulationInput.getParameter<int>("case");
+   Config::BlockHandle caseDescription = env.config()->getBlock( "Case"+std::to_string(caseNumber) );
+
+   const std::string material = caseDescription.getParameter<std::string>("material");
+   Config::BlockHandle materialDescription = env.config()->getBlock( "Mat_"+material );
+   const real_t densitySphere_SI = materialDescription.getParameter<real_t>("densitySphere_SI");
+   const real_t diameter_SI = materialDescription.getParameter<real_t>("diameter_SI");
+   const real_t restitutionCoeff = materialDescription.getParameter<real_t>("restitutionCoeff");
+   const real_t frictionCoeff = materialDescription.getParameter<real_t>("frictionCoeff");
+   const real_t poissonsRatio = materialDescription.getParameter<real_t>("poissonsRatio");
+
+   const std::string fluid = caseDescription.getParameter<std::string>("fluid");
+   Config::BlockHandle fluidDescription = env.config()->getBlock( "Fluid_"+fluid );
+   const real_t densityFluid_SI = fluidDescription.getParameter<real_t>("densityFluid_SI");
+   const real_t dynamicViscosityFluid_SI = fluidDescription.getParameter<real_t>("dynamicViscosityFluid_SI");
+
+   const std::string simulationVariant = simulationInput.getParameter<std::string>("variant");
+   Config::BlockHandle variantDescription = env.config()->getBlock( "Variant_"+simulationVariant );
+
+   const real_t impactRatio = simulationInput.getParameter<real_t>("impactRatio");
+   const bool applyOutflowBCAtTop = simulationInput.getParameter<bool>("applyOutflowBCAtTop");
+
+   // variant dependent parameters
+   const Vector3<real_t> domainSizeNonDim = variantDescription.getParameter<Vector3<real_t> >("domainSize");
+   const Vector3<uint_t> numberOfBlocksPerDirection = variantDescription.getParameter<Vector3<uint_t> >("numberOfBlocksPerDirection");
+   const real_t initialSphereHeight = variantDescription.getParameter<real_t>("initialSphereHeight");
+
+   // LBM
+   const real_t diameter = simulationInput.getParameter<real_t>("diameter");
+   const real_t magicNumber = simulationInput.getParameter<real_t>("magicNumber");
+   const real_t bulkViscRateFactor = simulationInput.getParameter<real_t>("bulkViscRateFactor");
+   const bool useOmegaBulkAdaption = simulationInput.getParameter<bool>("useOmegaBulkAdaption");
+
+   const uint_t numRPDSubCycles = simulationInput.getParameter<uint_t>("numRPDSubCycles");
+   const bool useLubricationCorrection = simulationInput.getParameter<bool>("useLubricationCorrection");
+   const real_t lubricationCutOffDistanceNormal = simulationInput.getParameter<real_t>("lubricationCutOffDistanceNormal");
+   const real_t lubricationCutOffDistanceTangentialTranslational = simulationInput.getParameter<real_t>("lubricationCutOffDistanceTangentialTranslational");
+   const real_t lubricationCutOffDistanceTangentialRotational = simulationInput.getParameter<real_t>("lubricationCutOffDistanceTangentialRotational");
+   const real_t lubricationMinimalGapSizeNonDim = simulationInput.getParameter<real_t>("lubricationMinimalGapSizeNonDim");
+
+   // Collision Response
+   const bool useVelocityVerlet = simulationInput.getParameter<bool>("useVelocityVerlet");
+   const real_t collisionTime = simulationInput.getParameter<real_t>("collisionTime");
+
+   const bool averageForceTorqueOverTwoTimeSteps = simulationInput.getParameter<bool>("averageForceTorqueOverTwoTimeSteps");
+   const bool conserveMomentum = simulationInput.getParameter<bool>("conserveMomentum");
+   const std::string reconstructorType = simulationInput.getParameter<std::string>("reconstructorType");
+
+   const bool fileIO = simulationInput.getParameter<bool>("fileIO");
+   const uint_t vtkIOFreq = simulationInput.getParameter<uint_t>("vtkIOFreq");
+   const std::string baseFolder = simulationInput.getParameter<std::string>("baseFolder");
+
+   WALBERLA_ROOT_SECTION()
+   {
+      if( fileIO )
+      {
+         // create base directory if it does not yet exist
+         filesystem::path tpath( baseFolder );
+         if( !filesystem::exists( tpath ) )
+            filesystem::create_directory( tpath );
+      }
+   }
+
+   //////////////////////////////////////
+   // SIMULATION PROPERTIES in SI units//
+   //////////////////////////////////////
+
+   const real_t impactAngle = std::atan(impactRatio);
+   const real_t densityRatio = densitySphere_SI / densityFluid_SI;
+   const real_t kinematicViscosityFluid_SI = dynamicViscosityFluid_SI / densityFluid_SI;
+
+   WALBERLA_LOG_INFO_ON_ROOT("SETUP OF CASE " << caseNumber);
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in SI units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - impact ratio = " << impactRatio );
+   WALBERLA_LOG_INFO_ON_ROOT(" - impact angle = " << impactAngle  << " (" << impactAngle * real_t(180) / math::pi << " degrees) ");
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter_SI << ", densityRatio = " << densityRatio);
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid_SI << ", dyn. visc = " << dynamicViscosityFluid_SI << ", kin. visc = " << kinematicViscosityFluid_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = <" << real_c(domainSizeNonDim[0]) * diameter_SI << "," << real_c(domainSizeNonDim[1]) * diameter_SI  << ","<< real_c(domainSizeNonDim[2]) * diameter_SI  << ">");
+
+
+   //////////////////////////
+   // NUMERICAL PARAMETERS //
+   //////////////////////////
+
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in simulation, i.e. lattice, units):");
+
+   const real_t dx_SI = diameter_SI / diameter;
+   const real_t sphereVolume = math::pi / real_t(6) * diameter * diameter * diameter;
+
+   real_t dt_SI, viscosity, omega;
+   real_t uIn = real_t(1);
+   real_t accelerationFactor = real_t(0);
+   bool applyArtificialGravityAfterAccelerating = false;
+   bool useFullGravityInNormalDirection = false;
+   bool artificiallyAccelerateSphere = false;
+   Vector3<real_t> gravitationalAccelerationVec(real_t(0));
+
+   if(simulationVariant=="Acceleration")
+   {
+      WALBERLA_LOG_INFO_ON_ROOT("USING MODE OF ARTIFICIALLY ACCELERATED SPHERE");
+
+      artificiallyAccelerateSphere = true;
+
+      real_t StTarget = variantDescription.getParameter<real_t>("StTarget");
+      const bool useStTargetInNormalDirection = variantDescription.getParameter<bool>("useStTargetInNormalDirection");
+      applyArtificialGravityAfterAccelerating = variantDescription.getParameter<bool>("applyArtificialGravityAfterAccelerating");
+      bool applyUInNormalDirection = variantDescription.getParameter<bool>("applyUInNormalDirection");
+      uIn = variantDescription.getParameter<real_t>("uIn");
+      accelerationFactor = variantDescription.getParameter<real_t>("accelerationFactor");
+
+      if(applyUInNormalDirection)
+      {
+         // the value for uIn is defined as the normal impact velocity, i.e. uNIn
+         uIn = uIn / std::cos(impactAngle);
+      }
+
+      real_t StTargetN;
+      if(useStTargetInNormalDirection)
+      {
+         StTargetN = StTarget;
+         StTarget = StTarget / std::cos(impactAngle);
+
+      } else
+      {
+         StTargetN = StTarget * std::cos(impactAngle);
+      }
+      WALBERLA_LOG_INFO_ON_ROOT(" - target St in settling direction = " << StTarget );
+      WALBERLA_LOG_INFO_ON_ROOT(" - target St in normal direction = " << StTargetN );
+
+      const real_t uIn_SI = StTarget * real_t(9) / densityRatio * kinematicViscosityFluid_SI / diameter_SI;
+      dt_SI = uIn / uIn_SI * dx_SI;
+      viscosity =  kinematicViscosityFluid_SI * dt_SI / ( dx_SI * dx_SI );
+
+      //note: no gravity when accelerating artificially
+      omega = lbm::collision_model::omegaFromViscosity(viscosity);
+
+      const real_t uNIn = uIn * std::cos(impactAngle);
+      const real_t Re_p = diameter * uIn / viscosity;
+
+      WALBERLA_LOG_INFO_ON_ROOT(" - target Reynolds number = " << Re_p );
+      WALBERLA_LOG_INFO_ON_ROOT(" - expected normal impact velocity = " << uNIn );
+      WALBERLA_LOG_INFO_ON_ROOT(" - expected tangential impact velocity = " << uIn * std::sin(impactAngle) );
+
+   } else{
+
+      WALBERLA_LOG_INFO_ON_ROOT("USING MODE OF SPHERE SETTLING UNDER GRAVITY");
+
+      const real_t gravitationalAcceleration_SI = variantDescription.getParameter<real_t>("gravitationalAcceleration_SI");
+      useFullGravityInNormalDirection = variantDescription.getParameter<bool>("useFullGravityInNormalDirection");
+      const real_t tau = variantDescription.getParameter<real_t>("tau");
+
+      const real_t ug_SI = std::sqrt((densityRatio-real_t(1)) * gravitationalAcceleration_SI * diameter_SI);
+      const real_t Ga = ug_SI * diameter_SI / kinematicViscosityFluid_SI;
+
+      viscosity = lbm::collision_model::viscosityFromOmega(real_t(1) / tau);
+      dt_SI = viscosity * dx_SI * dx_SI / kinematicViscosityFluid_SI;
+      real_t gravitationalAcceleration = gravitationalAcceleration_SI * dt_SI * dt_SI / dx_SI;
+      omega = real_t(1) / tau;
+
+      WALBERLA_LOG_INFO_ON_ROOT(" - ug = " << ug_SI );
+      WALBERLA_LOG_INFO_ON_ROOT(" - Galileo number = " << Ga );
+
+      gravitationalAccelerationVec = useFullGravityInNormalDirection ? Vector3<real_t>( impactRatio, real_t(0), -real_t(1) ) * gravitationalAcceleration
+                                                                     : Vector3<real_t>( std::sin(impactAngle), real_t(0), -std::cos(impactAngle) ) * gravitationalAcceleration;
+      WALBERLA_LOG_INFO_ON_ROOT(" - g " << gravitationalAcceleration);
+   }
+
+   const real_t omegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor);
+
+   const real_t densityFluid = real_t(1);
+   const real_t densitySphere = densityRatio;
+
+   const real_t dx = real_t(1);
+
+   const real_t responseTime = densityRatio * diameter * diameter / ( real_t(18) * viscosity );
+
+   const real_t particleMass = densitySphere * sphereVolume;
+   const real_t Mij = particleMass; // * particleMass / ( real_t(2) * particleMass ); // Mij = M for sphere-wall collision
+   const real_t lnDryResCoeff = std::log(restitutionCoeff);
+
+   const real_t stiffnessN = Mij * ( math::pi * math::pi + lnDryResCoeff * lnDryResCoeff ) / (collisionTime * collisionTime); // Costa et al., Eq. 18
+   const real_t dampingN = - real_t(2) * Mij * lnDryResCoeff / collisionTime; // Costa et al., Eq. 18
+
+   // from Biegert et al
+   const real_t kappa = real_t(2) * ( real_t(1) - poissonsRatio ) / ( real_t(2) - poissonsRatio ) ;
+   const real_t stiffnessT = kappa * stiffnessN;
+   const real_t dampingT = std::sqrt(kappa) * dampingN;
+
+   Vector3<uint_t> domainSize( uint_c(domainSizeNonDim[0] * diameter ), uint_c(domainSizeNonDim[1] * diameter ), uint_c(domainSizeNonDim[2] * diameter ) );
+
+   real_t initialSpherePosition = initialSphereHeight * diameter;
+
+   WALBERLA_LOG_INFO_ON_ROOT(" - dt_SI = " << dt_SI << " s, dx_SI = " << dx_SI << " m");
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize);
+   if(applyOutflowBCAtTop) WALBERLA_LOG_INFO_ON_ROOT(" - outflow BC at top");
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter << ", density = " << densitySphere );
+   WALBERLA_LOG_INFO_ON_ROOT(" - initial sphere position = " << initialSpherePosition );
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid << ", relaxation time (tau) = " << real_t(1) / omega << ", omega = " << omega << ", kin. visc = " << viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - magic number = " << magicNumber);
+   WALBERLA_LOG_INFO_ON_ROOT(" - omega bulk = " << omegaBulk << ", bulk visc = " << lbm_mesapd_coupling::bulkViscosityFromOmegaBulk(omegaBulk) << " ( bulk visc rate factor (conste) = " << bulkViscRateFactor << ")");
+   if(useOmegaBulkAdaption) WALBERLA_LOG_INFO_ON_ROOT(" - using omega bulk adaption in vicinity of sphere");
+   WALBERLA_LOG_INFO_ON_ROOT(" - gravitational acceleration = " << gravitationalAccelerationVec );
+   WALBERLA_LOG_INFO_ON_ROOT(" - Stokes response time = " << responseTime );
+   if( artificiallyAccelerateSphere )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - artificially accelerating sphere with factor " << accelerationFactor)
+      if(applyArtificialGravityAfterAccelerating)
+      {
+         WALBERLA_LOG_INFO_ON_ROOT(" - applying artificial gravity after accelerating" );
+      }
+   }
+   WALBERLA_LOG_INFO_ON_ROOT(" - integrator = " << (useVelocityVerlet ? "Velocity Verlet" : "Explicit Euler") );
+   if( vtkIOFreq > 0 )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing vtk files to folder \"" << baseFolder << "\" with frequency " << vtkIOFreq);
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("Collision Response properties:" );
+   WALBERLA_LOG_INFO_ON_ROOT(" - collision time = " << collisionTime );
+   WALBERLA_LOG_INFO_ON_ROOT(" - damping coeff n = " << dampingN );
+   WALBERLA_LOG_INFO_ON_ROOT(" - stiffness coeff n = " << stiffnessN );
+   WALBERLA_LOG_INFO_ON_ROOT(" - damping coeff t = " << dampingT );
+   WALBERLA_LOG_INFO_ON_ROOT(" - stiffness coeff t= " << stiffnessT );
+   WALBERLA_LOG_INFO_ON_ROOT(" - coeff of restitution = " << restitutionCoeff );
+   WALBERLA_LOG_INFO_ON_ROOT(" - coeff of friction = " << frictionCoeff );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Coupling properties:" );
+   WALBERLA_LOG_INFO_ON_ROOT(" - number of RPD sub cycles = " << numRPDSubCycles );
+   WALBERLA_LOG_INFO_ON_ROOT(" - lubrication correction = " << (useLubricationCorrection ? "yes" : "no") );
+   if( useLubricationCorrection )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT("  - lubrication correction cut off normal = " << lubricationCutOffDistanceNormal );
+      WALBERLA_LOG_INFO_ON_ROOT("  - lubrication correction cut off tangential translational = " << lubricationCutOffDistanceTangentialTranslational );
+      WALBERLA_LOG_INFO_ON_ROOT("  - lubrication correction cut off tangential rotational = " << lubricationCutOffDistanceTangentialRotational );
+      WALBERLA_LOG_INFO_ON_ROOT("  - lubrication correction minimal gap size non dim = " << lubricationMinimalGapSizeNonDim  << " ( = " << lubricationMinimalGapSizeNonDim * diameter * real_t(0.5) << " cells )");
+   }
+   WALBERLA_LOG_INFO_ON_ROOT(" - average forces over two LBM time steps = " << (averageForceTorqueOverTwoTimeSteps ? "yes" : "no") );
+   WALBERLA_LOG_INFO_ON_ROOT(" - conserve momentum = " << (conserveMomentum ? "yes" : "no") );
+   WALBERLA_LOG_INFO_ON_ROOT(" - reconstructor = " << reconstructorType );
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+
+   Vector3<uint_t> cellsPerBlockPerDirection( domainSize[0] / numberOfBlocksPerDirection[0],
+                                              domainSize[1] / numberOfBlocksPerDirection[1],
+                                              domainSize[2] / numberOfBlocksPerDirection[2] );
+   for( uint_t i = 0; i < 3; ++i ) {
+      WALBERLA_CHECK_EQUAL(cellsPerBlockPerDirection[i] * numberOfBlocksPerDirection[i], domainSize[i],
+                           "Unmatching domain decomposition in direction " << i << "!");
+   }
+
+   auto blocks = blockforest::createUniformBlockGrid( numberOfBlocksPerDirection[0], numberOfBlocksPerDirection[1], numberOfBlocksPerDirection[2],
+                                                      cellsPerBlockPerDirection[0], cellsPerBlockPerDirection[1], cellsPerBlockPerDirection[2], dx,
+                                                      0, false, false,
+                                                      true, true, false, //periodicity
+                                                      false );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Domain decomposition:");
+   WALBERLA_LOG_INFO_ON_ROOT(" - blocks per direction = " << numberOfBlocksPerDirection );
+   WALBERLA_LOG_INFO_ON_ROOT(" - cells per block = " << cellsPerBlockPerDirection );
+
+   //write domain decomposition to file
+   if( vtkIOFreq > 0 )
+   {
+      vtk::writeDomainDecomposition( blocks, "initial_domain_decomposition", baseFolder );
+   }
+
+
+   //////////////////
+   // RPD COUPLING //
+   //////////////////
+
+   auto rpdDomain = 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>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   // bounding planes
+   createPlaneSetup(ps,ss,blocks->getDomain(), applyOutflowBCAtTop);
+
+   // create sphere and store Uid
+   Vector3<real_t> initialPosition( real_t(0.5) * real_c(domainSize[0]), real_t(0.5) * real_c(domainSize[1]), initialSpherePosition );
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( diameter * real_t(0.5) );
+   ss->shapes[sphereShape]->updateMassAndInertia(densitySphere);
+
+   walberla::id_t sphereUid = 0;
+   // create sphere
+   if (rpdDomain->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);
+      p.setType(0);
+      sphereUid = p.getUid();
+   }
+   mpi::allReduceInplace(sphereUid, mpi::SUM);
+
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // add omega bulk field
+   BlockDataID omegaBulkFieldID = field::addToStorage<ScalarField_T>( blocks, "omega bulk field", omegaBulk, field::fzyx, uint_t(0) );
+
+   // create the lattice model
+
+   // TRT
+   //LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::TRT::constructWithMagicNumber( real_t(1) / relaxationTime ) );
+
+   // generated MRT
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+   WALBERLA_LOG_INFO_ON_ROOT("Using TRT-like lattice model!");
+   real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega );
+   real_t lambda_d = lbm::collision_model::TRT::lambda_d( omega, magicNumber );
+   //LatticeModel_T latticeModel = LatticeModel_T(omegaBulk, lambda_d, lambda_e);
+   LatticeModel_T latticeModel = LatticeModel_T(omegaBulkFieldID, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(lbm::collision_model::D3Q19MRT( omegaBulk, omegaBulk, lambda_d, lambda_e, lambda_e, lambda_d ));
+#else
+   WALBERLA_LOG_INFO_ON_ROOT("Using different lattice model!");
+   // generated model with a single omega
+   LatticeModel_T latticeModel = LatticeModel_T(omega);
+#endif
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         uint_t(1), field::fzyx );
+
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::fzyx, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor, applyOutflowBCAtTop), "boundary handling" );
+
+   // set up RPD functionality
+   std::function<void(void)> syncCall = [&ps,&rpdDomain](){
+      const real_t overlap = real_t( 1.5 );
+      mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+      syncNextNeighborFunc(*ps, *rpdDomain, overlap);
+   };
+
+   syncCall();
+
+   real_t timeStepSizeRPD = real_t(1)/real_t(numRPDSubCycles);
+   mesa_pd::kernel::ExplicitEulerWithShape explEulerIntegrator(timeStepSizeRPD);
+   mesa_pd::kernel::VelocityVerletWithShapePreForceUpdate  vvIntegratorPreForce(timeStepSizeRPD);
+   mesa_pd::kernel::VelocityVerletWithShapePostForceUpdate vvIntegratorPostForce(timeStepSizeRPD);
+
+   // linear model
+   mesa_pd::kernel::LinearSpringDashpot linearCollisionResponse(1);
+   linearCollisionResponse.setStiffnessN(0,0,stiffnessN);
+   linearCollisionResponse.setDampingN(0,0,dampingN);
+   linearCollisionResponse.setStiffnessT(0,0,stiffnessT);
+   linearCollisionResponse.setDampingT(0,0,dampingT);
+   //linearCollisionResponse.setFrictionCoefficientStatic(0,0,frictionCoeff); // not used in this test case
+   linearCollisionResponse.setFrictionCoefficientDynamic(0,0,frictionCoeff);
+
+   // nonlinear model for ACTM
+
+
+   mesa_pd::mpi::ReduceProperty reduceProperty;
+
+   mesa_pd::mpi::ReduceContactHistory reduceAndSwapContactHistory;
+
+   // set up coupling functionality
+   Vector3<real_t> gravitationalForce = (densitySphere - densityFluid) * sphereVolume * gravitationalAccelerationVec;
+   lbm_mesapd_coupling::AddForceOnParticlesKernel addGravitationalForce(gravitationalForce);
+   lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+   lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque;
+   lbm_mesapd_coupling::LubricationCorrectionKernel lubricationCorrectionKernel(viscosity, [lubricationMinimalGapSizeNonDim](real_t r){ return lubricationMinimalGapSizeNonDim * r;}, lubricationCutOffDistanceNormal,
+         lubricationCutOffDistanceTangentialTranslational, lubricationCutOffDistanceTangentialRotational );
+   lbm_mesapd_coupling::ParticleMappingKernel<BoundaryHandling_T> particleMappingKernel(blocks, boundaryHandlingID);
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // map planes into the LBM simulation -> act as no-slip boundaries
+   ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, particleMappingKernel, *accessor, NoSlip_Flag);
+
+   // map particles into the LBM simulation
+   ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+   blockforest::communication::UniformBufferedScheme< Stencil_T > optimizedPDFCommunicationScheme( blocks );
+   optimizedPDFCommunicationScheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) ); // optimized sync
+
+   blockforest::communication::UniformBufferedScheme< Stencil_T > fullPDFCommunicationScheme( blocks );
+   fullPDFCommunicationScheme.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) ); // full sync
+
+   // create the timeloop
+   const uint_t timesteps = uint_c( 1000000000 ); // just some large value
+
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+   timeloop.addFuncBeforeTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+   // vtk output
+   if( vtkIOFreq != uint_t(0) )
+   {
+      // sphere
+      auto particleVtkOutput = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleLinearVelocity>("velocity");
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleAngularVelocity>("angular velocity");
+      particleVtkOutput->setParticleSelector( [sphereShape](const mesa_pd::data::ParticleStorage::iterator& pIt) {return pIt->getShapeID() == sphereShape;} ); //limit output to sphere
+      auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "Particles", vtkIOFreq, baseFolder, "simulation_step");
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( particleVtkWriter ), "VTK (sphere data)" );
+
+      // flag field (written only once in the first time step, ghost layers are also written)
+      /*
+      auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field", timesteps, FieldGhostLayers, false, baseFolder );
+      flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( flagFieldVTK ), "VTK (flag field data)" );
+       */
+
+      // pdf field, as slice
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", vtkIOFreq, 0, false, baseFolder );
+
+      pdfFieldVTK->addBeforeFunction( fullPDFCommunicationScheme );
+
+      AABB sliceAABB( real_t(0), real_c(domainSize[1])*real_t(0.5)-real_t(1), 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 );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+
+      vtk::ChainedFilter combinedSliceFilter;
+      combinedSliceFilter.addFilter( fluidFilter );
+      combinedSliceFilter.addFilter( aabbSliceFilter );
+
+      pdfFieldVTK->addCellInclusionFilter( combinedSliceFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+
+      // omega bulk field
+      //timeloop.addFuncBeforeTimeStep( field::createVTKOutput<ScalarField_T, float>( omegaBulkFieldID, *blocks, "omega_bulk_field", vtkIOFreq, uint_t(0), false, baseFolder ), "VTK (omega bulk field)" );
+   }
+
+   // sweep for updating the particle mapping into the LBM simulation
+   timeloop.add() << Sweep( lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, lbm_mesapd_coupling::RegularParticlesSelector(), conserveMomentum), "Particle Mapping" );
+
+   // sweep for restoring PDFs in cells previously occupied by particles
+   if( reconstructorType == "EAN" )
+   {
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+      auto equilibriumAndNonEquilibriumSphereNormalReconstructor = lbm_mesapd_coupling::makeEquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "LBM Communication" )
+                     << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, equilibriumAndNonEquilibriumSphereNormalReconstructor, conserveMomentum)), "PDF Restore" );
+   }else if( reconstructorType == "Grad" )
+   {
+      bool recomputeTargetDensity = false;
+      auto gradReconstructor = lbm_mesapd_coupling::makeGradsMomentApproximationReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, omega, recomputeTargetDensity, true, true);
+
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" )
+                     << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, gradReconstructor, conserveMomentum) ), "PDF Restore" );
+   }else if( reconstructorType == "Eq" )
+   {
+      timeloop.add() << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, conserveMomentum)), "PDF Restore" );
+   }else if( reconstructorType == "Ext")
+   {
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+      auto extrapolationReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T, lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder, true>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+#else
+      auto extrapolationReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T, lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder, false>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+#endif
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "LBM Communication" )
+                     << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, extrapolationReconstructor, conserveMomentum)), "PDF Restore" );
+   } else
+   {
+      WALBERLA_ABORT("Reconstructor Type " << reconstructorType << " not implemented!");
+   }
+
+   // update bulk omega in all cells to adapt to changed particle position
+   if(useOmegaBulkAdaption)
+   {
+      using OmegaBulkAdapter_T = lbm_mesapd_coupling::OmegaBulkAdapter<ParticleAccessor_T, lbm_mesapd_coupling::RegularParticlesSelector>;
+      real_t defaultOmegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, real_t(1));
+      real_t adaptionLayerSize = real_t(2);
+      shared_ptr<OmegaBulkAdapter_T> omegaBulkAdapter = make_shared<OmegaBulkAdapter_T>(blocks, omegaBulkFieldID, accessor, defaultOmegaBulk, omegaBulk, adaptionLayerSize, lbm_mesapd_coupling::RegularParticlesSelector());
+
+      // initial adaption
+      for (auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) {
+         (*omegaBulkAdapter)(blockIt.get());
+      }
+
+      timeloop.add() << Sweep( makeSharedSweep(omegaBulkAdapter), "Omega Bulk Adapter");
+   }
+
+
+   // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+   timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                  << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+   // stream + collide LBM step
+   // generated LBM sweep
+   timeloop.add() << Sweep( LatticeModel_T::Sweep( pdfFieldID ), "LB stream & collide" );
+   // walberla sweeps:
+   //timeloop.add() << Sweep( makeSharedSweep( lbm::makeCellwiseSweep< LatticeModel_T, FlagField_T >( pdfFieldID, flagFieldID, Fluid_Flag ) ), "cell-wise LB sweep" );
+
+   // evaluation functionality
+   std::string loggingFileName( baseFolder + "/LoggingObliqueWetCollision.txt");
+   std::string forceLoggingFileName( baseFolder + "/ForceLoggingObliqueWetCollision.txt");
+   if( fileIO  )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing logging output to file \"" << loggingFileName << "\"");
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing force logging output to file \"" << forceLoggingFileName << "\"");
+   }
+   SpherePropertyLogger<ParticleAccessor_T> logger( accessor, sphereUid, loggingFileName, forceLoggingFileName, fileIO, diameter, uIn, impactRatio, numRPDSubCycles, gravitationalForce[0], gravitationalForce[2] );
+
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+   // evaluation quantities
+   uint_t numBounces = uint_t(0);
+   uint_t tImpact = uint_t(0);
+
+   real_t curVel(real_t(0)), oldVel(real_t(0));
+   real_t maxSettlingVel = real_t(0);
+
+   real_t minGapSize(real_t(0));
+
+   real_t actualSt(real_t(0));
+   real_t actualRe(real_t(0));
+
+   WALBERLA_LOG_INFO_ON_ROOT("Running for maximum of " << timesteps << " timesteps!");
+   
+   const bool useOpenMP = false;
+
+   uint_t averagingSampleSize = uint_c(real_t(1) / uIn);
+   std::vector<Vector3<real_t>> forceValues(averagingSampleSize, Vector3<real_t>(real_t(0)));
+
+   // generally: z: normal direction, x: tangential direction
+
+   uint_t endTimestep = timesteps; // will be adapted after bounce
+   // time loop
+   for (uint_t i = 0; i < endTimestep; ++i )
+   {
+      // perform a single simulation step -> this contains LBM and setting of the hydrodynamic interactions
+      timeloop.singleStep( timeloopTiming );
+
+
+      if( averageForceTorqueOverTwoTimeSteps && i!= 0)
+      {
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, averageHydrodynamicForceTorque, *accessor );
+      }
+
+      Vector3<real_t> hydForce(real_t(0));
+      Vector3<real_t> lubForce(real_t(0));
+      Vector3<real_t> collisionForce(real_t(0));
+
+      Vector3<real_t> hydTorque(real_t(0));
+      Vector3<real_t> lubTorque(real_t(0));
+      Vector3<real_t> collisionTorque(real_t(0));
+
+      for(auto subCycle = uint_t(0); subCycle < numRPDSubCycles; ++subCycle )
+      {
+
+         timeloopTiming["RPD"].start();
+
+         if( useVelocityVerlet )
+         {
+
+            Vector3<real_t> oldForce;
+            Vector3<real_t> oldTorque;
+
+            if(artificiallyAccelerateSphere)
+            {
+               // since the pre-force step of VV updates the position based on velocity and old force, we set oldForce to zero here for this step while artificially accelerating
+               // to not perturb the step after which artificial acceleration is switched off (which requires valid oldForce values then) we store the old force and then re-apply it
+               auto idx = accessor->uidToIdx(sphereUid);
+               if(idx != accessor->getInvalidIdx())
+               {
+                  oldForce = accessor->getOldForce(idx);
+                  accessor->setOldForce(idx, Vector3<real_t>(real_t(0)));
+
+                  oldTorque = accessor->getOldTorque(idx);
+                  accessor->setOldTorque(idx, Vector3<real_t>(real_t(0)));
+               }
+            }
+
+            ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPreForce, *accessor);
+            syncCall();
+
+            if(artificiallyAccelerateSphere)
+            {
+               // re-apply old force
+               auto idx = accessor->uidToIdx(sphereUid);
+               if(idx != accessor->getInvalidIdx())
+               {
+                  accessor->setOldForce(idx, oldForce);
+                  accessor->setOldTorque(idx, oldTorque);
+               }
+            }
+         }
+
+         // lubrication correction
+         ps->forEachParticlePairHalf(useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor,
+                                     [&lubricationCorrectionKernel,&rpdDomain](const size_t idx1, const size_t idx2, auto& ac)
+                                     {
+                                        mesa_pd::collision_detection::AnalyticContactDetection acd;
+                                        acd.getContactThreshold() = lubricationCorrectionKernel.getNormalCutOffDistance();
+                                        mesa_pd::kernel::DoubleCast double_cast;
+                                        mesa_pd::mpi::ContactFilter contact_filter;
+                                        if (double_cast(idx1, idx2, ac, acd, ac ))
+                                        {
+                                           if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain))
+                                           {
+                                              double_cast(acd.getIdx1(), acd.getIdx2(), ac, lubricationCorrectionKernel, ac, acd.getContactNormal(), acd.getPenetrationDepth());
+                                           }
+                                        }
+                                     },
+                                     *accessor );
+
+         lubForce = getForce(sphereUid,*accessor);
+         lubTorque = getTorque(sphereUid,*accessor);
+
+         // one could add linked cells here
+
+         // collision response
+         ps->forEachParticlePairHalf(useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor,
+                                     [&linearCollisionResponse, &rpdDomain, timeStepSizeRPD]
+                                     (const size_t idx1, const size_t idx2, auto& ac)
+                                     {
+                                        mesa_pd::collision_detection::AnalyticContactDetection acd;
+                                        mesa_pd::kernel::DoubleCast double_cast;
+                                        mesa_pd::mpi::ContactFilter contact_filter;
+                                        if (double_cast(idx1, idx2, ac, acd, ac ))
+                                        {
+                                           if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain))
+                                           {
+                                              linearCollisionResponse(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth(), timeStepSizeRPD);
+                                           }
+                                        }
+                                     },
+                                     *accessor );
+
+         collisionForce = getForce(sphereUid,*accessor) - lubForce;
+         collisionTorque = getTorque(sphereUid,*accessor) - lubTorque;
+
+         reduceAndSwapContactHistory(*ps);
+
+         // add hydrodynamic force
+         lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction;
+         ps->forEachParticle(useOpenMP, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, addHydrodynamicInteraction, *accessor );
+
+         hydForce = getForce(sphereUid,*accessor) - lubForce - collisionForce;
+         WALBERLA_ASSERT(!std::isnan(hydForce[0]) && !std::isnan(hydForce[1]) && !std::isnan(hydForce[2]), "Found nan value in hydrodynamic force = " << hydForce);
+         hydTorque = getTorque(sphereUid,*accessor) - lubTorque - collisionTorque;
+
+         ps->forEachParticle( useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addGravitationalForce, *accessor );
+
+         reduceProperty.operator()<mesa_pd::ForceTorqueNotification>(*ps);
+
+         // integration
+         if( useVelocityVerlet ) ps->forEachParticle( useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPostForce, *accessor);
+         else ps->forEachParticle( useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, explEulerIntegrator, *accessor);
+
+         syncCall();
+
+         if( artificiallyAccelerateSphere )
+         {
+            lbm_mesapd_coupling::RegularParticlesSelector sphereSelector;
+            real_t newSphereVel = uIn * (exp(-accelerationFactor * real_t(i) / responseTime ) - real_t(1));
+            ps->forEachParticle(useOpenMP, sphereSelector, *accessor,
+                                [newSphereVel,impactAngle](const size_t idx, ParticleAccessor_T& ac){
+                                   ac.setLinearVelocity(idx, Vector3<real_t>( -std::sin(impactAngle), real_t(0), std::cos(impactAngle) ) * newSphereVel);
+                                   ac.setAngularVelocity(idx, Vector3<real_t>(real_t(0)));},
+                                *accessor);
+         }
+
+         timeloopTiming["RPD"].end();
+
+         // logging
+         timeloopTiming["Logging"].start();
+         logger(i, subCycle, hydForce, lubForce, collisionForce, hydTorque, lubTorque, collisionTorque);
+         timeloopTiming["Logging"].end();
+
+      }
+
+      // store hyd force to average
+      forceValues[i % averagingSampleSize] = hydForce;
+
+      ps->forEachParticle( useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor );
+
+
+      // check for termination
+      oldVel = curVel;
+      curVel = logger.getSettlingVelocity();
+
+      maxSettlingVel = std::min(maxSettlingVel, curVel);
+      minGapSize = std::min(minGapSize, logger.getGapSize());
+
+      // detect the bounce
+      if( oldVel < real_t(0) && curVel > real_t(0) && logger.getGapSize() < real_t(1))
+      {
+
+         ++numBounces;
+
+         actualSt = densityRatio * std::abs(maxSettlingVel) * diameter / ( real_t(9) * viscosity );
+         actualRe = std::abs(maxSettlingVel) * diameter / viscosity;
+
+         WALBERLA_LOG_INFO_ON_ROOT("Detected bounce with max settling velocity " << maxSettlingVel << " -> St = "  << actualSt );
+
+         // end simulation after one non-dim timestep
+         uint_t remainingTimeSteps = uint_t(real_t(1) * diameter / std::abs(maxSettlingVel));
+         endTimestep = i + remainingTimeSteps;
+         WALBERLA_LOG_INFO_ON_ROOT("Will terminate simulation after " << remainingTimeSteps << " time steps, i.e. at time step " << endTimestep);
+      }
+
+      // impact times are measured when the contact between sphere and wall is broken up again
+      if( tImpact == uint_t(0) && numBounces == uint_t(1) && logger.getGapSize() > real_t(0) )
+      {
+         tImpact = i;
+         WALBERLA_LOG_INFO_ON_ROOT("Detected impact time at time step " << tImpact);
+      }
+
+      // check if sphere is close to bottom plane
+      if( logger.getGapSize() < real_t(1) * diameter && artificiallyAccelerateSphere) {
+         WALBERLA_LOG_INFO_ON_ROOT("Switching off acceleration!");
+         artificiallyAccelerateSphere = false;
+
+         if(applyArtificialGravityAfterAccelerating)
+         {
+            // to avoid a too large deceleration due to the missing gravitational force, we apply the averaged hydrodynamic force
+            // (that would have been balanced by the gravitational force) as a kind of artificial gravity
+            Vector3<real_t> artificialGravitationalForce = -std::accumulate(forceValues.begin(), forceValues.end(), Vector3<real_t>(real_t(0))) / real_t(averagingSampleSize);
+            WALBERLA_LOG_INFO_ON_ROOT("Applying artificial gravitational and buoyancy force of " << artificialGravitationalForce);
+            real_t actualGravitationalAcceleration = -artificialGravitationalForce[2] / ( (densitySphere - densityFluid) * sphereVolume );
+            WALBERLA_LOG_INFO_ON_ROOT("This would correspond to a gravitational acceleration of g = " << actualGravitationalAcceleration << ", g_SI = " << actualGravitationalAcceleration * dx_SI / (dt_SI * dt_SI) );
+            addGravitationalForce = lbm_mesapd_coupling::AddForceOnParticlesKernel(artificialGravitationalForce);
+         }
+      }
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("Terminating simulation");
+   WALBERLA_LOG_INFO_ON_ROOT("Maximum settling velocities: " << maxSettlingVel );
+
+   std::string summaryFile(baseFolder + "/Summary.txt");
+   WALBERLA_LOG_INFO_ON_ROOT("Writing summary file " << summaryFile);
+   WALBERLA_ROOT_SECTION()
+   {
+      std::ofstream file;
+      file.open( summaryFile.c_str());
+
+      file << "waLBerla Revision = " << std::string(WALBERLA_GIT_SHA1).substr(0,8) << "\n";
+      file << "\nInput parameters:\n";
+      file << "case: " << caseNumber << "\n";
+      file << "fluid: " << fluid << "\n";
+      file << "material: " << material << "\n";
+      file << "variant: " << simulationVariant << "\n";
+      file << "LBM parameters:\n";
+      file << " - magic number = " << magicNumber << "\n";
+      file << " - bulk viscosity rate factor = " << omegaBulk << "\n";
+      file << " - use omega bulk adaption = " << useOmegaBulkAdaption << " (layer size = 2)\n";
+      file << "Collision parameters:\n";
+      file << " - subCycles = " << numRPDSubCycles << "\n";
+      file << " - collision time (Tc) = " << collisionTime << "\n";
+      file << "use lubrication correction = " << useLubricationCorrection << "\n";
+      file << " - minimum gap size non dim = " << lubricationMinimalGapSizeNonDim << "\n";
+      file << " - lubrication correction cut off normal = " << lubricationCutOffDistanceNormal << "\n";
+      file << " - lubrication correction cut off tangential translational = " << lubricationCutOffDistanceTangentialTranslational << "\n";
+      file << " - lubrication correction cut off tangential rotational = " << lubricationCutOffDistanceTangentialRotational << "\n";
+      file << "reconstructor type " << reconstructorType << "\n";
+      file << "apply outflow BC at top = " << applyOutflowBCAtTop << "\n";
+
+      file << "\nOutput quantities:\n";
+      file << "actual St = " << actualSt << "\n";
+      file << "actual Re = " << actualRe << "\n";
+      file << "impact times = " << tImpact << "\n";
+      file << "settling velocity = " << maxSettlingVel << "\n";
+      file << "maximal overlap = " << std::abs(minGapSize) << " (" << std::abs(minGapSize)/diameter << "%)\n";
+
+      file.close();
+   }
+
+
+
+
+   timeloopTiming.logResultOnRoot();
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace oblique_wet_collision
+
+int main( int argc, char **argv ){
+   oblique_wet_collision::main(argc, argv);
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/SettlingSphereInBox.cpp b/apps/benchmarks/FluidParticleCoupling/SettlingSphereInBox.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6fa7822310fd770d223ec3beeefd3279b7aca029
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/SettlingSphereInBox.cpp
@@ -0,0 +1,911 @@
+//======================================================================================================================
+//
+//  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 SettlingSphereInBox.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.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/math/all.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/logging/all.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/StabilityChecker.h"
+#include "field/communication/PackInfo.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.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/Reconstructor.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/utility/AddForceOnParticlesKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h"
+#include "lbm_mesapd_coupling/utility/OmegaBulkAdaption.h"
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/kernel/SpringDashpot.h"
+#include "mesa_pd/kernel/VelocityVerletWithShape.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/mpi/ReduceProperty.h"
+#include "mesa_pd/mpi/ContactFilter.h"
+#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+#include "lbm/vtk/all.h"
+
+#include "GeneratedLBM.h"
+
+#include <functional>
+
+#define USE_TRT_LIKE_LATTICE_MODEL
+
+namespace settling_sphere_in_box
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::GeneratedLBM;
+//using LatticeModel_T = lbm::D3Q19< lbm::collision_model::D3Q19MRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+using ScalarField_T = GhostLayerField< real_t, 1>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// 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" );
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using NoSlip_T = lbm::NoSlip< LatticeModel_T, flag_t >;
+   using MO_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, NoSlip_T, MO_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ),
+                                  MO_T( "MO", MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+//*******************************************************************************************************************
+
+//*******************************************************************************************************************
+/*!\brief Evaluating the position and velocity of the sphere
+ *
+ */
+//*******************************************************************************************************************
+template< typename ParticleAccessor_T>
+class SpherePropertyLogger
+{
+public:
+   SpherePropertyLogger( const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid,
+         const std::string & fileName, bool fileIO,
+         real_t dx_SI, real_t dt_SI, real_t diameter, real_t gravitationalForceMag) :
+      ac_( ac ), sphereUid_( sphereUid ), fileName_( fileName ), fileIO_(fileIO),
+      dx_SI_( dx_SI ), dt_SI_( dt_SI ), diameter_( diameter ), gravitationalForceMag_( gravitationalForceMag ),
+      position_( real_t(0) ), maxVelocity_( real_t(0) )
+   {
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str() );
+            file << "#\t t\t posZ\t gapZ/D\t velZ\t velZ_SI\t fZ\t fZ/fGravi\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()(const uint_t timestep)
+   {
+      Vector3<real_t> pos(real_t(0));
+      Vector3<real_t> transVel(real_t(0));
+      Vector3<real_t> hydForce(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);
+            transVel = ac_->getLinearVelocity(idx);
+         }
+         hydForce = ac_->getHydrodynamicForce(idx);
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( pos[0], mpi::SUM );
+         mpi::allReduceInplace( pos[1], mpi::SUM );
+         mpi::allReduceInplace( pos[2], mpi::SUM );
+
+         mpi::allReduceInplace( transVel[0], mpi::SUM );
+         mpi::allReduceInplace( transVel[1], mpi::SUM );
+         mpi::allReduceInplace( transVel[2], mpi::SUM );
+
+         mpi::allReduceInplace( hydForce[0], mpi::SUM );
+         mpi::allReduceInplace( hydForce[1], mpi::SUM );
+         mpi::allReduceInplace( hydForce[2], mpi::SUM );
+      }
+
+      position_ = pos[2];
+      maxVelocity_ = std::max(maxVelocity_, -transVel[2]);
+
+      if( fileIO_ )
+         writeToFile( timestep, pos, transVel, hydForce);
+   }
+
+   real_t getPosition() const
+   {
+      return position_;
+   }
+
+   real_t getMaxVelocity() const
+   {
+      return maxVelocity_;
+   }
+
+private:
+   void writeToFile( const uint_t timestep, const Vector3<real_t> & position, const Vector3<real_t> & velocity, const Vector3<real_t> & hydForce )
+   {
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( fileName_.c_str(), std::ofstream::app );
+
+         auto scaledPosition = position / diameter_;
+         auto velocity_SI = velocity * dx_SI_ / dt_SI_;
+         auto normalizedHydForce = hydForce / gravitationalForceMag_;
+
+
+         file << timestep << "\t" << real_c(timestep) * dt_SI_ << "\t"
+              << "\t" << position[2] << "\t" << scaledPosition[2] - real_t(0.5)
+              << "\t" << velocity[2] << "\t" << velocity_SI[2]
+              << "\t" << hydForce[2] << "\t" << normalizedHydForce[2]
+              << "\n";
+         file.close();
+      }
+   }
+
+   shared_ptr< ParticleAccessor_T > ac_;
+   const walberla::id_t sphereUid_;
+   std::string fileName_;
+   bool fileIO_;
+   real_t dx_SI_, dt_SI_, diameter_, gravitationalForceMag_;
+
+   real_t position_;
+   real_t maxVelocity_;
+};
+
+
+void createPlaneSetup(const shared_ptr<mesa_pd::data::ParticleStorage> & ps, const shared_ptr<mesa_pd::data::ShapeStorage> & ss, const math::AABB & simulationDomain)
+{
+   // create bounding planes
+   mesa_pd::data::Particle p0 = *ps->create(true);
+   p0.setPosition(simulationDomain.minCorner());
+   p0.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,1) ));
+   p0.setOwner(mpi::MPIManager::instance()->rank());
+   p0.setType(0);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p1 = *ps->create(true);
+   p1.setPosition(simulationDomain.maxCorner());
+   p1.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,-1) ));
+   p1.setOwner(mpi::MPIManager::instance()->rank());
+   p1.setType(0);
+   mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p2 = *ps->create(true);
+   p2.setPosition(simulationDomain.minCorner());
+   p2.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(1,0,0) ));
+   p2.setOwner(mpi::MPIManager::instance()->rank());
+   p2.setType(0);
+   mesa_pd::data::particle_flags::set(p2.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p2.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p3 = *ps->create(true);
+   p3.setPosition(simulationDomain.maxCorner());
+   p3.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(-1,0,0) ));
+   p3.setOwner(mpi::MPIManager::instance()->rank());
+   p3.setType(0);
+   mesa_pd::data::particle_flags::set(p3.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p3.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p4 = *ps->create(true);
+   p4.setPosition(simulationDomain.minCorner());
+   p4.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,1,0) ));
+   p4.setOwner(mpi::MPIManager::instance()->rank());
+   p4.setType(0);
+   mesa_pd::data::particle_flags::set(p4.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p4.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p5 = *ps->create(true);
+   p5.setPosition(simulationDomain.maxCorner());
+   p5.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,-1,0) ));
+   p5.setOwner(mpi::MPIManager::instance()->rank());
+   p5.setType(0);
+   mesa_pd::data::particle_flags::set(p5.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p5.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+}
+
+//////////
+// MAIN //
+//////////
+
+//*******************************************************************************************************************
+/*!\brief Testcase that simulates the settling of a sphere inside a rectangular column filled with viscous fluid
+ *
+ * see: ten Cate, Nieuwstad, Derksen, Van den Akker - "Particle imaging velocimetry experiments and lattice-Boltzmann
+ * simulations on a single sphere settling under gravity" (2002), Physics of Fluids, doi: 10.1063/1.1512918
+ */
+//*******************************************************************************************************************
+
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   ///////////////////
+   // Customization //
+   ///////////////////
+
+   // simulation control
+   bool shortrun = false;
+   bool funcTest = false;
+   bool fileIO = true;
+   uint_t vtkIOFreq = 0;
+   std::string baseFolder = "vtk_out_SettlingSphere";
+   std::string fileNameEnding = "";
+
+   // physical setup
+   uint_t fluidType = 1;
+
+   //numerical parameters
+   uint_t numberOfCellsInHorizontalDirection = uint_t(135);
+   bool averageForceTorqueOverTwoTimSteps = true;
+   bool conserveMomentum = false;
+   uint_t numRPDSubCycles = uint_t(1);
+   bool useVelocityVerlet = true;
+   std::string reconstructorType = "Grad"; // Eq, EAN, Ext, Grad
+   real_t bulkViscRateFactor = real_t(1);
+   real_t magicNumber = real_t(3)/real_t(16);
+   real_t characteristicVelocity = real_t(0.02);
+   bool useOmegaBulkAdaption = false;
+   real_t adaptionLayerSize = real_t(2);
+   bool useLubricationCorrection = true;
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--shortrun" )             == 0 ) { shortrun = true; continue; }
+      if( std::strcmp( argv[i], "--funcTest" )             == 0 ) { funcTest = true; continue; }
+      if( std::strcmp( argv[i], "--noLogging" )            == 0 ) { fileIO = false; continue; }
+      if( std::strcmp( argv[i], "--vtkIOFreq" )            == 0 ) { vtkIOFreq = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--fluidType" )            == 0 ) { fluidType = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--numRPDSubCycles" )      == 0 ) { numRPDSubCycles = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--resolution" )           == 0 ) { numberOfCellsInHorizontalDirection = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--noForceAveraging" )     == 0 ) { averageForceTorqueOverTwoTimSteps = false; continue; }
+      if( std::strcmp( argv[i], "--conserveMomentum" )     == 0 ) { conserveMomentum = true; continue; }
+      if( std::strcmp( argv[i], "--baseFolder" )           == 0 ) { baseFolder = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--useEulerIntegrator" )   == 0 ) { useVelocityVerlet = false; continue; }
+      if( std::strcmp( argv[i], "--reconstructorType" )    == 0 ) { reconstructorType = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--bulkViscRateFactor" )   == 0 ) { bulkViscRateFactor = real_c(std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--magicNumber" )          == 0 ) { magicNumber = real_c(std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--velocity" )             == 0 ) { characteristicVelocity = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--fileName" )             == 0 ) { fileNameEnding = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--useOmegaBulkAdaption" ) == 0 ) { useOmegaBulkAdaption = true; continue; }
+      if( std::strcmp( argv[i], "--adaptionLayerSize" )    == 0 ) { adaptionLayerSize = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--noLubricationCorrection" ) == 0 ) { useLubricationCorrection = false; continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   if( funcTest )
+   {
+      walberla::logging::Logging::instance()->setLogLevel(logging::Logging::LogLevel::WARNING);
+   }
+
+   if( fileIO )
+   {
+      // create base directory if it does not yet exist
+      filesystem::path tpath( baseFolder );
+      if( !filesystem::exists( tpath ) )
+         filesystem::create_directory( tpath );
+   }
+
+   //////////////////////////////////////
+   // SIMULATION PROPERTIES in SI units//
+   //////////////////////////////////////
+
+   // values are mainly taken from the reference paper
+   const real_t diameter_SI = real_t(15e-3);
+   const real_t densitySphere_SI = real_t(1120);
+
+   real_t densityFluid_SI, dynamicViscosityFluid_SI;
+   real_t expectedSettlingVelocity_SI;
+   switch( fluidType )
+   {
+      case 1:
+         // Re_p around 1.5
+         densityFluid_SI = real_t(970);
+         dynamicViscosityFluid_SI = real_t(373e-3);
+         expectedSettlingVelocity_SI = real_t(0.035986);
+         break;
+      case 2:
+         // Re_p around 4.1
+         densityFluid_SI = real_t(965);
+         dynamicViscosityFluid_SI = real_t(212e-3);
+         expectedSettlingVelocity_SI = real_t(0.05718);
+         break;
+      case 3:
+         // Re_p around 11.6
+         densityFluid_SI = real_t(962);
+         dynamicViscosityFluid_SI = real_t(113e-3);
+         expectedSettlingVelocity_SI = real_t(0.087269);
+         break;
+      case 4:
+         // Re_p around 31.9
+         densityFluid_SI = real_t(960);
+         dynamicViscosityFluid_SI = real_t(58e-3);
+         expectedSettlingVelocity_SI = real_t(0.12224);
+         break;
+      default:
+         WALBERLA_ABORT("Only four different fluids are supported! Choose type between 1 and 4.");
+   }
+   const real_t kinematicViscosityFluid_SI = dynamicViscosityFluid_SI / densityFluid_SI;
+
+   const real_t gravitationalAcceleration_SI = real_t(9.81);
+
+   const real_t ug_SI = std::sqrt((densitySphere_SI/densityFluid_SI-real_t(1))*diameter_SI*gravitationalAcceleration_SI);
+   const real_t GalileoNumber = ug_SI * diameter_SI / (dynamicViscosityFluid_SI/densityFluid_SI);
+
+   Vector3<real_t> domainSize_SI(real_t(100e-3), real_t(100e-3), real_t(160e-3));
+   //shift starting gap a bit upwards to match the reported (plotted) values
+   const real_t startingGapSize_SI = real_t(120e-3) + real_t(0.25) * diameter_SI;
+
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in SI units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid type = " << fluidType );
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter_SI << ", density = " << densitySphere_SI << ", starting gap size = " << startingGapSize_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid_SI << ", dyn. visc = " << dynamicViscosityFluid_SI << ", kin. visc = " << kinematicViscosityFluid_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - expected settling velocity = " << expectedSettlingVelocity_SI << " --> Re_p = " << expectedSettlingVelocity_SI * diameter_SI / kinematicViscosityFluid_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - gravitational velocity = " << ug_SI << " --> Ga = " << GalileoNumber );
+
+
+   //////////////////////////
+   // NUMERICAL PARAMETERS //
+   //////////////////////////
+
+
+   const real_t dx_SI = domainSize_SI[0] / real_c(numberOfCellsInHorizontalDirection);
+   const Vector3<uint_t> domainSize( uint_c(floor(domainSize_SI[0] / dx_SI + real_t(0.5)) ),
+                                     uint_c(floor(domainSize_SI[1] / dx_SI + real_t(0.5)) ),
+                                     uint_c(floor(domainSize_SI[2] / dx_SI + real_t(0.5)) ) );
+   const real_t diameter = diameter_SI / dx_SI;
+   const real_t sphereVolume = math::pi / real_t(6) * diameter * diameter * diameter;
+
+   //const real_t dt_SI = characteristicVelocity / ug_SI * dx_SI; // this uses Ga for parameterization
+   const real_t dt_SI = characteristicVelocity / expectedSettlingVelocity_SI * dx_SI; // this uses Re for parameterization (only possible since settling velocity is known)
+
+   const real_t viscosity =  kinematicViscosityFluid_SI * dt_SI / ( dx_SI * dx_SI );
+   const real_t omega = lbm::collision_model::omegaFromViscosity(viscosity);
+   const real_t relaxationTime = real_t(1) / omega;
+   const real_t omegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor);
+
+   const real_t gravitationalAcceleration = gravitationalAcceleration_SI * dt_SI * dt_SI / dx_SI;
+
+   const real_t densityFluid = real_t(1);
+   const real_t densitySphere = densityFluid * densitySphere_SI / densityFluid_SI;
+
+   const real_t expectedSettlingVelocity = expectedSettlingVelocity_SI * dt_SI / dx_SI;
+   const real_t ug = std::sqrt((densitySphere/densityFluid-real_t(1))*diameter*gravitationalAcceleration);
+   const real_t GalileoNumber_LBM = ug * diameter / viscosity;
+
+   const real_t dx = real_t(1);
+
+   const uint_t timesteps = funcTest ? 1 : ( shortrun ? uint_t(200) : uint_t( 250000 ) );
+
+   WALBERLA_LOG_INFO_ON_ROOT(" - dx_SI = " << dx_SI << ", dt_SI = " << dt_SI);
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in simulation, i.e. lattice, units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize);
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter << ", density = " << densitySphere );
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid << ", relaxation time (tau) = " << relaxationTime << ", omega = " << omega << " kin. visc = " << viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - magic number = " << magicNumber);
+   WALBERLA_LOG_INFO_ON_ROOT(" - omegaBulk = " << omegaBulk << ", bulk visc. = " << lbm_mesapd_coupling::bulkViscosityFromOmegaBulk(omegaBulk) << " (bvrf " << bulkViscRateFactor << ")");
+   WALBERLA_LOG_INFO_ON_ROOT(" - use omega bulk adaption = " << useOmegaBulkAdaption << " (adaption layer size = " << adaptionLayerSize << ")");
+   WALBERLA_LOG_INFO_ON_ROOT(" - gravitational acceleration = " << gravitationalAcceleration );
+   WALBERLA_LOG_INFO_ON_ROOT(" - expected settling velocity = " << expectedSettlingVelocity << " --> Re_p = " << expectedSettlingVelocity * diameter / viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - gravitational velocity = " << ug << " --> Ga = " << GalileoNumber_LBM );
+   WALBERLA_LOG_INFO_ON_ROOT(" - integrator = " << (useVelocityVerlet ? "Velocity Verlet" : "Explicit Euler") );
+   WALBERLA_LOG_INFO_ON_ROOT(" - conserve momentum = " << (conserveMomentum ? "yes" : "no") );
+   WALBERLA_LOG_INFO_ON_ROOT(" - reconstructor type = " << reconstructorType );
+   WALBERLA_LOG_INFO_ON_ROOT(" - lubrication correction = " << useLubricationCorrection );
+
+   if( vtkIOFreq > 0 )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing vtk files to folder \"" << baseFolder << "\" with frequency " << vtkIOFreq);
+   }
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+   Vector3<uint_t> numberOfBlocksPerDirection( uint_t(1), uint_t(1), uint_t(4) );
+   Vector3<uint_t> cellsPerBlockPerDirection( domainSize[0] / numberOfBlocksPerDirection[0],
+                                              domainSize[1] / numberOfBlocksPerDirection[1],
+                                              domainSize[2] / numberOfBlocksPerDirection[2] );
+   for( uint_t i = 0; i < 3; ++i ) {
+      WALBERLA_CHECK_EQUAL(cellsPerBlockPerDirection[i] * numberOfBlocksPerDirection[i], domainSize[i],
+                           "Unmatching domain decomposition in direction " << i << "!");
+   }
+
+   auto blocks = blockforest::createUniformBlockGrid( numberOfBlocksPerDirection[0], numberOfBlocksPerDirection[1], numberOfBlocksPerDirection[2],
+                                                      cellsPerBlockPerDirection[0], cellsPerBlockPerDirection[1], cellsPerBlockPerDirection[2], dx,
+                                                      0, false, false,
+                                                      false, false, false, //periodicity
+                                                      false );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Domain decomposition:");
+   WALBERLA_LOG_INFO_ON_ROOT(" - blocks per direction = " << numberOfBlocksPerDirection );
+   WALBERLA_LOG_INFO_ON_ROOT(" - cells per block = " << cellsPerBlockPerDirection );
+
+   //write domain decomposition to file
+   if( vtkIOFreq > 0 )
+   {
+      vtk::writeDomainDecomposition( blocks, "initial_domain_decomposition", baseFolder );
+   }
+
+   //////////////////
+   // RPD COUPLING //
+   //////////////////
+
+   auto rpdDomain = 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>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   // bounding planes
+   createPlaneSetup(ps,ss,blocks->getDomain());
+
+   // create sphere and store Uid
+   Vector3<real_t> initialPosition( real_t(0.5) * real_c(domainSize[0]), real_t(0.5) * real_c(domainSize[1]), startingGapSize_SI / dx_SI + real_t(0.5) * diameter);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( diameter * real_t(0.5) );
+   ss->shapes[sphereShape]->updateMassAndInertia(densitySphere);
+
+   walberla::id_t sphereUid = 0;
+   if (rpdDomain->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);
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // add omega bulk field
+   BlockDataID omegaBulkFieldID = field::addToStorage<ScalarField_T>( blocks, "omega bulk field", omegaBulk, field::fzyx, uint_t(0) );
+
+   // create the lattice model
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+   WALBERLA_LOG_INFO_ON_ROOT("Using TRTlike model!");
+   real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega );
+   real_t lambda_d = lbm::collision_model::TRT::lambda_d( omega, magicNumber );
+   LatticeModel_T latticeModel = LatticeModel_T(omegaBulkFieldID, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(omegaBulk, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(lbm::collision_model::D3Q19MRT( omegaBulk, omegaBulk, lambda_d, lambda_e, lambda_e, lambda_d ));
+#else
+   WALBERLA_LOG_INFO_ON_ROOT("Using KBC model!");
+   // generated KBC
+   LatticeModel_T latticeModel = LatticeModel_T(omega);
+#endif
+
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (fzyx)", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         uint_t(1), field::fzyx );
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::fzyx, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // set up RPD functionality
+   std::function<void(void)> syncCall = [ps,rpdDomain](){
+      const real_t overlap = real_t( 1.5 );
+      mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+      syncNextNeighborFunc(*ps, *rpdDomain, overlap);
+   };
+
+   syncCall();
+
+   mesa_pd::kernel::ExplicitEulerWithShape explEulerIntegrator(real_t(1)/real_t(numRPDSubCycles));
+   mesa_pd::kernel::VelocityVerletWithShapePreForceUpdate  vvIntegratorPreForce(real_t(1)/real_t(numRPDSubCycles));
+   mesa_pd::kernel::VelocityVerletWithShapePostForceUpdate vvIntegratorPostForce(real_t(1)/real_t(numRPDSubCycles));
+
+   mesa_pd::kernel::SpringDashpot collisionResponse(1);
+   mesa_pd::mpi::ReduceProperty reduceProperty;
+
+   // set up coupling functionality
+   Vector3<real_t> gravitationalForce( real_t(0), real_t(0), -(densitySphere - densityFluid) * gravitationalAcceleration * sphereVolume );
+   lbm_mesapd_coupling::AddForceOnParticlesKernel addGravitationalForce(gravitationalForce);
+   lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction;
+   lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+   lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque;
+   lbm_mesapd_coupling::LubricationCorrectionKernel lubricationCorrectionKernel(viscosity, [](real_t r){ return real_t(0.0016) * r;});
+
+   lbm_mesapd_coupling::RegularParticlesSelector sphereSelector;
+
+   lbm_mesapd_coupling::ParticleMappingKernel<BoundaryHandling_T> particleMappingKernel(blocks, boundaryHandlingID);
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // map planes into the LBM simulation -> act as no-slip boundaries
+   ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, particleMappingKernel, *accessor, NoSlip_Flag);
+
+   // map particles into the LBM simulation
+   ps->forEachParticle(false, sphereSelector, *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+
+   blockforest::communication::UniformBufferedScheme< Stencil_T > optimizedPDFCommunicationScheme( blocks );
+   optimizedPDFCommunicationScheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) ); // optimized sync
+
+   blockforest::communication::UniformBufferedScheme< Stencil_T > fullPDFCommunicationScheme( blocks );
+   fullPDFCommunicationScheme.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) ); // full sync
+
+   // create the timeloop
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+
+   timeloop.addFuncBeforeTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+   // vtk output
+   if( vtkIOFreq != uint_t(0) )
+   {
+      // spheres
+      auto particleVtkOutput = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleOwner>("owner");
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleLinearVelocity>("velocity");
+      auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "Particles", vtkIOFreq, baseFolder, "simulation_step");
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( particleVtkWriter ), "VTK (sphere data)" );
+
+      // flag field (written only once in the first time step, ghost layers are also written)
+      //auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field", timesteps, FieldGhostLayers, false, baseFolder );
+      //flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+      //timeloop.addFuncBeforeTimeStep( vtk::writeFiles( flagFieldVTK ), "VTK (flag field data)" );
+
+      // pdf field
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", vtkIOFreq, 0, false, baseFolder );
+
+      pdfFieldVTK->addBeforeFunction( fullPDFCommunicationScheme );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+      pdfFieldVTK->addCellInclusionFilter( fluidFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+
+      // omega bulk field
+      timeloop.addFuncBeforeTimeStep( field::createVTKOutput<ScalarField_T, float>( omegaBulkFieldID, *blocks, "omega_bulk_field", vtkIOFreq, uint_t(0), false, baseFolder ), "VTK (omega bulk field)" );
+
+   }
+
+   // sweep for updating the particle mapping into the LBM simulation
+   timeloop.add() << Sweep( lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, sphereSelector, conserveMomentum), "Particle Mapping" );
+
+   // sweep for restoring PDFs in cells previously occupied by particles
+   if( reconstructorType == "EAN")
+   {
+
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+      auto equilibriumAndNonEquilibriumSphereNormalReconstructor = lbm_mesapd_coupling::makeEquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+      auto reconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, equilibriumAndNonEquilibriumSphereNormalReconstructor, conserveMomentum);
+
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" )
+                     << Sweep( makeSharedSweep(reconstructionManager), "PDF Restore" );
+
+   } else if( reconstructorType == "Ext" )
+   {
+      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, uint_t(3), true);
+
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" )
+                     << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, extrapolationSphereNormalReconstructor, conserveMomentum) ), "PDF Restore" );
+   } else if( reconstructorType == "Grad")
+   {
+      auto gradReconstructor = lbm_mesapd_coupling::makeGradsMomentApproximationReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, omega, false, true, true);
+
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" )
+                     << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, gradReconstructor, conserveMomentum) ), "PDF Restore" );
+   } else if( reconstructorType == "Eq")
+   {
+      timeloop.add() << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, conserveMomentum) ), "PDF Restore" );
+   } else {
+      WALBERLA_ABORT("Unknown reconstructor type " << reconstructorType);
+   }
+
+   // update bulk omega in all cells to adapt to changed particle position
+   if( useOmegaBulkAdaption )
+   {
+      using OmegaBulkAdapter_T = lbm_mesapd_coupling::OmegaBulkAdapter<ParticleAccessor_T, decltype(sphereSelector)>;
+      real_t defaultOmegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, real_t(1));
+      shared_ptr<OmegaBulkAdapter_T> omegaBulkAdapter = make_shared<OmegaBulkAdapter_T>(blocks, omegaBulkFieldID, accessor, defaultOmegaBulk, omegaBulk, adaptionLayerSize, sphereSelector);
+      timeloop.add() << Sweep( makeSharedSweep(omegaBulkAdapter), "Omega Bulk Adapter");
+   }
+
+   bool useStreamCollide = true;
+   auto bhSweep = BoundaryHandling_T::getBlockSweep( boundaryHandlingID );
+
+   // generated sweeps
+   auto lbmSweep = LatticeModel_T::Sweep( pdfFieldID );
+   if( useStreamCollide )
+   {
+      // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+      timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                     << Sweep(bhSweep, "Boundary Handling" );
+
+      // stream + collide LBM step
+      timeloop.add() << Sweep( lbmSweep, "LB sweep" );
+   }
+   else
+   {
+      // collide LBM step
+      timeloop.add() << Sweep([&lbmSweep](IBlock * const block){lbmSweep.collide(block);}, "cell-wise collide LB sweep" );
+
+      // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+      timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                     << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+      // stream LBM step
+      timeloop.add() << Sweep([&lbmSweep](IBlock * const block){lbmSweep.stream(block);}, "cell-wise stream LB sweep" );
+   }
+
+   // evaluation functionality
+   std::string loggingFileName( baseFolder + "/LoggingSettlingSphere_");
+   loggingFileName += std::to_string(fluidType);
+   loggingFileName += "_recon" + reconstructorType;
+   loggingFileName += "_bvrf" + std::to_string(uint_c(bulkViscRateFactor));
+   loggingFileName += "_mn" + std::to_string(float(magicNumber));
+   if( useOmegaBulkAdaption ) loggingFileName += "_uOBA" + std::to_string(uint_c(adaptionLayerSize));
+   if( !fileNameEnding.empty()) loggingFileName += "_" + fileNameEnding;
+   loggingFileName += ".txt";
+   if( fileIO  )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing logging output to file \"" << loggingFileName << "\"");
+   }
+   SpherePropertyLogger<ParticleAccessor_T> logger( accessor, sphereUid, loggingFileName, fileIO, dx_SI, dt_SI, diameter, -gravitationalForce[2] );
+
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+   real_t terminationPosition = diameter * real_t(0.501); // right before sphere touches the bottom wall
+   real_t oldPos = initialPosition[2];
+
+   const bool useOpenMP = false;
+
+   // time loop
+   for (uint_t i = 0; i < timesteps; ++i )
+   {
+      // perform a single simulation step -> this contains LBM and setting of the hydrodynamic interactions
+      timeloop.singleStep( timeloopTiming );
+
+      timeloopTiming["RPD"].start();
+
+      if( averageForceTorqueOverTwoTimSteps && i!= 0)
+      {
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, averageHydrodynamicForceTorque, *accessor );
+      }
+
+      for(auto subCycle = uint_t(0); subCycle < numRPDSubCycles; ++subCycle )
+      {
+
+         if( useVelocityVerlet )
+         {
+            ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPreForce, *accessor);
+            syncCall();
+         }
+
+         ps->forEachParticle(useOpenMP, sphereSelector, *accessor, addHydrodynamicInteraction, *accessor );
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addGravitationalForce, *accessor );
+
+         // lubrication correction
+         if(useLubricationCorrection)
+         {
+            ps->forEachParticlePairHalf(useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor,
+                                        [&lubricationCorrectionKernel,rpdDomain](const size_t idx1, const size_t idx2, auto& ac)
+                                        {
+                                           //TODO change this to storing copy, not reference
+                                           mesa_pd::collision_detection::AnalyticContactDetection acd;
+                                           acd.getContactThreshold() = lubricationCorrectionKernel.getNormalCutOffDistance();
+                                           mesa_pd::kernel::DoubleCast double_cast;
+                                           mesa_pd::mpi::ContactFilter contact_filter;
+                                           if (double_cast(idx1, idx2, ac, acd, ac ))
+                                           {
+                                              if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain))
+                                              {
+                                                 double_cast(idx1, idx2, ac, lubricationCorrectionKernel, ac, acd.getContactNormal(), acd.getPenetrationDepth());
+                                              }
+                                           }
+                                        },
+                                        *accessor );
+         }
+
+         reduceProperty.operator()<mesa_pd::ForceTorqueNotification>(*ps);
+
+         if( useVelocityVerlet ) ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPostForce, *accessor);
+         else ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, explEulerIntegrator, *accessor);
+
+         syncCall();
+      }
+
+      timeloopTiming["RPD"].end();
+
+      // evaluation
+      timeloopTiming["Logging"].start();
+      logger(i);
+      timeloopTiming["Logging"].end();
+
+      // reset after logging here
+      ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor );
+
+      // check for termination
+      if ( logger.getPosition() < terminationPosition || oldPos < logger.getPosition() )
+      {
+         WALBERLA_LOG_INFO_ON_ROOT("Sphere reached terminal position " << logger.getPosition() << " after " << i << " timesteps!");
+         break;
+      }
+      oldPos = logger.getPosition();
+   }
+
+   timeloopTiming.logResultOnRoot();
+
+   // check the result
+   if ( !funcTest && !shortrun )
+   {
+      real_t relErr = std::fabs( expectedSettlingVelocity - logger.getMaxVelocity()) / expectedSettlingVelocity;
+      WALBERLA_LOG_INFO_ON_ROOT( "Expected maximum settling velocity: " << expectedSettlingVelocity );
+      WALBERLA_LOG_INFO_ON_ROOT( "Simulated maximum settling velocity: " << logger.getMaxVelocity() );
+      WALBERLA_LOG_INFO_ON_ROOT( "Relative error: " << relErr );
+
+      // the relative error has to be below 10%
+      WALBERLA_CHECK_LESS( relErr, real_t(0.1) );
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace settling_sphere_in_box
+
+int main( int argc, char **argv ){
+   settling_sphere_in_box::main(argc, argv);
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/SphereMovingWithPrescribedVelocity.cpp b/apps/benchmarks/FluidParticleCoupling/SphereMovingWithPrescribedVelocity.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9e1b866792a5e5088c5e6940f8b89aa9074ede81
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/SphereMovingWithPrescribedVelocity.cpp
@@ -0,0 +1,874 @@
+//======================================================================================================================
+//
+//  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 SphereMovingWithPrescribedVelocity.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.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/math/all.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/logging/all.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/interpolators/FieldInterpolatorCreators.h"
+#include "field/interpolators/TrilinearFieldInterpolator.h"
+#include "field/StabilityChecker.h"
+#include "field/communication/PackInfo.h"
+#include "field/adaptors/AdaptorCreators.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/Adaptors.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.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/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.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/utility/AddForceOnParticlesKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/OmegaBulkAdaption.h"
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/kernel/SpringDashpot.h"
+#include "mesa_pd/kernel/VelocityVerlet.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/mpi/ReduceProperty.h"
+#include "mesa_pd/mpi/ContactFilter.h"
+#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+#include "lbm/vtk/all.h"
+
+#include "GeneratedLBM.h"
+
+#include <functional>
+
+#define USE_TRT_LIKE_LATTICE_MODEL
+
+namespace sphere_moving_with_prescribed_velocity
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::GeneratedLBM;
+//using LatticeModel_T = lbm::D3Q19< lbm::collision_model::D3Q19MRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+using ScalarField_T = GhostLayerField< real_t, 1>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// 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" );
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using NoSlip_T = lbm::NoSlip< LatticeModel_T, flag_t >;
+   using MO_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, NoSlip_T, MO_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ),
+                                  MO_T( "MO", MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+//*******************************************************************************************************************
+
+
+//*******************************************************************************************************************
+/*!\brief Evaluating the position and velocity of the sphere
+ *
+ */
+//*******************************************************************************************************************
+template< typename ParticleAccessor_T>
+class SpherePropertyLogger
+{
+public:
+   SpherePropertyLogger( const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid,
+         const std::string & fileName, bool fileIO,
+         real_t diameter, real_t velocity, real_t forceMag) :
+      ac_( ac ), sphereUid_( sphereUid ), fileName_( fileName ), fileIO_(fileIO),
+      diameter_( diameter ), velocity_(velocity), forceMag_( forceMag ),
+      position_( real_t(0) )
+   {
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str() );
+            file << "#\t t\t posZ\t posZ/D\t fZ\t fZ/fSt\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()()
+   {
+      real_t pos(real_t(0));
+      real_t hydForce(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)[2];
+         }
+         hydForce = ac_->getHydrodynamicForce(idx)[2];
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( pos, mpi::SUM );
+
+         mpi::allReduceInplace( hydForce, mpi::SUM );
+      }
+
+      position_ = pos;
+      hydForce_ = hydForce;
+
+   }
+
+   real_t getPosition() const
+   {
+      return position_;
+   }
+
+   void writeToFile( const uint_t timestep, real_t density1, real_t density2, real_t totalMass )
+   {
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( fileName_.c_str(), std::ofstream::app );
+
+         auto scaledPosition = position_ / diameter_;
+         auto normalizedHydForce = hydForce_ / std::abs(forceMag_);
+
+         file << std::setprecision(10)
+              << timestep << "\t" << real_c(timestep) / (diameter_ / velocity_) << "\t"
+              << "\t" << position_ << "\t" << scaledPosition
+              << "\t" << hydForce_ << "\t" << normalizedHydForce
+              << "\t" << density1 << "\t" << density2 << "\t" << totalMass
+              << "\n";
+         file.close();
+      }
+   }
+
+private:
+   shared_ptr< ParticleAccessor_T > ac_;
+   const walberla::id_t sphereUid_;
+   std::string fileName_;
+   bool fileIO_;
+   real_t diameter_, velocity_, forceMag_;
+
+   real_t position_, hydForce_;
+};
+
+
+void createPlaneSetup(const shared_ptr<mesa_pd::data::ParticleStorage> & ps, const shared_ptr<mesa_pd::data::ShapeStorage> & ss, const math::AABB & simulationDomain)
+{
+   mesa_pd::data::Particle p2 = *ps->create(true);
+   p2.setPosition(simulationDomain.minCorner());
+   p2.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(1,0,0) ));
+   p2.setOwner(mpi::MPIManager::instance()->rank());
+   p2.setType(0);
+   mesa_pd::data::particle_flags::set(p2.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p2.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p3 = *ps->create(true);
+   p3.setPosition(simulationDomain.maxCorner());
+   p3.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(-1,0,0) ));
+   p3.setOwner(mpi::MPIManager::instance()->rank());
+   p3.setType(0);
+   mesa_pd::data::particle_flags::set(p3.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p3.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p4 = *ps->create(true);
+   p4.setPosition(simulationDomain.minCorner());
+   p4.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,1,0) ));
+   p4.setOwner(mpi::MPIManager::instance()->rank());
+   p4.setType(0);
+   mesa_pd::data::particle_flags::set(p4.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p4.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p5 = *ps->create(true);
+   p5.setPosition(simulationDomain.maxCorner());
+   p5.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,-1,0) ));
+   p5.setOwner(mpi::MPIManager::instance()->rank());
+   p5.setType(0);
+   mesa_pd::data::particle_flags::set(p5.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p5.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+}
+
+template< typename DensityInterpolator_T>
+real_t getDensityAtPosition(const shared_ptr<StructuredBlockStorage> & blocks, BlockDataID densityInterpolatorID, Vector3<real_t> position)
+{
+
+   real_t density = real_t(0);
+   for( auto & block: *blocks)
+   {
+      if(block.getAABB().contains(position))
+      {
+         auto densityInterpolator = block.getData<DensityInterpolator_T>(densityInterpolatorID);
+         densityInterpolator->get(position, &density);
+      }
+
+   }
+   mpi::reduceInplace(density, mpi::SUM);
+
+   return density;
+}
+
+template< typename BoundaryHandling_T>
+real_t getAverageDensityInSystem(const shared_ptr<StructuredBlockStorage> & blocks, BlockDataID pdfFieldID, BlockDataID boundaryHandlingID)
+{
+   real_t totalMass = real_t(0);
+   uint_t count = uint_t(0);
+   for( auto & block: *blocks)
+   {
+      auto pdfField = block.getData<PdfField_T>(pdfFieldID);
+      auto boundaryHandling = block.getData<BoundaryHandling_T>(boundaryHandlingID);
+      WALBERLA_FOR_ALL_CELLS_XYZ(pdfField,
+            if(boundaryHandling->isDomain(x,y,z))
+            {
+               totalMass += pdfField->getDensity(x,y,z);
+               ++count;
+            }
+      );
+   }
+
+   mpi::reduceInplace(totalMass, mpi::SUM);
+   mpi::reduceInplace(count, mpi::SUM);
+
+   return totalMass/real_c(count);
+}
+
+//////////
+// MAIN //
+//////////
+
+//*******************************************************************************************************************
+/*!\brief Test of a sphere that is moving with a prescribed velocity.
+ *
+ * It is used to investigate fluctuations in the density field around the particle and how the bulk viscosity
+ * or others (PDF reconstructor, resolution, velocity) influence this behavior.
+ *
+ * With the option artificiallyAccelerateSphere, the sphere is gradually accelerated until the final velocity is reached.
+ * This gets rid of the strong oscillations that would appear in an abrupt start.
+ *
+ * Results of force plot:
+ * - mn=0.125 (2/16) reduces oscillations compared to 0.1875 (3/16) a bit, especially the peaks
+ * - ug=0.005 leads to higher oscillations than ug=0.01 -> since relaxation time is smaller
+ * - bvrf=100 reduces oscillations, especially below the mean. Peaks remain almost the same
+ * - Grad vs Ext reconstructor -> almost same behavior
+ * - D=25 decreases oscillations -> since relaxation time is bigger
+ *
+ * NOTE: Run at least with 3 processes due to periodicity!
+ */
+//*******************************************************************************************************************
+
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   ///////////////////
+   // Customization //
+   ///////////////////
+
+   // simulation control
+   bool funcTest = false;
+   bool fileIO = true;
+   uint_t vtkIOFreq = 0;
+   std::string baseFolder = "vtk_out_MovingWithPrescribedVelocity";
+   std::string fileNameEnding = "";
+   bool logDensity = true;
+   bool vtkOutputAtEnd = true;
+
+   //numerical parameters
+   bool averageForceTorqueOverTwoTimSteps = true;
+   bool conserveMomentum = false;
+   uint_t numRPDSubCycles = uint_t(1);
+   std::string reconstructorType = "Grad"; // Eq, EAN, Ext, Grad
+   real_t bulkViscRateFactor = real_t(1);
+   real_t magicNumber = real_t(0.1875);
+   bool useOmegaBulkAdaption = false;
+   real_t adaptionLayerSize = real_t(2);
+   uint_t blocksInX = uint_t(1);
+   uint_t blocksInY = uint_t(1);
+   uint_t blocksInZ = uint_t(4);
+   real_t initialSpherePosition = real_t(0.5); // in ratio to domain height
+
+   // simulation setup
+   real_t velocity = real_t(0.02);
+   real_t Re = real_t(164);
+   real_t diameter = real_t(20);
+   real_t numberOfPasses = real_t(3);
+   real_t domainWidthNonDim = real_t(4);
+   real_t domainHeightNonDim = real_t(8);
+   bool usePeriodicSetup = false;
+   bool artificiallyAccelerateSphere = false;
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--funcTest" )             == 0 ) { funcTest = true; continue; }
+      if( std::strcmp( argv[i], "--noLogging" )            == 0 ) { fileIO = false; continue; }
+      if( std::strcmp( argv[i], "--noVtkOutputAtEnd" )     == 0 ) { vtkOutputAtEnd = false; continue; }
+      if( std::strcmp( argv[i], "--logDensity" )           == 0 ) { logDensity = true; continue; }
+      if( std::strcmp( argv[i], "--vtkIOFreq" )            == 0 ) { vtkIOFreq = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--numRPDSubCycles" )      == 0 ) { numRPDSubCycles = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--noForceAveraging" )     == 0 ) { averageForceTorqueOverTwoTimSteps = false; continue; }
+      if( std::strcmp( argv[i], "--conserveMomentum" )     == 0 ) { conserveMomentum = true; continue; }
+      if( std::strcmp( argv[i], "--baseFolder" )           == 0 ) { baseFolder = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--reconstructorType" )    == 0 ) { reconstructorType = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--bulkViscRateFactor" )   == 0 ) { bulkViscRateFactor = real_c(std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--magicNumber" )          == 0 ) { magicNumber = real_c(std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--velocity" )             == 0 ) { velocity = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--Re" )                   == 0 ) { Re = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--diameter" )             == 0 ) { diameter = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--domainWidth" )          == 0 ) { domainWidthNonDim = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--domainHeight" )         == 0 ) { domainHeightNonDim = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--fileName" )             == 0 ) { fileNameEnding = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--useOmegaBulkAdaption" ) == 0 ) { useOmegaBulkAdaption = true; continue; }
+      if( std::strcmp( argv[i], "--adaptionLayerSize" )    == 0 ) { adaptionLayerSize = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--numberOfPasses" )       == 0 ) { numberOfPasses = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--initialSpherePosition" )== 0 ) { initialSpherePosition = real_c(std::atof( argv[++i] )); continue; }
+      if( std::strcmp( argv[i], "--usePeriodicSetup" )     == 0 ) { usePeriodicSetup = true; continue; }
+      if( std::strcmp( argv[i], "--artificiallyAccelerateSphere" )     == 0 ) { artificiallyAccelerateSphere = true; continue; }
+      if( std::strcmp( argv[i], "--blocksInX" )            == 0 ) { blocksInX = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--blocksInY" )            == 0 ) { blocksInY = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--blocksInZ" )            == 0 ) { blocksInZ = uint_c( std::atof( argv[++i] ) ); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   if( funcTest )
+   {
+      walberla::logging::Logging::instance()->setLogLevel(logging::Logging::LogLevel::WARNING);
+   }
+
+   if( fileIO )
+   {
+      // create base directory if it does not yet exist
+      filesystem::path tpath( baseFolder );
+      if( !filesystem::exists( tpath ) )
+         filesystem::create_directory( tpath );
+   }
+
+   //if(adaptionLayerSize > real_t(2)) WALBERLA_LOG_WARNING_ON_ROOT("Adaption layer size is larger than permitted by parallel setup!");
+
+   //////////////////////////
+   // NUMERICAL PARAMETERS //
+   //////////////////////////
+
+   real_t viscosity = velocity * diameter / Re;
+   const real_t omega = lbm::collision_model::omegaFromViscosity(viscosity);
+   const real_t relaxationTime = real_t(1) / omega;
+   const real_t omegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor);
+
+   const Vector3<uint_t> domainSize( uint_c(domainWidthNonDim * diameter), uint_c(domainWidthNonDim * diameter), uint_c(domainHeightNonDim * diameter) );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in simulation, i.e. lattice, units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize);
+   WALBERLA_LOG_INFO_ON_ROOT(" - Re = " << Re);
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter << ", constant velocity = " << velocity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - relaxation time (tau) = " << relaxationTime << ", omega = " << omega << " kin. visc = " << viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - magic number " << magicNumber);
+   WALBERLA_LOG_INFO_ON_ROOT(" - omegaBulk = " << omegaBulk << ", bulk visc. = " << lbm_mesapd_coupling::bulkViscosityFromOmegaBulk(omegaBulk) << " (bvrf " << bulkViscRateFactor << ")");
+   WALBERLA_LOG_INFO_ON_ROOT(" - use omega bulk adaption = " << useOmegaBulkAdaption << " (adaption layer size = " << adaptionLayerSize << ")");
+   WALBERLA_LOG_INFO_ON_ROOT(" - reconstructor type = " << reconstructorType );
+
+   if( vtkIOFreq > 0 )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing vtk files to folder \"" << baseFolder << "\" with frequency " << vtkIOFreq);
+   }
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+   Vector3<uint_t> numberOfBlocksPerDirection( blocksInX, blocksInY, blocksInZ );
+   Vector3<uint_t> cellsPerBlockPerDirection( domainSize[0] / numberOfBlocksPerDirection[0],
+                                              domainSize[1] / numberOfBlocksPerDirection[1],
+                                              domainSize[2] / numberOfBlocksPerDirection[2] );
+   for( uint_t i = 0; i < 3; ++i ) {
+      WALBERLA_CHECK_EQUAL(cellsPerBlockPerDirection[i] * numberOfBlocksPerDirection[i], domainSize[i],
+                           "Unmatching domain decomposition in direction " << i << "!");
+   }
+
+   real_t dx = real_t(1);
+   auto blocks = blockforest::createUniformBlockGrid( numberOfBlocksPerDirection[0], numberOfBlocksPerDirection[1], numberOfBlocksPerDirection[2],
+                                                      cellsPerBlockPerDirection[0], cellsPerBlockPerDirection[1], cellsPerBlockPerDirection[2], dx,
+                                                      0, false, false,
+                                                      usePeriodicSetup, usePeriodicSetup, true, //periodicity
+                                                      false );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Domain decomposition:");
+   WALBERLA_LOG_INFO_ON_ROOT(" - blocks per direction = " << numberOfBlocksPerDirection );
+   WALBERLA_LOG_INFO_ON_ROOT(" - cells per block = " << cellsPerBlockPerDirection );
+
+   //write domain decomposition to file
+   if( vtkIOFreq > 0 )
+   {
+      vtk::writeDomainDecomposition( blocks, "initial_domain_decomposition", baseFolder );
+   }
+
+   //////////////////
+   // RPD COUPLING //
+   //////////////////
+
+   auto rpdDomain = 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>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   // bounding planes
+   if(!usePeriodicSetup) createPlaneSetup(ps,ss,blocks->getDomain());
+
+   // create sphere and store Uid
+   Vector3<real_t> initialPosition( real_t(0.5) * real_c(domainSize[0]), real_t(0.5) * real_c(domainSize[1]), initialSpherePosition * real_c(domainSize[2]));
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( diameter * real_t(0.5) );
+   //ss->shapes[sphereShape]->updateMassAndInertia(densityRatio);
+
+   walberla::id_t sphereUid = 0;
+   if (rpdDomain->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);
+      p.setLinearVelocity(Vector3<real_t>(0.0,0.0,velocity));
+      sphereUid = p.getUid();
+   }
+   mpi::allReduceInplace(sphereUid, mpi::SUM);
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // add omega bulk field
+   BlockDataID omegaBulkFieldID = field::addToStorage<ScalarField_T>( blocks, "omega bulk field", omegaBulk, field::fzyx, uint_t(0) );
+
+   // create the lattice model
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+   real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega );
+   real_t lambda_d = lbm::collision_model::TRT::lambda_d( omega, magicNumber );
+   //LatticeModel_T latticeModel = LatticeModel_T(omegaBulk, lambda_d, lambda_e);
+   LatticeModel_T latticeModel = LatticeModel_T(omegaBulkFieldID, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(lbm::collision_model::D3Q19MRT( omegaBulk, omegaBulk, lambda_d, lambda_e, lambda_e, lambda_d ));
+#else
+   // generated KBC
+   LatticeModel_T latticeModel = LatticeModel_T(omega);
+#endif
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (fzyx)", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         uint_t(1), field::fzyx );
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::fzyx, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // interpolation functionality
+   using DensityAdaptor_T = typename lbm::Adaptor< LatticeModel_T >::Density;
+   BlockDataID densityAdaptorID = field::addFieldAdaptor<  DensityAdaptor_T >( blocks, pdfFieldID, "density adaptor" );
+
+   using DensityInterpolator_T = typename field::TrilinearFieldInterpolator<DensityAdaptor_T, FlagField_T>;
+   BlockDataID densityInterpolatorID = field::addFieldInterpolator< DensityInterpolator_T, FlagField_T >( blocks, densityAdaptorID, flagFieldID, Fluid_Flag );
+
+   // set up RPD functionality
+   std::function<void(void)> syncCall = [ps,rpdDomain, adaptionLayerSize ](){
+      const real_t overlap = real_t( 1.5 ) + std::max(real_t(0), adaptionLayerSize - real_t(2));
+      mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+      syncNextNeighborFunc(*ps, *rpdDomain, overlap);
+   };
+
+   syncCall();
+
+   mesa_pd::kernel::ExplicitEulerWithShape explEulerIntegrator(real_t(1)/real_t(numRPDSubCycles));
+
+   mesa_pd::kernel::SpringDashpot collisionResponse(1);
+   mesa_pd::mpi::ReduceProperty reduceProperty;
+
+   // set up coupling functionality
+   //Vector3<real_t> gravitationalForce( real_t(0), real_t(0), -(densityRatio - real_t(1)) * gravitationalAcceleration * sphereVolume );
+   //lbm_mesapd_coupling::AddForceOnParticlesKernel addGravitationalForce(gravitationalForce);
+   lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction;
+   lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+   lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque;
+
+   lbm_mesapd_coupling::RegularParticlesSelector sphereSelector;
+
+   lbm_mesapd_coupling::ParticleMappingKernel<BoundaryHandling_T> particleMappingKernel(blocks, boundaryHandlingID);
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // map planes into the LBM simulation -> act as no-slip boundaries
+   ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, particleMappingKernel, *accessor, NoSlip_Flag);
+
+   // map particles into the LBM simulation
+   ps->forEachParticle(false, sphereSelector, *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+
+   blockforest::communication::UniformBufferedScheme< Stencil_T > optimizedPDFCommunicationScheme( blocks );
+   optimizedPDFCommunicationScheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) ); // optimized sync
+
+   blockforest::communication::UniformBufferedScheme< Stencil_T > fullPDFCommunicationScheme( blocks );
+   fullPDFCommunicationScheme.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) ); // full sync
+
+   // create the timeloop
+
+   const uint_t timesteps = uint_c(numberOfPasses * real_c(domainSize[2]) / velocity);
+   WALBERLA_LOG_INFO_ON_ROOT("Running for " << timesteps << " time steps");
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+
+   timeloop.addFuncBeforeTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+   // vtk output
+   if( vtkIOFreq != uint_t(0) )
+   {
+      // spheres
+      auto particleVtkOutput = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleOwner>("owner");
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleLinearVelocity>("velocity");
+      auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "Particles", vtkIOFreq, baseFolder, "simulation_step");
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( particleVtkWriter ), "VTK (sphere data)" );
+
+      // flag field (written only once in the first time step, ghost layers are also written)
+      //auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field", timesteps, FieldGhostLayers, false, baseFolder );
+      //flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+      //timeloop.addFuncBeforeTimeStep( vtk::writeFiles( flagFieldVTK ), "VTK (flag field data)" );
+
+      // pdf field
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", vtkIOFreq, 0, false, baseFolder );
+
+      pdfFieldVTK->addBeforeFunction( fullPDFCommunicationScheme );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+      pdfFieldVTK->addCellInclusionFilter( fluidFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+
+      // omega bulk field
+      timeloop.addFuncBeforeTimeStep( field::createVTKOutput<ScalarField_T, float>( omegaBulkFieldID, *blocks, "omega_bulk_field", vtkIOFreq, uint_t(0), false, baseFolder ), "VTK (omega bulk field)" );
+
+   }
+
+   // sweep for updating the particle mapping into the LBM simulation
+   timeloop.add() << Sweep( lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, sphereSelector, conserveMomentum), "Particle Mapping" );
+
+   // sweep for restoring PDFs in cells previously occupied by particles
+   if( reconstructorType == "EAN")
+   {
+
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+      auto equilibriumAndNonEquilibriumSphereNormalReconstructor = lbm_mesapd_coupling::makeEquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+      auto reconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, equilibriumAndNonEquilibriumSphereNormalReconstructor, conserveMomentum);
+
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" )
+                     << Sweep( makeSharedSweep(reconstructionManager), "PDF Restore" );
+
+   } else if( reconstructorType == "Grad")
+   {
+      bool recomputeDensity = false;
+      auto gradReconstructor = lbm_mesapd_coupling::makeGradsMomentApproximationReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, omega, recomputeDensity, true, true);
+
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" )
+                     << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, gradReconstructor, conserveMomentum) ), "PDF Restore" );
+   } else if( reconstructorType == "Eq")
+   {
+      timeloop.add() << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, conserveMomentum) ), "PDF Restore" );
+   } else if( reconstructorType == "Ext")
+   {
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+      auto extrapolationReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T, lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder, true>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+      timeloop.add() << BeforeFunction( fullPDFCommunicationScheme, "LBM Communication" )
+                     << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, extrapolationReconstructor, conserveMomentum)), "PDF Restore" );
+   } else {
+      WALBERLA_ABORT("Unknown reconstructor type " << reconstructorType);
+   }
+
+   // update bulk omega in all cells to adapt to changed particle position
+   if(useOmegaBulkAdaption)
+   {
+      using OmegaBulkAdapter_T = lbm_mesapd_coupling::OmegaBulkAdapter<ParticleAccessor_T, decltype(sphereSelector)>;
+      real_t defaultOmegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, real_t(1));
+      shared_ptr<OmegaBulkAdapter_T> omegaBulkAdapter = make_shared<OmegaBulkAdapter_T>(blocks, omegaBulkFieldID, accessor, defaultOmegaBulk, omegaBulk, adaptionLayerSize, sphereSelector);
+      timeloop.add() << Sweep( makeSharedSweep(omegaBulkAdapter), "Omega Bulk Adapter");
+   }
+
+
+   bool useStreamCollide = true;
+   auto bhSweep = BoundaryHandling_T::getBlockSweep( boundaryHandlingID );
+
+   // generated sweeps
+   auto lbmSweep = LatticeModel_T::Sweep( pdfFieldID );
+   if( useStreamCollide )
+   {
+      // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+      timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                     << Sweep(bhSweep, "Boundary Handling" );
+
+      // stream + collide LBM step
+      timeloop.add() << Sweep( lbmSweep, "LB sweep" );
+   }
+   else
+   {
+      // collide LBM step
+      timeloop.add() << Sweep([&lbmSweep](IBlock * const block){lbmSweep.collide(block);}, "cell-wise collide LB sweep" );
+
+      // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+      timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                     << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+      // stream LBM step
+      timeloop.add() << Sweep([&lbmSweep](IBlock * const block){lbmSweep.stream(block);}, "cell-wise stream LB sweep" );
+   }
+
+   // evaluation functionality
+   std::string loggingFileName( baseFolder + "/LoggingPrescribedMovingSphere");
+   loggingFileName += "_Re" + std::to_string(uint_c(Re));
+   loggingFileName += "_D" + std::to_string(uint_c(diameter));
+   loggingFileName += "_vel" + std::to_string(float(velocity));
+   loggingFileName += "_recon" + reconstructorType;
+   loggingFileName += "_bvrf" + std::to_string(uint_c(bulkViscRateFactor));
+   loggingFileName += "_mn" + std::to_string(float(magicNumber));
+   if( useOmegaBulkAdaption ) loggingFileName += "_uOBA" + std::to_string(uint_c(adaptionLayerSize));
+   if( conserveMomentum ) loggingFileName += "_conserveMomentum";
+   if( artificiallyAccelerateSphere ) loggingFileName += "_acc";
+   if( !fileNameEnding.empty()) loggingFileName += "_" + fileNameEnding;
+   //loggingFileName += "_SBB";
+   loggingFileName += ".txt";
+
+   if( fileIO  )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing logging output to file \"" << loggingFileName << "\"");
+   }
+   SpherePropertyLogger<ParticleAccessor_T > logger( accessor, sphereUid, loggingFileName, fileIO, diameter, velocity,  real_t(3) * math::pi * diameter * viscosity * velocity);
+
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+   const bool useOpenMP = false;
+
+   const real_t densityRatio = real_t(7800) / real_t(935);
+   const real_t responseTime = densityRatio * diameter * diameter / ( real_t(18) * viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - response time " << responseTime);
+   const real_t accelerationFactor = real_t(1) / (real_t(0.1) * responseTime);
+
+   // time loop
+   for (uint_t i = 0; i < timesteps; ++i )
+   {
+      // perform a single simulation step -> this contains LBM and setting of the hydrodynamic interactions
+      timeloop.singleStep( timeloopTiming );
+
+      timeloopTiming["RPD"].start();
+
+      if( averageForceTorqueOverTwoTimSteps && i!= 0)
+      {
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, averageHydrodynamicForceTorque, *accessor );
+      }
+
+      reduceProperty.operator()<mesa_pd::ForceTorqueNotification>(*ps);
+         
+      ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, explEulerIntegrator, *accessor);
+      syncCall();
+
+      timeloopTiming["RPD"].end();
+
+      if( artificiallyAccelerateSphere )
+      {
+         // accelerating after reading from a checkpoint file does not make sense, as current actual runtime is not known
+         real_t newSphereVel = velocity * (exp(-accelerationFactor * real_t(i) ) - real_t(1));
+         ps->forEachParticle(useOpenMP, sphereSelector, *accessor, [newSphereVel](const size_t idx, ParticleAccessor_T& ac){ ac.setLinearVelocity(idx, Vector3<real_t>(real_t(0), real_t(0), newSphereVel));}, *accessor);
+      }
+
+      // evaluation
+      timeloopTiming["Logging"].start();
+
+      logger();
+
+      real_t density1 = real_t(0);
+      real_t density2 = real_t(0);
+      real_t averageDensity = real_t(0);
+      if(logDensity)
+      {
+         density1 = getDensityAtPosition<DensityInterpolator_T >(blocks, densityInterpolatorID, Vector3<real_t>(real_c(domainSize[0])*real_t(0.25), real_c(domainSize[1])*real_t(0.5), real_c(domainSize[2])*real_t(0.5)));
+         density2 = getDensityAtPosition<DensityInterpolator_T >(blocks, densityInterpolatorID, Vector3<real_t>(real_c(domainSize[0])*real_t(0.75), real_c(domainSize[1])*real_t(0.5), logger.getPosition()));
+         averageDensity = getAverageDensityInSystem<BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID);
+      }
+      logger.writeToFile(i, density1, density2, averageDensity);
+      timeloopTiming["Logging"].end();
+
+      // reset after logging here
+      ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor );
+
+   }
+
+   timeloopTiming.logResultOnRoot();
+
+   if(vtkOutputAtEnd)
+   {
+      std::string vtkFileName = "fluid_field";
+      vtkFileName += "_bvrf" + std::to_string(uint_c(bulkViscRateFactor));
+      if( useOmegaBulkAdaption ) vtkFileName += "_uOBA" + std::to_string(uint_c(adaptionLayerSize));
+
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, vtkFileName, uint_t(1), 0, false, baseFolder );
+
+      pdfFieldVTK->addBeforeFunction( fullPDFCommunicationScheme );
+
+      AABB sliceAABB( real_t(0), real_c(domainSize[1])*real_t(0.5)-real_t(1), 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 );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+
+      vtk::ChainedFilter combinedSliceFilter;
+      combinedSliceFilter.addFilter( fluidFilter );
+      combinedSliceFilter.addFilter( aabbSliceFilter );
+
+      pdfFieldVTK->addCellInclusionFilter( combinedSliceFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      vtk::writeFiles(pdfFieldVTK)();
+   }
+
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace sphere_moving_with_prescribed_velocity
+
+int main( int argc, char **argv ){
+   sphere_moving_with_prescribed_velocity::main(argc, argv);
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/SphereWallCollision.cpp b/apps/benchmarks/FluidParticleCoupling/SphereWallCollision.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..61a3844f14a27fc0c6bfc89e953a419f6653ed11
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/SphereWallCollision.cpp
@@ -0,0 +1,1538 @@
+//======================================================================================================================
+//
+//  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 SphereWallCollision.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/waLBerlaBuildInfo.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/math/all.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/logging/all.h"
+#include "core/mpi/Broadcast.h"
+#include "core/mpi/Reduce.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/adaptors/AdaptorCreators.h"
+#include "field/communication/PackInfo.h"
+#include "field/interpolators/FieldInterpolatorCreators.h"
+#include "field/interpolators/NearestNeighborFieldInterpolator.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/Adaptors.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.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/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.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/utility/AddForceOnParticlesKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h"
+#include "lbm_mesapd_coupling/utility/OmegaBulkAdaption.h"
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/domain/BlockForestDataHandling.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/LinearSpringDashpot.h"
+#include "mesa_pd/kernel/NonLinearSpringDashpot.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/kernel/VelocityVerletWithShape.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/mpi/ReduceProperty.h"
+#include "mesa_pd/mpi/ReduceContactHistory.h"
+#include "mesa_pd/mpi/ContactFilter.h"
+#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+#include "lbm/vtk/all.h"
+
+#include "GeneratedLBM.h"
+
+#include <functional>
+
+#define USE_TRT_LIKE_LATTICE_MODEL
+
+namespace sphere_wall_collision
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::GeneratedLBM;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+using ScalarField_T = GhostLayerField< real_t, 1>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// 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" );
+const FlagUID SimplePressure_Flag("simple pressure");
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using NoSlip_T = lbm::NoSlip< LatticeModel_T, flag_t >;
+   using MO_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using SimplePressure_T = lbm::SimplePressure< LatticeModel_T, flag_t >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, NoSlip_T, MO_T, SimplePressure_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac,
+                       bool applyOutflowBCAtTop) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ), applyOutflowBCAtTop_(applyOutflowBCAtTop) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ),
+                                  MO_T( "MO", MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ),
+                                  SimplePressure_T( "SimplePressure", SimplePressure_Flag, pdfField, real_t(1) ) );
+
+      if(applyOutflowBCAtTop_)
+      {
+         const auto simplePressure = flagField->getFlag( SimplePressure_Flag );
+
+         CellInterval domainBB = storage->getDomainCellBB();
+         domainBB.xMin() -= cell_idx_c( FieldGhostLayers );
+         domainBB.xMax() += cell_idx_c( FieldGhostLayers );
+
+         domainBB.yMin() -= cell_idx_c( FieldGhostLayers );
+         domainBB.yMax() += cell_idx_c( FieldGhostLayers );
+
+         domainBB.zMin() -= cell_idx_c( FieldGhostLayers );
+         domainBB.zMax() += cell_idx_c( FieldGhostLayers );
+
+         // TOP
+         CellInterval top( domainBB.xMin(), domainBB.yMin(), domainBB.zMax(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() );
+         storage->transformGlobalToBlockLocalCellInterval( top, *block );
+         handling->forceBoundary( simplePressure, top );
+      }
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+
+   bool applyOutflowBCAtTop_;
+};
+//*******************************************************************************************************************
+
+//*******************************************************************************************************************
+/*!\brief Evaluating the position and velocity of the sphere
+ *
+ */
+//*******************************************************************************************************************
+template< typename ParticleAccessor_T>
+class SpherePropertyLogger
+{
+public:
+   SpherePropertyLogger( const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid,
+                         const std::string & fileName, bool fileIO,
+                         real_t diameter, real_t uIn, uint_t numberOfSubSteps) :
+      ac_( ac ), sphereUid_( sphereUid ), fileName_( fileName ), fileIO_(fileIO),
+      diameter_( diameter ), uIn_( uIn ), numberOfSubSteps_(numberOfSubSteps),
+      gap_( real_t(0) ), settlingVelocity_( real_t(0) )
+   {
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str() );
+            file << "#\t t/tref\t gapSize\t gapSize/D\t velZ\t velZ/uIn\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()(const uint_t timeStep, const uint_t subStep )
+   {
+      real_t curTimestep = real_c(timeStep) + real_c(subStep) / real_c(numberOfSubSteps_);
+
+      Vector3<real_t> pos(real_t(0));
+      Vector3<real_t> transVel(real_t(0));
+
+      size_t idx = ac_->uidToIdx(sphereUid_);
+      if( idx != std::numeric_limits<size_t>::max())
+      {
+         if(!mesa_pd::data::particle_flags::isSet( ac_->getFlags(idx), mesa_pd::data::particle_flags::GHOST))
+         {
+            pos = ac_->getPosition(idx);
+            transVel = ac_->getLinearVelocity(idx);
+         }
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( pos[0], mpi::SUM );
+         mpi::allReduceInplace( pos[1], mpi::SUM );
+         mpi::allReduceInplace( pos[2], mpi::SUM );
+         mpi::allReduceInplace( transVel[2], mpi::SUM );
+      }
+
+      position_ = pos;
+      gap_ = pos[2] - real_t(0.5) * diameter_;
+      settlingVelocity_ = transVel[2];
+
+      WALBERLA_ROOT_SECTION()
+      {
+         // coarsen the output to the result file
+         if(subStep == 0)
+         {
+            gapLogging_.push_back(gap_);
+            velocityLogging_.push_back(settlingVelocity_);
+         }
+      }
+
+      if( fileIO_ )
+         writeToFile( curTimestep, gap_, settlingVelocity_);
+   }
+
+   real_t getGapSize() const
+   {
+      return gap_;
+   }
+
+   real_t getSettlingVelocity() const
+   {
+      return settlingVelocity_;
+   }
+
+   Vector3<real_t> getPosition() const
+   {
+      return position_;
+   }
+
+   void writeResult(std::string filename, uint_t tImpact)
+   {
+      WALBERLA_ROOT_SECTION()
+      {
+         WALBERLA_CHECK_EQUAL(gapLogging_.size(), velocityLogging_.size());
+         std::ofstream file;
+         file.open( filename.c_str());
+
+         file << "#\t t\t t/tref\t gapSize\t gapSize/D\t velZ\t velZ/uIn\n";
+         real_t tref = diameter_ / uIn_;
+
+         for( uint_t i = uint_t(0); i < gapLogging_.size(); ++i)
+         {
+            int timestep = int(i) - int(tImpact);
+            file << timestep << "\t" << real_c(timestep) / tref<< "\t"
+                 << "\t" << gapLogging_[i] << "\t" << gapLogging_[i] / diameter_
+                 << "\t" << velocityLogging_[i] << "\t" << velocityLogging_[i] / uIn_
+                 << "\n";
+         }
+
+         file.close();
+      }
+   }
+
+
+private:
+   void writeToFile( real_t timestep, real_t gap, real_t velocity )
+   {
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( fileName_.c_str(), std::ofstream::app );
+
+         real_t tref = diameter_ / uIn_;
+
+         file << timestep << "\t" << timestep / tref<< "\t"
+              << "\t" << gap << "\t" << gap / diameter_
+              << "\t" << velocity << "\t" << velocity / uIn_
+              << "\n";
+         file.close();
+      }
+   }
+
+
+   shared_ptr< ParticleAccessor_T > ac_;
+   const walberla::id_t sphereUid_;
+   std::string fileName_;
+   bool fileIO_;
+   real_t diameter_, uIn_;
+   uint_t numberOfSubSteps_;
+
+   real_t gap_, settlingVelocity_;
+   Vector3<real_t> position_;
+
+   std::vector<real_t> gapLogging_, velocityLogging_;
+
+};
+
+
+void createPlaneSetup(const shared_ptr<mesa_pd::data::ParticleStorage> & ps, const shared_ptr<mesa_pd::data::ShapeStorage> & ss, const math::AABB & simulationDomain, bool applyOutflowBCAtTop )
+{
+   // create bounding planes
+   mesa_pd::data::Particle&& p0 = *ps->create(true);
+   p0.setPosition(simulationDomain.minCorner());
+   p0.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,1) ));
+   p0.setOwner(mpi::MPIManager::instance()->rank());
+   p0.setType(0);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   if(!applyOutflowBCAtTop)
+   {
+      //only create top plane when no outflow BC should be set there
+      mesa_pd::data::Particle&& p1 = *ps->create(true);
+      p1.setPosition(simulationDomain.maxCorner());
+      p1.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,-1) ));
+      p1.setOwner(mpi::MPIManager::instance()->rank());
+      p1.setType(0);
+      mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+      mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+   }
+
+}
+
+struct NonCollidingSphereSelector
+{
+   explicit NonCollidingSphereSelector( real_t radius ) : radius_(radius) {}
+
+   template< typename ParticleAccessor_T >
+   bool inline operator()(const size_t particleIdx, const ParticleAccessor_T & ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+      return !mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::FIXED) &&
+             !mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::GLOBAL) &&
+             ac.getPosition(particleIdx)[2] > radius_;
+   }
+private:
+   real_t radius_;
+};
+
+
+class ForcesOnSphereLogger
+{
+public:
+   ForcesOnSphereLogger( const std::string & fileName, bool fileIO, real_t weightForce, uint_t numberOfSubSteps ) :
+         fileName_( fileName ), fileIO_( fileIO ), weightForce_( weightForce ), numberOfSubSteps_( numberOfSubSteps )
+   {
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str() );
+            file << "#t\t BuoyAndGravF\t HydInteractionF\t LubricationF\t CollisionForce\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()(const uint_t timeStep, const uint_t subStep, real_t hydForce, real_t lubForce, real_t collisionForce )
+   {
+      real_t curTimestep = real_c(timeStep) + real_c(subStep) / real_c(numberOfSubSteps_);
+
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str(), std::ofstream::app );
+
+            file << std::setprecision(10) << curTimestep << "\t" << weightForce_ << "\t" << hydForce << "\t" << lubForce << "\t" << collisionForce << "\n";
+            file.close();
+         }
+      }
+
+   }
+
+
+private:
+
+   std::string fileName_;
+   bool fileIO_;
+
+   real_t weightForce_;
+   uint_t numberOfSubSteps_;
+};
+
+template<typename ParticleAccessor_T>
+real_t getForce(walberla::id_t uid, ParticleAccessor_T & ac)
+{
+   auto idx = ac.uidToIdx(uid);
+   real_t force = real_t(0);
+   if(idx != ac.getInvalidIdx())
+   {
+      force = ac.getForce(idx)[2];
+   }
+   WALBERLA_MPI_SECTION()
+   {
+      mpi::allReduceInplace( force, mpi::SUM );
+   }
+   return force;
+}
+
+
+real_t getAcceleratedSphereVelocity(real_t accelerationRate, uint_t timestep, real_t tref, real_t finalVelocity)
+{
+   return finalVelocity * (std::exp(-real_c(timestep)/(tref*accelerationRate))-real_t(1));
+}
+
+template< typename DensityInterpolator_T>
+void logDensityToFile(const shared_ptr<StructuredBlockStorage> & blocks, BlockDataID densityInterpolatorID, real_t surfaceDistance,
+                      Vector3<real_t> spherePosition, real_t diameter, std::string fileName, uint_t timestep, real_t averageDensityInSystem )
+{
+   real_t evalRadius = real_t(0.5) * diameter + surfaceDistance;
+   std::vector< Vector3<real_t> > evaluationPositions;
+   evaluationPositions.push_back(spherePosition + Vector3<real_t>(real_t(0), real_t(0), evalRadius));
+   evaluationPositions.push_back(spherePosition + Vector3<real_t>(evalRadius / sqrt(real_t(2)), real_t(0), evalRadius / sqrt(real_t(2))));
+   evaluationPositions.push_back(spherePosition + Vector3<real_t>(evalRadius, real_t(0), real_t(0)));
+   evaluationPositions.push_back(spherePosition + Vector3<real_t>(evalRadius / sqrt(real_t(2)), real_t(0), -evalRadius / sqrt(real_t(2))));
+   evaluationPositions.push_back(spherePosition + Vector3<real_t>(real_t(0), real_t(0), -evalRadius));
+
+   std::vector<real_t> densityAtPos(evaluationPositions.size(), real_t(0));
+
+   for( auto & block: *blocks)
+   {
+      auto densityInterpolator = block.getData<DensityInterpolator_T>(densityInterpolatorID);
+      for(auto i = uint_t(0); i < evaluationPositions.size(); ++i)
+      {
+         auto pos = evaluationPositions[i];
+         if(block.getAABB().contains(pos))
+         {
+            densityInterpolator->get(pos, &densityAtPos[i]);
+         }
+      }
+   }
+
+   for(auto i = uint_t(0); i < densityAtPos.size(); ++i)
+   {
+      // reduce to root
+      mpi::reduceInplace(densityAtPos[i], mpi::SUM);
+   }
+
+   WALBERLA_ROOT_SECTION()
+   {
+      std::ofstream file;
+      file.open( fileName.c_str(), std::ofstream::app );
+
+      file << std::setprecision(8) << timestep << "\t" << averageDensityInSystem;
+      for(auto density: densityAtPos)
+      {
+         file << std::setprecision(8) << "\t" << density;
+      }
+      file << "\n";
+      file.close();
+   }
+}
+
+template< typename BoundaryHandling_T>
+real_t getAverageDensityInSystem(const shared_ptr<StructuredBlockStorage> & blocks, BlockDataID pdfFieldID, BlockDataID boundaryHandlingID)
+{
+   real_t totalMass = real_t(0);
+   uint_t count = uint_t(0);
+   for( auto & block: *blocks)
+   {
+      auto pdfField = block.getData<PdfField_T>(pdfFieldID);
+      auto boundaryHandling = block.getData<BoundaryHandling_T>(boundaryHandlingID);
+      WALBERLA_FOR_ALL_CELLS_XYZ(pdfField,
+                                 if(boundaryHandling->isDomain(x,y,z))
+                                 {
+                                    totalMass += pdfField->getDensity(x,y,z);
+                                    ++count;
+                                 }
+      );
+   }
+
+   // reduce to root
+   mpi::reduceInplace(totalMass, mpi::SUM);
+   mpi::reduceInplace(count, mpi::SUM);
+
+   return totalMass/real_c(count);
+}
+
+
+//////////
+// MAIN //
+//////////
+
+//*******************************************************************************************************************
+/*!\brief PHYSICAL Test case of a sphere settling and colliding with a wall submerged in a viscous fluid.
+ *
+ * The trajectory of the bouncing sphere is logged and compared to reference experiments.
+ *
+ * for experiments see: Gondret, Lance, Petit - "Bouncing motion of spherical particles in fluids" 2002
+ * for simulations see e.g.: Biegert, Vowinckel, Meiburg - "A collision model for grain-resolving simulations of flows over
+ * dense, mobile, polydisperse granular sediment beds" 2017
+ */
+//*******************************************************************************************************************
+
+
+int main( int argc, char **argv )
+{
+   Environment env( argc, argv );
+
+   if( !env.config() )
+   {
+      WALBERLA_ABORT("Usage: " << argv[0] << " path-to-configuration-file \n");
+   }
+
+   Config::BlockHandle simulationInput = env.config()->getBlock( "BouncingSphere" );
+
+   // setup description from separate block
+   const std::string setup = simulationInput.getParameter<std::string>("setup");
+   Config::BlockHandle setupDescription = env.config()->getBlock( setup );
+
+   const real_t Re = setupDescription.getParameter<real_t>("Re");
+   const real_t densityFluid_SI = setupDescription.getParameter<real_t>("densityFluid_SI");
+   const real_t dynamicViscosityFluid_SI = setupDescription.getParameter<real_t>("dynamicViscosityFluid_SI");
+   const real_t densitySphere_SI = setupDescription.getParameter<real_t>("densitySphere_SI");
+   const real_t diameter_SI = setupDescription.getParameter<real_t>("diameter_SI");
+   const real_t gravitationalAcceleration_SI = setupDescription.getParameter<real_t>("gravitationalAcceleration_SI");
+   const real_t restitutionCoeff = setupDescription.getParameter<real_t>("restitutionCoeff");
+   const uint_t numberOfBouncesUntilTermination = setupDescription.getParameter<uint_t>("numberOfBouncesUntilTermination");
+   const Vector3<real_t> domainSizeNonDim = setupDescription.getParameter<Vector3<real_t> >("domainSize");
+   const Vector3<uint_t> numberOfBlocksPerDirection = setupDescription.getParameter<Vector3<uint_t> >("numberOfBlocksPerDirection");
+
+   // numerical parameters
+
+   // control
+   const bool randomizeInitialSpherePosition = simulationInput.getParameter<bool>("randomizeInitialSpherePosition");
+   const bool initializeSphereVelocity = simulationInput.getParameter<bool>("initializeSphereVelocity");
+   const bool applyOutflowBCAtTop = simulationInput.getParameter<bool>("applyOutflowBCAtTop");
+   bool artificiallyAccelerateSphere = simulationInput.getParameter<bool>("artificiallyAccelerateSphere");
+
+   // LBM
+   const real_t uIn = simulationInput.getParameter<real_t>("uIn");
+   const real_t diameter = simulationInput.getParameter<real_t>("diameter");
+   const real_t magicNumber = simulationInput.getParameter<real_t>("magicNumber");
+   const real_t bulkViscRateFactor = simulationInput.getParameter<real_t>("bulkViscRateFactor");
+   const bool useOmegaBulkAdaption = simulationInput.getParameter<bool>("useOmegaBulkAdaption");
+
+   const uint_t numRPDSubCycles = simulationInput.getParameter<uint_t>("numRPDSubCycles");
+   const bool useLubricationCorrection = simulationInput.getParameter<bool>("useLubricationCorrection");
+   const real_t lubricationCutOffDistance = simulationInput.getParameter<real_t>("lubricationCutOffDistance");
+   const real_t lubricationMinimalGapSizeNonDim = simulationInput.getParameter<real_t>("lubricationMinimalGapSizeNonDim");
+   const bool useVelocityVerlet = simulationInput.getParameter<bool>("useVelocityVerlet");
+
+   // Collision Response
+   const real_t collisionTime = simulationInput.getParameter<real_t>("collisionTime");
+   const real_t StCrit = simulationInput.getParameter<real_t>("StCrit");
+   const bool useACTM = simulationInput.getParameter<bool>("useACTM");
+
+   const bool averageForceTorqueOverTwoTimeSteps = simulationInput.getParameter<bool>("averageForceTorqueOverTwoTimeSteps");
+   const bool conserveMomentum = simulationInput.getParameter<bool>("conserveMomentum");
+   const bool disableFluidForceDuringContact = simulationInput.getParameter<bool>("disableFluidForceDuringContact");
+   const std::string reconstructorType = simulationInput.getParameter<std::string>("reconstructorType");
+
+   const bool fileIO = simulationInput.getParameter<bool>("fileIO");
+   const uint_t vtkIOFreq = simulationInput.getParameter<uint_t>("vtkIOFreq");
+   const std::string baseFolder = simulationInput.getParameter<std::string>("baseFolder");
+   bool vtkOutputOnCollision = simulationInput.getParameter<bool>("vtkOutputOnCollision");
+
+   bool writeCheckPointFile = simulationInput.getParameter<bool>("writeCheckPointFile", false);
+   bool readFromCheckPointFile = simulationInput.getParameter<bool>("readFromCheckPointFile", false);
+
+   WALBERLA_ROOT_SECTION()
+   {
+      if( fileIO )
+      {
+         // create base directory if it does not yet exist
+         filesystem::path tpath( baseFolder );
+         if( !filesystem::exists( tpath ) )
+            filesystem::create_directory( tpath );
+      }
+   }
+
+
+   //////////////////////////////////////
+   // SIMULATION PROPERTIES in SI units//
+   //////////////////////////////////////
+
+   const real_t densityRatio = densitySphere_SI / densityFluid_SI;
+   const real_t kinematicViscosityFluid_SI = dynamicViscosityFluid_SI / densityFluid_SI;
+   // here, we do the analogy with experiments via the Galileo number which is a-priori well-defined in contrast to the Stokes number which is a result of the simulation
+   // -> no, this is not a good idea as the sphere in the experiments has in some cases not yet reached the terminal settling velocity
+   // thus, it is better to pose the reported resulting Re as an input parameter
+   const real_t uIn_SI = Re * kinematicViscosityFluid_SI / diameter_SI;
+   const real_t ug_SI = std::sqrt((densityRatio-real_t(1)) * gravitationalAcceleration_SI * diameter_SI);
+   const real_t Ga = ug_SI * diameter_SI / kinematicViscosityFluid_SI;
+
+   //////////////////////////
+   // NUMERICAL PARAMETERS //
+   //////////////////////////
+
+   const real_t dx_SI = diameter_SI / diameter;
+   const real_t sphereVolume = math::pi / real_t(6) * diameter * diameter * diameter;
+   const real_t dt_SI = uIn / uIn_SI * dx_SI;
+   const real_t viscosity =  kinematicViscosityFluid_SI * dt_SI / ( dx_SI * dx_SI );
+   const real_t gravitationalAcceleration = gravitationalAcceleration_SI * dt_SI * dt_SI / dx_SI;
+   const real_t omega = lbm::collision_model::omegaFromViscosity(viscosity);
+
+   const real_t Re_p = diameter * uIn / viscosity;
+   const real_t St = densityRatio * uIn * diameter / ( real_t(9) * viscosity );
+
+   const real_t omegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor);
+
+   const real_t densityFluid = real_t(1);
+   const real_t densitySphere = densityRatio;
+
+   const real_t dx = real_t(1);
+
+   const real_t responseTime = densityRatio * diameter * diameter / ( real_t(18) * viscosity );
+   const real_t accelerationFactor = real_t(1) / (real_t(0.1) * responseTime);
+   const real_t tref = diameter / uIn;
+
+   const real_t particleMass = densityRatio * sphereVolume;
+   const real_t Mij = particleMass; // * particleMass / ( real_t(2) * particleMass ); // Mij = M for sphere-wall collision
+   const real_t lnDryResCoeff = std::log(restitutionCoeff);
+   const real_t stiffnessCoeff = Mij * ( math::pi * math::pi + lnDryResCoeff * lnDryResCoeff ) / (collisionTime * collisionTime); // Costa et al., Eq. 18
+   const real_t dampingCoeff = - real_t(2) * Mij * lnDryResCoeff / collisionTime; // Costa et al., Eq. 18
+   //const real_t contactDuration = real_t(2) * math::pi * Mij / ( std::sqrt( real_t(4) * Mij * stiffnessCoeff - dampingCoeff * dampingCoeff )); //formula from Uhlman
+
+   const real_t var_a = std::sqrt( math::pi * math::pi + (std::log(restitutionCoeff) * std::log(restitutionCoeff) ) );
+   const real_t TnLimit1 = var_a * diameter / uIn * std::exp(-std::asin(math::pi / var_a ) / math::pi); // Costa et al, Eq. 20
+   const real_t TnLimit2 = std::sqrt(diameter / gravitationalAcceleration * var_a * var_a / std::abs(real_t(1) - densityFluid/densitySphere)); // Costa et al, Eq. 21
+
+   const real_t overlapDueToWeight = std::abs(densitySphere - densityFluid) * gravitationalAcceleration * sphereVolume / stiffnessCoeff; // Costa et al., given in text after Eq. 34
+
+   const real_t uInCrit = real_t(9) * StCrit * viscosity / ( densityRatio * diameter);
+
+   Vector3<uint_t> domainSize( uint_c(domainSizeNonDim[0] * diameter ), uint_c(domainSizeNonDim[1] * diameter ), uint_c(domainSizeNonDim[2] * diameter ) );
+
+   real_t initialSpherePosition = real_c(domainSize[2]) - real_t(1.5) * diameter;
+
+   if( randomizeInitialSpherePosition )
+   {
+      uint_t seed1 = uint_c(std::chrono::system_clock::now().time_since_epoch().count());
+      mpi::broadcastObject(seed1); // root process chooses seed and broadcasts it
+      std::mt19937 g1 (seed1);
+
+      real_t initialPositionOffset = real_t(0.5)*math::realRandom<real_t>(real_t(-1), real_t(1), g1);
+      initialSpherePosition += initialPositionOffset;
+   }
+
+
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in SI units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter_SI << ", densityRatio = " << densityRatio);
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid_SI << ", dyn. visc = " << dynamicViscosityFluid_SI << ", kin. visc = " << kinematicViscosityFluid_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - ug = " << ug_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - Galileo number = " << Ga );
+   WALBERLA_LOG_INFO_ON_ROOT(" - target Reynolds number = " << Re );
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = <" << real_c(domainSizeNonDim[0]) * diameter_SI << "," << real_c(domainSizeNonDim[1]) * diameter_SI  << ","<< real_c(domainSizeNonDim[2]) * diameter_SI  << ">");
+   WALBERLA_LOG_INFO_ON_ROOT(" - dx = " << dx_SI);
+   WALBERLA_LOG_INFO_ON_ROOT(" - dt = " << dt_SI);
+
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in simulation, i.e. lattice, units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize);
+   if(applyOutflowBCAtTop) WALBERLA_LOG_INFO_ON_ROOT(" - outflow BC at top");
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter << ", density = " << densitySphere );
+   WALBERLA_LOG_INFO_ON_ROOT(" - initial sphere position = " << initialSpherePosition );
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid << ", relaxation time (tau) = " << real_t(1) / omega << ", omega = " << omega << ", kin. visc = " << viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - magic number = " << magicNumber);
+   WALBERLA_LOG_INFO_ON_ROOT(" - omega bulk = " << omegaBulk << ", bulk visc = " << lbm_mesapd_coupling::bulkViscosityFromOmegaBulk(omegaBulk) << " ( bulk visc rate factor (conste) = " << bulkViscRateFactor << ")");
+   if(useOmegaBulkAdaption) WALBERLA_LOG_INFO_ON_ROOT(" - using omega bulk adaption in vicinity of sphere");
+   WALBERLA_LOG_INFO_ON_ROOT(" - gravitational acceleration = " << gravitationalAcceleration );
+   WALBERLA_LOG_INFO_ON_ROOT(" - expected settling velocity = " << uIn );
+   WALBERLA_LOG_INFO_ON_ROOT(" - target Reynolds number = " << Re_p );
+   WALBERLA_LOG_INFO_ON_ROOT(" - target Stokes number = " << St );
+   WALBERLA_LOG_INFO_ON_ROOT(" - tref = " << tref );
+   WALBERLA_LOG_INFO_ON_ROOT(" - Stokes response time = " << responseTime );
+   if( artificiallyAccelerateSphere )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - artificially accelerating sphere with factor " << accelerationFactor)
+   }
+   WALBERLA_LOG_INFO_ON_ROOT(" - integrator = " << (useVelocityVerlet ? "Velocity Verlet" : "Explicit Euler") );
+   if( vtkIOFreq > 0 )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing vtk files to folder \"" << baseFolder << "\" with frequency " << vtkIOFreq);
+   }
+   if(vtkOutputOnCollision)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing vtk files during collision events");
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("Collision Response properties:" );
+   WALBERLA_LOG_INFO_ON_ROOT(" - collision time = " << collisionTime );
+   if(useACTM)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - using nonlinear collision model with ACTM" );
+   }
+   else
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - using linear collision model with fixed parameters:" );
+      WALBERLA_LOG_INFO_ON_ROOT("  - damping coeff = " << dampingCoeff );
+      WALBERLA_LOG_INFO_ON_ROOT("  - stiffness coeff = " << stiffnessCoeff );
+      WALBERLA_LOG_INFO_ON_ROOT(" - overlap due to particle weight = " << overlapDueToWeight);
+   }
+   WALBERLA_LOG_INFO_ON_ROOT(" - TnCrit1 = " << TnLimit1 << ", TnCrit2 = " << TnLimit2 );
+   WALBERLA_LOG_INFO_ON_ROOT(" - coeff of restitution = " << restitutionCoeff );
+   WALBERLA_LOG_INFO_ON_ROOT(" - number of RPD sub cycles = " << numRPDSubCycles );
+   WALBERLA_LOG_INFO_ON_ROOT(" - lubrication correction = " << (useLubricationCorrection ? "yes" : "no") );
+   if( useLubricationCorrection )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT("  - lubrication correction cut off = " << lubricationCutOffDistance );
+      WALBERLA_LOG_INFO_ON_ROOT("  - lubrication correction minimal gap size non dim = " << lubricationMinimalGapSizeNonDim  << " ( = " << lubricationMinimalGapSizeNonDim * diameter * real_t(0.5) << " cells )");
+   }
+   WALBERLA_LOG_INFO_ON_ROOT("Coupling properties:" );
+   WALBERLA_LOG_INFO_ON_ROOT(" - disable hydrodynamic forces during contact = " << (disableFluidForceDuringContact ? "yes" : "no") );
+   if(disableFluidForceDuringContact) WALBERLA_LOG_INFO_ON_ROOT("  - StCrit = " << StCrit << ", uInCrit = " << uInCrit );
+   WALBERLA_LOG_INFO_ON_ROOT(" - average forces over two LBM time steps = " << (averageForceTorqueOverTwoTimeSteps ? "yes" : "no") );
+   WALBERLA_LOG_INFO_ON_ROOT(" - conserve momentum = " << (conserveMomentum ? "yes" : "no") );
+   WALBERLA_LOG_INFO_ON_ROOT(" - reconstructor = " << reconstructorType );
+
+   std::string checkPointFileName = "checkPointingFile_" + setup
+                                    + "_uIn" + std::to_string(uIn)
+                                    + "_d" + std::to_string(diameter)
+                                    + "_bulkViscFactor" + std::to_string(bulkViscRateFactor)
+                                    + "_mn" + std::to_string(magicNumber);
+   if(useOmegaBulkAdaption) checkPointFileName +="_omegaBulkAdaption";
+   if(applyOutflowBCAtTop) checkPointFileName +="_outflowBC";
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+   checkPointFileName +="_TRTlike";
+#else
+   checkPointFileName += "_other";
+#endif
+
+   WALBERLA_LOG_INFO_ON_ROOT("Checkpointing:");
+   WALBERLA_LOG_INFO_ON_ROOT(" - read from checkpoint file = " << (readFromCheckPointFile ? "yes" : "no"));
+   WALBERLA_LOG_INFO_ON_ROOT(" - write checkpoint file = " << (writeCheckPointFile ? "yes" : "no"));
+   if( readFromCheckPointFile || writeCheckPointFile) WALBERLA_LOG_INFO_ON_ROOT(" - checkPointingFileName = " << checkPointFileName );
+
+   if(readFromCheckPointFile && writeCheckPointFile )
+   {
+      // decide which option to choose
+      if(filesystem::exists( checkPointFileName+"_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;
+      }
+   }
+
+   if(readFromCheckPointFile && artificiallyAccelerateSphere)
+   {
+      // accelerating after reading from a checkpoint file does not make sense, as current actual runtime is not known
+      WALBERLA_LOG_INFO_ON_ROOT("NOTE: switching off artificial acceleration of sphere due to reading from check point file");
+      artificiallyAccelerateSphere = false;
+   }
+
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+
+   Vector3<uint_t> cellsPerBlockPerDirection( domainSize[0] / numberOfBlocksPerDirection[0],
+                                              domainSize[1] / numberOfBlocksPerDirection[1],
+                                              domainSize[2] / numberOfBlocksPerDirection[2] );
+   for( uint_t i = 0; i < 3; ++i ) {
+      WALBERLA_CHECK_EQUAL(cellsPerBlockPerDirection[i] * numberOfBlocksPerDirection[i], domainSize[i],
+                           "Unmatching domain decomposition in direction " << i << "!");
+   }
+
+   auto blocks = blockforest::createUniformBlockGrid( numberOfBlocksPerDirection[0], numberOfBlocksPerDirection[1], numberOfBlocksPerDirection[2],
+                                                      cellsPerBlockPerDirection[0], cellsPerBlockPerDirection[1], cellsPerBlockPerDirection[2], dx,
+                                                      0, false, false,
+                                                      true, true, false, //periodicity
+                                                      false );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Domain decomposition:");
+   WALBERLA_LOG_INFO_ON_ROOT(" - blocks per direction = " << numberOfBlocksPerDirection );
+   WALBERLA_LOG_INFO_ON_ROOT(" - cells per block = " << cellsPerBlockPerDirection );
+
+   //write domain decomposition to file
+   if( vtkIOFreq > 0 )
+   {
+      vtk::writeDomainDecomposition( blocks, "initial_domain_decomposition", baseFolder );
+   }
+
+
+   //////////////////
+   // RPD COUPLING //
+   //////////////////
+
+   auto rpdDomain = std::make_shared<mesa_pd::domain::BlockForestDomain>(blocks->getBlockForestPointer());
+
+   //init data structures
+   auto ps = walberla::make_shared<mesa_pd::data::ParticleStorage>(1);
+   BlockDataID particleStorageID;
+   if(readFromCheckPointFile)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT( "Initializing particles from checkpointing file!" );
+      particleStorageID = blocks->loadBlockData( checkPointFileName + "_mesa.txt", mesa_pd::domain::createBlockForestDataHandling(ps), "Particle Storage" );
+   } else {
+      particleStorageID = blocks->addBlockData(mesa_pd::domain::createBlockForestDataHandling(ps), "Particle Storage");
+   }
+
+   auto ss = walberla::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   // bounding planes
+   createPlaneSetup(ps,ss,blocks->getDomain(), applyOutflowBCAtTop);
+
+   // create sphere and store Uid
+   Vector3<real_t> initialPosition( real_t(0.5) * real_c(domainSize[0]), real_t(0.5) * real_c(domainSize[1]), initialSpherePosition );
+   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();
+         }
+      }
+   } else
+   {
+      // create sphere
+      if (rpdDomain->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);
+         p.setType(0);
+         if( initializeSphereVelocity ) p.setLinearVelocity(Vector3<real_t>(real_t(0), real_t(0), -real_t(0.1) * uIn));
+         sphereUid = p.getUid();
+      }
+   }
+   mpi::allReduceInplace(sphereUid, mpi::SUM);
+
+   if(sphereUid == 0) WALBERLA_ABORT("No sphere present - aborting!"); //something went wrong in the checkpointing probably
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // add omega bulk field
+   BlockDataID omegaBulkFieldID = field::addToStorage<ScalarField_T>( blocks, "omega bulk field", omegaBulk, field::fzyx, uint_t(0) );
+
+   // create the lattice model
+
+   // TRT
+   //LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::TRT::constructWithMagicNumber( real_t(1) / relaxationTime ) );
+
+   // generated MRT
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+   WALBERLA_LOG_INFO_ON_ROOT("Using TRT-like lattice model!");
+   real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega );
+   real_t lambda_d = lbm::collision_model::TRT::lambda_d( omega, magicNumber );
+   //LatticeModel_T latticeModel = LatticeModel_T(omegaBulk, lambda_d, lambda_e);
+   LatticeModel_T latticeModel = LatticeModel_T(omegaBulkFieldID, lambda_d, lambda_e);
+   //LatticeModel_T latticeModel = LatticeModel_T(lbm::collision_model::D3Q19MRT( omegaBulk, omegaBulk, lambda_d, lambda_e, lambda_e, lambda_d ));
+#else
+   WALBERLA_LOG_INFO_ON_ROOT("Using different lattice model!");
+   // generated model with a single omega
+   LatticeModel_T latticeModel = LatticeModel_T(omega);
+#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),
+                                                                              uint_t(1), field::fzyx );
+
+      pdfFieldID = blocks->loadBlockData( checkPointFileName+"_lbm.txt", dataHandling, "pdf field" );
+
+   } else {
+      // add PDF field
+      pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field", latticeModel,
+                                                                Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                uint_t(1), field::fzyx );
+   }
+
+
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::fzyx, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor, applyOutflowBCAtTop), "boundary handling" );
+
+   // interpolation functionality
+   using DensityAdaptor_T = typename lbm::Adaptor< LatticeModel_T >::Density;
+   BlockDataID densityAdaptorID = field::addFieldAdaptor<  DensityAdaptor_T >( blocks, pdfFieldID, "density adaptor" );
+
+   using DensityInterpolator_T = typename field::NearestNeighborFieldInterpolator<DensityAdaptor_T, FlagField_T>;
+   BlockDataID densityInterpolatorID = field::addFieldInterpolator< DensityInterpolator_T, FlagField_T >( blocks, densityAdaptorID, flagFieldID, Fluid_Flag );
+
+   // set up RPD functionality
+   std::function<void(void)> syncCall = [&ps,&rpdDomain](){
+      const real_t overlap = real_t( 1.5 );
+      mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+      syncNextNeighborFunc(*ps, *rpdDomain, overlap);
+   };
+
+   syncCall();
+
+   real_t timeStepSizeRPD = real_t(1)/real_t(numRPDSubCycles);
+   mesa_pd::kernel::ExplicitEulerWithShape explEulerIntegrator(timeStepSizeRPD);
+   mesa_pd::kernel::VelocityVerletWithShapePreForceUpdate  vvIntegratorPreForce(timeStepSizeRPD);
+   mesa_pd::kernel::VelocityVerletWithShapePostForceUpdate vvIntegratorPostForce(timeStepSizeRPD);
+
+   // linear model
+   mesa_pd::kernel::LinearSpringDashpot linearCollisionResponse(1);
+   linearCollisionResponse.setStiffnessN(0,0,stiffnessCoeff);
+   linearCollisionResponse.setDampingN(0,0,dampingCoeff);
+
+   // nonlinear model for ACTM
+   mesa_pd::kernel::NonLinearSpringDashpot nonLinearCollisionResponse(1, collisionTime);
+   nonLinearCollisionResponse.setLnCORsqr(0,0,lnDryResCoeff * lnDryResCoeff);
+   nonLinearCollisionResponse.setMeff(0,0,Mij);
+
+   mesa_pd::mpi::ReduceProperty reduceProperty;
+
+   mesa_pd::mpi::ReduceContactHistory reduceAndSwapContactHistory;
+
+   // set up coupling functionality
+   Vector3<real_t> gravitationalForce( real_t(0), real_t(0), -(densitySphere - densityFluid) * gravitationalAcceleration * sphereVolume );
+   lbm_mesapd_coupling::AddForceOnParticlesKernel addGravitationalForce(gravitationalForce);
+   lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+   lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque;
+   lbm_mesapd_coupling::LubricationCorrectionKernel lubricationCorrectionKernel(viscosity, [lubricationMinimalGapSizeNonDim](real_t r){ return lubricationMinimalGapSizeNonDim * r;}, lubricationCutOffDistance, real_t(0), real_t(0) ); // no tangential components needed
+   lbm_mesapd_coupling::ParticleMappingKernel<BoundaryHandling_T> particleMappingKernel(blocks, boundaryHandlingID);
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // map planes into the LBM simulation -> act as no-slip boundaries
+   ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, particleMappingKernel, *accessor, NoSlip_Flag);
+
+   // map particles into the LBM simulation
+   ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+   blockforest::communication::UniformBufferedScheme< Stencil_T > optimizedPDFCommunicationScheme( blocks );
+   optimizedPDFCommunicationScheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) ); // optimized sync
+
+   blockforest::communication::UniformBufferedScheme< Stencil_T > fullPDFCommunicationScheme( blocks );
+   fullPDFCommunicationScheme.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) ); // full sync
+
+   // create the timeloop
+   const uint_t timesteps = uint_c( real_t(3) * initialPosition[2] / uIn);
+
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+   timeloop.addFuncBeforeTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+   // vtk output
+   if( vtkIOFreq != uint_t(0) )
+   {
+      // sphere
+      auto particleVtkOutput = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleOwner>("owner");
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleUid>("uid");
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleShapeID>("shapeID");
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleLinearVelocity>("velocity");
+      particleVtkOutput->setParticleSelector( [sphereShape](const mesa_pd::data::ParticleStorage::iterator& pIt) {return pIt->getShapeID() == sphereShape;} ); //limit output to sphere
+      auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "Particles", vtkIOFreq, baseFolder, "simulation_step");
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( particleVtkWriter ), "VTK (sphere data)" );
+
+      // flag field (written only once in the first time step, ghost layers are also written)
+
+      auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field", timesteps, FieldGhostLayers, false, baseFolder );
+      flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( flagFieldVTK ), "VTK (flag field data)" );
+
+
+      // pdf field, as slice
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", vtkIOFreq, 0, false, baseFolder );
+
+      pdfFieldVTK->addBeforeFunction( fullPDFCommunicationScheme );
+
+      AABB sliceAABB( real_t(0), real_c(domainSize[1])*real_t(0.5)-real_t(1), 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 );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+
+      vtk::ChainedFilter combinedSliceFilter;
+      combinedSliceFilter.addFilter( fluidFilter );
+      combinedSliceFilter.addFilter( aabbSliceFilter );
+
+      pdfFieldVTK->addCellInclusionFilter( combinedSliceFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+
+      // omega bulk field
+      //timeloop.addFuncBeforeTimeStep( field::createVTKOutput<ScalarField_T, float>( omegaBulkFieldID, *blocks, "omega_bulk_field", vtkIOFreq, uint_t(0), false, baseFolder ), "VTK (omega bulk field)" );
+   }
+
+
+
+   // update bulk omega in all cells to adapt to changed particle position
+   if(useOmegaBulkAdaption)
+   {
+      using OmegaBulkAdapter_T = lbm_mesapd_coupling::OmegaBulkAdapter<ParticleAccessor_T, lbm_mesapd_coupling::RegularParticlesSelector>;
+      real_t defaultOmegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, real_t(1));
+      real_t adaptionLayerSize = real_t(2);
+      shared_ptr<OmegaBulkAdapter_T> omegaBulkAdapter = make_shared<OmegaBulkAdapter_T>(blocks, omegaBulkFieldID, accessor, defaultOmegaBulk, omegaBulk, adaptionLayerSize, lbm_mesapd_coupling::RegularParticlesSelector());
+
+      // initial adaption
+      for (auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) {
+         (*omegaBulkAdapter)(blockIt.get());
+      }
+
+      timeloop.add() << Sweep( makeSharedSweep(omegaBulkAdapter), "Omega Bulk Adapter");
+   }
+
+
+   // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+   timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                  << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+   // stream + collide LBM step
+   // generated LBM sweep
+   timeloop.add() << Sweep( LatticeModel_T::Sweep( pdfFieldID ), "LB stream & collide" );
+   // walberla sweeps:
+   //timeloop.add() << Sweep( makeSharedSweep( lbm::makeCellwiseSweep< LatticeModel_T, FlagField_T >( pdfFieldID, flagFieldID, Fluid_Flag ) ), "cell-wise LB sweep" );
+
+   // this is carried out after the particle integration, it corrects the flag field and restores missing PDF information
+   // then, the checkpointing file can be written, as otherwise some cells are invalid and can not be recovered
+   SweepTimeloop timeloopAfterParticles( blocks->getBlockStorage(), timesteps );
+
+   // sweep for updating the particle mapping into the LBM simulation
+   timeloopAfterParticles.add() << Sweep( lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID,boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, lbm_mesapd_coupling::RegularParticlesSelector(), conserveMomentum), "Particle Mapping" );
+
+   // sweep for restoring PDFs in cells previously occupied by particles
+   if( reconstructorType == "EAN" )
+   {
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+      auto equilibriumAndNonEquilibriumSphereNormalReconstructor = lbm_mesapd_coupling::makeEquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+      timeloopAfterParticles.add() << BeforeFunction( fullPDFCommunicationScheme, "LBM Communication" )
+                                   << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, equilibriumAndNonEquilibriumSphereNormalReconstructor, conserveMomentum)), "PDF Restore" );
+   }else if( reconstructorType == "Grad" )
+   {
+      bool recomputeTargetDensity = false;
+      auto gradReconstructor = lbm_mesapd_coupling::makeGradsMomentApproximationReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, omega, recomputeTargetDensity,true);
+
+      timeloopAfterParticles.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" )
+                                   << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, gradReconstructor, conserveMomentum) ), "PDF Restore" );
+   }else if( reconstructorType == "Eq" )
+   {
+      timeloopAfterParticles.add() << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, conserveMomentum)), "PDF Restore" );
+   }else if( reconstructorType == "Ext")
+   {
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+#ifdef USE_TRT_LIKE_LATTICE_MODEL
+      auto extrapolationReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T, lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder, true>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+#else
+      auto extrapolationReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T, lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder, false>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, uint_t(3), true);
+#endif
+      timeloopAfterParticles.add() << BeforeFunction( fullPDFCommunicationScheme, "LBM Communication" )
+                                   << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, extrapolationReconstructor, conserveMomentum)), "PDF Restore" );
+   } else
+   {
+      WALBERLA_ABORT("Reconstructor Type " << reconstructorType << " not implemented!");
+   }
+
+
+
+
+   // evaluation functionality
+   std::string loggingFileName( baseFolder + "/LoggingSphereWallCollision.txt");
+   if( fileIO  )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing logging output to file \"" << loggingFileName << "\"");
+   }
+   SpherePropertyLogger<ParticleAccessor_T> logger( accessor, sphereUid, loggingFileName, fileIO, diameter, uIn, numRPDSubCycles );
+
+   std::string forceLoggingFileName( baseFolder + "/ForceLoggingSphereWallCollision.txt");
+   if( fileIO  )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing force logging output to file \"" << forceLoggingFileName << "\"");
+   }
+   ForcesOnSphereLogger sphereForceLogger(forceLoggingFileName, fileIO, gravitationalForce[2], numRPDSubCycles);
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+   // evaluation quantities
+   uint_t numBounces = uint_t(0);
+   uint_t tImpact = uint_t(0);
+   std::vector<uint_t> impactTimes;
+
+   real_t curVel(real_t(0)), oldVel(real_t(0));
+   real_t maxSettlingVel = real_t(0);
+   std::vector<real_t> maxSettlingVelBetweenBounces;
+
+   real_t minGapSize(real_t(0));
+   real_t maxGapSize(real_t(0));
+   std::vector<real_t> maxGapSizeBetweenBounces;
+
+   real_t actualSt(real_t(0));
+   real_t actualRe(real_t(0));
+
+   WALBERLA_LOG_INFO_ON_ROOT("Running for maximum of " << timesteps << " timesteps!");
+   
+   const bool useOpenMP = false;
+
+   // special vtk output
+   auto pdfFieldVTKCollision = vtk::createVTKOutput_BlockData( blocks, "collision_fluid_field", 1, 1, false, baseFolder );
+
+   blockforest::communication::UniformBufferedScheme< stencil::D3Q27 > pdfGhostLayerSync( blocks );
+   pdfGhostLayerSync.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) );
+   pdfFieldVTKCollision->addBeforeFunction( pdfGhostLayerSync );
+
+   AABB boxAABB( initialPosition[0] - diameter, initialPosition[1] - diameter, -real_t(1),
+                 initialPosition[0] + diameter, initialPosition[1] + diameter, real_t(2) * diameter);
+   vtk::AABBCellFilter aabbBoxFilter( boxAABB );
+
+   field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+   fluidFilter.addFlag( Fluid_Flag );
+
+   vtk::ChainedFilter combinedSliceFilter;
+   combinedSliceFilter.addFilter( fluidFilter );
+   combinedSliceFilter.addFilter( aabbBoxFilter );
+
+   pdfFieldVTKCollision->addCellInclusionFilter( combinedSliceFilter );
+
+   pdfFieldVTKCollision->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+   pdfFieldVTKCollision->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+
+   // time loop
+   for (uint_t i = 0; i < timesteps; ++i )
+   {
+      // perform a single simulation step -> this contains LBM and setting of the hydrodynamic interactions
+      timeloop.singleStep( timeloopTiming );
+
+
+      if( averageForceTorqueOverTwoTimeSteps && i!= 0)
+      {
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, averageHydrodynamicForceTorque, *accessor );
+      }
+
+      real_t hydForce(real_t(0));
+      real_t lubForce(real_t(0));
+      real_t collisionForce(real_t(0));
+
+      for(auto subCycle = uint_t(0); subCycle < numRPDSubCycles; ++subCycle )
+      {
+
+         timeloopTiming["RPD"].start();
+
+         if( useVelocityVerlet )
+         {
+
+            Vector3<real_t> oldForce;
+
+            if(artificiallyAccelerateSphere)
+            {
+               // since the pre-force step of VV updates the position based on velocity and old force, we set oldForce to zero here for this step while artificially accelerating
+               // to not perturb the step after which artificial acceleration is switched off (which requires valid oldForce values then) we store the old force and then re-apply it
+               auto idx = accessor->uidToIdx(sphereUid);
+               if(idx != accessor->getInvalidIdx())
+               {
+                  oldForce = accessor->getOldForce(idx);
+                  accessor->setOldForce(idx, Vector3<real_t>(real_t(0)));
+               }
+            }
+
+            ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPreForce, *accessor);
+            syncCall();
+
+            if(artificiallyAccelerateSphere)
+            {
+               // re-apply old force
+               auto idx = accessor->uidToIdx(sphereUid);
+               if(idx != accessor->getInvalidIdx())
+               {
+                  accessor->setOldForce(idx, oldForce);
+               }
+            }
+         }
+
+         // lubrication correction
+         ps->forEachParticlePairHalf(useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor,
+                                     [&lubricationCorrectionKernel,&rpdDomain](const size_t idx1, const size_t idx2, auto& ac)
+                                     {
+                                        mesa_pd::collision_detection::AnalyticContactDetection acd;
+                                        acd.getContactThreshold() = lubricationCorrectionKernel.getNormalCutOffDistance();
+                                        mesa_pd::kernel::DoubleCast double_cast;
+                                        mesa_pd::mpi::ContactFilter contact_filter;
+                                        if (double_cast(idx1, idx2, ac, acd, ac ))
+                                        {
+                                           if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain))
+                                           {
+                                              double_cast(acd.getIdx1(), acd.getIdx2(), ac, lubricationCorrectionKernel, ac, acd.getContactNormal(), acd.getPenetrationDepth());
+                                           }
+                                        }
+                                     },
+                                     *accessor );
+
+         lubForce = getForce(sphereUid,*accessor);
+
+         // one could add linked cells here
+
+         // collision response
+         ps->forEachParticlePairHalf(useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor,
+                                     [&linearCollisionResponse, &nonLinearCollisionResponse, &rpdDomain, timeStepSizeRPD, useACTM]
+                                     (const size_t idx1, const size_t idx2, auto& ac)
+                                     {
+                                        mesa_pd::collision_detection::AnalyticContactDetection acd;
+                                        mesa_pd::kernel::DoubleCast double_cast;
+                                        mesa_pd::mpi::ContactFilter contact_filter;
+                                        if (double_cast(idx1, idx2, ac, acd, ac ))
+                                        {
+                                           if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain))
+                                           {
+                                              if(useACTM) nonLinearCollisionResponse(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth(), timeStepSizeRPD);
+                                              else        linearCollisionResponse(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth(), timeStepSizeRPD);
+                                           }
+                                        }
+                                     },
+                                     *accessor );
+
+         collisionForce = getForce(sphereUid,*accessor) - lubForce;
+
+         reduceAndSwapContactHistory(*ps);
+
+         // add hydrodynamic force
+         if( disableFluidForceDuringContact )
+         {
+            lbm_mesapd_coupling::StokesNumberBasedSphereSelector sphereSelector(StCrit, densityFluid, densitySphere, viscosity);
+            lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction;
+            ps->forEachParticle(useOpenMP, sphereSelector, *accessor, addHydrodynamicInteraction, *accessor );
+         }
+         else
+         {
+            lbm_mesapd_coupling::RegularParticlesSelector sphereSelector;
+            lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction;
+            ps->forEachParticle(useOpenMP, sphereSelector, *accessor, addHydrodynamicInteraction, *accessor );
+         }
+
+         hydForce = getForce(sphereUid,*accessor) - lubForce - collisionForce;
+
+         ps->forEachParticle( useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addGravitationalForce, *accessor );
+
+         reduceProperty.operator()<mesa_pd::ForceTorqueNotification>(*ps);
+
+         // integration
+         if( useVelocityVerlet ) ps->forEachParticle( useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPostForce, *accessor);
+         else ps->forEachParticle( useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, explEulerIntegrator, *accessor);
+
+         syncCall();
+
+         if( artificiallyAccelerateSphere)
+         {
+            // overwrite velocity of particle with prescribed one
+            lbm_mesapd_coupling::RegularParticlesSelector sphereSelector;
+            real_t newSphereVel = uIn * (exp(-accelerationFactor * real_t(i) ) - real_t(1));
+            ps->forEachParticle(useOpenMP, sphereSelector, *accessor, [newSphereVel](const size_t idx, ParticleAccessor_T& ac){ ac.setLinearVelocity(idx, Vector3<real_t>(real_t(0), real_t(0), newSphereVel));}, *accessor);
+         }
+
+         timeloopTiming["RPD"].end();
+
+         // logging
+         timeloopTiming["Logging"].start();
+         logger(i, subCycle);
+         sphereForceLogger(i, subCycle, hydForce, lubForce, collisionForce);
+         timeloopTiming["Logging"].end();
+
+      }
+
+      ps->forEachParticle( useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor );
+
+
+      // update particle mapping
+      timeloopAfterParticles.singleStep(timeloopTiming);
+
+      // check for termination
+      oldVel = curVel;
+      curVel = logger.getSettlingVelocity();
+
+      maxSettlingVel = std::min(maxSettlingVel, curVel);
+      minGapSize = std::min(minGapSize, logger.getGapSize());
+
+      if(numBounces >= uint_t(1))
+      {
+         maxGapSize = std::max(maxGapSize, logger.getGapSize());
+      }
+
+      // detect a bounce
+      if( oldVel < real_t(0) && curVel > real_t(0) && logger.getGapSize() < real_t(1))
+      {
+
+         ++numBounces;
+         WALBERLA_LOG_INFO_ON_ROOT("Detected bounce number " << numBounces);
+         WALBERLA_LOG_INFO_ON_ROOT("Max settling velocity " << maxSettlingVel << " -> St = "  << densityRatio * std::abs(maxSettlingVel) * diameter / ( real_t(9) * viscosity ) );
+
+         if(numBounces == uint_t(1))
+         {
+            actualSt = densityRatio * std::abs(maxSettlingVel) * diameter / ( real_t(9) * viscosity );
+            actualRe = std::abs(maxSettlingVel) * diameter / viscosity;
+         }
+
+         // reset and store quantities
+         maxSettlingVelBetweenBounces.push_back(maxSettlingVel);
+         maxSettlingVel = real_t(0);
+
+         if(numBounces > uint_t(1))
+         {
+            maxGapSizeBetweenBounces.push_back(maxGapSize);
+            maxGapSize = real_t(0);
+         }
+
+         if( numBounces >= numberOfBouncesUntilTermination )
+         {
+            break;
+         }
+      }
+
+      // impact times are measured when the contact between sphere and wall is broken up again
+      if( tImpact == uint_t(0) && numBounces == uint_t(1) && logger.getGapSize() > real_t(0) )
+      {
+         tImpact = i;
+         WALBERLA_LOG_INFO_ON_ROOT("Detected impact time at time step " << tImpact);
+
+         // switch off special vtk output after first bounce
+         if(vtkOutputOnCollision) vtkOutputOnCollision = false;
+      }
+
+      if( numBounces > impactTimes.size() && logger.getGapSize() > real_t(0))
+      {
+         impactTimes.push_back(i);
+      }
+
+      // evaluate density around sphere
+      if(fileIO)
+      {
+         std::string densityLoggingFileName(baseFolder + "/DensityLogging.txt");
+         if(i == uint_t(0))
+         {
+            WALBERLA_ROOT_SECTION()
+            {
+               std::ofstream file;
+               file.open( densityLoggingFileName.c_str());
+               file << "# t \t avgDensity \t densitiesAtDifferentPositions \n";
+               file.close();
+            }
+         }
+
+         real_t surfaceDistance = real_t(2);
+         real_t avgDensity = getAverageDensityInSystem<BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID);
+         logDensityToFile<DensityInterpolator_T>(blocks, densityInterpolatorID, surfaceDistance, logger.getPosition(), diameter, densityLoggingFileName, i, avgDensity);
+      }
+
+
+      // check if sphere is close to bottom plane
+      if( logger.getGapSize() < real_t(1) * diameter ) {
+
+         // write a single checkpointing file before any collision relevant parameters take effect
+         if( writeCheckPointFile )
+         {
+            WALBERLA_LOG_INFO_ON_ROOT("Writing checkpointing file in time step " << timeloop.getCurrentTimeStep());
+
+            blocks->saveBlockData( checkPointFileName + "_lbm.txt", pdfFieldID );
+            blocks->saveBlockData( checkPointFileName + "_mesa.txt", particleStorageID );
+
+            writeCheckPointFile = false;
+         }
+
+         // switch off acceleration after writing of check point file
+         artificiallyAccelerateSphere = false;
+
+      }
+
+      if(vtkOutputOnCollision && logger.getGapSize() < real_t(0))
+      {
+         pdfFieldVTKCollision->write();
+      }
+
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("Detected " << numBounces << " bounces, terminating simulation.");
+   WALBERLA_LOG_INFO_ON_ROOT("Maximum settling velocities: " );
+   for( auto vel : maxSettlingVelBetweenBounces)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - vel = " << vel << " -> St = "  << densityRatio * std::abs(vel) * diameter / ( real_t(9) * viscosity ) << ", Re = " << std::abs(vel) * diameter / viscosity );
+   }
+   WALBERLA_LOG_INFO_ON_ROOT("Maximum gap sizes between bounces:")
+   for( auto gapSize : maxGapSizeBetweenBounces)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - gap size = " << gapSize << ", gap size / diameter = "  << gapSize / diameter );
+   }
+
+
+   std::string resultFile(baseFolder + "/ResultBouncingSphere.txt");
+   WALBERLA_LOG_INFO_ON_ROOT("Writing logging file " << resultFile);
+   logger.writeResult(resultFile, tImpact);
+
+   std::string summaryFile(baseFolder + "/Summary.txt");
+   WALBERLA_LOG_INFO_ON_ROOT("Writing summary file " << summaryFile);
+   WALBERLA_ROOT_SECTION()
+   {
+      std::ofstream file;
+      file.open( summaryFile.c_str());
+
+      file << "waLBerla Revision = " << std::string(WALBERLA_GIT_SHA1).substr(0,8) << "\n";
+      file << "\nInput parameters:\n";
+      file << "Ga = " << Ga << "\n";
+      file << "uIn = " << uIn << "\n";
+      file << "LBM parameters:\n";
+      file << " - magic number = " << magicNumber << "\n";
+      file << " - bulk viscosity rate factor = " << bulkViscRateFactor << "\n";
+      file << " - use omega bulk adaption = " << useOmegaBulkAdaption << " (layer size = 2)\n";
+      file << "Collision parameters:\n";
+      file << " - subCycles = " << numRPDSubCycles << "\n";
+      file << " - collision time (Tc) = " << collisionTime << "\n";
+      file << " - use ACTM = " << useACTM << "\n";
+      file << "use lubrication correction = " << useLubricationCorrection << "\n";
+      file << " - minimum gap size non dim = " << lubricationMinimalGapSizeNonDim << "\n";
+      file << " - cut off distance = " << lubricationCutOffDistance << "\n";
+      file << "switch off fluid interaction force during contact = " << disableFluidForceDuringContact << "\n";
+      file << " - St_Crit = " << StCrit << "\n";
+      file << "reconstructor type " << reconstructorType << "\n";
+      file << "apply outflow BC at top = " << applyOutflowBCAtTop << "\n";
+      file << "started from checkpoint file = " << readFromCheckPointFile << " ( " << checkPointFileName << ")\n";
+
+      file << "\nOutput quantities:\n";
+      file << "actual St = " << actualSt << "\n";
+      file << "actual Re = " << actualRe << "\n";
+      file << "impact times:\n";
+      uint_t bounce = uint_t(0);
+      for( auto impact : impactTimes)
+      {
+         file << " - " << bounce++ << ": impact time = " << impact << "\n";
+      }
+      file << "settling velocities:\n";
+      bounce = uint_t(0);
+      for( auto vel : maxSettlingVelBetweenBounces)
+      {
+        file << " - " << bounce++ << ": vel = " << vel << " -> St = "  << densityRatio * std::abs(vel) * diameter / ( real_t(9) * viscosity ) << ", Re = " << std::abs(vel) * diameter / viscosity << "\n";
+      }
+      file << "maximal overlap = " << std::abs(minGapSize) << " (" << std::abs(minGapSize)/diameter * real_t(100) << "%)\n";
+      file << "maximum gap sizes:\n";
+      bounce = uint_t(1);
+      for( auto gapSize : maxGapSizeBetweenBounces)
+      {
+         file << " - " << bounce++ << ": gap size = " << gapSize << ", gap size / diameter = "  << gapSize / diameter << "\n";
+      }
+
+      file.close();
+   }
+
+
+
+
+   timeloopTiming.logResultOnRoot();
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace sphere_wall_collision
+
+int main( int argc, char **argv ){
+   sphere_wall_collision::main(argc, argv);
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/inputObliqueWetCollision.dat b/apps/benchmarks/FluidParticleCoupling/inputObliqueWetCollision.dat
new file mode 100644
index 0000000000000000000000000000000000000000..e4a1d7fc4c611b5796d9b80fcb0678ab5da1ce22
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/inputObliqueWetCollision.dat
@@ -0,0 +1,137 @@
+Setup
+{
+
+   case 3;
+
+   impactRatio 0;
+
+   variant Acceleration; // Gravity or Acceleration
+
+   // control
+   applyOutflowBCAtTop true;
+
+   // LBM
+   diameter 30;
+   magicNumber 0.1875;
+   bulkViscRateFactor 100.;
+   useOmegaBulkAdaption true;
+
+   // RPD
+   numRPDSubCycles 10;
+   useLubricationCorrection true;
+   lubricationCutOffDistanceNormal 0.666667;
+   lubricationCutOffDistanceTangentialTranslational 0.5;
+   lubricationCutOffDistanceTangentialRotational 0.5;
+   lubricationMinimalGapSizeNonDim 0.002;
+
+   useVelocityVerlet true;
+
+   collisionTime 120;
+
+   // coupling
+   averageForceTorqueOverTwoTimeSteps true;
+   conserveMomentum false;
+   reconstructorType Grad; // Eq EAN Grad Ext
+
+   // IO properties
+   baseFolder vtk_out_ObliqueWetCollision;
+   fileIO true;
+   vtkIOFreq 0;
+
+}
+
+Variant_Gravity
+{
+   gravitationalAcceleration_SI    0.25;
+   tau                             0.502;
+   useFullGravityInNormalDirection false;
+   domainSize                      <20,10,10>; // multiples of diameter
+   numberOfBlocksPerDirection      <8,5,5>;
+   initialSphereHeight             7; // multiple of diameter
+}
+
+Variant_Acceleration
+{
+   uIn                                     0.02;
+   StTarget                                100;
+   useStTargetInNormalDirection            true;
+   domainSize                              <12,12,24>; // multiples of diameter
+   numberOfBlocksPerDirection              <5,5,8>;
+   initialSphereHeight                     22.5; // multiple of diameter
+   applyArtificialGravityAfterAccelerating true;
+   applyUInNormalDirection                 true;
+   accelerationFactor                      25;
+}
+
+Case1
+{
+   material Steel;
+   fluid Glycerol78;
+}
+
+Case2
+{
+   material Glass;
+   fluid Glycerol45;
+}
+
+Case3
+{
+   material Glass;
+   fluid Glycerol33;
+}
+
+Case4
+{
+   material Steel;
+   fluid Glycerol37;
+}
+
+Mat_Steel
+{
+   densitySphere_SI             7780; // kg/m**3
+   diameter_SI                  12.7e-3; // m
+   restitutionCoeff             0.97;
+   frictionCoeff                0.02;
+   poissonsRatio                0.27; //Joseph 2001
+}
+
+Mat_Glass
+{
+   densitySphere_SI             2540; // kg/m**3
+   diameter_SI                  12.7e-3; // m
+   restitutionCoeff             0.97;
+   frictionCoeff                0.15;
+   poissonsRatio                0.23; //Joseph 2001
+}
+
+// from https://www.internetchemie.info/chemie-lexikon/stoffe/g/glycerol.php
+Fluid_Water
+{
+   densityFluid_SI              998; // kg/m**3
+   dynamicViscosityFluid_SI     1e-3; // Pa s
+}
+
+Fluid_Glycerol33
+{
+   densityFluid_SI              1081; // kg/m**3
+   dynamicViscosityFluid_SI     2.5e-3; // Pa s
+}
+
+Fluid_Glycerol37
+{
+   densityFluid_SI              1091; // kg/m**3
+   dynamicViscosityFluid_SI     3e-3; // Pa s
+}
+
+Fluid_Glycerol45
+{
+   densityFluid_SI              1113; // kg/m**3
+   dynamicViscosityFluid_SI     4.5e-3; // Pa s
+}
+
+Fluid_Glycerol78
+{
+   densityFluid_SI              1203; // kg/m**3
+   dynamicViscosityFluid_SI     48e-3; // Pa s
+}
diff --git a/apps/benchmarks/FluidParticleCoupling/inputSphereWallCollision.dat b/apps/benchmarks/FluidParticleCoupling/inputSphereWallCollision.dat
new file mode 100644
index 0000000000000000000000000000000000000000..a93ce9c09034e40173a53d47c0d1e9026c37329e
--- /dev/null
+++ b/apps/benchmarks/FluidParticleCoupling/inputSphereWallCollision.dat
@@ -0,0 +1,122 @@
+BouncingSphere
+{
+   setup Case_St152;
+
+   // simulation quantities
+
+   // control
+   randomizeInitialSpherePosition false;
+   initializeSphereVelocity false; // with 10% of final velocity
+   applyOutflowBCAtTop true;
+   artificiallyAccelerateSphere true;
+
+   // LBM
+   uIn 0.02;
+   diameter 20;
+   magicNumber 0.1875;
+   bulkViscRateFactor 100.;
+   useOmegaBulkAdaption true;
+
+   // RPD
+   numRPDSubCycles 100;
+   useLubricationCorrection true;
+   lubricationCutOffDistance 0.666667;
+   lubricationMinimalGapSizeNonDim 0.0005;
+
+   useVelocityVerlet true;
+
+   collisionTime 60;
+   StCrit 5;
+   useACTM false;
+
+   // coupling
+   averageForceTorqueOverTwoTimeSteps true;
+   conserveMomentum false;
+   disableFluidForceDuringContact false;
+   reconstructorType Grad; // Eq EAN Grad Ext
+
+   // IO properties
+   baseFolder vtk_out_SphereWallCollision;
+   fileIO true;
+   vtkIOFreq 100;
+   vtkOutputOnCollision false;
+
+   // checkpointing
+   writeCheckPointFile true;
+   readFromCheckPointFile true;
+}
+
+// Fig. 10d
+Case_St27
+{
+   Re                           30;
+   densityFluid_SI              965; // kg/m**3
+   dynamicViscosityFluid_SI     100e-3; // Pa s
+   densitySphere_SI             7800; // kg/m**3
+   diameter_SI                  6e-3; // m
+   gravitationalAcceleration_SI 9.81; // m/s**2
+   restitutionCoeff             0.97;
+   numberOfBouncesUntilTermination 2;
+   domainSize                   <12,12,48>; // multiples of diameter
+   numberOfBlocksPerDirection   <5,5,8>;
+}
+
+// Fig. 10c
+Case_St100
+{
+   Re                           110;
+   densityFluid_SI              953; // kg/m**3
+   dynamicViscosityFluid_SI     20e-3; // Pa s
+   densitySphere_SI             7800; // kg/m**3
+   diameter_SI                  4e-3; // m
+   gravitationalAcceleration_SI 9.81; // m/s**2
+   restitutionCoeff             0.97;
+   numberOfBouncesUntilTermination 2;
+   domainSize                   <12,12,48>; // multiples of diameter
+   numberOfBlocksPerDirection   <5,5,8>;
+}
+
+// Fig. 2 a,b
+Case_St152
+{
+   Re                           164;
+   densityFluid_SI              935; // kg/m**3
+   dynamicViscosityFluid_SI     10e-3; // Pa s
+   densitySphere_SI             7800; // kg/m**3
+   diameter_SI                  0.003; // m
+   gravitationalAcceleration_SI 9.81; // m/s**2
+   restitutionCoeff             0.97;
+   numberOfBouncesUntilTermination 4;
+   domainSize                   <12,12,64>; // multiples of diameter
+   numberOfBlocksPerDirection   <5,5,8>;
+}
+
+// Fig. 10b
+Case_St192
+{
+   Re                           212;
+   densityFluid_SI              953; // kg/m**3
+   dynamicViscosityFluid_SI     20e-3; // Pa s
+   densitySphere_SI             7800; // kg/m**3
+   diameter_SI                  6e-3; // m
+   gravitationalAcceleration_SI 9.81; // m/s**2
+   restitutionCoeff             0.97;
+   numberOfBouncesUntilTermination 2;
+   domainSize                   <12,12,64>; // multiples of diameter
+   numberOfBlocksPerDirection   <5,5,8>;
+}
+
+// Fig. 10a
+Case_St742
+{
+   Re                           788;
+   densityFluid_SI              920; // kg/m**3
+   dynamicViscosityFluid_SI     5e-3; // Pa s
+   densitySphere_SI             7800; // kg/m**3
+   diameter_SI                  0.005; // m
+   gravitationalAcceleration_SI 9.81; // m/s**2
+   restitutionCoeff             0.97;
+   numberOfBouncesUntilTermination 2;
+   domainSize                   <12,12,64>; // multiples of diameter
+   numberOfBlocksPerDirection   <5,5,8>;
+}
diff --git a/python/mesa_pd.py b/python/mesa_pd.py
index 94cd212a7d618e9381898cb0c3068883052c2bff..c4ba4e61ae2faeb121ef953b493d28bb7c73b2cf 100755
--- a/python/mesa_pd.py
+++ b/python/mesa_pd.py
@@ -66,6 +66,13 @@ if __name__ == '__main__':
    ps.addProperty("dv",               "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
    ps.addProperty("dw",               "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
 
+   # Properties for lbm_mesapd_coupling:
+   ps.addProperty("hydrodynamicForce", "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+   ps.addProperty("hydrodynamicTorque", "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+   ps.addProperty("oldHydrodynamicForce", "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+   ps.addProperty("oldHydrodynamicTorque", "walberla::mesa_pd::Vec3", defValue="real_t(0)", syncMode="NEVER")
+
+
    ch.addProperty("tangentialSpringDisplacement", "walberla::mesa_pd::Vec3", defValue="real_t(0)")
    ch.addProperty("isSticking",                   "bool",                    defValue="false")
    ch.addProperty("impactVelocityMagnitude",      "real_t",                  defValue="real_t(0)")
diff --git a/src/lbm_mesapd_coupling/CMakeLists.txt b/src/lbm_mesapd_coupling/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1f7af49cecad9842836ab3c0e31ee65219412ea1
--- /dev/null
+++ b/src/lbm_mesapd_coupling/CMakeLists.txt
@@ -0,0 +1,8 @@
+
+###################################################################################################
+#
+# Module lbm - mesapd - coupling
+#
+###################################################################################################
+
+waLBerla_add_module( DEPENDS boundary blockforest core domain_decomposition field lbm mesa_pd stencil )
diff --git a/src/lbm_mesapd_coupling/DataTypes.h b/src/lbm_mesapd_coupling/DataTypes.h
new file mode 100644
index 0000000000000000000000000000000000000000..a71680613d72db044f8dca16dcec91eb8f8bedfb
--- /dev/null
+++ b/src/lbm_mesapd_coupling/DataTypes.h
@@ -0,0 +1,37 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file DataTypes.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "field/GhostLayerField.h"
+#include "core/DataTypes.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*
+ * Typedefs specific to the lbm - mesa_pd coupling
+ */
+using ParticleField_T = walberla::GhostLayerField< walberla::id_t, 1 >;
+
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/mapping/ParticleBoundingBox.h b/src/lbm_mesapd_coupling/mapping/ParticleBoundingBox.h
new file mode 100644
index 0000000000000000000000000000000000000000..aa32158a99094b283016cace0a35632aae300dff
--- /dev/null
+++ b/src/lbm_mesapd_coupling/mapping/ParticleBoundingBox.h
@@ -0,0 +1,101 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleBoundingBox.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/debug/Debug.h"
+
+#include "domain_decomposition/StructuredBlockStorage.h"
+
+#include "mesa_pd/common/AABBConversion.h"
+#include "mesa_pd/data/Flags.h"
+#include "mesa_pd/data/IAccessor.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*
+ * Obtain a block-local cell bounding box from a given AABB (e.g. the particle's AABB)
+ * If the given AABB is (partly) infinite, AABBIsInfinite should be set to true (e.g. for infinite particles)
+ */
+CellInterval getCellBBFromAABB( const math::AABB & aabb, bool AABBIsInfinite,
+                                const IBlock & block, StructuredBlockStorage & blockStorage,
+                                const uint_t numberOfGhostLayersToInclude)
+{
+
+   CellInterval cellBB;
+
+   if( AABBIsInfinite )
+   {
+      auto level = blockStorage.getLevel(block);
+      const real_t dx = blockStorage.dx(level);
+      const real_t dy = blockStorage.dy(level);
+      const real_t dz = blockStorage.dz(level);
+      Vector3<real_t> aabbExtensionByGhostLayers(real_c(numberOfGhostLayersToInclude) * dx,
+                                                 real_c(numberOfGhostLayersToInclude) * dy,
+                                                 real_c(numberOfGhostLayersToInclude) * dz);
+      auto extendedBlockAABB = blockStorage.getAABB(block.getId()).getExtended( aabbExtensionByGhostLayers );
+
+      // intersect the (partly) infinite aabb with the block AABB, extended by its ghost layers
+      // then determine the cell bounding box of the intersection
+      blockStorage.getCellBBFromAABB( cellBB, aabb.getIntersection( extendedBlockAABB ), level );
+
+      // if (partly) infinite aabb does not intersect with the extended block AABB, return an empty interval
+      if( cellBB.empty() ) return CellInterval();
+   }
+   else
+   {
+      blockStorage.getCellBBFromAABB( cellBB, aabb, blockStorage.getLevel(block) );
+   }
+
+   WALBERLA_ASSERT( !cellBB.empty() );
+
+   cellBB.xMin() -= cell_idx_t(1); cellBB.yMin() -= cell_idx_t(1); cellBB.zMin() -= cell_idx_t(1);
+   cellBB.xMax() += cell_idx_t(1); cellBB.yMax() += cell_idx_t(1); cellBB.zMax() += cell_idx_t(1);
+
+   CellInterval blockBB = blockStorage.getBlockCellBB( block );
+
+   cell_idx_t layers = cell_idx_c( numberOfGhostLayersToInclude );
+
+   blockBB.xMin() -= layers; blockBB.yMin() -= layers; blockBB.zMin() -= layers;
+   blockBB.xMax() += layers; blockBB.yMax() += layers; blockBB.zMax() += layers;
+
+   cellBB.intersect( blockBB );
+
+   blockStorage.transformGlobalToBlockLocalCellInterval( cellBB, block );
+
+   return cellBB;
+
+}
+
+template< typename ParticleAccessor_T >
+CellInterval getParticleCellBB(const size_t particleIdx, const ParticleAccessor_T& ac,
+                               const IBlock & block, StructuredBlockStorage & blockStorage,
+                               const uint_t numberOfGhostLayersToInclude)
+{
+   return getCellBBFromAABB(mesa_pd::getParticleAABB(particleIdx, ac),
+                            mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::INFINITE),
+                            block, blockStorage, numberOfGhostLayersToInclude );
+}
+
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/mapping/ParticleMapping.h b/src/lbm_mesapd_coupling/mapping/ParticleMapping.h
new file mode 100644
index 0000000000000000000000000000000000000000..507156382398311804affce080bc800df8abc1ad
--- /dev/null
+++ b/src/lbm_mesapd_coupling/mapping/ParticleMapping.h
@@ -0,0 +1,122 @@
+//======================================================================================================================
+//
+//  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 ParticleMapping.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "ParticleBoundingBox.h"
+
+#include "core/debug/Debug.h"
+#include "core/math/Vector3.h"
+#include "domain_decomposition/StructuredBlockStorage.h"
+#include "field/FlagField.h"
+
+#include "mesa_pd/common/Contains.h"
+#include "mesa_pd/data/IAccessor.h"
+#include "mesa_pd/kernel/SingleCast.h"
+
+#include <functional>
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*
+ * Kernel that can be used to map MESA_PD particles into the domain with a given LBM boundary flag.
+ * In coupled simulations, usually carried out for walls or fixed particles.
+ *
+ */
+template< typename BoundaryHandling_T>
+class ParticleMappingKernel
+{
+public:
+   ParticleMappingKernel(const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID)
+   : blockStorage_(blockStorage), boundaryHandlingID_(boundaryHandlingID){}
+
+   template<typename ParticleAccessor_T>
+   void operator()(const size_t particleIdx, const ParticleAccessor_T& ac, const FlagUID & obstacle )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt )
+      {
+         mapParticleOnBlock( particleIdx, ac, *blockIt, obstacle );
+      }
+   }
+
+private:
+
+   template< typename ParticleAccessor_T >
+   void mapParticleOnBlock( const size_t particleIdx, const ParticleAccessor_T& ac,
+                           IBlock & block, const FlagUID & obstacle )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      WALBERLA_ASSERT_EQUAL( &block.getBlockStorage(), &(blockStorage_->getBlockStorage()) );
+
+      BoundaryHandling_T * boundaryHandling = block.getData< BoundaryHandling_T >( boundaryHandlingID_);
+      WALBERLA_ASSERT_NOT_NULLPTR( boundaryHandling );
+
+      auto * flagField = boundaryHandling->getFlagField();
+      WALBERLA_ASSERT_NOT_NULLPTR( flagField );
+      WALBERLA_ASSERT( flagField->flagExists( obstacle ) );
+
+      const auto obstacleFlag = flagField->getFlag( obstacle );
+
+      auto cellBB = getParticleCellBB(particleIdx, ac, block, *blockStorage_, flagField->nrOfGhostLayers() );
+
+      if( cellBB.empty() ) return;
+
+      mesa_pd::kernel::SingleCast singleCast;
+      mesa_pd::ContainsPointFunctor containsPointFctr;
+
+      Vector3<real_t> startCellCenter = blockStorage_->getBlockLocalCellCenter( block, cellBB.min() );
+      auto blockLevel = blockStorage_->getLevel(block);
+      const real_t dx = blockStorage_->dx( blockLevel );
+      const real_t dy = blockStorage_->dy( blockLevel );
+      const real_t dz = blockStorage_->dz( blockLevel );
+
+      real_t cz = startCellCenter[2];
+      for( cell_idx_t z = cellBB.zMin(); z <= cellBB.zMax(); ++z )
+      {
+         real_t cy = startCellCenter[1];
+         for( cell_idx_t y = cellBB.yMin(); y <= cellBB.yMax(); ++y )
+         {
+            real_t cx = startCellCenter[0];
+            for( cell_idx_t x = cellBB.xMin(); x <= cellBB.xMax(); ++x )
+            {
+               if( singleCast(particleIdx, ac, containsPointFctr, ac, Vector3<real_t>(cx,cy,cz)) )
+               {
+                  boundaryHandling->forceBoundary(obstacleFlag, x, y, z);
+               }
+               cx += dx;
+            }
+            cy += dy;
+         }
+         cz += dz;
+      }
+   }
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+   BlockDataID boundaryHandlingID_;
+};
+
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h b/src/lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h
new file mode 100644
index 0000000000000000000000000000000000000000..29c161d4f0378d60e416e4dac2a3e887d2185ccd
--- /dev/null
+++ b/src/lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h
@@ -0,0 +1,354 @@
+//======================================================================================================================
+//
+//  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 MovingParticleMapping.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/debug/Debug.h"
+#include "core/cell/Cell.h"
+#include "domain_decomposition/StructuredBlockStorage.h"
+#include "field/FlagField.h"
+
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/mapping/ParticleBoundingBox.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/utility/ParticleFunctions.h"
+
+#include "mesa_pd/common/Contains.h"
+#include "mesa_pd/data/IAccessor.h"
+#include "mesa_pd/kernel/SingleCast.h"
+#include "mesa_pd/data/Flags.h"
+
+#include <functional>
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*!\brief Maps the moving particles into the simulation domain and updates the mapping
+ *
+ * Cells that are inside a particle, will be marked with the 'obstacle' flag.
+ * 'Inside' means that the cell center is contained in the particle.
+ * Thus, a containsPoint function has to be available for all particles/shapes.
+ *
+ * Cells that in the last time step were inside the particle but are now outside of it, i.e. the particle has moved,
+ * will be marked with the 'formerObstacle' flag.
+ * The 'formerObstacle' flag is used in a second step by the PDFReconstruction class (see PDFReconstruction.h) to
+ * re-initialize the missing PDFs. Afterwards, the 'formerObstacle' flag is removed and the 'fluid' flag is set.
+ *
+ * It is not strictly required that the mapping has been initialized with the mapping kernel from below.
+ *
+ * The 'mappingParticleSelector' can be used to include/exclude certain particles from the mapping.
+ */
+template< typename PdfField_T, typename BoundaryHandling_T, typename ParticleAccessor_T, typename ParticleSelector_T>
+class MovingParticleMapping
+{
+   static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+public:
+
+   using FlagField_T = typename BoundaryHandling_T::FlagField;
+   using flag_t = typename BoundaryHandling_T::flag_t;
+
+   MovingParticleMapping( const shared_ptr<StructuredBlockStorage> & blockStorage,
+                          const BlockDataID & pdfFieldID,
+                          const BlockDataID & boundaryHandlingID,
+                          const BlockDataID & particleFieldID,
+                          const shared_ptr<ParticleAccessor_T>& ac,
+                          const FlagUID & obstacle, const FlagUID & formerObstacle,
+                          const ParticleSelector_T& mappingParticleSelector,
+                          bool conserveMomentumWhenMapping)
+   : blockStorage_( blockStorage ), pdfFieldID_( pdfFieldID ), boundaryHandlingID_( boundaryHandlingID ),
+     particleFieldID_( particleFieldID ), ac_( ac ),
+     obstacle_( obstacle ), formerObstacle_( formerObstacle ), mappingParticleSelector_( mappingParticleSelector ),
+     conserveMomentumWhenMapping_( conserveMomentumWhenMapping )
+   {}
+
+   void inline operator()( IBlock * const block )
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+
+      PdfField_T * pdfField = block->getData< PdfField_T >( pdfFieldID_ );
+      BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+      auto * flagField = boundaryHandling->getFlagField();
+      ParticleField_T * particleField = block->getData< ParticleField_T >( particleFieldID_ );
+
+      WALBERLA_ASSERT_NOT_NULLPTR( boundaryHandling );
+      WALBERLA_ASSERT_NOT_NULLPTR( flagField );
+      WALBERLA_ASSERT_NOT_NULLPTR( particleField );
+      WALBERLA_ASSERT_EQUAL( flagField->xyzSize(), particleField->xyzSize() );
+      WALBERLA_ASSERT( flagField->flagExists( obstacle_ ) );
+
+      const flag_t       obstacle = flagField->getFlag( obstacle_ );
+      const flag_t formerObstacle = flagField->flagExists( formerObstacle_ ) ? flagField->getFlag( formerObstacle_ ) :
+                                    flagField->registerFlag( formerObstacle_ );
+
+      const real_t dx = blockStorage_->dx( blockStorage_->getLevel(*block) );
+      const real_t dy = blockStorage_->dy( blockStorage_->getLevel(*block) );
+      const real_t dz = blockStorage_->dz( blockStorage_->getLevel(*block) );
+
+      for( size_t idx = 0; idx < ac_->size(); ++idx )
+      {
+         if (mappingParticleSelector_(idx, *ac_))
+         {
+            mapParticleAndUpdateMapping( idx, block, pdfField, boundaryHandling, flagField, particleField, obstacle, formerObstacle, dx, dy, dz );
+         }
+      }
+   }
+
+private:
+
+   void inline mapParticleAndUpdateMapping(const size_t particleIdx, IBlock * const block,
+                                           PdfField_T * pdfField, BoundaryHandling_T * boundaryHandling, FlagField_T * flagField, ParticleField_T * particleField,
+                                           const flag_t & obstacle, const flag_t & formerObstacle,
+                                           real_t dx, real_t dy, real_t dz)
+   {
+      // policy: every particle manages only its own flags
+
+      auto cellBB = getParticleCellBB( particleIdx, *ac_, *block, *blockStorage_, flagField->nrOfGhostLayers() );
+
+      Vector3<real_t> startCellCenter = blockStorage_->getBlockLocalCellCenter( *block, cellBB.min() );
+
+      mesa_pd::kernel::SingleCast singleCast;
+      mesa_pd::ContainsPointFunctor containsPointFctr;
+
+      auto particleUid = ac_->getUid(particleIdx);
+
+      Vector3<real_t> currentCellCenter = startCellCenter;
+      for( cell_idx_t z = cellBB.zMin(); z <= cellBB.zMax(); ++z )
+      {
+         currentCellCenter[1] = startCellCenter[1];
+         for( cell_idx_t y = cellBB.yMin(); y <= cellBB.yMax(); ++y )
+         {
+            currentCellCenter[0] = startCellCenter[0];
+            for( cell_idx_t x = cellBB.xMin(); x <= cellBB.xMax(); ++x )
+            {
+
+               flag_t & cellFlagPtr = flagField->get(x,y,z);
+
+               if( singleCast(particleIdx, *ac_, containsPointFctr, *ac_, currentCellCenter) )
+               {
+                  // cell is inside particle, now check the flag of the cell
+                  // and let this cell refer to the present particle (via Uid)
+
+                  // cell is currently fluid and now newly occupied by the particle
+                  if( boundaryHandling->isDomain(x,y,z))
+                  {
+                     // set obstacle flag
+                     boundaryHandling->forceBoundary( obstacle, x, y, z );
+                     (*particleField)(x, y, z) = particleUid;
+
+                     if( conserveMomentumWhenMapping_ && pdfField->isInInnerPart(Cell(x,y,z)) ) {
+                        // this removes the fluid from the simulation, together with the containing momentum
+                        // to ensure momentum conservation, the momentum of this fluid cell has to be added to the particle
+                        // see Aidun, C. K., Lu, Y., & Ding, E. J. (1998). Direct analysis of particulate suspensions with inertia using the discrete Boltzmann equation. Journal of Fluid Mechanics, 373, 287-311.
+
+                        // force = momentum / dt, with dt = 1
+                        Vector3<real_t> force = pdfField->getMomentumDensity(x, y, z);
+                        lbm_mesapd_coupling::addHydrodynamicForceAtWFPosAtomic( particleIdx, *ac_, force, currentCellCenter );
+                     }
+
+                  }
+
+                  // cell is already an obstacle (maybe from another particle)
+                  if( isFlagSet( cellFlagPtr, obstacle ) ) {
+                     auto formerParticleUid = (*particleField)(x, y, z);
+                     auto formerParticleIdx = ac_->uidToIdx(formerParticleUid);
+                     if(!isSet(ac_->getFlags(formerParticleIdx), mesa_pd::data::particle_flags::FIXED) )
+                     {
+                        // only claim this cell if it not from a fixed particle, i.e. the fixed particle keeps the cell in all cases
+                        // this is IMPORTANT to not mess up the mapping of cells that are contained inside fixed cells but are sporadically
+                        // occupied by a moving particle as well (during collision and the there present overlap),
+                        // since the fixed particle usually do not get remapped in every timestep
+                        (*particleField)(x, y, z) = particleUid;
+                     }
+                  }
+
+                  // cell is a former obstacle cell (maybe from another particle that has moved away)
+                  if( isFlagSet( cellFlagPtr, formerObstacle ) )
+                  {
+                     boundaryHandling->setBoundary( obstacle, x, y, z );
+                     removeFlag( cellFlagPtr, formerObstacle );
+                     (*particleField)(x, y, z) = particleUid;
+                  }
+
+                  // else: IMPORTANT in all other cases, another boundary flag is already present in the cell
+                  // this could e.g. be an inflow boundary condition
+                  // we do NOT want to overwrite this boundary condition and thus skip it, even though the particle overlaps
+
+               }
+               else
+               {
+                  // cell is outside particle
+                  if( isFlagSet( cellFlagPtr, obstacle ) && ((*particleField)(x,y,z) == particleUid) )
+                  {
+                     // cell was previously occupied by this particle
+                     boundaryHandling->removeBoundary( obstacle, x, y, z );
+                     addFlag( cellFlagPtr, formerObstacle );
+                     // entry at (*particleField)(x,y,z) should still point to the previous particle.
+                     // If during initialization the overlap between neighboring blocks
+                     // was chosen correctly/large enough, the particle should still be on this block.
+                     // The particle information is needed in the PDF reconstruction step.
+                     // There, the flag will be removed and replaced by a domain flag after reconstruction.
+                  }
+               }
+               currentCellCenter[0] += dx;
+            }
+            currentCellCenter[1] += dy;
+         }
+         currentCellCenter[2] += dz;
+      }
+   }
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+
+   const BlockDataID pdfFieldID_;
+   const BlockDataID boundaryHandlingID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+
+   const FlagUID obstacle_;
+   const FlagUID formerObstacle_;
+
+   ParticleSelector_T mappingParticleSelector_;
+
+   bool conserveMomentumWhenMapping_;
+
+};
+
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename ParticleAccessor_T, typename ParticleSelector_T>
+MovingParticleMapping< PdfField_T, BoundaryHandling_T, ParticleAccessor_T, ParticleSelector_T>
+makeMovingParticleMapping( const shared_ptr<StructuredBlockStorage> & blockStorage,
+                           const BlockDataID & pdfFieldID,
+                           const BlockDataID & boundaryHandlingID,
+                           const BlockDataID & particleFieldID,
+                           const shared_ptr<ParticleAccessor_T>& ac,
+                           const FlagUID & obstacle, const FlagUID & formerObstacle,
+                           const ParticleSelector_T& mappingParticleSelector,
+                           bool conserveMomentumWhenMapping)
+{
+   return MovingParticleMapping<PdfField_T, BoundaryHandling_T, ParticleAccessor_T, ParticleSelector_T>
+         (blockStorage, pdfFieldID, boundaryHandlingID, particleFieldID, ac, obstacle, formerObstacle, mappingParticleSelector, conserveMomentumWhenMapping);
+}
+
+////////////////////
+// MAPPING KERNEL //
+////////////////////
+
+
+/*!\brief Kernel that maps all particles onto all blocks to the "moving obstacle" boundary condition, that requires the additional particleField
+ *
+ * Cells that are inside the particles are set to 'obstacle' and the UID of the particle is stored in the particleField for later reference.
+ *
+ * Example:
+ * lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+ * ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+ *
+ * This kernel is usually applied before the start of the simulation, after the particle and fluid data structures have been created.
+ * As a result, a consistent mapping of the particles into the domain is obtained.
+ * For the update of the mapping during time stepping, use the class MovingParticleMapping (above).
+ *
+ * This mapping can also be used for stationary particle if the force and torque on this particle are to be evaluated.
+ */
+template< typename BoundaryHandling_T>
+class MovingParticleMappingKernel
+{
+public:
+   MovingParticleMappingKernel(const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID, const BlockDataID & particleFieldID)
+         : blockStorage_(blockStorage), boundaryHandlingID_(boundaryHandlingID), particleFieldID_(particleFieldID){}
+
+   template<typename ParticleAccessor_T>
+   void operator()(const size_t particleIdx, const ParticleAccessor_T& ac, const FlagUID & obstacle )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt )
+      {
+         mapMovingParticleOnBlock( particleIdx, ac, *blockIt, obstacle );
+      }
+   }
+
+private:
+
+   template< typename ParticleAccessor_T >
+   void mapMovingParticleOnBlock( const size_t particleIdx, const ParticleAccessor_T& ac,
+                                  IBlock & block, const FlagUID & obstacle )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      WALBERLA_ASSERT_EQUAL( &block.getBlockStorage(), &(blockStorage_->getBlockStorage()) );
+
+      BoundaryHandling_T * boundaryHandling = block.getData< BoundaryHandling_T >( boundaryHandlingID_);
+      WALBERLA_ASSERT_NOT_NULLPTR( boundaryHandling );
+
+      auto * flagField = boundaryHandling->getFlagField();
+      WALBERLA_ASSERT_NOT_NULLPTR( flagField );
+      WALBERLA_ASSERT( flagField->flagExists( obstacle ) );
+
+      const auto obstacleFlag = flagField->getFlag( obstacle );
+
+      auto * particleField = block.getData< ParticleField_T >( particleFieldID_ );
+      WALBERLA_ASSERT_NOT_NULLPTR( particleField );
+      WALBERLA_ASSERT_EQUAL( flagField->xyzSize(), particleField->xyzSize() );
+
+      auto cellBB = getParticleCellBB(particleIdx, ac, block, *blockStorage_, flagField->nrOfGhostLayers() );
+
+      if( cellBB.empty() ) return;
+
+      mesa_pd::kernel::SingleCast singleCast;
+      mesa_pd::ContainsPointFunctor containsPointFctr;
+
+      Vector3<real_t> startCellCenter = blockStorage_->getBlockLocalCellCenter( block, cellBB.min() );
+      auto blockLevel = blockStorage_->getLevel(block);
+      const real_t dx = blockStorage_->dx( blockLevel );
+      const real_t dy = blockStorage_->dy( blockLevel );
+      const real_t dz = blockStorage_->dz( blockLevel );
+
+      real_t cz = startCellCenter[2];
+      for( cell_idx_t z = cellBB.zMin(); z <= cellBB.zMax(); ++z )
+      {
+         real_t cy = startCellCenter[1];
+         for( cell_idx_t y = cellBB.yMin(); y <= cellBB.yMax(); ++y )
+         {
+            real_t cx = startCellCenter[0];
+            for( cell_idx_t x = cellBB.xMin(); x <= cellBB.xMax(); ++x )
+            {
+               if( singleCast(particleIdx, ac, containsPointFctr, ac, Vector3<real_t>(cx,cy,cz)) )
+               {
+                  boundaryHandling->forceBoundary(obstacleFlag, x, y, z);
+                  (*particleField)(x,y,z) = ac.getUid(particleIdx);
+               }
+               cx += dx;
+            }
+            cy += dy;
+         }
+         cz += dz;
+      }
+   }
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+   BlockDataID boundaryHandlingID_;
+   BlockDataID particleFieldID_;
+};
+
+
+} // namespace lbm_rpd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h b/src/lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h
new file mode 100644
index 0000000000000000000000000000000000000000..eeba79e77a31c0989ea397c859babb05c6eca8a0
--- /dev/null
+++ b/src/lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h
@@ -0,0 +1,294 @@
+//======================================================================================================================
+//
+//  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 CurvedLinear.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "boundary/Boundary.h"
+
+#include "core/DataTypes.h"
+#include "core/cell/CellInterval.h"
+#include "core/config/Config.h"
+#include "core/debug/Debug.h"
+#include "core/logging/all.h"
+
+#include "field/FlagField.h"
+
+#include "lbm/field/PdfField.h"
+
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/utility/ParticleFunctions.h"
+
+#include "mesa_pd/common/ParticleFunctions.h"
+#include "mesa_pd/common/RayParticleIntersection.h"
+#include "mesa_pd/data/IAccessor.h"
+#include "mesa_pd/kernel/SingleCast.h"
+
+#include "stencil/Directions.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+//**************************************************************************************************************************************
+/*!
+*   \brief Linear boundary handling for moving particles
+*
+*   This boundary condition implements the CLI scheme from
+*   Ginzburg et al. - "Two-Relaxation-Time Lattice Boltzmann Scheme: About Parametrization, Velocity, Pressure and Mixed Boundary Conditions" (2008)
+*   References to equations and tables in the code documentation are contained in this paper.
+*
+*   It uses one additional cell in inverse direction of the boundary handling for a linear interpolation.
+*
+*   In case not enough cells are available, one fall back solution is available:
+*   If no additional cell is available, the scheme is replaced by the bounce back scheme (see SimpleBB.h).
+*
+*/
+//**************************************************************************************************************************************
+template< typename LatticeModel_T, typename FlagField_T, typename ParticleAccessor_T >
+class CurvedLinear : public Boundary< typename FlagField_T::flag_t >
+{
+   using PDFField_T = lbm::PdfField< LatticeModel_T >;
+   using Stencil_T = typename LatticeModel_T::Stencil;
+   using flag_t = typename FlagField_T::flag_t;
+
+   static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+public:
+
+   static shared_ptr<BoundaryConfiguration> createConfiguration( const Config::BlockHandle& )
+   {
+      WALBERLA_ABORT( "A CurvedLinear boundary cannot be created from a config file" );
+      return make_shared<BoundaryConfiguration>();
+   }
+
+   inline CurvedLinear( const BoundaryUID & boundaryUID, const FlagUID & uid, PDFField_T * const pdfField, const FlagField_T * const flagField,
+                        ParticleField_T * const particleField, const shared_ptr<ParticleAccessor_T>& ac,
+                        const flag_t domain, const StructuredBlockStorage & blockStorage, const IBlock & block );
+
+   void pushFlags( std::vector< FlagUID >& uids ) const { uids.push_back( uid_ ); }
+
+   void beforeBoundaryTreatment() const {}
+   void  afterBoundaryTreatment() const {}
+
+   template< typename Buffer_T >
+   void packCell( Buffer_T &, const cell_idx_t, const cell_idx_t, const cell_idx_t ) const {}
+
+   template< typename Buffer_T >
+   void registerCell( Buffer_T &, const flag_t, const cell_idx_t, const cell_idx_t, const cell_idx_t ) {}
+
+   void registerCell( const flag_t, const cell_idx_t, const cell_idx_t, const cell_idx_t, const BoundaryConfiguration& ) {}
+   void registerCells( const flag_t, const CellInterval&, const BoundaryConfiguration& ) {}
+   template< typename CellIterator >
+   void registerCells( const flag_t, const CellIterator&, const CellIterator&, const BoundaryConfiguration& ) {}
+
+   void unregisterCell( const flag_t, const cell_idx_t, const cell_idx_t, const cell_idx_t ) const {}
+
+   inline void treatDirection( const cell_idx_t  x, const cell_idx_t  y, const cell_idx_t  z, const stencil::Direction dir,
+                               const cell_idx_t nx, const cell_idx_t ny, const cell_idx_t nz, const flag_t mask );
+
+private:
+
+   const FlagUID uid_;
+
+
+   PDFField_T  * const  pdfField_;
+   const FlagField_T * const flagField_;
+   ParticleField_T * const particleField_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+
+   flag_t domainMask_;
+
+   const StructuredBlockStorage & blockStorage_;
+   const IBlock & block_;
+
+   real_t lengthScalingFactor_;
+   real_t forceScalingFactor_;
+
+}; // class CurvedLinear
+
+
+template< typename LatticeModel_T, typename FlagField_T, typename ParticleAccessor_T >
+inline CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >::CurvedLinear( const BoundaryUID & boundaryUID, const FlagUID & uid, PDFField_T * const pdfField, const FlagField_T * const flagField,
+                                                                                      ParticleField_T * const particleField, const shared_ptr<ParticleAccessor_T>& ac,
+                                                                                      const flag_t domain, const StructuredBlockStorage & blockStorage, const IBlock & block ):
+Boundary<flag_t>( boundaryUID ), uid_( uid ), pdfField_( pdfField ), flagField_( flagField ), particleField_( particleField ), ac_( ac ), domainMask_(domain), blockStorage_( blockStorage ), block_( block )
+{
+   WALBERLA_ASSERT_NOT_NULLPTR( pdfField_ );
+   WALBERLA_ASSERT_NOT_NULLPTR( flagField_ );
+   WALBERLA_ASSERT_NOT_NULLPTR( particleField_ );
+   WALBERLA_ASSERT( flagField_->isRegistered( domainMask_ )  );
+
+   // force scaling factor to account for different dx on this block
+   const real_t dxCurrentLevel = blockStorage_.dx( blockStorage_.getLevel(block) );
+   lengthScalingFactor_ = dxCurrentLevel;
+   forceScalingFactor_ = lengthScalingFactor_ * lengthScalingFactor_;
+}
+
+
+// requires: getVelocityAtPosition (was velFromWF), addForceAtPosition (was addForceAtPos), intersectionRatio?
+template< typename LatticeModel_T, typename FlagField_T, typename ParticleAccessor_T >
+#ifndef NDEBUG
+inline void CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >::treatDirection( const cell_idx_t  x, const cell_idx_t  y, const cell_idx_t  z, const stencil::Direction dir,
+                                                                                             const cell_idx_t nx, const cell_idx_t ny, const cell_idx_t nz, const flag_t mask )
+#else
+inline void CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >::treatDirection( const cell_idx_t  x, const cell_idx_t  y, const cell_idx_t  z, const stencil::Direction dir,
+                                                                                             const cell_idx_t nx, const cell_idx_t ny, const cell_idx_t nz, const flag_t /*mask*/ )
+#endif
+{
+   WALBERLA_ASSERT_EQUAL  ( nx, x + cell_idx_t( stencil::cx[ dir ] ) );
+   WALBERLA_ASSERT_EQUAL  ( ny, y + cell_idx_t( stencil::cy[ dir ] ) );
+   WALBERLA_ASSERT_EQUAL  ( nz, z + cell_idx_t( stencil::cz[ dir ] ) );
+   WALBERLA_ASSERT_UNEQUAL( mask & this->mask_, numeric_cast<flag_t>(0) );
+   WALBERLA_ASSERT_EQUAL  ( mask & this->mask_, this->mask_ ); // only true if "this->mask_" only contains one single flag, which is the case for the
+                                                               // current implementation of this boundary condition
+   WALBERLA_ASSERT_UNEQUAL( particleField_->get(nx,ny,nz), ac_->getInvalidUid() );
+
+   // determine distance to real boundary, i.e. delta value
+   // cell center of the near-boundary fluid cell
+   Cell nearBoundaryCell(x,y,z);
+   Vector3< real_t > cellCenter = blockStorage_.getBlockLocalCellCenter(block_, nearBoundaryCell);
+
+   // direction of the ray (from the fluid cell center to the boundary cell)
+   Vector3< real_t > direction( lengthScalingFactor_ * real_c( stencil::cx[ dir ] ),
+                                lengthScalingFactor_ * real_c( stencil::cy[ dir ] ),
+                                lengthScalingFactor_ * real_c( stencil::cz[ dir ] ) );
+
+   //get particle index
+   auto particleIdx = ac_->uidToIdx(particleField_->get( nx, ny, nz ));
+   WALBERLA_ASSERT_UNEQUAL( particleIdx, ac_->getInvalidIdx(), "Index of particle is invalid!" );
+
+   real_t delta = real_t(0);
+   real_t pdf_new = real_t(0);
+   real_t alpha = real_t(0);
+
+   // get the cell indices of the cell further away from the obstacle
+   const cell_idx_t xff = x - cell_idx_c( stencil::cx[ dir ] );
+   const cell_idx_t yff = y - cell_idx_c( stencil::cy[ dir ] );
+   const cell_idx_t zff = z - cell_idx_c( stencil::cz[ dir ] );
+
+   // check if CLI can be applied, i.e. the further-away-cell has to be a valid (i.e. fluid) cell
+   if( flagField_->isPartOfMaskSet( xff, yff, zff, domainMask_ ) )
+   {
+      // apply CLI scheme
+
+      // delta value obtained by ray - particle intersection
+      // depending on the implementation for the specific particle, either an analytical formula (e.g. for the sphere) or a line search algorithm is used
+
+      // (if applicable) line search accuracy
+      const real_t tolerance = real_t( 1e-4 ) * lengthScalingFactor_;
+
+      mesa_pd::kernel::SingleCast singleCast;
+      mesa_pd::RayParticleIntersectionRatioFunctor intersectionRatioFctr;
+      delta = singleCast(particleIdx, *ac_, intersectionRatioFctr, *ac_, cellCenter, direction, tolerance );
+
+      WALBERLA_ASSERT_LESS_EQUAL( delta, real_t(1));
+      WALBERLA_ASSERT_GREATER_EQUAL( delta, real_t(0));
+
+      // coefficients from Table 3
+      const real_t kappa0 = ( real_t(1) - real_t(2) * delta ) / ( real_t(1) + real_t(2) * delta );
+
+      // Eq. (4.1)
+      pdf_new = /*kappa1*/ pdfField_->get( x  , y  , z  , Stencil_T::idx[dir] )
+                + kappa0 * pdfField_->get( xff, yff, zff, Stencil_T::idx[dir] )
+                - kappa0 * pdfField_->get( x  , y  , z  , Stencil_T::invDirIdx(dir) );
+
+      // Table 4
+      alpha = real_t(4) / ( real_t(1) + real_t(2) * delta);
+
+   }
+   else
+   {
+      // fall back to Simple Bounce back
+
+      delta = real_t(0.5);
+      pdf_new = pdfField_->get( x, y, z, Stencil_T::idx[dir] );
+      alpha = real_t(2);
+   }
+
+   // assumed boundary position
+   const Vector3<real_t> boundaryPosition( cellCenter + delta * direction );
+
+   // obtain the velocity at the obstacle boundary
+   auto boundaryVelocity = mesa_pd::getVelocityAtWFPoint(particleIdx, *ac_, boundaryPosition );
+
+   // include effect of boundary velocity
+   if( LatticeModel_T::compressible )
+   {
+      const auto density = pdfField_->getDensity(x,y,z);
+      pdf_new -= real_t(3.0) * alpha * density * LatticeModel_T::w[ Stencil_T::idx[dir] ] *
+                 ( real_c( stencil::cx[ dir ] ) * boundaryVelocity[0] +
+                   real_c( stencil::cy[ dir ] ) * boundaryVelocity[1] +
+                   real_c( stencil::cz[ dir ] ) * boundaryVelocity[2] );
+   }
+   else
+   {
+      pdf_new -= real_t(3.0) * alpha * LatticeModel_T::w[ Stencil_T::idx[dir] ] *
+                 ( real_c( stencil::cx[ dir ] ) * boundaryVelocity[0] +
+                   real_c( stencil::cy[ dir ] ) * boundaryVelocity[1] +
+                   real_c( stencil::cz[ dir ] ) * boundaryVelocity[2] );
+   }
+
+   // carry out the boundary handling
+   pdfField_->get( nx, ny, nz, Stencil_T::invDirIdx(dir) ) = pdf_new;
+
+   // check if fluid cell is not inside ghost layer ( = is in inner part), since then no force is allowed to be added
+   if( !pdfField_->isInInnerPart( nearBoundaryCell ) )
+   {
+      return;
+   }
+
+   // calculate the force on the obstacle
+   // original work (MEM):      Ladd - Numerical simulations of particulate suspensions via a discretized Boltzmann equation. Part 1. Theoretical foundation (1994)
+   // improved version (GIMEM): Wen et al. - Galilean invariant fluid-solid interfacial dynamics in lattice Boltzmann simulations (2014)
+
+   real_t pdf_old = pdfField_->get( x, y, z, Stencil_T::idx[dir] );
+
+   if(LatticeModel_T::compressible)
+   {
+      // this artificially introduces the zero-centering of the PDF values here (see also /lbm/field/PdfField.h)
+      // it is important to have correct force values when two or more particles are so close such that there is no fluid cell between
+      // as a consequence, some (non-zero) PDF contributions would be missing after summing up the force contributions
+      // those would need to be added artificially, see e.g. Ernst, Dietzel, Sommerfeld - A lattice Boltzmann method for simulating transport and agglomeration of resolved particles, Acta Mech, 2013
+      // instead, we use the trick there that we just require the deviations from the equilibrium to get the correct force as it is already used for the incompressible case
+      pdf_old -= LatticeModel_T::w[ Stencil_T::idx[dir] ];
+      pdf_new -= LatticeModel_T::w[ Stencil_T::idx[dir] ];
+   }
+
+   // MEM: F = pdf_old + pdf_new - common
+   const real_t forceMEM = pdf_old + pdf_new;
+
+   // correction from Wen
+   const real_t correction = pdf_old - pdf_new;
+
+   // force consists of the MEM part and the galilean invariance correction including the boundary velocity
+   Vector3<real_t> force( real_c( stencil::cx[dir] ) * forceMEM - correction * boundaryVelocity[0],
+                          real_c( stencil::cy[dir] ) * forceMEM - correction * boundaryVelocity[1],
+                          real_c( stencil::cz[dir] ) * forceMEM - correction * boundaryVelocity[2] );
+
+   force *= forceScalingFactor_;
+
+   // add the force onto the particle at the obstacle boundary
+   lbm_mesapd_coupling::addHydrodynamicForceAtWFPosAtomic( particleIdx, *ac_, force, boundaryPosition );
+
+}
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/momentum_exchange_method/boundary/SimpleBB.h b/src/lbm_mesapd_coupling/momentum_exchange_method/boundary/SimpleBB.h
new file mode 100644
index 0000000000000000000000000000000000000000..aeafb2b255fd965b248c71ee71c56b3c27972348
--- /dev/null
+++ b/src/lbm_mesapd_coupling/momentum_exchange_method/boundary/SimpleBB.h
@@ -0,0 +1,241 @@
+//======================================================================================================================
+//
+//  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 SimpleBB.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "boundary/Boundary.h"
+
+#include "core/DataTypes.h"
+#include "core/cell/CellInterval.h"
+#include "core/config/Config.h"
+#include "core/debug/Debug.h"
+#include "core/logging/all.h"
+
+#include "field/FlagField.h"
+
+#include "lbm/field/PdfField.h"
+
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/utility/ParticleFunctions.h"
+
+#include "mesa_pd/common/ParticleFunctions.h"
+#include "mesa_pd/data/IAccessor.h"
+
+#include "stencil/Directions.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+//**************************************************************************************************************************************
+/*!
+*   \brief Bounce back boundary handling for moving particles
+*
+*   This boundary condition implements the bounce back scheme to model a no-slip boundary condition for moving particles
+*
+*/
+//**************************************************************************************************************************************
+
+template< typename LatticeModel_T, typename FlagField_T, typename ParticleAccessor_T >
+class SimpleBB : public Boundary< typename FlagField_T::flag_t >
+{
+   using PDFField_T = lbm::PdfField< LatticeModel_T >;
+   using Stencil_T = typename LatticeModel_T::Stencil;
+   using flag_t = typename FlagField_T::flag_t;
+
+   static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+public:
+
+   static shared_ptr<BoundaryConfiguration> createConfiguration( const Config::BlockHandle& )
+   {
+      WALBERLA_ABORT( "A SimpleBB boundary cannot be created from a config file" );
+      return make_shared<BoundaryConfiguration>();
+   }
+
+   inline SimpleBB( const BoundaryUID & boundaryUID, const FlagUID & uid, PDFField_T * const pdfField, const FlagField_T * const flagField,
+                    ParticleField_T * const particleField, const shared_ptr<ParticleAccessor_T>& ac,
+                    const flag_t domain, const StructuredBlockStorage & blockStorage, const IBlock & block );
+
+   void pushFlags( std::vector< FlagUID >& uids ) const { uids.push_back( uid_ ); }
+
+   void beforeBoundaryTreatment() const {}
+   void  afterBoundaryTreatment() const {}
+
+   template< typename Buffer_T >
+   void packCell( Buffer_T &, const cell_idx_t, const cell_idx_t, const cell_idx_t ) const {}
+
+   template< typename Buffer_T >
+   void registerCell( Buffer_T &, const flag_t, const cell_idx_t, const cell_idx_t, const cell_idx_t ) {}
+
+   void registerCell( const flag_t, const cell_idx_t, const cell_idx_t, const cell_idx_t, const BoundaryConfiguration& ) {}
+   void registerCells( const flag_t, const CellInterval&, const BoundaryConfiguration& ) {}
+   template< typename CellIterator >
+   void registerCells( const flag_t, const CellIterator&, const CellIterator&, const BoundaryConfiguration& ) {}
+
+   void unregisterCell( const flag_t, const cell_idx_t, const cell_idx_t, const cell_idx_t ) const {}
+
+   inline void treatDirection( const cell_idx_t  x, const cell_idx_t  y, const cell_idx_t  z, const stencil::Direction dir,
+                               const cell_idx_t nx, const cell_idx_t ny, const cell_idx_t nz, const flag_t mask );
+
+private:
+
+   const FlagUID uid_;
+
+
+   PDFField_T  * const  pdfField_;
+   const FlagField_T * const flagField_;
+   ParticleField_T * const particleField_;
+   shared_ptr<ParticleAccessor_T> ac_;
+
+   flag_t domainMask_;
+
+   const StructuredBlockStorage & blockStorage_;
+   const IBlock & block_;
+
+   real_t lengthScalingFactor_;
+   real_t forceScalingFactor_;
+
+}; // class SimpleBB
+
+
+template< typename LatticeModel_T, typename FlagField_T, typename ParticleAccessor_T >
+inline SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor_T >::SimpleBB( const BoundaryUID & boundaryUID, const FlagUID & uid, PDFField_T * const pdfField, const FlagField_T * const flagField,
+                                                                              ParticleField_T * const particleField, const shared_ptr<ParticleAccessor_T>& ac,
+                                                                              const flag_t domain, const StructuredBlockStorage & blockStorage, const IBlock & block ):
+Boundary<flag_t>( boundaryUID ), uid_( uid ), pdfField_( pdfField ), flagField_( flagField ), particleField_( particleField ), ac_( ac ), domainMask_(domain), blockStorage_( blockStorage ), block_( block )
+{
+   WALBERLA_ASSERT_NOT_NULLPTR( pdfField_ );
+   WALBERLA_ASSERT_NOT_NULLPTR( flagField_ );
+   WALBERLA_ASSERT_NOT_NULLPTR( particleField_ );
+   WALBERLA_ASSERT( flagField_->isRegistered( domainMask_ )  );
+
+   // force scaling factor to account for different dx on this block
+   const real_t dxCurrentLevel = blockStorage_.dx( blockStorage_.getLevel(block) );
+   lengthScalingFactor_ = dxCurrentLevel;
+   forceScalingFactor_ = lengthScalingFactor_ * lengthScalingFactor_;
+}
+
+// requires: getVelocityAtPosition (was velFromWF), addForceAtPosition (was addForceAtPos)
+template< typename LatticeModel_T, typename FlagField_T, typename ParticleAccessor_T >
+#ifndef NDEBUG
+inline void SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor_T >::treatDirection( const cell_idx_t  x, const cell_idx_t  y, const cell_idx_t  z, const stencil::Direction dir,
+                                                                                         const cell_idx_t nx, const cell_idx_t ny, const cell_idx_t nz, const flag_t mask )
+#else
+inline void SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor_T >::treatDirection( const cell_idx_t  x, const cell_idx_t  y, const cell_idx_t  z, const stencil::Direction dir,
+                                                                                         const cell_idx_t nx, const cell_idx_t ny, const cell_idx_t nz, const flag_t /*mask*/ )
+#endif
+{
+   WALBERLA_ASSERT_EQUAL  ( nx, x + cell_idx_t( stencil::cx[ dir ] ) );
+   WALBERLA_ASSERT_EQUAL  ( ny, y + cell_idx_t( stencil::cy[ dir ] ) );
+   WALBERLA_ASSERT_EQUAL  ( nz, z + cell_idx_t( stencil::cz[ dir ] ) );
+   WALBERLA_ASSERT_UNEQUAL( mask & this->mask_, numeric_cast<flag_t>(0) );
+   WALBERLA_ASSERT_EQUAL  ( mask & this->mask_, this->mask_ ); // only true if "this->mask_" only contains one single flag, which is the case for the
+                                                               // current implementation of this boundary condition
+   WALBERLA_ASSERT_UNEQUAL( particleField_->get(nx,ny,nz), ac_->getInvalidUid(), "UID of particle is invalid!" );
+
+   real_t pdf_old = pdfField_->get( x, y, z, Stencil_T::idx[dir] );
+
+   // apply bounce back scheme
+   const real_t delta = real_c( 0.5 );
+   real_t pdf_new = pdf_old;
+   const real_t alpha = real_c(2);
+
+   // get coordinates of fluid cell center
+   Cell nearBoundaryCell(x,y,z);
+   Vector3< real_t > cellCenter = blockStorage_.getBlockLocalCellCenter(block_, nearBoundaryCell );
+
+   // get vector from fluid cell center to boundary cell center
+   Vector3< real_t > direction( lengthScalingFactor_ * real_c( stencil::cx[ dir ] ),
+                                lengthScalingFactor_ * real_c( stencil::cy[ dir ] ),
+                                lengthScalingFactor_ * real_c( stencil::cz[ dir ] ) );
+
+   //get particle index
+   auto particleIdx = ac_->uidToIdx(particleField_->get( nx, ny, nz ));
+   WALBERLA_ASSERT_UNEQUAL( particleIdx, ac_->getInvalidIdx(), "Index of particle is invalid!" );
+
+   // assumed boundary position
+   const Vector3<real_t> boundaryPosition( cellCenter + delta * direction );
+
+   auto boundaryVelocity = mesa_pd::getVelocityAtWFPoint(particleIdx, *ac_, boundaryPosition );
+
+   // include effect of boundary velocity
+   if( LatticeModel_T::compressible )
+   {
+       const auto density  = pdfField_->getDensity(x,y,z);
+       pdf_new -= real_c(3.0) * alpha * density * LatticeModel_T::w[ Stencil_T::idx[dir] ] *
+                     ( real_c( stencil::cx[ dir ] ) * boundaryVelocity[0] +
+                       real_c( stencil::cy[ dir ] ) * boundaryVelocity[1] +
+                       real_c( stencil::cz[ dir ] ) * boundaryVelocity[2] );
+   }
+   else
+   {
+       pdf_new -= real_c(3.0) * alpha * LatticeModel_T::w[ Stencil_T::idx[dir] ] *
+                     ( real_c( stencil::cx[ dir ] ) * boundaryVelocity[0] +
+                       real_c( stencil::cy[ dir ] ) * boundaryVelocity[1] +
+                       real_c( stencil::cz[ dir ] ) * boundaryVelocity[2] );
+   }
+
+   // carry out the boundary handling
+   pdfField_->get( nx, ny, nz, Stencil_T::invDirIdx(dir) ) = pdf_new;
+
+   // check if fluid cell is not inside ghost layer ( = is in inner part), since then no force is allowed to be added
+   if( !pdfField_->isInInnerPart( nearBoundaryCell ) )
+   {
+      return;
+   }
+
+   // calculate the force on the obstacle
+   // original work (MEM):      Ladd - Numerical simulations of particulate suspensions via a discretized Boltzmann equation. Part 1. Theoretical foundation (1994)
+   // improved version (GIMEM): Wen et al. - Galilean invariant fluid-solid interfacial dynamics in lattice Boltzmann simulations (2014)
+
+   if(LatticeModel_T::compressible)
+   {
+      // this artificially introduces the zero-centering of the PDF values here (see also /lbm/field/PdfField.h)
+      // it is important to have correct force values when two or more particles are so close such that there is no fluid cell between
+      // as a consequence, some (non-zero) PDF contributions would be missing after summing up the force contributions
+      // those would need to be added artificially, see e.g. Ernst, Dietzel, Sommerfeld - A lattice Boltzmann method for simulating transport and agglomeration of resolved particles, Acta Mech, 2013
+      // instead, we use the trick there that we just require the deviations from the equilibrium to get the correct force as it is already used for the incompressible case
+      pdf_old -= LatticeModel_T::w[ Stencil_T::idx[dir] ];
+      pdf_new -= LatticeModel_T::w[ Stencil_T::idx[dir] ];
+   }
+
+   // MEM: F = pdf_old + pdf_new - common
+   const real_t forceMEM = pdf_old + pdf_new;
+
+   // correction from Wen
+   const real_t correction = pdf_old - pdf_new;
+
+
+   // force consists of the MEM part and the galilean invariance correction including the boundary velocity
+   Vector3<real_t> force( real_c( stencil::cx[dir] ) * forceMEM - correction * boundaryVelocity[0],
+                          real_c( stencil::cy[dir] ) * forceMEM - correction * boundaryVelocity[1],
+                          real_c( stencil::cz[dir] ) * forceMEM - correction * boundaryVelocity[2] );
+
+
+   force *= forceScalingFactor_;
+
+   // add the force onto the particle at the obstacle boundary
+   lbm_mesapd_coupling::addHydrodynamicForceAtWFPosAtomic( particleIdx, *ac_, force, boundaryPosition );
+
+}
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/ExtrapolationDirectionFinder.h b/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/ExtrapolationDirectionFinder.h
new file mode 100644
index 0000000000000000000000000000000000000000..22ebdfb3b72b2e98a43e2696d45cd316ae3c50f8
--- /dev/null
+++ b/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/ExtrapolationDirectionFinder.h
@@ -0,0 +1,158 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ExtrapolationDirectionFinder.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+
+#pragma once
+
+#include "domain_decomposition/StructuredBlockStorage.h"
+
+#include "field/Field.h"
+
+#include "mesa_pd/data/IAccessor.h"
+
+#include "stencil/Directions.h"
+#include "stencil/D3Q6.h"
+#include "stencil/D3Q27.h"
+
+
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+
+//**************************************************************************************************************************************
+/*!
+ *   \brief Classes to be used with some Reconstructor variants to find an extrapolation direction
+ *
+ *   The goal is to find a suitable extrapolation direction which is a three dimensional vector with cell index offsets to the current cell.
+ *   Each ExtrapolationDirectionFinder class must exactly implement the member function
+ *   template< typename ParticleAccessor_T >
+ *   Vector3<cell_idx_t> operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z,
+ *                                   const size_t particleIdx, const ParticleAccessor_T & ac) const
+ *   that returns an appropriate extrapolation direction (i.e. a certain lattice direction).
+ *
+ *   Different variants are available:
+ *
+ *   - FlagFieldNormalExtrapolationDirectionFinder:
+ *     Uses information from the direct (N,S,E,W,T,B) neighbors' flags to find the extrapolation direction
+ *     by checking for the domain (here: fluid) flag.
+ *     This can lead to incorrect directions in close packing scenarios or in the vicinity of other boundaries.
+ *
+ *   - SphereNormalExtrapolationDirectionFinder:
+ *     Calculates the local normal of the particle which is in its current form only correct for spherical shapes.
+ *     The corresponding direction with cell offsets is then determined based on this normal.
+ *
+ */
+//**************************************************************************************************************************************
+
+// finds lattice direction in given stencil that corresponds best to the provided direction
+template < typename Stencil_T >
+Vector3<cell_idx_t> findCorrespondingLatticeDirection( const Vector3<real_t> & direction )
+{
+   stencil::Direction correspondingDirection = stencil::C;
+   real_t innerProduct = real_t(0);
+   for( auto d = Stencil_T::beginNoCenter(); d != Stencil_T::end(); ++d )
+   {
+      // compute inner product <dir,c_i>
+      real_t temporaryInnerProduct = direction[0] * stencil::cNorm[0][*d] + direction[1] * stencil::cNorm[1][*d] + direction[2] * stencil::cNorm[2][*d];
+      if( temporaryInnerProduct > innerProduct )
+      {
+         // largest inner product is the most fitting lattice direction
+         innerProduct = temporaryInnerProduct;
+         correspondingDirection = *d;
+      }
+   }
+   return Vector3<cell_idx_t>(stencil::cx[correspondingDirection],
+                              stencil::cy[correspondingDirection],
+                              stencil::cz[correspondingDirection]);
+}
+
+
+template< typename BoundaryHandling_T >
+class FlagFieldNormalExtrapolationDirectionFinder
+{
+public:
+
+   FlagFieldNormalExtrapolationDirectionFinder( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID )
+   : blockStorage_( blockStorage ), boundaryHandlingID_( boundaryHandlingID )
+   {}
+
+   template< typename ParticleAccessor_T >
+   Vector3<cell_idx_t> operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z,
+                                   const size_t /*particleIdx*/, const ParticleAccessor_T & /*ac*/) const
+   {
+      BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+
+      WALBERLA_ASSERT_NOT_NULLPTR( boundaryHandling );
+
+      Vector3<cell_idx_t> extrapolationDirection;
+
+      for( auto directNeighborDir = stencil::D3Q6::begin(); directNeighborDir != stencil::D3Q6::end(); ++directNeighborDir)
+      {
+         if( boundaryHandling->isDomain( Cell( x + directNeighborDir.cx(), y + directNeighborDir.cy(), z + directNeighborDir.cz() ) ) )
+         {
+            extrapolationDirection[0] += directNeighborDir.cx();
+            extrapolationDirection[1] += directNeighborDir.cy();
+            extrapolationDirection[2] += directNeighborDir.cz();
+         }
+      }
+
+      return extrapolationDirection;
+   }
+
+private:
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+   const BlockDataID boundaryHandlingID_;
+};
+
+
+class SphereNormalExtrapolationDirectionFinder
+{
+public:
+
+   explicit SphereNormalExtrapolationDirectionFinder( const shared_ptr<StructuredBlockStorage> & blockStorage )
+   : blockStorage_( blockStorage )
+   {}
+
+   template< typename ParticleAccessor_T >
+   Vector3<cell_idx_t> operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z,
+                                   const size_t particleIdx, const ParticleAccessor_T & ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      real_t cx, cy, cz;
+      blockStorage_->getBlockLocalCellCenter( *block, Cell(x,y,z), cx, cy, cz );
+
+      Vector3<real_t> particleCenterPosition = ac.getPosition(particleIdx);
+      WALBERLA_ASSERT( !math::isnan(particleCenterPosition) );
+
+      Vector3<real_t> particleNormal( cx - particleCenterPosition[0], cy - particleCenterPosition[1], cz - particleCenterPosition[2] );
+
+      return findCorrespondingLatticeDirection< stencil::D3Q27 >( particleNormal );
+   }
+
+private:
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/PdfReconstructionManager.h b/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/PdfReconstructionManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..dcf4c750ece66a25a5ee24019d41ac43a10877bb
--- /dev/null
+++ b/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/PdfReconstructionManager.h
@@ -0,0 +1,245 @@
+//======================================================================================================================
+//
+//  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 PdfReconstructionManager.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+
+#pragma once
+
+#include "core/debug/Debug.h"
+#include "domain_decomposition/StructuredBlockStorage.h"
+#include "field/Field.h"
+#include "field/FlagField.h"
+#include "field/iterators/IteratorMacros.h"
+
+#include "lbm_mesapd_coupling/DataTypes.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.h"
+#include "lbm_mesapd_coupling/utility/ParticleFunctions.h"
+
+#include "mesa_pd/data/IAccessor.h"
+
+#include <functional>
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+
+//**************************************************************************************************************************************
+/*!\brief Class to manage the reconstruction of PDFs that is needed when cells are becoming uncovered by moving obstacles.
+ *
+ * Due to the explicit mapping of particles into the fluid domain via flags, the PDFs of cells that turned from obstacle to fluid
+ * are missing and must be reconstructed in order to continue with the simulation.
+ * This class is to be used as a sweep in a LBM simulation with moving obstacles and calls for each cell that is tagged as
+ * 'formerObstacle' the specified reconstructor (see Reconstructor.h for the available variants).
+ * After the successful reconstruction of all PDFs, the flags are updated to 'fluid'.
+ * For small obstacle fractions, an optimized variant is available that only looks for 'formerObstacle' cells in the vicinity
+ * of available particles. It is activated via the 'optimizeForSmallObstacleFraction' argument in the constructor.
+ *
+ *
+ * Note:
+ * A potential problem arises when the cell that is to be reconstructed, i.e. filled with fluid, is surrounded (given by the LBM stencil)
+ * by only boundary cells. Consequently, the successive calls to LBM will simulate a local system containing a single fluid cell
+ * surrounded by boundary cells with different velocity boundary conditions. This system is not divergence free (as required by the mass
+ * conservation equation) and will thus lead to mass loss/gain depending on the boundary conditions.
+ * This is a very rare case but if it happens, it will almost always lead to malicious oscillations in the density.
+ * Note that this can also happen if several cells are locally "trapped" by surrounding boundary cells. Such a case can not be detected
+ * easily and also not be curred easily.
+ * It has to be inspected whether the behavior arising from this issue leads to problems in real simulations.
+ */
+//**************************************************************************************************************************************
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename ParticleAccessor_T, typename Reconstructor_T>
+class PdfReconstructionManager
+{
+   static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+public:
+
+   using FlagField_T = typename BoundaryHandling_T::FlagField;
+   using flag_t = typename BoundaryHandling_T::flag_t;
+
+   inline PdfReconstructionManager( const shared_ptr<StructuredBlockStorage> & blockStorage,
+                                    const BlockDataID & pdfFieldID,
+                                    const BlockDataID & boundaryHandlingID,
+                                    const BlockDataID & particleFieldID,
+                                    const shared_ptr<ParticleAccessor_T> & ac,
+                                    const FlagUID & formerObstacle, const FlagUID & fluid,
+                                    const shared_ptr<Reconstructor_T> & reconstructor,
+                                    bool conserveMomentumWhenRestoring,
+                                    const bool optimizeForSmallObstacleFraction = false ) :
+      blockStorage_( blockStorage ), pdfFieldID_( pdfFieldID), boundaryHandlingID_( boundaryHandlingID ),
+      particleFieldID_( particleFieldID ), ac_( ac ),
+      reconstructor_ ( reconstructor ), formerObstacle_( formerObstacle ), fluid_( fluid ),
+      conserveMomentumWhenRestoring_( conserveMomentumWhenRestoring ),
+      optimizeForSmallObstacleFraction_( optimizeForSmallObstacleFraction )
+   {}
+
+   void inline operator()( IBlock * const block );
+
+private:
+
+
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+
+   const BlockDataID pdfFieldID_;
+   const BlockDataID boundaryHandlingID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+
+   shared_ptr<Reconstructor_T> reconstructor_;
+
+   const FlagUID formerObstacle_;
+   const FlagUID fluid_;
+
+   bool conserveMomentumWhenRestoring_;
+   const bool optimizeForSmallObstacleFraction_;
+
+};
+
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename ParticleAccessor_T, typename Reconstructor_T>
+void PdfReconstructionManager< PdfField_T, BoundaryHandling_T, ParticleAccessor_T, Reconstructor_T >
+::operator()( IBlock * const block )
+{
+   WALBERLA_ASSERT_NOT_NULLPTR( block );
+
+   auto * pdfField         = block->getData< PdfField_T >( pdfFieldID_ );
+   auto * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+   auto * flagField        = boundaryHandling->getFlagField();
+   auto * particleField    = block->getData< ParticleField_T >( particleFieldID_ );
+
+   WALBERLA_ASSERT_NOT_NULLPTR( pdfField );
+   WALBERLA_ASSERT_NOT_NULLPTR( boundaryHandling );
+   WALBERLA_ASSERT_NOT_NULLPTR( flagField );
+   WALBERLA_ASSERT_NOT_NULLPTR( particleField );
+
+   WALBERLA_ASSERT_EQUAL( particleField->xyzSize(), flagField->xyzSize() );
+
+   WALBERLA_ASSERT( flagField->flagExists( formerObstacle_ ) );
+   WALBERLA_ASSERT( flagField->flagExists( fluid_ ) );
+
+   const flag_t formerObstacle = flagField->getFlag( formerObstacle_ );
+   const flag_t fluid          = flagField->getFlag( fluid_ );
+
+   // reconstruct all missing PDFs (only inside the domain, ghost layer values get communicated)
+
+   if( optimizeForSmallObstacleFraction_ )
+   {
+      const uint_t numberOfGhostLayersToInclude = uint_t(0);
+
+      for( size_t idx = 0; idx < ac_->size(); ++idx )
+      {
+         auto cellIntervalForReconstruction = getParticleCellBB( idx, *ac_, *block, *blockStorage_, numberOfGhostLayersToInclude );
+
+         WALBERLA_FOR_ALL_CELLS_IN_INTERVAL_XYZ(cellIntervalForReconstruction,
+                                                size_t particleIdx = ac_->uidToIdx(particleField->get(x,y,z)); // to only reconstruct cells that belonged to this particle
+                                                if (isFlagSet(flagField->get(x,y,z), formerObstacle) && particleIdx == idx) {
+                                                   (*reconstructor_)(block, x, y, z, pdfField, idx, *ac_);
+                                                   if( conserveMomentumWhenRestoring_ && pdfField->isInInnerPart(Cell(x,y,z)) ) {
+                                                      // the (artificially) added momentum in the restored fluid cell has to be subtracted from the former particle to ensure momentum conservation
+                                                      // force = momentum / dt, with dt = 1
+                                                      Vector3<real_t> force = pdfField->getMomentumDensity(x, y, z);
+                                                      Vector3< real_t > cellCenter = blockStorage_->getBlockLocalCellCenter(*block, Cell(x,y,z) );
+                                                      lbm_mesapd_coupling::addHydrodynamicForceAtWFPosAtomic( particleIdx, *ac_, -force, cellCenter );
+                                                   }
+                                                }
+
+         )
+      }
+   }
+   else
+   {
+      WALBERLA_FOR_ALL_CELLS_IN_INTERVAL_XYZ(flagField->xyzSize(),
+                                             if (isFlagSet(flagField->get(x,y,z), formerObstacle)) {
+                                                size_t particleIdx = ac_->uidToIdx(particleField->get(x,y,z));
+                                                WALBERLA_ASSERT_UNEQUAL( particleIdx, ac_->getInvalidIdx(), "Index of particle is invalid!" );
+                                                (*reconstructor_)(block, x, y, z, pdfField, particleIdx, *ac_);
+                                                if( conserveMomentumWhenRestoring_ && pdfField->isInInnerPart(Cell(x,y,z)) ) {
+                                                   // the (artificially) added momentum in the restored fluid cell has to be subtracted from the former particle to ensure momentum conservation
+                                                   // force = momentum / dt, with dt = 1
+                                                   Vector3<real_t> force = pdfField->getMomentumDensity(x, y, z);
+                                                   Vector3< real_t > cellCenter = blockStorage_->getBlockLocalCellCenter(*block, Cell(x,y,z) );
+                                                   lbm_mesapd_coupling::addHydrodynamicForceAtWFPosAtomic( particleIdx, *ac_, -force, cellCenter );
+                                                }
+                                             };
+      )
+   }
+
+   // update the flags from formerObstacle to fluid (inside domain & in ghost layers), invalidate entry in particle field
+   if( optimizeForSmallObstacleFraction_ )
+   {
+      const uint_t numberOfGhostLayersToInclude = flagField->nrOfGhostLayers();
+
+      for( size_t idx = 0; idx < ac_->size(); ++idx )
+      {
+         auto cellIntervalToCleanUp = getParticleCellBB( idx, *ac_, *block, *blockStorage_, numberOfGhostLayersToInclude );
+         WALBERLA_FOR_ALL_CELLS_IN_INTERVAL_XYZ(cellIntervalToCleanUp,
+                                                if (isFlagSet(flagField->get(x,y,z), formerObstacle)) {
+                                                   boundaryHandling->setDomain( fluid, x, y, z );
+                                                   removeFlag( flagField->get(x,y,z), formerObstacle );
+                                                   particleField->get(x,y,z) = ac_->getInvalidUid();
+                                                }
+         )
+      }
+   }
+   else
+   {
+      WALBERLA_FOR_ALL_CELLS_IN_INTERVAL_XYZ(flagField->xyzSizeWithGhostLayer(),
+                                             if (isFlagSet(flagField->get(x,y,z), formerObstacle)) {
+                                                boundaryHandling->setDomain( fluid, x, y, z );
+                                                removeFlag( flagField->get(x,y,z), formerObstacle );
+                                                particleField->get(x,y,z) = ac_->getInvalidUid();
+                                             }
+      )
+   }
+}
+
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename ParticleAccessor_T, typename Reconstructor_T >
+shared_ptr< PdfReconstructionManager<PdfField_T,BoundaryHandling_T,ParticleAccessor_T,Reconstructor_T> >
+makePdfReconstructionManager( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & pdfFieldID,
+                              const BlockDataID & boundaryHandlingID, const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T> & ac,
+                              const FlagUID & formerObstacle, const FlagUID & fluid,
+                              const shared_ptr<Reconstructor_T> & reconstructor,
+                              bool conserveMomentumWhenRestoring,
+                              const bool optimizeForSmallObstacleFraction = false)
+{
+   using RM_T = PdfReconstructionManager<PdfField_T,BoundaryHandling_T,ParticleAccessor_T,Reconstructor_T>;
+   return make_shared<RM_T>( RM_T(blockStorage, pdfFieldID, boundaryHandlingID, particleFieldID, ac,formerObstacle, fluid, reconstructor, conserveMomentumWhenRestoring, optimizeForSmallObstacleFraction) );
+}
+
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename ParticleAccessor_T >
+shared_ptr< PdfReconstructionManager<PdfField_T,BoundaryHandling_T,ParticleAccessor_T,EquilibriumReconstructor<BoundaryHandling_T> > >
+makePdfReconstructionManager( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & pdfFieldID,
+                              const BlockDataID & boundaryHandlingID, const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T> & ac,
+                              const FlagUID & formerObstacle, const FlagUID & fluid,
+                              bool conserveMomentumWhenRestoring,
+                              const bool optimizeForSmallObstacleFraction = false)
+{
+   using RM_T = PdfReconstructionManager<PdfField_T,BoundaryHandling_T,ParticleAccessor_T,EquilibriumReconstructor<BoundaryHandling_T> >;
+   return make_shared<RM_T>( RM_T(blockStorage, pdfFieldID, boundaryHandlingID, particleFieldID, ac,formerObstacle, fluid,
+                                  makeEquilibriumReconstructor<BoundaryHandling_T>(blockStorage,boundaryHandlingID), conserveMomentumWhenRestoring, optimizeForSmallObstacleFraction) );
+}
+
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.h b/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.h
new file mode 100644
index 0000000000000000000000000000000000000000..acb4fd95b1455af0973cde6b5ab80df1f0b99002
--- /dev/null
+++ b/src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.h
@@ -0,0 +1,919 @@
+//======================================================================================================================
+//
+//  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 Reconstructor.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+
+#pragma once
+
+#include "domain_decomposition/StructuredBlockStorage.h"
+
+#include "mesa_pd/data/IAccessor.h"
+#include "mesa_pd/common/ParticleFunctions.h"
+
+#include "lbm/lattice_model/EquilibriumDistribution.h"
+
+#include "stencil/D3Q19.h"
+#include "stencil/D3Q27.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+
+//**************************************************************************************************************************************
+/*!
+ *   \brief Classes to be used together with the PDFReconstruction class to reconstruct PDFs
+ *
+ *   Each reconstructor must exactly implement the member function
+ *    template< typename PdfField_T, typename ParticleAccessor_T  >
+ *    void operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+ *                     const size_t particleIdx, const ParticleAccessor_T & ac);
+ *   that reconstructs all PDFs in a specific cell with cell indices x,y,z on a given block.
+ *   Additionally, a pointer to the pdfField and information about the formerly present particle is provided (via idx and accessor).
+ *
+ *   Different variants are available:
+ *
+ *   - EquilibriumReconstructor:
+ *     Determines a local average density and sets the PDFs to their equilibrium values based on this density and the particle's velocity
+ *
+ *   - EquilibriumAndNonEquilibriumReconstructor:
+ *     First reconstructs the equilibrium values with the help of the EquilibriumReconstructor.
+ *     Then extrapolates the non-equilibrium part from neighboring cells and adds it to the reconstructed PDFs for better accuracy.
+ *     Defaults to EquilibriumReconstructor if no suitable cell for extrapolation is available.
+ *
+ *   - ExtrapolationReconstructor:
+ *     Extrapolates the PDFs of three or two neighboring cells that lie in extrapolation direction.
+ *     Optionally, a special treatment after the extrapolation can be used that sets certain moments directly (only D3Q19).
+ *     Defaults to EquilibriumAndNonEquilibriumReconstructor if not enough cells in this direction are available.
+ *
+ *   - GradsMomentReconstructor:
+ *     Uses Grad's moment approximation, i.e. equilibrium distribution function extended by pressure gradient information.
+ *
+ *   EquilibriumAndNonEquilibriumReconstructor and ExtrapolationReconstructor need an extrapolation direction
+ *   which is provided by the ExtrapolationDirectionFinder that is to be chosen (see ExtrapolationDirectionFinder.h).
+ *
+ *  For convenient construction, use the provided makeXReconstructor functions.
+ *
+ */
+//**************************************************************************************************************************************
+
+
+/*
+ * function that determines the number of valid (=fluid) cells for extrapolation in the given extrapolation direction
+ * If useDataFromGhostLayers = true, also cells inside the ghostlayer are considered valid so they have to contain valid data by communicating the PDF values beforehand
+ * Depending on the scheme, this can be an optimized PDF communication or has to be a full PDF communication.
+ */
+template< typename BoundaryHandling_T>
+uint_t getNumberOfExtrapolationCells( IBlock * const block, const BlockDataID & boundaryHandlingID,
+                                      const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z,
+                                      const Vector3<cell_idx_t> & extrapolationDirection, const uint_t maximumNumberOfNeededExtrapolationCells,
+                                      bool useDataFromGhostLayers )
+{
+   WALBERLA_ASSERT_NOT_NULLPTR( block );
+
+   BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID );
+   WALBERLA_ASSERT_NOT_NULLPTR( boundaryHandling );
+
+   if( extrapolationDirection == cell_idx_t(0) ) return uint_t(0);
+
+   CellInterval localDomain = (useDataFromGhostLayers) ? boundaryHandling->getFlagField()->xyzSizeWithGhostLayer() : boundaryHandling->getFlagField()->xyzSize();
+
+   for( auto numCells = uint_t(1); numCells <= maximumNumberOfNeededExtrapolationCells; ++numCells )
+   {
+      Cell checkCell( x + cell_idx_c(numCells) * extrapolationDirection[0], y + cell_idx_c(numCells) * extrapolationDirection[1], z + cell_idx_c(numCells) * extrapolationDirection[2] );
+
+      // check if cell is inside domain & fluid
+      if( !localDomain.contains( checkCell ) )
+         return numCells - uint_t(1);
+
+      if( !boundaryHandling->isDomain( checkCell ) )
+         return numCells - uint_t(1);
+   }
+   return maximumNumberOfNeededExtrapolationCells;
+}
+
+
+template< typename BoundaryHandling_T >
+class EquilibriumReconstructor
+{
+public:
+
+   EquilibriumReconstructor( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID,
+                             bool useDataFromGhostLayers = false)
+   : blockStorage_( blockStorage ), boundaryHandlingID_( boundaryHandlingID ), useDataFromGhostLayers_( useDataFromGhostLayers )
+   {}
+
+
+   template< typename PdfField_T, typename ParticleAccessor_T  >
+   void operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                    const size_t particleIdx, const ParticleAccessor_T & ac)
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( pdfField );
+      WALBERLA_ASSERT_UNEQUAL(particleIdx, ac.getInvalidIdx());
+
+      setLocalEquilibrium(block, x, y, z, pdfField, particleIdx, ac);
+   }
+
+private:
+
+   template< typename PdfField_T, typename ParticleAccessor_T  >
+   void setLocalEquilibrium( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                             const size_t particleIdx, const ParticleAccessor_T & ac )
+   {
+      const real_t averageDensity = getLocalAverageDensity( block, x, y, z, pdfField );
+
+      real_t cx, cy, cz;
+      blockStorage_->getBlockLocalCellCenter( *block, Cell(x,y,z), cx, cy, cz );
+
+      const auto velocity = mesa_pd::getVelocityAtWFPoint(particleIdx, ac, Vector3<real_t>(cx,cy,cz));
+
+      pdfField->setToEquilibrium( x, y, z, velocity, averageDensity );
+   }
+
+   template< typename PdfField_T>
+   real_t getLocalAverageDensity( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField ) const
+   {
+      BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+
+      WALBERLA_ASSERT_NOT_NULLPTR( boundaryHandling );
+
+      CellInterval localDomain = useDataFromGhostLayers_ ? pdfField->xyzSizeWithGhostLayer() : pdfField->xyzSize();
+
+      auto nAverage = uint_t(0);
+      auto averageDensity = real_t(0);
+      for( auto neighborDir = stencil::D3Q27::beginNoCenter(); neighborDir != stencil::D3Q27::end(); ++neighborDir )
+      {
+         Cell neighbor( x + neighborDir.cx(), y + neighborDir.cy(), z + neighborDir.cz() );
+
+         // check if neighbor cell is inside domain
+         if( !( localDomain.contains( neighbor ) ) )
+            continue;
+
+         // check if neighbor cell is a fluid cell
+         if( boundaryHandling->isDomain( neighbor ) )
+         {
+            // obtain density and add it up
+            averageDensity += pdfField->getDensity( neighbor );
+            ++nAverage;
+         }
+      }
+
+      return ( nAverage > uint_t( 0 ) ) ? averageDensity / real_c( nAverage ) : real_t(1.0);
+   }
+
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+   const BlockDataID boundaryHandlingID_;
+   const bool useDataFromGhostLayers_;
+
+};
+
+
+template< typename BoundaryHandling_T, typename ExtrapolationDirectionFinder_T >
+class EquilibriumAndNonEquilibriumReconstructor
+{
+public:
+
+   EquilibriumAndNonEquilibriumReconstructor( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID,
+                                              const shared_ptr<ExtrapolationDirectionFinder_T> & extrapolationDirectionFinder,
+                                              uint_t maximumNumberOfExtrapolationCells = uint_t(3),
+                                              bool useDataFromGhostLayers = false)
+   : blockStorage_( blockStorage ), boundaryHandlingID_( boundaryHandlingID ),
+     extrapolationDirectionFinder_( extrapolationDirectionFinder ),
+     maximumNumberOfExtrapolationCells_( maximumNumberOfExtrapolationCells ), useDataFromGhostLayers_( useDataFromGhostLayers ),
+     equilibriumReconstructor_( EquilibriumReconstructor< BoundaryHandling_T >( blockStorage, boundaryHandlingID, useDataFromGhostLayers ) )
+   {
+      WALBERLA_ASSERT_LESS_EQUAL(maximumNumberOfExtrapolationCells, uint_t(3), "Only supports up to quadratic extrapolation!");
+   }
+
+   template< typename PdfField_T, typename ParticleAccessor_T >
+   void operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                    const size_t particleIdx, const ParticleAccessor_T & ac)
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( pdfField );
+      WALBERLA_ASSERT_UNEQUAL(particleIdx, ac.getInvalidIdx());
+
+      Vector3<cell_idx_t> extrapolationDirection = (*extrapolationDirectionFinder_)( block, x, y, z, particleIdx, ac );
+
+      (*this)(block, x, y, z, pdfField, particleIdx, ac, extrapolationDirection);
+   }
+
+   template< typename PdfField_T, typename ParticleAccessor_T >
+   void operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                    const size_t particleIdx, const ParticleAccessor_T & ac, const Vector3<cell_idx_t> & extrapolationDirection )
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( pdfField );
+      WALBERLA_ASSERT_UNEQUAL(particleIdx, ac.getInvalidIdx());
+
+      equilibriumReconstructor_( block, x, y, z, pdfField, particleIdx, ac );
+
+      uint_t numberOfCellsForExtrapolation = getNumberOfExtrapolationCells<BoundaryHandling_T>( block, boundaryHandlingID_, x, y, z, extrapolationDirection, maximumNumberOfExtrapolationCells_, useDataFromGhostLayers_ );
+
+      if( enoughCellsForExtrapolation( numberOfCellsForExtrapolation ) )
+      {
+         extrapolateNonEquilibrium( x, y, z, pdfField, extrapolationDirection, numberOfCellsForExtrapolation );
+      }
+
+   }
+private:
+
+   template< typename PdfField_T >
+   void extrapolateNonEquilibrium( const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                                   const Vector3<cell_idx_t> & extrapolationDirection, const uint_t & numberOfCellsForExtrapolation)
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( pdfField );
+
+      WALBERLA_ASSERT_LESS_EQUAL(numberOfCellsForExtrapolation, maximumNumberOfExtrapolationCells_);
+      WALBERLA_ASSERT_GREATER(numberOfCellsForExtrapolation, uint_t(0), "Requires at least one point for extrapolation!");
+
+      if( enoughCellsForQuadraticExtrapolation( numberOfCellsForExtrapolation ) )
+      {
+         applyQuadraticExtrapolation(x, y, z, pdfField, extrapolationDirection);
+      }
+      else if ( enoughCellsForLinearExtrapolation( numberOfCellsForExtrapolation ) )
+      {
+         applyLinearExtrapolation(x, y, z, pdfField, extrapolationDirection);
+      }
+      else
+      {
+         applyConstantExtrapolation(x, y, z, pdfField, extrapolationDirection);
+      }
+   }
+
+   bool enoughCellsForQuadraticExtrapolation(uint_t numberOfCellsForExtrapolation )
+   {
+      return numberOfCellsForExtrapolation >= uint_t(3);
+   }
+
+   bool enoughCellsForLinearExtrapolation(uint_t numberOfCellsForExtrapolation )
+   {
+      return numberOfCellsForExtrapolation >= uint_t(2);
+   }
+
+   bool enoughCellsForExtrapolation(uint_t numberOfCellsForExtrapolation )
+   {
+      return numberOfCellsForExtrapolation >= uint_t(1);
+   }
+
+   template< typename PdfField_T >
+   void applyQuadraticExtrapolation( const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                                     const Vector3<cell_idx_t> & extrapolationDirection)
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR(pdfField);
+
+      using LatticeModel_T = typename PdfField_T::LatticeModel;
+
+      auto pdfsXf   = getNonEquilibriumPdfsInCell(x +   extrapolationDirection[0], y +   extrapolationDirection[1], z +   extrapolationDirection[2], pdfField);
+      auto pdfsXff  = getNonEquilibriumPdfsInCell(x + 2*extrapolationDirection[0], y + 2*extrapolationDirection[1], z + 2*extrapolationDirection[2], pdfField);
+      auto pdfsXfff = getNonEquilibriumPdfsInCell(x + 3*extrapolationDirection[0], y + 3*extrapolationDirection[1], z + 3*extrapolationDirection[2], pdfField);
+
+      for( auto d = LatticeModel_T::Stencil::begin(); d != LatticeModel_T::Stencil::end(); ++d )
+      {
+         pdfField->get( x, y, z, d.toIdx() ) += real_t(3) * pdfsXf[d.toIdx()] - real_t(3) * pdfsXff[d.toIdx()] + pdfsXfff[d.toIdx()];
+      }
+   }
+
+   template< typename PdfField_T >
+   void applyLinearExtrapolation( const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                                  const Vector3<cell_idx_t> & extrapolationDirection)
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR(pdfField);
+
+      using LatticeModel_T = typename PdfField_T::LatticeModel;
+
+      auto pdfsXf  = getNonEquilibriumPdfsInCell(x +   extrapolationDirection[0], y +   extrapolationDirection[1], z +   extrapolationDirection[2], pdfField);
+      auto pdfsXff = getNonEquilibriumPdfsInCell(x + 2*extrapolationDirection[0], y + 2*extrapolationDirection[1], z + 2*extrapolationDirection[2], pdfField);
+
+      for( auto d = LatticeModel_T::Stencil::begin(); d != LatticeModel_T::Stencil::end(); ++d )
+      {
+         pdfField->get( x, y, z, d.toIdx() ) += real_t(2) * pdfsXf[d.toIdx()] - pdfsXff[d.toIdx()];
+      }
+   }
+
+   template< typename PdfField_T >
+   void applyConstantExtrapolation( const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                                    const Vector3<cell_idx_t> & extrapolationDirection)
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR(pdfField);
+
+      using LatticeModel_T = typename PdfField_T::LatticeModel;
+
+      // = copy from neighbor cell
+      auto pdfsXf = getNonEquilibriumPdfsInCell(x + extrapolationDirection[0], y + extrapolationDirection[1], z + extrapolationDirection[2], pdfField);
+
+      for( auto d = LatticeModel_T::Stencil::begin(); d != LatticeModel_T::Stencil::end(); ++d )
+      {
+         pdfField->get( x, y, z, d.toIdx() ) += pdfsXf[d.toIdx()];
+      }
+   }
+
+   template< typename PdfField_T >
+   std::vector<real_t> getNonEquilibriumPdfsInCell( const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z,  PdfField_T * const pdfField ) const
+   {
+      using LatticeModel_T = typename PdfField_T::LatticeModel;
+
+      std::vector< real_t > nonEquilibriumPartOfPdfs(LatticeModel_T::Stencil::Size);
+
+      Vector3< real_t > velocity;
+      real_t density = pdfField->getDensityAndVelocity( velocity, x,y,z );
+
+      for( auto d = LatticeModel_T::Stencil::begin(); d != LatticeModel_T::Stencil::end(); ++d )
+      {
+         nonEquilibriumPartOfPdfs[d.toIdx()] = pdfField->get( x, y, z, d.toIdx() ) - lbm::EquilibriumDistribution< LatticeModel_T >::get( *d, velocity, density );
+      }
+      return nonEquilibriumPartOfPdfs;
+   }
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+   const BlockDataID boundaryHandlingID_;
+   shared_ptr<ExtrapolationDirectionFinder_T> extrapolationDirectionFinder_;
+   const uint_t maximumNumberOfExtrapolationCells_;
+   const bool useDataFromGhostLayers_;
+   EquilibriumReconstructor< BoundaryHandling_T > equilibriumReconstructor_;
+
+};
+
+template< typename BoundaryHandling_T, typename ExtrapolationDirectionFinder_T, bool EnforceNoSlipConstraintAfterExtrapolation = false >
+class ExtrapolationReconstructor
+{
+public:
+
+   ExtrapolationReconstructor( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID,
+                               const shared_ptr<ExtrapolationDirectionFinder_T> & extrapolationDirectionFinder,
+                               uint_t maximumNumberOfExtrapolationCells = uint_t(3),
+                               bool useDataFromGhostLayers = false)
+   : blockStorage_( blockStorage ), boundaryHandlingID_( boundaryHandlingID ),
+     extrapolationDirectionFinder_( extrapolationDirectionFinder ),
+     maximumNumberOfExtrapolationCells_(maximumNumberOfExtrapolationCells), useDataFromGhostLayers_(useDataFromGhostLayers),
+     alternativeReconstructor_( EquilibriumAndNonEquilibriumReconstructor< BoundaryHandling_T, ExtrapolationDirectionFinder_T >
+      ( blockStorage, boundaryHandlingID, extrapolationDirectionFinder, uint_t(1), useDataFromGhostLayers ) )
+   {
+      WALBERLA_ASSERT_LESS_EQUAL(maximumNumberOfExtrapolationCells, uint_t(3), "Only supports up to quadratic extrapolation!");
+   }
+
+   template< typename PdfField_T, typename ParticleAccessor_T >
+   void operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                    const size_t particleIdx, const ParticleAccessor_T & ac)
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      WALBERLA_ASSERT_NOT_NULLPTR(block);
+      WALBERLA_ASSERT_NOT_NULLPTR(pdfField);
+      WALBERLA_ASSERT_UNEQUAL(particleIdx, ac.getInvalidIdx());
+
+      Vector3<cell_idx_t> extrapolationDirection = (*extrapolationDirectionFinder_)(block, x, y, z, particleIdx, ac);
+
+      uint_t numberOfCellsForExtrapolation = getNumberOfExtrapolationCells<BoundaryHandling_T>( block, boundaryHandlingID_, x, y, z, extrapolationDirection,
+                                                                                                maximumNumberOfExtrapolationCells_, useDataFromGhostLayers_ );
+
+      if( enoughCellsForExtrapolation(numberOfCellsForExtrapolation) )
+      {
+         extrapolatePDFs( x, y, z, pdfField, extrapolationDirection, numberOfCellsForExtrapolation );
+         if( EnforceNoSlipConstraintAfterExtrapolation )
+         {
+            real_t cx, cy, cz;
+            blockStorage_->getBlockLocalCellCenter( *block, Cell(x,y,z), cx, cy, cz );
+            const auto localParticleVelocity = mesa_pd::getVelocityAtWFPoint(particleIdx, ac, Vector3<real_t>(cx,cy,cz));
+
+            enforceNoSlipConstraint( x, y, z, pdfField, localParticleVelocity );
+         }
+      }
+      else
+      {
+         alternativeReconstructor_( block, x, y, z, pdfField, particleIdx, ac, extrapolationDirection );
+      }
+
+   }
+
+private:
+
+   bool enoughCellsForExtrapolation( uint_t numberOfCellsForExtrapolation )
+   {
+      return numberOfCellsForExtrapolation >= uint_t(2);
+   }
+
+   template< typename PdfField_T >
+   void extrapolatePDFs( const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                         const Vector3<cell_idx_t> & extrapolationDirection, const uint_t & numberOfCellsForExtrapolation)
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( pdfField );
+
+      using LatticeModel_T = typename PdfField_T::LatticeModel;
+
+      if( numberOfCellsForExtrapolation == uint_t(3) )
+      {
+         // quadratic normal extrapolation
+         for( auto d = LatticeModel_T::Stencil::begin(); d != LatticeModel_T::Stencil::end(); ++d )
+         {
+            pdfField->get( x, y, z, d.toIdx() ) =   real_t(3) * pdfField->get( x +   extrapolationDirection[0], y +   extrapolationDirection[1], z +   extrapolationDirection[2], d.toIdx() )
+                                                  - real_t(3) * pdfField->get( x + 2*extrapolationDirection[0], y + 2*extrapolationDirection[1], z + 2*extrapolationDirection[2], d.toIdx() )
+                                                  +             pdfField->get( x + 3*extrapolationDirection[0], y + 3*extrapolationDirection[1], z + 3*extrapolationDirection[2], d.toIdx() );
+         }
+      } else { // numberOfCellsForExtrapolation == 2
+         // linear normal extrapolation
+         for( auto d = LatticeModel_T::Stencil::begin(); d != LatticeModel_T::Stencil::end(); ++d )
+         {
+            pdfField->get( x, y, z, d.toIdx() ) =   real_t(2) * pdfField->get( x +   extrapolationDirection[0], y +   extrapolationDirection[1], z +   extrapolationDirection[2], d.toIdx() )
+                                                  -             pdfField->get( x + 2*extrapolationDirection[0], y + 2*extrapolationDirection[1], z + 2*extrapolationDirection[2], d.toIdx() );
+         }
+      }
+   }
+
+   template< typename PdfField_T >
+   void enforceNoSlipConstraint( const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField, const Vector3<real_t> & localParticleVelocity)
+   {
+      using LatticeModel_T = typename PdfField_T::LatticeModel;
+
+      static_assert(std::is_same<typename LatticeModel_T::Stencil, stencil::D3Q19>::value || !EnforceNoSlipConstraintAfterExtrapolation, "Enforcing no-slip constraint after extrapolation currently only works with D3Q19 stencil!");
+
+      WALBERLA_ASSERT_NOT_NULLPTR( pdfField );
+      WALBERLA_ASSERT( !math::isnan(localParticleVelocity) );
+
+      // transforms to moment space (see MRT collision model) to set the particle's velocity in cell without affecting other moments
+      const real_t _1_2  = real_t(1) / real_t(2);
+      const real_t _1_3  = real_t(1) / real_t(3);
+      const real_t _1_4  = real_t(1) / real_t(4);
+      const real_t _1_6  = real_t(1) / real_t(6);
+      const real_t _1_8  = real_t(1) / real_t(8);
+      const real_t _1_12 = real_t(1) / real_t(12);
+      const real_t _1_16 = real_t(1) / real_t(16);
+      const real_t _1_18 = real_t(1) / real_t(18);
+      const real_t _1_24 = real_t(1) / real_t(24);
+      const real_t _1_36 = real_t(1) / real_t(36);
+      const real_t _1_48 = real_t(1) / real_t(48);
+      const real_t _1_72 = real_t(1) / real_t(72);
+
+      // restriction to D3Q19!
+      const real_t vC  = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::C]  );
+      const real_t vN  = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::N]  );
+      const real_t vS  = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::S]  );
+      const real_t vW  = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::W]  );
+      const real_t vE  = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::E]  );
+      const real_t vT  = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::T]  );
+      const real_t vB  = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::B]  );
+      const real_t vNW = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::NW] );
+      const real_t vNE = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::NE] );
+      const real_t vSW = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::SW] );
+      const real_t vSE = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::SE] );
+      const real_t vTN = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::TN] );
+      const real_t vTS = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::TS] );
+      const real_t vTW = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::TW] );
+      const real_t vTE = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::TE] );
+      const real_t vBN = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::BN] );
+      const real_t vBS = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::BS] );
+      const real_t vBW = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::BW] );
+      const real_t vBE = pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::BE] );
+
+      // transform to moment space and change momentum to particle's velocity ( * rho_0 )
+      const real_t m0  = vC + vN + vS + vW + vE + vT + vB + vNW + vNE + vSW + vSE + vTN + vTS + vTW + vTE + vBN + vBS + vBW + vBE;
+      const real_t m1  = -vC  + vNW + vNE + vSW + vSE + vTN + vTS + vTW + vTE + vBN + vBS + vBW + vBE;
+      const real_t m2  = vC - real_t(2) * ( vN + vS + vW + vE + vT + vB ) + vNW + vNE + vSW + vSE + vTN + vTS + vTW + vTE + vBN + vBS + vBW + vBE;
+      const real_t m3  = localParticleVelocity[0];
+      const real_t m4  = real_t(2) * vW - real_t(2) * vE - vNW + vNE - vSW + vSE - vTW + vTE - vBW + vBE;
+      const real_t m5  = localParticleVelocity[1];
+      const real_t m6  = real_t(-2) * vN + real_t(2) * vS + vNW + vNE - vSW - vSE + vTN - vTS + vBN - vBS;
+      const real_t m7  = localParticleVelocity[2];
+      const real_t m8  = real_t(-2) * vT + real_t(2) * vB + vTN + vTS + vTW + vTE - vBN - vBS - vBW - vBE;
+      const real_t m9  = -vN - vS + real_t(2) * vW + real_t(2) * vE - vT - vB + vNW + vNE + vSW + vSE - real_t(2) * vTN
+                         - real_t(2) * vTS + vTW + vTE - real_t(2) * vBN - real_t(2) * vBS + vBW + vBE;
+      const real_t m10 = vN + vS - real_t(2) * vW - real_t(2) * vE + vT + vB + vNW + vNE + vSW + vSE - real_t(2) * vTN
+                         - real_t(2) * vTS + vTW + vTE - real_t(2) * vBN - real_t(2) * vBS + vBW + vBE;
+      const real_t m11 = vN  + vS  - vT  - vB  + vNW + vNE + vSW + vSE - vTW - vTE - vBW - vBE;
+      const real_t m12 = -vN - vS  + vT  + vB  + vNW + vNE + vSW + vSE - vTW - vTE - vBW - vBE;
+      const real_t m13 = -vNW + vNE + vSW - vSE;
+      const real_t m14 =  vTN - vTS - vBN + vBS;
+      const real_t m15 = -vTW + vTE + vBW - vBE;
+      const real_t m16 = -vNW + vNE - vSW + vSE + vTW - vTE + vBW - vBE;
+      const real_t m17 = -vNW - vNE + vSW + vSE + vTN - vTS + vBN - vBS;
+      const real_t m18 = -vTN - vTS + vTW + vTE + vBN + vBS - vBW - vBE;
+
+      // transform back to PDF space
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::C] )  = _1_3  * m0  - _1_2  * m1  + _1_6  * m2;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::N] )  = _1_18 * m0  - _1_18 * m2  + _1_6  * m5  - _1_6  * m6  - _1_24 * m9  +
+                                                                            _1_24 * m10 + _1_8  * m11 - _1_8  * m12;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::S] )  = _1_18 * m0  - _1_18 * m2  - _1_6  * m5  + _1_6  * m6  - _1_24 * m9  +
+                                                                            _1_24 * m10 + _1_8  * m11 - _1_8  * m12;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::W] )  = _1_18 * m0  - _1_18 * m2  - _1_6  * m3  + _1_6  * m4  + _1_12 * m9  - _1_12 * m10;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::E] )  = _1_18 * m0  - _1_18 * m2  + _1_6  * m3  - _1_6  * m4  + _1_12 * m9  - _1_12 * m10;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::T] )  = _1_18 * m0  - _1_18 * m2  + _1_6  * m7  - _1_6  * m8  - _1_24 * m9  +
+                                                                            _1_24 * m10 - _1_8  * m11 + _1_8  * m12;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::B] )  = _1_18 * m0  - _1_18 * m2  - _1_6  * m7  + _1_6  * m8  - _1_24 * m9  +
+                                                                            _1_24 * m10 - _1_8  * m11 + _1_8  * m12;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::NW] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  - _1_12 * m3  - _1_24 * m4  +
+                                                                            _1_12 * m5  + _1_24 * m6  + _1_48 * m9  + _1_48 * m10 + _1_16 * m11 +
+                                                                            _1_16 * m12 - _1_4  * m13 - _1_8  * m16 - _1_8  * m17;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::NE] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  + _1_12 * m3  + _1_24 * m4  +
+                                                                            _1_12 * m5  + _1_24 * m6  + _1_48 * m9  + _1_48 * m10 + _1_16 * m11 +
+                                                                            _1_16 * m12 + _1_4  * m13 + _1_8  * m16 - _1_8  * m17;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::SW] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  - _1_12 * m3  - _1_24 * m4  -
+                                                                            _1_12 * m5  - _1_24 * m6  + _1_48 * m9  + _1_48 * m10 + _1_16 * m11 +
+                                                                            _1_16 * m12 + _1_4  * m13 - _1_8  * m16 + _1_8  * m17;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::SE] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  + _1_12 * m3  + _1_24 * m4  -
+                                                                            _1_12 * m5  - _1_24 * m6  + _1_48 * m9  + _1_48 * m10 + _1_16 * m11 +
+                                                                            _1_16 * m12 - _1_4  * m13 + _1_8  * m16 + _1_8  * m17;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::TN] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  + _1_12 * m5  + _1_24 * m6  +
+                                                                            _1_12 * m7  + _1_24 * m8  - _1_24 * m9  - _1_24 * m10 + _1_4  * m14 +
+                                                                            _1_8  * m17 - _1_8  * m18;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::TS] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  - _1_12 * m5  - _1_24 * m6  +
+                                                                            _1_12 * m7  + _1_24 * m8  - _1_24 * m9  - _1_24 * m10 - _1_4  * m14 -
+                                                                            _1_8  * m17 - _1_8  * m18;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::TW] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  - _1_12 * m3  - _1_24 * m4  +
+                                                                            _1_12 * m7  + _1_24 * m8  + _1_48 * m9  + _1_48 * m10 - _1_16 * m11 -
+                                                                            _1_16 * m12 - _1_4  * m15 + _1_8  * m16 + _1_8  * m18;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::TE] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  + _1_12 * m3  + _1_24 * m4  +
+                                                                            _1_12 * m7  + _1_24 * m8  + _1_48 * m9  + _1_48 * m10 - _1_16 * m11 -
+                                                                            _1_16 * m12 + _1_4  * m15 - _1_8  * m16 + _1_8  * m18;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::BN] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  + _1_12 * m5  + _1_24 * m6  -
+                                                                            _1_12 * m7  - _1_24 * m8  - _1_24 * m9  - _1_24 * m10 - _1_4  * m14 +
+                                                                            _1_8  * m17 + _1_8  * m18;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::BS] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  - _1_12 * m5  - _1_24 * m6  -
+                                                                            _1_12 * m7  - _1_24 * m8  - _1_24 * m9  - _1_24 * m10 + _1_4  * m14 -
+                                                                            _1_8  * m17 + _1_8  * m18;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::BW] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  - _1_12 * m3  - _1_24 * m4  -
+                                                                            _1_12 * m7  - _1_24 * m8  + _1_48 * m9  + _1_48 * m10 - _1_16 * m11 -
+                                                                            _1_16 * m12 + _1_4  * m15 + _1_8  * m16 - _1_8  * m18;
+      pdfField->get( x, y, z, LatticeModel_T::Stencil::idx[stencil::BE] ) = _1_36 * m0  + _1_24 * m1  + _1_72 * m2  + _1_12 * m3  + _1_24 * m4  -
+                                                                            _1_12 * m7  - _1_24 * m8  + _1_48 * m9  + _1_48 * m10 - _1_16 * m11 -
+                                                                            _1_16 * m12 - _1_4  * m15 - _1_8  * m16 - _1_8  * m18;
+   }
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+   const BlockDataID boundaryHandlingID_;
+   shared_ptr<ExtrapolationDirectionFinder_T> extrapolationDirectionFinder_;
+   const uint_t maximumNumberOfExtrapolationCells_;
+   const bool useDataFromGhostLayers_;
+
+   EquilibriumAndNonEquilibriumReconstructor< BoundaryHandling_T, ExtrapolationDirectionFinder_T > alternativeReconstructor_;
+};
+
+
+/*
+ * similar to Chikatamarla, Ansumali and Karlin - Grad's approximation for missing data in lattice Boltzmann simulations, EPL (2006)
+ * also in Dorschner, Chikatamarla, Boesch, Karlin - Grad's approximation for moving and stationary walls in entropic lattice Boltzmann simulations, Journal of Computational Physics, 2015
+ * omegaShear: relaxation RATE that determines the kinematic viscosity
+ *
+ * To obtain the pressure gradient information, finite differences with central differences (useCentralDifferences) are used if enouhg information available, else upwinding differences are applied.
+ * When useDataFromGhostLayers = true, a full ghost layer sync is required just before the reconstruction step. This is required to avoid inconsistencies when using parallelization as else the behavior close to block boarders is altered.
+ */
+template< typename BoundaryHandling_T >
+class GradsMomentApproximationReconstructor
+{
+public:
+
+   GradsMomentApproximationReconstructor( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID, real_t omegaShear,
+                                          bool recomputeTargetDensity = false, bool useCentralDifferences = true, bool useDataFromGhostLayers = false )
+         : blockStorage_( blockStorage ), boundaryHandlingID_( boundaryHandlingID ), omegaShear_(omegaShear),
+           recomputeTargetDensity_(recomputeTargetDensity), useCentralDifferences_(useCentralDifferences), useDataFromGhostLayers_(useDataFromGhostLayers)
+   {}
+
+
+   template< typename PdfField_T, typename ParticleAccessor_T  >
+   void operator()( IBlock * const block, const cell_idx_t & x, const cell_idx_t & y, const cell_idx_t & z, PdfField_T * const pdfField,
+                    const size_t particleIdx, const ParticleAccessor_T & ac)
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( pdfField );
+      WALBERLA_ASSERT_UNEQUAL(particleIdx, ac.getInvalidIdx());
+
+      using LatticeModel = typename PdfField_T::LatticeModel;
+      using Stencil = typename LatticeModel::Stencil;
+
+      BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+      WALBERLA_ASSERT_NOT_NULLPTR( boundaryHandling );
+
+      CellInterval localDomain = (useDataFromGhostLayers_) ? pdfField->xyzSizeWithGhostLayer() : pdfField->xyzSize();
+
+      std::vector<bool> availableStencilIndices(Stencil::Size, false);
+
+      auto nAverage = uint_t(0);
+      auto averageDensity = real_t(0);
+
+      // density and velocity used in the reconstruction
+      auto targetDensity = real_t(0);
+      Vector3<real_t> targetVelocity;
+
+      real_t cx, cy, cz;
+      blockStorage_->getBlockLocalCellCenter( *block, Cell(x,y,z), cx, cy, cz );
+
+      // 1. evaluate local average density and find available (=fluid) stencil directions
+      for( auto neighborDir = Stencil::beginNoCenter(); neighborDir != Stencil::end(); ++neighborDir )
+      {
+         Cell neighbor( x + neighborDir.cx(), y + neighborDir.cy(), z + neighborDir.cz() );
+
+         // check if neighbor cell is inside domain
+         if( !( localDomain.contains( neighbor ) ) )
+            continue;
+
+         // check if neighbor cell is a fluid cell
+         if( boundaryHandling->isDomain( neighbor ) )
+         {
+            // obtain density and add it up
+            averageDensity += pdfField->getDensity( neighbor );
+            ++nAverage;
+            availableStencilIndices[neighborDir.toIdx()] = true;
+         }
+      }
+      averageDensity = ( nAverage > uint_t( 0 ) ) ? averageDensity / real_c( nAverage ) : real_t(1.0);
+
+      // 2. evaluate target velocity
+      // we simply use the body velocity in the cell center here since the cell center is probably not far from the particle surface so this is a valid approximation
+      // alternatively, some interpolated velocity might be used here as well (see Chikatamarla et al)
+      targetVelocity = mesa_pd::getVelocityAtWFPoint(particleIdx, ac, Vector3<real_t>(cx,cy,cz));
+
+      // 3. compute target density (and adapt to compressible/incompressible)
+
+      // note: for compressible lattice models, the targetDensity is centered around 1
+      // for incompressible, the targetDensity is just the deviation from the average (=1)
+
+      if(recomputeTargetDensity_)
+      {
+         // in this variant the target density gets explicitly computed by considering the surrounding PDFs and using "stream" and "velocity bounce back" considerations like in Chikatamarla et al
+         for( auto q = Stencil::begin(); q != Stencil::end(); ++q )
+         {
+            if( availableStencilIndices[q.toInvIdx()])
+            {
+               // "stream"
+               auto invDir = q.inverseDir();
+               Cell neighbor( x + stencil::cx[invDir], y + stencil::cy[invDir], z + stencil::cz[invDir] );
+               targetDensity += pdfField->get(neighbor, *q);
+            }
+            else if(availableStencilIndices[q.toIdx()])
+            {
+               // "velocity bounce back"
+               Cell neighbor( x + stencil::cx[*q], y + stencil::cy[*q], z + stencil::cz[*q] );
+               targetDensity += pdfField->get(neighbor, q.inverseDir()); // "bounce back part"
+               if(LatticeModel::compressible)
+               {
+                  targetDensity += real_t(6) * averageDensity * LatticeModel::w[ Stencil::idx[*q] ] * ( real_c( stencil::cx[ *q ] ) * targetVelocity[0] +
+                                                                                                        real_c( stencil::cy[ *q ] ) * targetVelocity[1] +
+                                                                                                        real_c( stencil::cz[ *q ] ) * targetVelocity[2] ); //TODO use wall velocity here?
+               } else {
+                  targetDensity += real_t(6) * LatticeModel::w[ Stencil::idx[*q] ] * ( real_c( stencil::cx[ *q ] ) * targetVelocity[0] +
+                                                                                       real_c( stencil::cy[ *q ] ) * targetVelocity[1] +
+                                                                                       real_c( stencil::cz[ *q ] ) * targetVelocity[2] ); //TODO use wall velocity here?
+               }
+
+            } else{
+               // no neighboring information available (e.g. for center), so we use feq based on target velocity and average density
+               targetDensity += lbm::EquilibriumDistribution< LatticeModel >::get( *q, targetVelocity, averageDensity );
+            }
+         }
+      } else {
+         // alternatively, we can simply use the average density from the surrounding fluid cells
+         // only minor differences have been seen in comparison to recomputing
+         targetDensity = (LatticeModel::compressible) ? averageDensity : averageDensity - real_t(1);
+      }
+
+      // 4. compute pressure tensor from eq and neq parts
+      // 4.1 evaluate Peq -> will be done directly lateron
+
+      // 4.2 evaluate Pneq -> needs velocity gradient tensor
+      // grad(u) =
+      // | du1/dx1 du2/dx1 du3/dx1 |   | 0 1 2 |   | 0,0  0,1  0,2 |
+      // | du1/dx2 du2/dx2 du3/dx2 | = | 3 4 5 | = | 1,0  1,1  1,2 |
+      // | du1/dx3 du2/dx3 du3/dx3 |   | 6 7 8 |   | 2,0  2,1  2,2 |
+      // evaluation of velocity gradients done via central finite differences if two neighbors available
+      // else first-order FD upwinding is carried out if upwinding neighbor available
+      // else first-order finite differences if available if other neighbor is available
+      // else we assume gradient of 0 (if no fluid neighbors available in respective direction)
+
+      Matrix3<real_t> velocityGradient(real_t(0));
+      // check if both neighbors are available for central differences
+      if(useCentralDifferences_ && availableStencilIndices[Stencil::idx[stencil::E]] && availableStencilIndices[Stencil::idx[stencil::W]])
+      {
+         auto neighborVelocity1 = pdfField->getVelocity(Cell(x,y,z)+stencil::E);
+         auto neighborVelocity2 = pdfField->getVelocity(Cell(x,y,z)+stencil::W);
+         velocityGradient[0] = real_t(0.5) * ( neighborVelocity1[0] - neighborVelocity2[0]); // assuming dx = 1
+         velocityGradient[1] = real_t(0.5) * ( neighborVelocity1[1] - neighborVelocity2[1]); // assuming dx = 1
+         velocityGradient[2] = real_t(0.5) * ( neighborVelocity1[2] - neighborVelocity2[2]); // assuming dx = 1
+
+      } else {
+         //upwinding
+         stencil::Direction upwindingXDirection = (targetVelocity[0] > real_t(0) ) ? stencil::W : stencil::E;
+         stencil::Direction sourceXDirection = (availableStencilIndices[Stencil::idx[upwindingXDirection]]) ? upwindingXDirection
+                                                                                                            : ((availableStencilIndices[Stencil::idx[stencil::inverseDir[upwindingXDirection]]]) ? stencil::inverseDir[upwindingXDirection]
+                                                                                                                                                                                                 : stencil::C );
+         if(sourceXDirection == stencil::E)
+         {
+            auto neighborVelocity = pdfField->getVelocity(Cell(x,y,z)+sourceXDirection);
+            velocityGradient[0] = neighborVelocity[0] - targetVelocity[0]; // assuming dx = 1
+            velocityGradient[1] = neighborVelocity[1] - targetVelocity[1]; // assuming dx = 1
+            velocityGradient[2] = neighborVelocity[2] - targetVelocity[2]; // assuming dx = 1
+         }
+         if(sourceXDirection == stencil::W)
+         {
+            auto neighborVelocity = pdfField->getVelocity(Cell(x,y,z)+sourceXDirection);
+            velocityGradient[0] = targetVelocity[0] - neighborVelocity[0]; // assuming dx = 1
+            velocityGradient[1] = targetVelocity[1] - neighborVelocity[1]; // assuming dx = 1
+            velocityGradient[2] = targetVelocity[2] - neighborVelocity[2]; // assuming dx = 1
+         }
+         // else: 0
+      }
+
+      // check if both neighbors are available for central differences
+      if(useCentralDifferences_ && availableStencilIndices[Stencil::idx[stencil::N]] && availableStencilIndices[Stencil::idx[stencil::S]]) {
+         auto neighborVelocity1 = pdfField->getVelocity(Cell(x, y, z) + stencil::N);
+         auto neighborVelocity2 = pdfField->getVelocity(Cell(x, y, z) + stencil::S);
+         velocityGradient[3] = real_t(0.5) * (neighborVelocity1[0] - neighborVelocity2[0]); // assuming dx = 1
+         velocityGradient[4] = real_t(0.5) * (neighborVelocity1[1] - neighborVelocity2[1]); // assuming dx = 1
+         velocityGradient[5] = real_t(0.5) * (neighborVelocity1[2] - neighborVelocity2[2]); // assuming dx = 1
+      } else {
+         //upwinding
+         stencil::Direction upwindingYDirection = (targetVelocity[1] > real_t(0) ) ? stencil::S : stencil::N;
+         stencil::Direction sourceYDirection = (availableStencilIndices[Stencil::idx[upwindingYDirection]]) ? upwindingYDirection
+                                                                                                            : ((availableStencilIndices[Stencil::idx[stencil::inverseDir[upwindingYDirection]]]) ? stencil::inverseDir[upwindingYDirection]
+                                                                                                                                                                                                 : stencil::C );
+         if(sourceYDirection == stencil::N)
+         {
+            auto neighborVelocity = pdfField->getVelocity(Cell(x,y,z)+sourceYDirection);
+            velocityGradient[3] = neighborVelocity[0] - targetVelocity[0]; // assuming dx = 1
+            velocityGradient[4] = neighborVelocity[1] - targetVelocity[1]; // assuming dx = 1
+            velocityGradient[5] = neighborVelocity[2] - targetVelocity[2]; // assuming dx = 1
+         }
+         if(sourceYDirection == stencil::S)
+         {
+            auto neighborVelocity = pdfField->getVelocity(Cell(x,y,z)+sourceYDirection);
+            velocityGradient[3] = targetVelocity[0] - neighborVelocity[0]; // assuming dx = 1
+            velocityGradient[4] = targetVelocity[1] - neighborVelocity[1]; // assuming dx = 1
+            velocityGradient[5] = targetVelocity[2] - neighborVelocity[2]; // assuming dx = 1
+         }
+         // else: 0
+      }
+
+      if(Stencil::D == 3)
+      {
+         // only in 3D
+         // check if both neighbors are available for central differences
+         if(useCentralDifferences_ && availableStencilIndices[Stencil::idx[stencil::T]] && availableStencilIndices[Stencil::idx[stencil::B]]) {
+            auto neighborVelocity1 = pdfField->getVelocity(Cell(x, y, z) + stencil::T);
+            auto neighborVelocity2 = pdfField->getVelocity(Cell(x, y, z) + stencil::B);
+            velocityGradient[6] = real_t(0.5) * (neighborVelocity1[0] - neighborVelocity2[0]); // assuming dx = 1
+            velocityGradient[7] = real_t(0.5) * (neighborVelocity1[1] - neighborVelocity2[1]); // assuming dx = 1
+            velocityGradient[8] = real_t(0.5) * (neighborVelocity1[2] - neighborVelocity2[2]); // assuming dx = 1
+         } else {
+            //upwinding
+            stencil::Direction upwindingZDirection = (targetVelocity[2] > real_t(0)) ? stencil::B : stencil::T;
+            stencil::Direction sourceZDirection = (availableStencilIndices[Stencil::idx[upwindingZDirection]]) ? upwindingZDirection
+                                                                                                               : ((availableStencilIndices[Stencil::idx[stencil::inverseDir[upwindingZDirection]]]) ? stencil::inverseDir[upwindingZDirection]
+                                                                                                                                                                                                    : stencil::C);
+            if (sourceZDirection == stencil::T) {
+               auto neighborVelocity = pdfField->getVelocity(Cell(x, y, z) + sourceZDirection);
+               velocityGradient[6] = neighborVelocity[0] - targetVelocity[0]; // assuming dx = 1
+               velocityGradient[7] = neighborVelocity[1] - targetVelocity[1]; // assuming dx = 1
+               velocityGradient[8] = neighborVelocity[2] - targetVelocity[2]; // assuming dx = 1
+            }
+            if (sourceZDirection == stencil::B) {
+               auto neighborVelocity = pdfField->getVelocity(Cell(x, y, z) + sourceZDirection);
+               velocityGradient[6] = targetVelocity[0] - neighborVelocity[0]; // assuming dx = 1
+               velocityGradient[7] = targetVelocity[1] - neighborVelocity[1]; // assuming dx = 1
+               velocityGradient[8] = targetVelocity[2] - neighborVelocity[2]; // assuming dx = 1
+            }
+            // else: 0
+         }
+      }
+
+
+      Matrix3<real_t> pressureTensorNeq(real_t(0)); // without prefactor of rho, added later
+      const real_t preFac = - real_t(1)  / (real_t(3) * omegaShear_); // 2 * beta (in Chikatamarla et al) = omega related to kinematic viscosity
+      for(auto j = uint_t(0); j <= uint_t(2); ++j)
+      {
+         for(auto i = uint_t(0); i <= uint_t(2); ++i)
+         {
+            pressureTensorNeq(i,j) += preFac * (velocityGradient(i,j)+velocityGradient(j,i));
+         }
+      }
+
+      // 5. set PDFs to approximated equilibrium by Grad
+      // this is just the regular feq with additional Pneq part
+      if(LatticeModel::compressible)
+      {
+         for( auto q = Stencil::begin(); q != Stencil::end(); ++q )
+         {
+            const real_t velci = lbm::internal::multiplyVelocityDirection( *q, targetVelocity );
+
+            auto contributionFromPneq = real_t(0);
+            for(auto j = uint_t(0); j <= uint_t(2); ++j) {
+               for (auto i = uint_t(0); i <= uint_t(2); ++i) {
+                  //Pneq_a,b * c_q,a * c_q,b
+                  contributionFromPneq += pressureTensorNeq(i,j) * real_c(stencil::c[i][*q]) * real_c(stencil::c[j][*q]);
+               }
+            }
+            //- Pneq_a,b * cs**2 * delta_a,b
+            contributionFromPneq -= (pressureTensorNeq(0,0) + pressureTensorNeq(1,1) + pressureTensorNeq(2,2)) / real_t(3);
+
+            // all terms are multiplied with the density
+            real_t fGrad = LatticeModel::w[ q.toIdx() ] * targetDensity * (real_t(1) + real_t(3) * velci - real_t(1.5) * targetVelocity.sqrLength() + real_t(4.5) * velci * velci + real_t(4.5) * contributionFromPneq); // standard comp. feq + comp. Pneq
+            pdfField->get(x,y,z,*q) = fGrad;
+         }
+      }else{
+         // assume rho = 1 almost everywhere, targetDensity is just the deviation from 1
+         for( auto q = Stencil::begin(); q != Stencil::end(); ++q )
+         {
+            const real_t velci = lbm::internal::multiplyVelocityDirection( *q, targetVelocity );
+
+            auto contributionFromPneq = real_t(0);
+            for(auto j = uint_t(0); j <= uint_t(2); ++j) {
+               for (auto i = uint_t(0); i <= uint_t(2); ++i) {
+                  //Pneq_a,b * c_q,a * c_q,b
+                  contributionFromPneq += pressureTensorNeq(i,j) * real_c(stencil::c[i][*q]) * real_c(stencil::c[j][*q]);
+               }
+            }
+            //- Pneq_a,b * cs**2 * delta_a,b
+            contributionFromPneq -= (pressureTensorNeq(0,0) + pressureTensorNeq(1,1) + pressureTensorNeq(2,2)) / real_t(3);
+
+            // density deviation just appears as the leading order term
+            real_t fGrad = LatticeModel::w[ q.toIdx() ] * ( targetDensity + real_t(3) * velci - real_t(1.5) * targetVelocity.sqrLength() + real_t(4.5) * velci * velci + real_t(4.5) * contributionFromPneq); // standard incomp. feq + incomp Pneq
+            pdfField->get(x,y,z,*q) = fGrad;
+         }
+      }
+   }
+
+   void setOmegaShear(real_t omegaShear)
+   {
+      omegaShear_ = omegaShear;
+   }
+
+   real_t getOmegaShear()
+   {
+      return omegaShear_;
+   }
+
+private:
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+   const BlockDataID boundaryHandlingID_;
+   real_t omegaShear_;
+   const bool recomputeTargetDensity_;
+   const bool useCentralDifferences_;
+   const bool useDataFromGhostLayers_;
+};
+
+
+
+// make functionality
+
+template< typename BoundaryHandling_T>
+shared_ptr<EquilibriumReconstructor<BoundaryHandling_T> >
+makeEquilibriumReconstructor(const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID, bool useDataFromGhostLayers = false)
+{
+   using Rec_T = EquilibriumReconstructor<BoundaryHandling_T>;
+   return make_shared<Rec_T>(blockStorage, boundaryHandlingID, useDataFromGhostLayers);
+}
+
+template< typename BoundaryHandling_T, typename ExtrapolationDirectionFinder_T >
+shared_ptr<EquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T,ExtrapolationDirectionFinder_T> >
+makeEquilibriumAndNonEquilibriumReconstructor(const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID, const shared_ptr<ExtrapolationDirectionFinder_T> & extrapolationDirectionFinder,
+                                              uint_t maximumNumberOfExtrapolationCells = uint_t(3), bool useDataFromGhostLayers = false)
+{
+   using Rec_T = EquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T,ExtrapolationDirectionFinder_T>;
+   return make_shared<Rec_T>(blockStorage, boundaryHandlingID, extrapolationDirectionFinder, maximumNumberOfExtrapolationCells, useDataFromGhostLayers);
+}
+
+template< typename BoundaryHandling_T, typename ExtrapolationDirectionFinder_T, bool EnforceNoSlipConstraintAfterExtrapolation = false >
+shared_ptr<ExtrapolationReconstructor<BoundaryHandling_T,ExtrapolationDirectionFinder_T, EnforceNoSlipConstraintAfterExtrapolation> >
+makeExtrapolationReconstructor(const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID, const shared_ptr<ExtrapolationDirectionFinder_T> & extrapolationDirectionFinder,
+                               uint_t maximumNumberOfExtrapolationCells = uint_t(3), bool useDataFromGhostLayers = false)
+{
+   using Rec_T = ExtrapolationReconstructor<BoundaryHandling_T,ExtrapolationDirectionFinder_T, EnforceNoSlipConstraintAfterExtrapolation>;
+   return make_shared<Rec_T>(blockStorage, boundaryHandlingID, extrapolationDirectionFinder, maximumNumberOfExtrapolationCells, useDataFromGhostLayers);
+}
+
+template< typename BoundaryHandling_T>
+shared_ptr<GradsMomentApproximationReconstructor<BoundaryHandling_T> >
+makeGradsMomentApproximationReconstructor(const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & boundaryHandlingID, real_t omegaShear,
+                                          bool recomputeTargetDensity = false, bool useCentralDifferences = true, bool useDataFromGhostLayers = false)
+{
+   using Rec_T = GradsMomentApproximationReconstructor<BoundaryHandling_T>;
+   return make_shared<Rec_T>(blockStorage, boundaryHandlingID, omegaShear, recomputeTargetDensity, useCentralDifferences, useDataFromGhostLayers);
+}
+
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/AddAccelerationOnParticlesKernel.h b/src/lbm_mesapd_coupling/utility/AddAccelerationOnParticlesKernel.h
new file mode 100644
index 0000000000000000000000000000000000000000..b27ba1842a627415cd2d154b9bf1ef222ed37199
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/AddAccelerationOnParticlesKernel.h
@@ -0,0 +1,77 @@
+//======================================================================================================================
+//
+//  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 AddAccelerationOnParticlesKernel.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/math/Vector3.h"
+
+#include "mesa_pd/common/ParticleFunctions.h"
+#include "mesa_pd/data/IAccessor.h"
+
+#include <functional>
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*
+ * Kernel that adds a constant acceleration via F = m * a on particles
+ *
+ * Note that setting forces on ghost particles results in duplicated forces on these particles.
+ *
+ * If the force is constant for all particles, it is better to use AddForceOnParticlesKernel.
+ *
+ */
+class AddAccelerationOnParticlesKernel
+{
+
+public:
+
+   explicit AddAccelerationOnParticlesKernel( const Vector3<real_t> & acceleration )
+   : acceleration_( acceleration )
+     { }
+
+
+   template< typename ParticleAccessor_T >
+   void operator()(const size_t idx, ParticleAccessor_T& ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      auto force = acceleration_ / ac.getInvMass(idx);
+      
+      mesa_pd::addForceAtomic(idx, ac, force);
+   }
+
+   void setAcceleration( const Vector3<real_t> & newAcceleration )
+   {
+      acceleration_ = newAcceleration;
+   }
+
+   Vector3<real_t> getAcceleration() const
+   {
+      return acceleration_;
+   }
+
+private:
+   Vector3<real_t> acceleration_;
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/AddForceOnParticlesKernel.h b/src/lbm_mesapd_coupling/utility/AddForceOnParticlesKernel.h
new file mode 100644
index 0000000000000000000000000000000000000000..703550f2e75a598e162765faafaa59db40498096
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/AddForceOnParticlesKernel.h
@@ -0,0 +1,72 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file AddForceOnParticlesKernel.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/math/Vector3.h"
+
+#include "mesa_pd/common/ParticleFunctions.h"
+#include "mesa_pd/data/IAccessor.h"
+
+#include <functional>
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*
+ * Kernel that adds a constant force on particles
+ *
+ * Note that setting forces on ghost particles results in duplicated forces on these particles.
+ *
+ */
+class AddForceOnParticlesKernel
+{
+
+public:
+
+   explicit AddForceOnParticlesKernel( const Vector3<real_t> & force )
+   : force_( force )
+     { }
+
+   template< typename ParticleAccessor_T >
+   void operator()(const size_t idx, ParticleAccessor_T& ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      mesa_pd::addForceAtomic(idx, ac, force_);
+   }
+
+   void setForce( const Vector3<real_t> & newForce )
+   {
+      force_ = newForce;
+   }
+
+   Vector3<real_t> getForce() const
+   {
+      return force_;
+   }
+
+private:
+   Vector3<real_t> force_;
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h b/src/lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h
new file mode 100644
index 0000000000000000000000000000000000000000..4ca474cc0a24d59e9798bbfe4a387ac390a5da70
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h
@@ -0,0 +1,53 @@
+//======================================================================================================================
+//
+//  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 AddHydrodynamicInteractionKernel.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "mesa_pd/common/ParticleFunctions.h"
+#include "mesa_pd/data/IAccessor.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*
+ * Kernel that adds the current hydrodynamic forces and torques onto the particles as forces and torques
+ *
+ * Should usually be carried out on local and ghost particles.
+ */
+class AddHydrodynamicInteractionKernel
+{
+
+public:
+
+   AddHydrodynamicInteractionKernel() = default;
+
+   template< typename ParticleAccessor_T >
+   void operator()(const size_t idx, ParticleAccessor_T& ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      mesa_pd::addForceAtomic(idx, ac, ac.getHydrodynamicForce(idx));
+      mesa_pd::addTorqueAtomic(idx, ac, ac.getHydrodynamicTorque(idx));
+   }
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h b/src/lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h
new file mode 100644
index 0000000000000000000000000000000000000000..31c828e0d1c574d6b372048ff007ccaeefe931fc
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h
@@ -0,0 +1,65 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file AverageHydrodynamicForceTorqueKernel.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "mesa_pd/data/IAccessor.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*
+ * Kernel that averages the force/torque over two time steps in a rolling average fashion.
+ * This is said to damp oscillations of the interaction force/torque.
+ * See Ladd - " Numerical simulations of particulate suspensions via a discretized Boltzmann equation. Part 1. Theoretical foundation", 1994, p.
+ *
+ * Should usually be carried out on local and ghost particles.
+ */
+class AverageHydrodynamicForceTorqueKernel
+{
+
+public:
+
+   AverageHydrodynamicForceTorqueKernel( ) = default;
+
+   template< typename ParticleAccessor_T >
+   void operator()(const size_t idx, ParticleAccessor_T& ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      auto hydForce = ac.getHydrodynamicForce(idx);
+      auto hydTorque = ac.getHydrodynamicTorque(idx);
+
+      auto averageForce = real_t(0.5) * ( hydForce + ac.getOldHydrodynamicForce(idx) );
+      auto averageTorque = real_t(0.5) * ( hydTorque + ac.getOldHydrodynamicTorque(idx) );
+
+      // swap old values with new ones
+      ac.setOldHydrodynamicForce( idx, hydForce );
+      ac.setOldHydrodynamicTorque( idx, hydTorque );
+
+      // set values to averaged ones
+      ac.setHydrodynamicForce ( idx, averageForce );
+      ac.setHydrodynamicTorque ( idx, averageTorque );
+   }
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/InspectionProbe.h b/src/lbm_mesapd_coupling/utility/InspectionProbe.h
new file mode 100644
index 0000000000000000000000000000000000000000..4513cdfaf616982c2d11aa1d5253ce2d49459eab
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/InspectionProbe.h
@@ -0,0 +1,152 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file InspectionProbe.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/math/Vector3.h"
+#include "domain_decomposition/StructuredBlockStorage.h"
+#include "mesa_pd/data/Flags.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+
+/*
+ * Functionality that allows to monitor the state of a specific cell in the fluid-particle simulation.
+ * Options to output the state (fluid with density and velocity, or particle) on screen or to file are available.
+ * Also, the state of surrounding cells can be printed to screen.
+ * Can generally be used to track down the source of instabilities in a specific cell which often has its origin locally in the direct neighborhood of the cell.
+ *
+ */
+template< typename PdfField_T, typename BoundaryHandling_T, typename ParticleAccessor_T >
+class InspectionProbe
+{
+
+public:
+   InspectionProbe(Vector3<real_t> probeLocation,
+                   const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & pdfFieldID, const BlockDataID & boundaryHandlingID,
+                   const BlockDataID & particleFieldID, const shared_ptr< ParticleAccessor_T > & ac,
+                   bool printToScreen, bool printSurroundingState, std::string outputFileName)
+   : probeLocation_(probeLocation), blocks_(blocks), pdfFieldID_(pdfFieldID), boundaryHandlingID_(boundaryHandlingID), particleFieldID_(particleFieldID),
+     ac_(ac), printToScreen_(printToScreen), printSurroundingState_(printSurroundingState), outputFileName_(outputFileName)
+   {}
+
+   void operator()(real_t & rho, Vector3<real_t> & velocity)
+   {
+
+      Cell probeCellGlobal = blocks_->getCell(probeLocation_);
+      auto block = blocks_->getBlock(probeLocation_);
+      if(block != nullptr)
+      {
+         auto pdfField = block->template getData<PdfField_T>(pdfFieldID_);
+         auto boundaryHandling = block->template getData<BoundaryHandling_T>(boundaryHandlingID_);
+
+         Cell probeCell;
+         blocks_->transformGlobalToBlockLocalCell( probeCell, *block, probeCellGlobal );
+
+         if(printSurroundingState_) printStatusOfCellSurrounding(probeCell, *block);
+
+         rho = pdfField->getDensityAndVelocity(velocity,probeCell);
+
+         bool isFluid = boundaryHandling->isDomain(probeCell);
+
+         printToScreen(isFluid, rho, velocity);
+         writeToFile(isFluid, rho, velocity);
+
+      }
+   }
+
+private:
+
+   void printStatusOfCellSurrounding(Cell centerCell, const IBlock& block)
+   {
+      auto pdfField = block.getData<PdfField_T>(pdfFieldID_);
+      auto boundaryHandling = block.getData<BoundaryHandling_T>(boundaryHandlingID_);
+      auto particleField = block.getData<lbm_mesapd_coupling::ParticleField_T>(particleFieldID_);
+
+      std::stringstream outputString;
+
+      for(cell_idx_t z = cell_idx_t(-1); z <= cell_idx_t(1); ++z)
+      {
+         for(cell_idx_t y = cell_idx_t(1); y >= cell_idx_t(-1); --y)
+         {
+            outputString << "*------------------------------------------------------------------------------------------------------------------------------------------------------------------------*\n";
+            for(cell_idx_t x = cell_idx_t(-1); x <= cell_idx_t(1); ++x)
+            {
+               auto cell = centerCell + Cell(x,y,z);
+               auto cellCenter = blocks_->getBlockLocalCellCenter(block, cell);
+               real_t rho;
+               Vector3<real_t> velocity;
+               bool isFluid=false;
+               bool isFixed=false;
+               bool isGlobal=false;
+               if(boundaryHandling->isDomain(cell))
+               {
+                  isFluid = true;
+                  rho = pdfField->getDensityAndVelocity(velocity,cell);
+               } else{
+                  isFluid = false;
+                  rho = real_t(0);
+                  auto particleIdx = ac_->uidToIdx(particleField->get( cell ));
+                  velocity = mesa_pd::getVelocityAtWFPoint(particleIdx, *ac_, cellCenter );
+                  if(isSet(ac_->getFlags(particleIdx), mesa_pd::data::particle_flags::FIXED)) isFixed = true;
+                  if(isSet(ac_->getFlags(particleIdx), mesa_pd::data::particle_flags::GLOBAL)) isGlobal = true;
+               }
+               outputString << std::setprecision(5) << "| " << cellCenter << " (" << (isFluid?"F":(std::string("P")+(isFixed?"+F":"")+(isGlobal?"+G":""))) << ") " << rho << " " << velocity << " ";
+            }
+            outputString << "|\n";
+         }
+         outputString << "*------------------------------------------------------------------------------------------------------------------------------------------------------------------------*\n\n";
+      }
+      WALBERLA_LOG_INFO(outputString.str());
+   }
+
+   void printToScreen(bool isFluid, real_t rho, Vector3<real_t> velocity)
+   {
+      if(printToScreen_) WALBERLA_LOG_INFO("Values in probe at position " << probeLocation_ << ": " << (isFluid?"F":"P") << ", rho = " << rho << ", velocity = " << velocity);
+   }
+
+   void writeToFile(bool isFluid, real_t rho, Vector3<real_t> velocity)
+   {
+      if(!outputFileName_.empty())
+      {
+         std::ofstream file;
+         file.open( outputFileName_.c_str(), std::ofstream::app);
+
+         file << isFluid << " " << rho << " " << velocity[0] << " " << velocity[1] << " " << velocity[2] << std::endl;
+         file.close();
+      }
+   }
+
+   Vector3<real_t> probeLocation_;
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const ConstBlockDataID pdfFieldID_;
+   const ConstBlockDataID boundaryHandlingID_;
+   const ConstBlockDataID particleFieldID_;
+   shared_ptr< ParticleAccessor_T > ac_;
+   bool printToScreen_;
+   bool printSurroundingState_;
+   std::string outputFileName_;
+
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h b/src/lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h
new file mode 100644
index 0000000000000000000000000000000000000000..39ac7597b5106fdbb8d7922335c13c3d7c47ef8b
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h
@@ -0,0 +1,359 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LubricationCorrectionKernel.h
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/math/Vector3.h"
+
+#include "mesa_pd/common/ParticleFunctions.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/IAccessor.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+
+/*! \brief Computes lubrication corrections between two spheres 1 and 2 acting on sphere 1.
+ *
+ * The formulas for the lubrication corrections for two spheres with different radii are taken from
+ * Simeonov, Calantoni - Modeling mechanical contact and lubrication in Direct Numerical Simulations of colliding particles, IJMF, 2012
+ *
+ * interactionNormal12 is sphere2Center - sphere1Center, normalized
+ *
+ * radius1, radius2: radius of sphere 1 and 2
+ * u1, u2: linear/translational velocity of sphere 1 and 2
+ * omega1, omega2: angular velocity of sphere 1 and 2
+ *
+ * Returns force and torque via lubricationForce1 and lubricationTorque1
+ *
+ */
+inline
+void computeLubricationCorrectionSphereSphere( const Vector3<real_t> & interactionNormal12, const real_t gapSize, const real_t dynamicFluidViscosity,
+                                               const real_t radius1, const real_t radius2,
+                                               const Vector3<real_t> & u1, const Vector3<real_t> & u2,
+                                               const Vector3<real_t> & omega1, const Vector3<real_t> & omega2,
+                                               const real_t cutOffDistanceNormal, const real_t cutOffDistanceTangentialTranslational,
+                                               const real_t cutOffDistanceTangentialRotational,
+                                               Vector3<real_t> & lubricationForce1, Vector3<real_t> & lubricationTorque1 )
+{
+   WALBERLA_ASSERT_FLOAT_EQUAL(interactionNormal12.length(), real_t(1), "InteractionNormal has to be normalized!");
+
+   real_t kappa = radius2 / radius1;
+   real_t epsilon = gapSize / radius1;
+
+   Vector3<real_t> e12 = -interactionNormal12;
+   Vector3<real_t> u12 =  u2 - u1;
+   Vector3<real_t> omega12 = omega1 + omega2;
+
+   lubricationForce1 = Vector3<real_t>(real_t(0));
+   lubricationTorque1 = Vector3<real_t>(real_t(0));
+
+   if( gapSize < cutOffDistanceNormal )
+   {
+      // add lubrication force due to normal translation
+      real_t epsres = cutOffDistanceNormal / radius1;
+      Vector3<real_t> Fn1 = real_t(6) * walberla::math::pi * radius1 * dynamicFluidViscosity *
+                            ( kappa * kappa / ( ( real_t(1) + kappa ) * ( real_t(1) + kappa ) ) ) * ( real_t(1) / epsilon - real_t(1) / epsres) *
+                            math::dot(u12, e12) * e12;
+
+      lubricationForce1 += Fn1;
+   }
+
+   if( gapSize < cutOffDistanceTangentialTranslational )
+   {
+      // add lubrication force and torque due to tangential translation
+      real_t epsres = cutOffDistanceTangentialTranslational / radius1;
+      real_t logEpsDivEpsres = std::log(epsilon/epsres);
+      Vector3<real_t> Ftt1 = real_t(6) * walberla::math::pi * radius1 * dynamicFluidViscosity *
+                             ( - real_t(4) * kappa * (real_t(2) + kappa + real_t(2) * kappa * kappa ) / ( real_t(15) * (real_t(1) + kappa) * (real_t(1) + kappa) * (real_t(1) + kappa) ) ) * logEpsDivEpsres *
+                             (u12 - math::dot(u12,e12) * e12);
+
+      lubricationForce1 += Ftt1;
+
+      Vector3<real_t> Ttt1 = real_t(8) * walberla::math::pi * radius1 * radius1 * dynamicFluidViscosity *
+                             ( - kappa * (real_t(4) + kappa) / (real_t(10) * (real_t(1)+kappa) * (real_t(1)+kappa) ) ) * logEpsDivEpsres *
+                             math::cross(e12, u12);
+
+      lubricationTorque1 += Ttt1;
+   }
+
+   if( gapSize < cutOffDistanceTangentialRotational )
+   {
+      // add lubrication force and torque due to tangential rotation
+      real_t epsres = cutOffDistanceTangentialRotational / radius1;
+      real_t logEpsDivEpsres = std::log(epsilon/epsres);
+      Vector3<real_t> Ftr1 = real_t(6) * walberla::math::pi * radius1 * radius1 * dynamicFluidViscosity *
+                             (real_t(2) * kappa * kappa / (real_t(15) * (real_t(1) + kappa) * (real_t(1) + kappa) ) ) * logEpsDivEpsres *
+                             math::cross(omega12 + real_t(4) / kappa * omega1 + real_t(4) * kappa * omega2, e12);
+
+      lubricationForce1 += Ftr1;
+
+      Vector3<real_t> tempOmega = omega1 + kappa * omega2 / real_t(4);
+
+      Vector3<real_t> Ttr1 = real_t(8) * walberla::math::pi * radius1 * radius1 * radius1 * dynamicFluidViscosity *
+                             (real_t(2) * kappa / ( real_t(5) * (real_t(1) + kappa))) * logEpsDivEpsres *
+                             (tempOmega - math::dot(tempOmega, e12) * e12);
+
+      lubricationTorque1 += Ttr1;
+
+   }
+
+   // note: lubrication due to normal rotation is dropped here because of too low influence, see also Simeonov
+
+}
+
+
+/*! \brief Computes lubrication corrections between a sphere and a half space acting on the sphere.
+ *
+ * The formulas for the lubrication corrections are taken from
+ * Simeonov, Calantoni - Modeling mechanical contact and lubrication in Direct Numerical Simulations of colliding particles, IJMF, 2012
+ * with kappa -> infinity
+ * *
+ * radius1: radius of sphere
+ * u1, u2: linear/translational velocity of sphere and half space
+ * omega1: angular velocity of sphere 1, half space is assumed to have zero angular velocity
+ *
+ * Returns force and torque via lubricationForce1 and lubricationTorque1
+ */
+inline
+void computeLubricationCorrectionSphereHalfSpace( const Vector3<real_t> & interactionNormal12, const real_t gapSize, const real_t dynamicFluidViscosity,
+                                                  const real_t radius1,
+                                                  const Vector3<real_t> & u1, const Vector3<real_t> & u2,
+                                                  const Vector3<real_t> & omega1,
+                                                  const real_t cutOffDistanceNormal, const real_t cutOffDistanceTangentialTranslational,
+                                                  const real_t cutOffDistanceTangentialRotational,
+                                                  Vector3<real_t> & lubricationForce1, Vector3<real_t> & lubricationTorque1 )
+{
+   WALBERLA_ASSERT_FLOAT_EQUAL(interactionNormal12.length(), real_t(1), "InteractionNormal has to be normalized!");
+
+   real_t epsilon = gapSize / radius1;
+
+   Vector3<real_t> e12 = -interactionNormal12;
+   Vector3<real_t> u12 =  u2 - u1;
+   Vector3<real_t> omega12 = omega1;
+
+   lubricationForce1 = Vector3<real_t>(real_t(0));
+   lubricationTorque1 = Vector3<real_t>(real_t(0));
+
+   if( gapSize < cutOffDistanceNormal )
+   {
+      // add lubrication force due to normal translation
+      real_t epsres = cutOffDistanceNormal / radius1;
+      Vector3<real_t> Fn1 = real_t(6) * walberla::math::pi * radius1 * dynamicFluidViscosity *
+                            ( real_t(1) / epsilon - real_t(1) / epsres) *
+                            math::dot(u12, e12) * e12;
+
+      lubricationForce1 += Fn1;
+
+   }
+
+   if( gapSize < cutOffDistanceTangentialTranslational )
+   {
+      // add lubrication force and torque due to tangential translation
+      real_t epsres = cutOffDistanceTangentialTranslational / radius1;
+      real_t logEpsDivEpsres = std::log(epsilon/epsres);
+      Vector3<real_t> Ftt1 = real_t(6) * walberla::math::pi * radius1 * dynamicFluidViscosity *
+                             ( - real_t(8) / real_t(15) ) * logEpsDivEpsres *
+                             (u12 - math::dot(u12,e12) * e12);
+
+      lubricationForce1 += Ftt1;
+
+      Vector3<real_t> Ttt1 = real_t(8) * walberla::math::pi * radius1 * radius1 * dynamicFluidViscosity *
+                             ( - real_t(1) / real_t(10) ) * logEpsDivEpsres *
+                             math::cross(e12, u12);
+
+      lubricationTorque1 += Ttt1;
+   }
+
+   if( gapSize < cutOffDistanceTangentialRotational )
+   {
+      // add lubrication force and torque due to tangential rotation
+      real_t epsres = cutOffDistanceTangentialRotational / radius1;
+      real_t logEpsDivEpsres = std::log(epsilon/epsres);
+      Vector3<real_t> Ftr1 = real_t(6) * walberla::math::pi * radius1 * radius1 * dynamicFluidViscosity *
+                             ( real_t(2) / real_t(15) ) * logEpsDivEpsres *
+                             math::cross(omega12, e12);
+
+      lubricationForce1 += Ftr1;
+
+      Vector3<real_t> Ttr1 = real_t(8) * walberla::math::pi * radius1 * radius1 * radius1 * dynamicFluidViscosity *
+                             ( real_t(2) / real_t(5) ) * logEpsDivEpsres *
+                             (omega1 - math::dot(omega1, e12) * e12);
+
+      lubricationTorque1 += Ttr1;
+
+   }
+
+   // note: lubrication due to normal rotation is dropped here because of too low influence, see also Simeonov
+
+}
+
+
+/**
+ * Applies a correction for the unresolved lubrication forces and torques on the the interacting particles
+ *
+ * For gap sizes (in cells) below minimalGapSizeFunction(radius), no longer the actual gap size is used in the formulas.
+ * This value/function has to be found by calibration with collision experiments.
+ * For negative gap sizes, i.e. there is an overlap of the surfaces, no lubrication corrections are applied at any time.
+ *
+ * Three different cut off distance have to be specified: normal, tangential translational and tangential rotational.
+ * These distances have to be determined by extra simulations to evaluate up to which resolution the forces can still be reliably predicted by the simulation itself.
+ * Note that these cutoff distances are in cell size "units" and not non-dimensionalized as they are not physically motivated but instead numerical parameters.
+ *
+ * The formulas for the lubrication corrections for two spheres with different radii are taken from
+ * Simeonov, Calantoni - Modeling mechanical contact and lubrication in Direct Numerical Simulations of colliding particles, IJMF, 2012
+ *
+ * By choosing the respective cutoff distances equal to 0, these contributions can be switched off.
+ *
+ * Should be used in combination with mesa_pd's contact detection algorithm to determine the interaction partners and the gap size.
+ *
+ */
+class LubricationCorrectionKernel
+{
+public:
+
+   explicit LubricationCorrectionKernel( real_t dynamicFluidViscosity,
+                                         std::function<real_t(real_t)> minimalGapSizeFunction,
+                                         real_t cutOffDistanceNormal = real_t(2) / real_t(3),
+                                         real_t cutOffDistanceTangentialTranslational = real_t(0.5),
+                                         real_t cutOffDistanceTangentialRotational = real_t(0.5) )
+         : dynamicFluidViscosity_( dynamicFluidViscosity ), minimalGapSizeFunction_( minimalGapSizeFunction ),
+           cutOffDistanceNormal_( cutOffDistanceNormal ), cutOffDistanceTangentialTranslational_( cutOffDistanceTangentialTranslational ),
+           cutOffDistanceTangentialRotational_( cutOffDistanceTangentialRotational )
+   {}
+
+   template <typename Shape1_T, typename Shape2_T, typename ParticleAccessor_T>
+   inline void operator()( const size_t /*idx1*/, const size_t /*idx2*/,
+                           const Shape1_T& /*shape1*/, const Shape2_T& /*shape2*/,
+                           ParticleAccessor_T& /*ac*/, const Vector3<real_t>& /*interactionNormal*/, const real_t& /*gapSize*/)
+   {
+      WALBERLA_ABORT("Lubrication correction not implemented!")
+   }
+
+   template <typename ParticleAccessor_T>
+   inline void operator()( const size_t idx1, const size_t idx2,
+                           const mesa_pd::data::Sphere& sphere1, const mesa_pd::data::Sphere& sphere2,
+                           ParticleAccessor_T& ac, const Vector3<real_t>& interactionNormal, const real_t& gapSize)
+   {
+      WALBERLA_ASSERT_UNEQUAL(idx1, idx2, "interacting with itself!");
+
+      // interaction normal is from sphere 2 to sphere 1
+
+      if( gapSize > real_t(0))
+      {
+         real_t radius1 = sphere1.getRadius();
+         real_t radius2 = sphere2.getRadius();
+         Vector3<real_t> u1 = ac.getLinearVelocity(idx1);
+         Vector3<real_t> u2 = ac.getLinearVelocity(idx2);
+         Vector3<real_t> omega1 = ac.getAngularVelocity(idx1);
+         Vector3<real_t> omega2 = ac.getAngularVelocity(idx2);
+
+         // compute and add lubrication corrections on sphere 1
+         auto gap1 = std::max(gapSize, minimalGapSizeFunction_(radius1) ); // TODO check this, maybe one should use an average radius here to assert symmetry of forces?
+         Vector3<real_t> lubricationForce1(real_t(0));
+         Vector3<real_t> lubricationTorque1(real_t(0));
+         computeLubricationCorrectionSphereSphere(interactionNormal, gap1, dynamicFluidViscosity_, radius1, radius2, u1, u2, omega1, omega2,
+                                                  cutOffDistanceNormal_, cutOffDistanceTangentialTranslational_, cutOffDistanceTangentialRotational_,
+                                                  lubricationForce1, lubricationTorque1 );
+
+         mesa_pd::addForceAtomic(idx1, ac, lubricationForce1);
+         mesa_pd::addTorqueAtomic(idx1, ac, lubricationTorque1);
+
+         // compute and add lubrication corrections on sphere 2
+         auto gap2 = std::max(gapSize, minimalGapSizeFunction_(radius2) );
+         Vector3<real_t> lubricationForce2(real_t(0));
+         Vector3<real_t> lubricationTorque2(real_t(0));
+         computeLubricationCorrectionSphereSphere( - interactionNormal, gap2, dynamicFluidViscosity_, radius2, radius1, u2, u1, omega2, omega1,
+                                                  cutOffDistanceNormal_, cutOffDistanceTangentialTranslational_, cutOffDistanceTangentialRotational_,
+                                                  lubricationForce2, lubricationTorque2 );
+
+         mesa_pd::addForceAtomic(idx2, ac, lubricationForce2);
+         mesa_pd::addTorqueAtomic(idx2, ac, lubricationTorque2);
+
+      }
+      // else: no lubrication correction
+
+   }
+
+   template <typename ParticleAccessor_T>
+   inline void operator()( const size_t idx1, const size_t idx2,
+                           const mesa_pd::data::Sphere& sphere, const mesa_pd::data::HalfSpace& /*halfSpace*/,
+                           ParticleAccessor_T& ac, const Vector3<real_t>& interactionNormal, const real_t& gapSize)
+   {
+      // interaction normal is the normal of the half space
+
+      if( gapSize > real_t(0))
+      {
+         real_t radius1 = sphere.getRadius();
+         Vector3<real_t> u1 = ac.getLinearVelocity(idx1);
+         Vector3<real_t> u2 = ac.getLinearVelocity(idx2);
+         Vector3<real_t> omega1 = ac.getAngularVelocity(idx1);
+         // angular velocity of half space is zero!
+
+         // compute and add lubrication corrections on sphere 1
+         auto gap1 = std::max(gapSize, minimalGapSizeFunction_(radius1) );
+         Vector3<real_t> lubricationForce1(real_t(0));
+         Vector3<real_t> lubricationTorque1(real_t(0));
+         computeLubricationCorrectionSphereHalfSpace(interactionNormal, gap1, dynamicFluidViscosity_, radius1, u1, u2, omega1,
+                                                     cutOffDistanceNormal_, cutOffDistanceTangentialTranslational_, cutOffDistanceTangentialRotational_,
+                                                     lubricationForce1, lubricationTorque1 );
+
+         mesa_pd::addForceAtomic(idx1, ac, lubricationForce1);
+         mesa_pd::addTorqueAtomic(idx1, ac, lubricationTorque1);
+
+         // NOTE: no lubrication corrections added to the half space!
+
+      }
+      // else: no lubrication correction
+   }
+
+   template <typename ParticleAccessor_T>
+   inline void operator()( const size_t idx1, const size_t idx2,
+                           const mesa_pd::data::HalfSpace& halfSpace, const mesa_pd::data::Sphere& sphere,
+                           ParticleAccessor_T& ac, const Vector3<real_t>& interactionNormal, const real_t& gapSize)
+   {
+      operator()(idx2, idx1, sphere, halfSpace, ac, -interactionNormal, gapSize);
+   }
+
+   void setDynamicFluidViscosity( const real_t dynamicFluidViscosity){ dynamicFluidViscosity_ = dynamicFluidViscosity; }
+   void setMinimalGapSizeFunction( std::function<real_t(real_t)> minimalGapSizeFunction ){ minimalGapSizeFunction_ = minimalGapSizeFunction; }
+   void setNormalCutOffDistance( const real_t cutOffDistanceNormal){ cutOffDistanceNormal_ = cutOffDistanceNormal; }
+   void setTangentialTranslationalCutOffDistance( const real_t cutOffDistanceTangentialTranslational){ cutOffDistanceTangentialTranslational_ = cutOffDistanceTangentialTranslational; }
+   void setTangentialRotationalCutOffDistance( const real_t cutOffDistanceTangentialRotational){ cutOffDistanceTangentialRotational_ = cutOffDistanceTangentialRotational; }
+
+   real_t getDynamicFluidViscosity() const { return dynamicFluidViscosity_; }
+   real_t getMinimalGapSize(real_t radius) const { return minimalGapSizeFunction_(radius); }
+   real_t getNormalCutOffDistance() const { return cutOffDistanceNormal_; }
+   real_t getTangentialTranslationalCutOffDistance() const { return cutOffDistanceTangentialTranslational_; }
+   real_t getTangentialRotationalCutOffDistance() const { return cutOffDistanceTangentialRotational_; }
+
+private:
+
+   real_t dynamicFluidViscosity_;
+   std::function<real_t(real_t)> minimalGapSizeFunction_;
+   real_t cutOffDistanceNormal_;
+   real_t cutOffDistanceTangentialTranslational_;
+   real_t cutOffDistanceTangentialRotational_;
+};
+
+} //namespace lbm_mesapd_coupling
+} //namespace walberla
\ No newline at end of file
diff --git a/src/lbm_mesapd_coupling/utility/OmegaBulkAdaption.h b/src/lbm_mesapd_coupling/utility/OmegaBulkAdaption.h
new file mode 100644
index 0000000000000000000000000000000000000000..226c03c2f16a2a8ba1b399545a5f38057ed29340
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/OmegaBulkAdaption.h
@@ -0,0 +1,176 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file OmegaBulkAdaption.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "mesa_pd/common/AABBConversion.h"
+#include "domain_decomposition/StructuredBlockStorage.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+// utility functions
+
+real_t bulkViscosityFromOmegaBulk(real_t omegaBulk)
+{
+   return real_t(2) / real_t(9) * ( real_t(1) / omegaBulk - real_t(0.5) );
+}
+
+
+real_t omegaBulkFromBulkViscosity(real_t bulkViscosity)
+{
+   return real_t(2) / ( real_t(9) * bulkViscosity + real_t(1) );
+}
+
+// see Khirevich et al. - Coarse- and fine-grid numerical behavior of MRT/TRT lattice-Boltzmann schemes in regular and random sphere packings
+// LambdaBulk is the "magic parameter" here, i.e. the ratio between Lambda_e and Lambda_nu, Eq. 19
+real_t omegaBulkFromOmega(real_t omega, real_t LambdaBulk = real_t(1))
+{
+   return real_t(1) / (LambdaBulk * ( real_t(1) / omega - real_t(1)/ real_t(2) ) + real_t(1)/ real_t(2) );
+}
+
+/*
+ * Adapts all cells that are inside a sphere of radius interactionRadius + adaptionLayerSize around the particle position (center)
+ */
+template< typename ParticleAccessor_T, typename ParticleSelector_T >
+class OmegaBulkAdapter
+{
+   static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+public:
+   OmegaBulkAdapter(const shared_ptr<StructuredBlockStorage> & blockStorage,
+                    const BlockDataID & omegaBulkFieldID,
+                    const shared_ptr<ParticleAccessor_T>& ac,
+                    const real_t defaultOmegaBulk,
+                    const real_t adaptedOmegaBulk,
+                    const real_t adaptionLayerSize,
+                    ParticleSelector_T particleSelector)
+         : blockStorage_( blockStorage ),  omegaBulkFieldID_( omegaBulkFieldID ), ac_( ac ),
+           defaultOmegaBulk_(defaultOmegaBulk),
+           adaptedOmegaBulk_(adaptedOmegaBulk),
+           adaptionLayerSize_(adaptionLayerSize),
+           particleSelector_( particleSelector )
+   {}
+
+
+   void inline operator()( IBlock * const block )
+   {
+
+      using namespace lbm_mesapd_coupling;
+
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+
+      auto * omegaBulkField = block->getData< GhostLayerField< real_t, 1> >( omegaBulkFieldID_ );
+
+      WALBERLA_ASSERT_NOT_NULLPTR( omegaBulkField );
+
+      // reset whole field to default
+      WALBERLA_FOR_ALL_CELLS_XYZ(omegaBulkField, omegaBulkField->get(x,y,z) = defaultOmegaBulk_; )
+
+      const auto blockLevel = blockStorage_->getLevel(*block);
+      const real_t dx = blockStorage_->dx( blockLevel );
+      const real_t dy = blockStorage_->dy( blockLevel );
+      const real_t dz = blockStorage_->dz( blockLevel );
+
+      for( size_t particleIdx = 0; particleIdx < ac_->size(); ++particleIdx )
+      {
+         // currently only works for finite particles
+         if (particleSelector_(particleIdx, *ac_) && !mesa_pd::data::particle_flags::isSet( ac_->getFlags(particleIdx), mesa_pd::data::particle_flags::INFINITE))
+         {
+
+            auto particlePosition = ac_->getPosition(particleIdx);
+            auto particleInteractionRadius = ac_->getInteractionRadius(particleIdx);
+            auto extendedParticleAABB = mesa_pd::getAABBFromInteractionRadius(particlePosition, particleInteractionRadius).getExtended(adaptionLayerSize_);
+            auto cellBB = getCellBBFromAABB( extendedParticleAABB, false, *block, *blockStorage_, uint_t(0));
+            // no ghost layers are included -> omega field has to be communicated afterwards if ghost layer values are needed in LBM sweep
+
+            auto adaptionRadius = particleInteractionRadius + adaptionLayerSize_;
+
+            Vector3<real_t> startCellCenter = blockStorage_->getBlockLocalCellCenter( *block, cellBB.min() );
+
+            real_t cz = startCellCenter[2];
+            for( cell_idx_t z = cellBB.zMin(); z <= cellBB.zMax(); ++z )
+            {
+               real_t cy = startCellCenter[1];
+               for( cell_idx_t y = cellBB.yMin(); y <= cellBB.yMax(); ++y )
+               {
+                  real_t cx = startCellCenter[0];
+                  for( cell_idx_t x = cellBB.xMin(); x <= cellBB.xMax(); ++x )
+                  {
+                     auto cellCenter = Vector3<real_t>(cx,cy,cz);
+                     auto sqrDistanceToParticleCenter = (cellCenter-particlePosition).sqrLength();
+
+                     if(sqrDistanceToParticleCenter < adaptionRadius * adaptionRadius)
+                     {
+                        // change omega to given adaption value
+                        omegaBulkField->get(x,y,z) = adaptedOmegaBulk_;
+                     }
+                     cx += dx;
+                  }
+                  cy += dy;
+               }
+               cz += dz;
+            }
+         }
+      }
+   }
+
+   void setDefaultOmegaBulk(real_t defaultOmegaBulk)
+   {
+      defaultOmegaBulk_ = defaultOmegaBulk;
+   }
+   real_t getDefaultOmegaBulk()
+   {
+      return defaultOmegaBulk_;
+   }
+
+   void setAdaptedOmegaBulk(real_t adaptedOmegaBulk)
+   {
+      adaptedOmegaBulk_ = adaptedOmegaBulk;
+   }
+   real_t getAdaptedOmegaBulk()
+   {
+      return adaptedOmegaBulk_;
+   }
+
+   void setAdaptionLayerSize( real_t adaptionLayerSize )
+   {
+      adaptionLayerSize_ = adaptionLayerSize;
+   }
+   real_t getAdaptionLayerSize()
+   {
+      return adaptionLayerSize_;
+   }
+
+private:
+
+   shared_ptr<StructuredBlockStorage> blockStorage_;
+   BlockDataID omegaBulkFieldID_;
+   shared_ptr<ParticleAccessor_T> ac_;
+   real_t defaultOmegaBulk_;
+   real_t adaptedOmegaBulk_;
+   real_t adaptionLayerSize_;
+   ParticleSelector_T particleSelector_;
+
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/ParticleFunctions.h b/src/lbm_mesapd_coupling/utility/ParticleFunctions.h
new file mode 100644
index 0000000000000000000000000000000000000000..ab72a6a84b37c9b94a13ceaa2f1c057e2fcd27c3
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/ParticleFunctions.h
@@ -0,0 +1,93 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleFunctions.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/math/AABB.h"
+#include "core/math/Limits.h"
+
+#include "mesa_pd/data/Flags.h"
+#include "mesa_pd/data/IAccessor.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/kernel/SingleCast.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+
+/**
+ * Force is applied at the center of mass.
+ */
+template <typename ParticleAccessor_T>
+inline void addHydrodynamicForceAtomic(const size_t p_idx, ParticleAccessor_T& ac, const Vector3<real_t>& f)
+{
+   // Increasing the force and torque on this particle
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicForceRef(p_idx)[0]  += f[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicForceRef(p_idx)[1]  += f[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicForceRef(p_idx)[2]  += f[2];
+}
+
+template <typename ParticleAccessor_T>
+inline void addHydrodynamicForceAtWFPosAtomic(const size_t p_idx, ParticleAccessor_T& ac, const Vector3<real_t>& f, const Vector3<real_t>& wf_pt)
+{
+   // Increasing the force and torque on this particle
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicForceRef(p_idx)[0]  += f[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicForceRef(p_idx)[1]  += f[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicForceRef(p_idx)[2]  += f[2];
+
+   const auto t = cross(( wf_pt - ac.getPosition(p_idx) ), f);
+
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicTorqueRef(p_idx)[0] += t[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicTorqueRef(p_idx)[1] += t[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getHydrodynamicTorqueRef(p_idx)[2] += t[2];
+}
+
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/ParticleSelector.h b/src/lbm_mesapd_coupling/utility/ParticleSelector.h
new file mode 100644
index 0000000000000000000000000000000000000000..9baf0a4a1bb36d3938a6fcaefeeb0b10f480a1dd
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/ParticleSelector.h
@@ -0,0 +1,116 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla is distributed in the hope that it will be useful, but WITHOUT 
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
+//  for more details.
+//  
+//  You should have received a copy of the GNU General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ParticleSelector.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "mesa_pd/data/Flags.h"
+#include "mesa_pd/data/IAccessor.h"
+#include "mesa_pd/data/shape/Sphere.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+struct RegularParticlesSelector
+{
+   template< typename ParticleAccessor_T >
+   bool inline operator()(const size_t particleIdx, const ParticleAccessor_T & ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+      return !mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::FIXED) &&
+             !mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::GLOBAL);
+   }
+};
+
+struct FixedParticlesSelector
+{
+   template< typename ParticleAccessor_T >
+   bool inline operator()(const size_t particleIdx, const ParticleAccessor_T & ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+      return mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::FIXED) &&
+             !mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::GLOBAL);
+   }
+};
+
+struct GlobalParticlesSelector
+{
+   template< typename ParticleAccessor_T >
+   bool inline operator()(const size_t particleIdx, const ParticleAccessor_T & ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+      return mesa_pd::data::particle_flags::isSet( ac.getFlags(particleIdx), mesa_pd::data::particle_flags::GLOBAL);
+   }
+};
+
+// Only select spheres with a Stokes number above a critical Stokes number
+// note: the density here is hardcoded and thus assumes a constant density throughout all particles
+// this is due to the fact that otherwise the density would have to be re-computed from the inverse mass and the volume in the shape storage
+// it also currently only works with spheres
+// it accesses the impact velocity stored in the old contact history and thus has to be used AFTER the reduce contact history call
+struct StokesNumberBasedSphereSelector
+{
+   explicit StokesNumberBasedSphereSelector( real_t StCrit, real_t densityFluid, real_t densityParticle, real_t kinematicViscosity ) :
+         StCrit_(StCrit), densityFluid_(densityFluid), densityParticle_(densityParticle), kinematicViscosity_(kinematicViscosity) {}
+
+   template< typename ParticleAccessor_T >
+   bool inline operator()(const size_t particleIdx, const ParticleAccessor_T & ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      lbm_mesapd_coupling::RegularParticlesSelector regularParticlesSelector;
+
+      if(regularParticlesSelector(particleIdx, ac))
+      {
+         // check for non-fixed spheres
+         if( ac.getShape(particleIdx)->getShapeType() == mesa_pd::data::Sphere::SHAPE_TYPE )
+         {
+            auto contactHistories = ac.getOldContactHistory(particleIdx);
+
+            if(contactHistories.empty()) return true; // no active contacts
+
+            // find maximum impact velocity among all active contacts
+            real_t maximumImpactVelocity = real_t(0);
+            for( auto & history : contactHistories )
+            {
+               maximumImpactVelocity = std::max(maximumImpactVelocity, history.second.getImpactVelocityMagnitude());
+            }
+
+            auto sphereShape = *static_cast< mesa_pd::data::Sphere*>(ac.getShape(particleIdx));
+            real_t diameter = real_t(2) * sphereShape.getRadius();
+
+            real_t St = (densityParticle_ / densityFluid_) * (maximumImpactVelocity *  diameter / kinematicViscosity_) / real_t(9);
+
+            if( St < StCrit_) return true;
+         }
+      }
+
+      return false;
+   }
+
+private:
+   real_t StCrit_;
+   real_t densityFluid_;
+   real_t densityParticle_;
+   real_t kinematicViscosity_;
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h b/src/lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h
new file mode 100644
index 0000000000000000000000000000000000000000..1e7dd22c42e296f9f98e694def5a457480764683
--- /dev/null
+++ b/src/lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h
@@ -0,0 +1,54 @@
+//======================================================================================================================
+//
+//  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 ResetHydrodynamicForceTorqueKernel.h
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/math/Vector3.h"
+
+#include "mesa_pd/data/IAccessor.h"
+
+namespace walberla {
+namespace lbm_mesapd_coupling {
+
+/*
+ * Kernel that resets the values of hydrodynamicForce and hydrodynamicTorque currently stored to zero
+ *
+ * Should usually be carried out on local and ghost particles.
+ */
+class ResetHydrodynamicForceTorqueKernel
+{
+
+public:
+
+   ResetHydrodynamicForceTorqueKernel() = default;
+
+   template< typename ParticleAccessor_T>
+   void operator()(const size_t idx, ParticleAccessor_T& ac) const
+   {
+      static_assert(std::is_base_of<mesa_pd::data::IAccessor, ParticleAccessor_T>::value, "Provide a valid accessor as template");
+
+      ac.setHydrodynamicForce(idx, Vector3<real_t>(real_t(0)));
+      ac.setHydrodynamicTorque(idx, Vector3<real_t>(real_t(0)));
+   }
+};
+
+} // namespace lbm_mesapd_coupling
+} // namespace walberla
diff --git a/src/mesa_pd/common/ParticleFunctions.h b/src/mesa_pd/common/ParticleFunctions.h
index 195dec2c7718535a3d71fd50e5ef2928830b2e13..d19045ab05c4cce40858672b5572188a337c8a5c 100644
--- a/src/mesa_pd/common/ParticleFunctions.h
+++ b/src/mesa_pd/common/ParticleFunctions.h
@@ -92,5 +92,27 @@ inline void addForceAtWFPosAtomic(const size_t p_idx, Accessor& ac, const Vec3&
    ac.getTorqueRef(p_idx)[2] += t[2];
 }
 
+/**
+ * Torque is directly applied on the particle.
+ */
+template <typename Accessor>
+inline void addTorqueAtomic(const size_t p_idx, Accessor& ac, const Vec3& t)
+{
+   // Increasing the torque on this particle
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getTorqueRef(p_idx)[0]  += t[0];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getTorqueRef(p_idx)[1]  += t[1];
+#ifdef _OPENMP
+#pragma omp atomic
+#endif
+   ac.getTorqueRef(p_idx)[2]  += t[2];
+}
+
+
 } //namespace mesa_pd
 } //namespace walberla
diff --git a/src/mesa_pd/data/ParticleAccessor.h b/src/mesa_pd/data/ParticleAccessor.h
index f71f39af2a0fa421d4965f3ec0e5f14f0d07ebfd..ebb81768e28487d7c32bdddcba4a8863a6c79902 100644
--- a/src/mesa_pd/data/ParticleAccessor.h
+++ b/src/mesa_pd/data/ParticleAccessor.h
@@ -144,6 +144,22 @@ public:
    walberla::mesa_pd::Vec3& getDwRef(const size_t p_idx) {return ps_->getDwRef(p_idx);}
    void setDw(const size_t p_idx, walberla::mesa_pd::Vec3 const & v) { ps_->setDw(p_idx, v);}
    
+   walberla::mesa_pd::Vec3 const & getHydrodynamicForce(const size_t p_idx) const {return ps_->getHydrodynamicForce(p_idx);}
+   walberla::mesa_pd::Vec3& getHydrodynamicForceRef(const size_t p_idx) {return ps_->getHydrodynamicForceRef(p_idx);}
+   void setHydrodynamicForce(const size_t p_idx, walberla::mesa_pd::Vec3 const & v) { ps_->setHydrodynamicForce(p_idx, v);}
+   
+   walberla::mesa_pd::Vec3 const & getHydrodynamicTorque(const size_t p_idx) const {return ps_->getHydrodynamicTorque(p_idx);}
+   walberla::mesa_pd::Vec3& getHydrodynamicTorqueRef(const size_t p_idx) {return ps_->getHydrodynamicTorqueRef(p_idx);}
+   void setHydrodynamicTorque(const size_t p_idx, walberla::mesa_pd::Vec3 const & v) { ps_->setHydrodynamicTorque(p_idx, v);}
+   
+   walberla::mesa_pd::Vec3 const & getOldHydrodynamicForce(const size_t p_idx) const {return ps_->getOldHydrodynamicForce(p_idx);}
+   walberla::mesa_pd::Vec3& getOldHydrodynamicForceRef(const size_t p_idx) {return ps_->getOldHydrodynamicForceRef(p_idx);}
+   void setOldHydrodynamicForce(const size_t p_idx, walberla::mesa_pd::Vec3 const & v) { ps_->setOldHydrodynamicForce(p_idx, v);}
+   
+   walberla::mesa_pd::Vec3 const & getOldHydrodynamicTorque(const size_t p_idx) const {return ps_->getOldHydrodynamicTorque(p_idx);}
+   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);}
+   
    std::unordered_set<walberla::mpi::MPIRank> const & getNeighborState(const size_t p_idx) const {return ps_->getNeighborState(p_idx);}
    std::unordered_set<walberla::mpi::MPIRank>& getNeighborStateRef(const size_t p_idx) {return ps_->getNeighborStateRef(p_idx);}
    void setNeighborState(const size_t p_idx, std::unordered_set<walberla::mpi::MPIRank> const & v) { ps_->setNeighborState(p_idx, v);}
@@ -289,6 +305,22 @@ public:
    void setDw(const size_t /*p_idx*/, walberla::mesa_pd::Vec3 const & v) { dw_ = v;}
    walberla::mesa_pd::Vec3& getDwRef(const size_t /*p_idx*/) {return dw_;}
    
+   walberla::mesa_pd::Vec3 const & getHydrodynamicForce(const size_t /*p_idx*/) const {return hydrodynamicForce_;}
+   void setHydrodynamicForce(const size_t /*p_idx*/, walberla::mesa_pd::Vec3 const & v) { hydrodynamicForce_ = v;}
+   walberla::mesa_pd::Vec3& getHydrodynamicForceRef(const size_t /*p_idx*/) {return hydrodynamicForce_;}
+   
+   walberla::mesa_pd::Vec3 const & getHydrodynamicTorque(const size_t /*p_idx*/) const {return hydrodynamicTorque_;}
+   void setHydrodynamicTorque(const size_t /*p_idx*/, walberla::mesa_pd::Vec3 const & v) { hydrodynamicTorque_ = v;}
+   walberla::mesa_pd::Vec3& getHydrodynamicTorqueRef(const size_t /*p_idx*/) {return hydrodynamicTorque_;}
+   
+   walberla::mesa_pd::Vec3 const & getOldHydrodynamicForce(const size_t /*p_idx*/) const {return oldHydrodynamicForce_;}
+   void setOldHydrodynamicForce(const size_t /*p_idx*/, walberla::mesa_pd::Vec3 const & v) { oldHydrodynamicForce_ = v;}
+   walberla::mesa_pd::Vec3& getOldHydrodynamicForceRef(const size_t /*p_idx*/) {return oldHydrodynamicForce_;}
+   
+   walberla::mesa_pd::Vec3 const & getOldHydrodynamicTorque(const size_t /*p_idx*/) const {return oldHydrodynamicTorque_;}
+   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_;}
+   
    std::unordered_set<walberla::mpi::MPIRank> const & getNeighborState(const size_t /*p_idx*/) const {return neighborState_;}
    void setNeighborState(const size_t /*p_idx*/, std::unordered_set<walberla::mpi::MPIRank> const & v) { neighborState_ = v;}
    std::unordered_set<walberla::mpi::MPIRank>& getNeighborStateRef(const size_t /*p_idx*/) {return neighborState_;}
@@ -328,6 +360,10 @@ private:
    walberla::real_t heatFlux_;
    walberla::mesa_pd::Vec3 dv_;
    walberla::mesa_pd::Vec3 dw_;
+   walberla::mesa_pd::Vec3 hydrodynamicForce_;
+   walberla::mesa_pd::Vec3 hydrodynamicTorque_;
+   walberla::mesa_pd::Vec3 oldHydrodynamicForce_;
+   walberla::mesa_pd::Vec3 oldHydrodynamicTorque_;
    std::unordered_set<walberla::mpi::MPIRank> neighborState_;
 };
 
diff --git a/src/mesa_pd/data/ParticleStorage.h b/src/mesa_pd/data/ParticleStorage.h
index 7ff16c341846c3169f16fdcee9a75b96a704b7fc..53c157277713dcb3ba503cb85bd9cf893617508f 100644
--- a/src/mesa_pd/data/ParticleStorage.h
+++ b/src/mesa_pd/data/ParticleStorage.h
@@ -95,6 +95,10 @@ public:
       using heatFlux_type = walberla::real_t;
       using dv_type = walberla::mesa_pd::Vec3;
       using dw_type = walberla::mesa_pd::Vec3;
+      using hydrodynamicForce_type = walberla::mesa_pd::Vec3;
+      using hydrodynamicTorque_type = walberla::mesa_pd::Vec3;
+      using oldHydrodynamicForce_type = walberla::mesa_pd::Vec3;
+      using oldHydrodynamicTorque_type = walberla::mesa_pd::Vec3;
       using neighborState_type = std::unordered_set<walberla::mpi::MPIRank>;
 
       
@@ -194,6 +198,22 @@ public:
       dw_type& getDwRef() {return storage_.getDwRef(i_);}
       void setDw(dw_type const & v) { storage_.setDw(i_, v);}
       
+      hydrodynamicForce_type const & getHydrodynamicForce() const {return storage_.getHydrodynamicForce(i_);}
+      hydrodynamicForce_type& getHydrodynamicForceRef() {return storage_.getHydrodynamicForceRef(i_);}
+      void setHydrodynamicForce(hydrodynamicForce_type const & v) { storage_.setHydrodynamicForce(i_, v);}
+      
+      hydrodynamicTorque_type const & getHydrodynamicTorque() const {return storage_.getHydrodynamicTorque(i_);}
+      hydrodynamicTorque_type& getHydrodynamicTorqueRef() {return storage_.getHydrodynamicTorqueRef(i_);}
+      void setHydrodynamicTorque(hydrodynamicTorque_type const & v) { storage_.setHydrodynamicTorque(i_, v);}
+      
+      oldHydrodynamicForce_type const & getOldHydrodynamicForce() const {return storage_.getOldHydrodynamicForce(i_);}
+      oldHydrodynamicForce_type& getOldHydrodynamicForceRef() {return storage_.getOldHydrodynamicForceRef(i_);}
+      void setOldHydrodynamicForce(oldHydrodynamicForce_type const & v) { storage_.setOldHydrodynamicForce(i_, v);}
+      
+      oldHydrodynamicTorque_type const & getOldHydrodynamicTorque() const {return storage_.getOldHydrodynamicTorque(i_);}
+      oldHydrodynamicTorque_type& getOldHydrodynamicTorqueRef() {return storage_.getOldHydrodynamicTorqueRef(i_);}
+      void setOldHydrodynamicTorque(oldHydrodynamicTorque_type const & v) { storage_.setOldHydrodynamicTorque(i_, v);}
+      
       neighborState_type const & getNeighborState() const {return storage_.getNeighborState(i_);}
       neighborState_type& getNeighborStateRef() {return storage_.getNeighborStateRef(i_);}
       void setNeighborState(neighborState_type const & v) { storage_.setNeighborState(i_, v);}
@@ -282,6 +302,10 @@ public:
    using heatFlux_type = walberla::real_t;
    using dv_type = walberla::mesa_pd::Vec3;
    using dw_type = walberla::mesa_pd::Vec3;
+   using hydrodynamicForce_type = walberla::mesa_pd::Vec3;
+   using hydrodynamicTorque_type = walberla::mesa_pd::Vec3;
+   using oldHydrodynamicForce_type = walberla::mesa_pd::Vec3;
+   using oldHydrodynamicTorque_type = walberla::mesa_pd::Vec3;
    using neighborState_type = std::unordered_set<walberla::mpi::MPIRank>;
 
    
@@ -381,6 +405,22 @@ public:
    dw_type& getDwRef(const size_t idx) {return dw_[idx];}
    void setDw(const size_t idx, dw_type const & v) { dw_[idx] = v; }
    
+   hydrodynamicForce_type const & getHydrodynamicForce(const size_t idx) const {return hydrodynamicForce_[idx];}
+   hydrodynamicForce_type& getHydrodynamicForceRef(const size_t idx) {return hydrodynamicForce_[idx];}
+   void setHydrodynamicForce(const size_t idx, hydrodynamicForce_type const & v) { hydrodynamicForce_[idx] = v; }
+   
+   hydrodynamicTorque_type const & getHydrodynamicTorque(const size_t idx) const {return hydrodynamicTorque_[idx];}
+   hydrodynamicTorque_type& getHydrodynamicTorqueRef(const size_t idx) {return hydrodynamicTorque_[idx];}
+   void setHydrodynamicTorque(const size_t idx, hydrodynamicTorque_type const & v) { hydrodynamicTorque_[idx] = v; }
+   
+   oldHydrodynamicForce_type const & getOldHydrodynamicForce(const size_t idx) const {return oldHydrodynamicForce_[idx];}
+   oldHydrodynamicForce_type& getOldHydrodynamicForceRef(const size_t idx) {return oldHydrodynamicForce_[idx];}
+   void setOldHydrodynamicForce(const size_t idx, oldHydrodynamicForce_type const & v) { oldHydrodynamicForce_[idx] = v; }
+   
+   oldHydrodynamicTorque_type const & getOldHydrodynamicTorque(const size_t idx) const {return oldHydrodynamicTorque_[idx];}
+   oldHydrodynamicTorque_type& getOldHydrodynamicTorqueRef(const size_t idx) {return oldHydrodynamicTorque_[idx];}
+   void setOldHydrodynamicTorque(const size_t idx, oldHydrodynamicTorque_type const & v) { oldHydrodynamicTorque_[idx] = v; }
+   
    neighborState_type const & getNeighborState(const size_t idx) const {return neighborState_[idx];}
    neighborState_type& getNeighborStateRef(const size_t idx) {return neighborState_[idx];}
    void setNeighborState(const size_t idx, neighborState_type const & v) { neighborState_[idx] = v; }
@@ -500,6 +540,10 @@ public:
    std::vector<heatFlux_type> heatFlux_ {};
    std::vector<dv_type> dv_ {};
    std::vector<dw_type> dw_ {};
+   std::vector<hydrodynamicForce_type> hydrodynamicForce_ {};
+   std::vector<hydrodynamicTorque_type> hydrodynamicTorque_ {};
+   std::vector<oldHydrodynamicForce_type> oldHydrodynamicForce_ {};
+   std::vector<oldHydrodynamicTorque_type> oldHydrodynamicTorque_ {};
    std::vector<neighborState_type> neighborState_ {};
    std::unordered_map<uid_type, size_t> uidToIdx_;
    static_assert(std::is_same<uid_type, id_t>::value,
@@ -534,6 +578,10 @@ ParticleStorage::Particle& ParticleStorage::Particle::operator=(const ParticleSt
    getHeatFluxRef() = rhs.getHeatFlux();
    getDvRef() = rhs.getDv();
    getDwRef() = rhs.getDw();
+   getHydrodynamicForceRef() = rhs.getHydrodynamicForce();
+   getHydrodynamicTorqueRef() = rhs.getHydrodynamicTorque();
+   getOldHydrodynamicForceRef() = rhs.getOldHydrodynamicForce();
+   getOldHydrodynamicTorqueRef() = rhs.getOldHydrodynamicTorque();
    getNeighborStateRef() = rhs.getNeighborState();
    return *this;
 }
@@ -565,6 +613,10 @@ ParticleStorage::Particle& ParticleStorage::Particle::operator=(ParticleStorage:
    getHeatFluxRef() = std::move(rhs.getHeatFluxRef());
    getDvRef() = std::move(rhs.getDvRef());
    getDwRef() = std::move(rhs.getDwRef());
+   getHydrodynamicForceRef() = std::move(rhs.getHydrodynamicForceRef());
+   getHydrodynamicTorqueRef() = std::move(rhs.getHydrodynamicTorqueRef());
+   getOldHydrodynamicForceRef() = std::move(rhs.getOldHydrodynamicForceRef());
+   getOldHydrodynamicTorqueRef() = std::move(rhs.getOldHydrodynamicTorqueRef());
    getNeighborStateRef() = std::move(rhs.getNeighborStateRef());
    return *this;
 }
@@ -597,6 +649,10 @@ void swap(ParticleStorage::Particle lhs, ParticleStorage::Particle rhs)
    std::swap(lhs.getHeatFluxRef(), rhs.getHeatFluxRef());
    std::swap(lhs.getDvRef(), rhs.getDvRef());
    std::swap(lhs.getDwRef(), rhs.getDwRef());
+   std::swap(lhs.getHydrodynamicForceRef(), rhs.getHydrodynamicForceRef());
+   std::swap(lhs.getHydrodynamicTorqueRef(), rhs.getHydrodynamicTorqueRef());
+   std::swap(lhs.getOldHydrodynamicForceRef(), rhs.getOldHydrodynamicForceRef());
+   std::swap(lhs.getOldHydrodynamicTorqueRef(), rhs.getOldHydrodynamicTorqueRef());
    std::swap(lhs.getNeighborStateRef(), rhs.getNeighborStateRef());
 }
 
@@ -629,6 +685,10 @@ std::ostream& operator<<( std::ostream& os, const ParticleStorage::Particle& p )
          "heatFlux            : " << p.getHeatFlux() << "\n" <<
          "dv                  : " << p.getDv() << "\n" <<
          "dw                  : " << p.getDw() << "\n" <<
+         "hydrodynamicForce   : " << p.getHydrodynamicForce() << "\n" <<
+         "hydrodynamicTorque  : " << p.getHydrodynamicTorque() << "\n" <<
+         "oldHydrodynamicForce: " << p.getOldHydrodynamicForce() << "\n" <<
+         "oldHydrodynamicTorque: " << p.getOldHydrodynamicTorque() << "\n" <<
          "neighborState       : " << p.getNeighborState() << "\n" <<
          "================================" << std::endl;
    return os;
@@ -731,6 +791,10 @@ inline ParticleStorage::iterator ParticleStorage::create(const id_t& uid)
    heatFlux_.emplace_back(real_t(0));
    dv_.emplace_back(real_t(0));
    dw_.emplace_back(real_t(0));
+   hydrodynamicForce_.emplace_back(real_t(0));
+   hydrodynamicTorque_.emplace_back(real_t(0));
+   oldHydrodynamicForce_.emplace_back(real_t(0));
+   oldHydrodynamicTorque_.emplace_back(real_t(0));
    neighborState_.emplace_back();
    uid_.back() = uid;
    uidToIdx_[uid] = uid_.size() - 1;
@@ -788,6 +852,10 @@ inline ParticleStorage::iterator ParticleStorage::erase(iterator& it)
    heatFlux_.pop_back();
    dv_.pop_back();
    dw_.pop_back();
+   hydrodynamicForce_.pop_back();
+   hydrodynamicTorque_.pop_back();
+   oldHydrodynamicForce_.pop_back();
+   oldHydrodynamicTorque_.pop_back();
    neighborState_.pop_back();
    return it;
 }
@@ -832,6 +900,10 @@ inline void ParticleStorage::reserve(const size_t size)
    heatFlux_.reserve(size);
    dv_.reserve(size);
    dw_.reserve(size);
+   hydrodynamicForce_.reserve(size);
+   hydrodynamicTorque_.reserve(size);
+   oldHydrodynamicForce_.reserve(size);
+   oldHydrodynamicTorque_.reserve(size);
    neighborState_.reserve(size);
 }
 
@@ -861,6 +933,10 @@ inline void ParticleStorage::clear()
    heatFlux_.clear();
    dv_.clear();
    dw_.clear();
+   hydrodynamicForce_.clear();
+   hydrodynamicTorque_.clear();
+   oldHydrodynamicForce_.clear();
+   oldHydrodynamicTorque_.clear();
    neighborState_.clear();
    uidToIdx_.clear();
 }
@@ -891,6 +967,10 @@ inline size_t ParticleStorage::size() const
    //WALBERLA_ASSERT_EQUAL( uid_.size(), heatFlux.size() );
    //WALBERLA_ASSERT_EQUAL( uid_.size(), dv.size() );
    //WALBERLA_ASSERT_EQUAL( uid_.size(), dw.size() );
+   //WALBERLA_ASSERT_EQUAL( uid_.size(), hydrodynamicForce.size() );
+   //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(), neighborState.size() );
    return uid_.size();
 }
@@ -1292,6 +1372,42 @@ public:
    walberla::mesa_pd::Vec3 const & operator()(const data::Particle& p) const {return p.getDw();}
 };
 ///Predicate that selects a certain property from a Particle
+class SelectParticleHydrodynamicForce
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getHydrodynamicForceRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getHydrodynamicForceRef();}
+   walberla::mesa_pd::Vec3 const & operator()(const data::Particle& p) const {return p.getHydrodynamicForce();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleHydrodynamicTorque
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getHydrodynamicTorqueRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getHydrodynamicTorqueRef();}
+   walberla::mesa_pd::Vec3 const & operator()(const data::Particle& p) const {return p.getHydrodynamicTorque();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleOldHydrodynamicForce
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getOldHydrodynamicForceRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getOldHydrodynamicForceRef();}
+   walberla::mesa_pd::Vec3 const & operator()(const data::Particle& p) const {return p.getOldHydrodynamicForce();}
+};
+///Predicate that selects a certain property from a Particle
+class SelectParticleOldHydrodynamicTorque
+{
+public:
+   using return_type = walberla::mesa_pd::Vec3;
+   walberla::mesa_pd::Vec3& operator()(data::Particle& p) const {return p.getOldHydrodynamicTorqueRef();}
+   walberla::mesa_pd::Vec3& operator()(data::Particle&& p) const {return p.getOldHydrodynamicTorqueRef();}
+   walberla::mesa_pd::Vec3 const & operator()(const data::Particle& p) const {return p.getOldHydrodynamicTorque();}
+};
+///Predicate that selects a certain property from a Particle
 class SelectParticleNeighborState
 {
 public:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index ef1f0177a9c58db2092d9b712df077323d9a030b..b7032214d314cae434db0a84da4eace9bdf30c52 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -14,6 +14,7 @@ add_subdirectory( gather )
 add_subdirectory( geometry )
 add_subdirectory( gui )
 add_subdirectory( lbm )
+add_subdirectory( lbm_mesapd_coupling )
 add_subdirectory( mesa_pd )
 add_subdirectory( mesh )
 add_subdirectory( pde )
diff --git a/tests/lbm_mesapd_coupling/CMakeLists.txt b/tests/lbm_mesapd_coupling/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..40a818addf0e4ae264fcbb1d1dd1ebc4bb5bf150
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/CMakeLists.txt
@@ -0,0 +1,40 @@
+###################################################################################################
+#
+# Tests for LBM_MESAPD_COUPLING functionality
+#
+###################################################################################################
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_Mapping FILES mapping/ParticleMapping.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field )
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_Mapping PROCESSES 3)
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_MEM_MovingMapping FILES momentum_exchange_method/MovingParticleMapping.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field vtk)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_MovingMapping PROCESSES 3)
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_MEM_DragForceSphere FILES momentum_exchange_method/DragForceSphere.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field vtk )
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_DragForceSphere COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_DragForceSphere> --funcTest PROCESSES 2)
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects FILES momentum_exchange_method/ForceBetweenTwoStationaryObjects.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field vtk )
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjectsSS1 COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects> PROCESSES 1)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjectsSS2 COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects> --useSBB PROCESSES 1)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjectsSS3 COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects> --useCompressible PROCESSES 1)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjectsSS4 COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects> --systemVelocity 0.1 PROCESSES 1)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjectsSW1 COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects> --useSphereWallSetup PROCESSES 1)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjectsSW2 COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects> --useSphereWallSetup --useSBB PROCESSES 1)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjectsSW3 COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects> --useSphereWallSetup --useCompressible PROCESSES 1)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjectsSW4 COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_ForceBetweenTwoStationaryObjects> --useSphereWallSetup --systemVelocity 0.1 PROCESSES 1)
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_MEM_SettlingSphere FILES momentum_exchange_method/SettlingSphere.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field vtk )
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_SettlingSphere COMMAND $<TARGET_FILE:LBM_MESAPD_COUPLING_MEM_SettlingSphere> --funcTest PROCESSES 4)
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_MEM_PdfReconstruction FILES momentum_exchange_method/PdfReconstruction.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field vtk)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_PdfReconstruction PROCESSES 2)
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_MEM_UpdateParticleMapping FILES momentum_exchange_method/UpdateParticleMapping.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field vtk)
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_MEM_UpdateParticleMapping PROCESSES 1)
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_UTILITY_LubricationCorrection FILES utility/LubricationCorrection.cpp DEPENDS mesa_pd lbm_mesapd_coupling )
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_UTILITY_LubricationCorrection PROCESSES 1 )
+
+waLBerla_compile_test( NAME LBM_MESAPD_COUPLING_UTILITY_InspectionProbe FILES utility/InspectionProbe.cpp DEPENDS core mesa_pd lbm lbm_mesapd_coupling domain_decomposition field )
+waLBerla_execute_test( NAME LBM_MESAPD_COUPLING_UTILITY_InspectionProbe PROCESSES 1 )
+
diff --git a/tests/lbm_mesapd_coupling/mapping/ParticleMapping.cpp b/tests/lbm_mesapd_coupling/mapping/ParticleMapping.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b879418b6c7a914890fd49b7a99cf8134b992041
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/mapping/ParticleMapping.cpp
@@ -0,0 +1,849 @@
+//======================================================================================================================
+//
+//  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 ParticleMapping.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/Debug.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/math/all.h"
+#include "core/mpi/MPIManager.h"
+#include "core/mpi/Reduce.h"
+
+#include "field/AddToStorage.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+
+#include "lbm_mesapd_coupling/mapping/ParticleMapping.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.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/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/kernel/SingleCast.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+
+namespace particle_mapping
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::D3Q19<lbm::collision_model::SRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+const uint_t FieldGhostLayers = 1;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+// boundary handling
+using NoSlip_T = lbm::NoSlip< LatticeModel_T, flag_t >;
+using BoundaryHandling_T = BoundaryHandling< FlagField_T, Stencil_T, NoSlip_T >;
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag ( "fluid" );
+const FlagUID NoSlip_Flag  ( "no slip" );
+
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+
+class MyBoundaryHandling
+{
+public:
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID) :
+      flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ) {}
+
+   BoundaryHandling_T * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const;
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+
+}; // class MyBoundaryHandling
+
+BoundaryHandling_T * MyBoundaryHandling::operator()( IBlock * const block, const StructuredBlockStorage * const /*storage*/ ) const
+{
+   WALBERLA_ASSERT_NOT_NULLPTR( block );
+   //WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+   FlagField_T * flagField       = block->getData< FlagField_T >( flagFieldID_ );
+   PdfField_T *  pdfField        = block->getData< PdfField_T > ( pdfFieldID_ );
+
+   const auto fluid = flagField->flagExists( Fluid_Flag ) ? flagField->getFlag( Fluid_Flag ) : flagField->registerFlag( Fluid_Flag );
+
+   BoundaryHandling_T * handling = new BoundaryHandling_T( "fixed obstacle boundary handling", flagField, fluid,
+                                                           NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ) );
+
+   handling->fillWithDomain( FieldGhostLayers );
+
+   return handling;
+}
+
+
+
+class SphereMappingChecker
+{
+public:
+   SphereMappingChecker(const shared_ptr< StructuredBlockStorage > & blocks,
+                        const BlockDataID & boundaryHandlingID, real_t sphereRadius) :
+         blocks_( blocks ), boundaryHandlingID_( boundaryHandlingID ),
+         sphereRadius_( sphereRadius ), sphereVolume_( math::pi * real_t(4) / real_t(3) * sphereRadius * sphereRadius * sphereRadius )
+   {
+      WALBERLA_ASSERT(blocks->isXPeriodic());
+   }
+
+   // check the mapping in the inner domain of the block and check mapped volume against real sphere volume
+   void operator()(std::string & testIdentifier, const Vector3<real_t> & pos, bool periodic )
+   {
+      uint_t cellCounter( uint_t(0) );
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         BoundaryHandling_T * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         FlagField_T *        flagField        = boundaryHandling->getFlagField();
+
+         auto xyzSize = flagField->xyzSize();
+
+         for (auto cellIt : xyzSize) {
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter(*blockIt, cellIt);
+            real_t distance = (cellCenter - pos).length();
+            if( periodic )
+            {
+               Vector3<real_t> periodicOffset(blocks_->getDomain().xSize(),0,0);
+               // check if other (periodic) copies are closer
+               distance = std::min(distance, (cellCenter - (pos-periodicOffset)).length());
+               distance = std::min(distance, (cellCenter - (pos+periodicOffset)).length());
+            }
+
+            if (distance < sphereRadius_) {
+               WALBERLA_CHECK(boundaryHandling->isBoundary(cellIt),
+                              testIdentifier << "Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected boundary cell. Distance cell center - particle center = "
+                                             << distance << ".");
+               ++cellCounter;
+            } else {
+               WALBERLA_CHECK(boundaryHandling->isDomain(cellIt),
+                              testIdentifier << "Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected domain cell. Distance cell center - particle center = "
+                                             << distance << ".");
+            }
+         }
+      }
+
+      mpi::allReduceInplace(cellCounter, mpi::SUM);
+
+      // mapped volume has to be - approximately - the same as the real volume
+      real_t mappedVolume = real_c(cellCounter); // dx=1
+      WALBERLA_CHECK(std::fabs( mappedVolume - sphereVolume_ ) / sphereVolume_ <= real_t(0.1),
+                     "Mapped volume " << mappedVolume << " does not fit to real sphere volume " << sphereVolume_ << ".");
+   }
+
+   // checks only the mapping in the ghost layers
+   void checkGhostLayer(std::string & testIdentifier, const Vector3<real_t> & pos, bool periodic )
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         BoundaryHandling_T * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         FlagField_T *        flagField        = boundaryHandling->getFlagField();
+
+         auto xyzSizeWGl = flagField->xyzSizeWithGhostLayer();
+
+         for (auto cellIt : xyzSizeWGl) {
+            if( flagField->isInInnerPart(cellIt) ) continue;
+
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter( *blockIt, cellIt);
+            real_t distance = (cellCenter - pos).length();
+            if( periodic )
+            {
+               Vector3<real_t> periodicOffset(blocks_->getDomain().xSize(),0,0);
+               // check if other (periodic) copies are closer
+               distance = std::min(distance, (cellCenter - (pos-periodicOffset)).length());
+               distance = std::min(distance, (cellCenter - (pos+periodicOffset)).length());
+            }
+
+            if( distance < sphereRadius_ )
+            {
+               WALBERLA_CHECK( boundaryHandling->isBoundary(cellIt),
+                               testIdentifier << ": Invalid mapping in ghost layer cell " << cellIt
+                                              << " with center at " << cellCenter
+                                              << " - expected boundary cell. Distance cell center - particle center = "
+                                              << distance << ".");
+            }
+            else
+            {
+               WALBERLA_CHECK( boundaryHandling->isDomain(cellIt),
+                               testIdentifier << ": Invalid mapping in ghost layer cell " << cellIt
+                                              << " with center at " << cellCenter
+                                              << " - expected domain cell. Distance cell center - particle center = "
+                                              << distance << "." );
+            }
+         }
+      }
+   }
+
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+   real_t sphereRadius_, sphereVolume_;
+
+};
+
+
+class HalfSpaceMappingChecker {
+public:
+   HalfSpaceMappingChecker(const shared_ptr<StructuredBlockStorage> &blocks,
+                           const BlockDataID &boundaryHandlingID) :
+         blocks_(blocks), boundaryHandlingID_(boundaryHandlingID) {
+      WALBERLA_ASSERT(blocks->isXPeriodic());
+   }
+
+   // check the mapping in the inner domain of the block
+   void operator()(std::string & testIdentifier, const Vector3<real_t> & pos, const Vector3<real_t> & normal )
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         BoundaryHandling_T * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         FlagField_T *        flagField        = boundaryHandling->getFlagField();
+
+         auto xyzSize = flagField->xyzSize();
+
+         for (auto cellIt : xyzSize) {
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter(*blockIt, cellIt);
+            real_t distance = (cellCenter - pos) * normal;
+
+            if (distance <= real_t(0)) {
+               WALBERLA_CHECK(boundaryHandling->isBoundary(cellIt),
+                              testIdentifier << "Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected boundary cell. Distance cell center - particle center = "
+                                             << distance << ".");
+            } else {
+               WALBERLA_CHECK(boundaryHandling->isDomain(cellIt),
+                              testIdentifier << "Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected domain cell. Distance cell center - particle center = "
+                                             << distance << ".");
+            }
+         }
+      }
+   }
+
+   // checks only the mapping in the ghost layers
+   void checkGhostLayer(std::string & testIdentifier, const Vector3<real_t> & pos, const Vector3<real_t> & normal  )
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         BoundaryHandling_T * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         FlagField_T *        flagField        = boundaryHandling->getFlagField();
+
+         auto xyzSizeWGl = flagField->xyzSizeWithGhostLayer();
+
+         for (auto cellIt : xyzSizeWGl) {
+            if( flagField->isInInnerPart(cellIt) ) continue;
+
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter(*blockIt, cellIt);
+            real_t distance = (cellCenter - pos) * normal;
+
+            if (distance <= real_t(0))
+            {
+               WALBERLA_CHECK( boundaryHandling->isBoundary(cellIt),
+                               testIdentifier << ": Invalid mapping in ghost layer cell " << cellIt
+                                              << " with center at " << cellCenter
+                                              << " - expected boundary cell. Distance cell center - particle center = "
+                                              << distance << ".");
+            }
+            else
+            {
+               WALBERLA_CHECK( boundaryHandling->isDomain(cellIt),
+                               testIdentifier << ": Invalid mapping in ghost layer cell " << cellIt
+                                              << " with center at " << cellCenter
+                                              << " - expected domain cell. Distance cell center - particle center = "
+                                              << distance << "." );
+            }
+         }
+      }
+   }
+
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+};
+
+class MappingResetter
+{
+public:
+   MappingResetter(const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & boundaryHandlingID) :
+         blocks_( blocks ), boundaryHandlingID_( boundaryHandlingID )
+   { }
+
+   void operator()()
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         BoundaryHandling_T * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         FlagField_T *        flagField        = boundaryHandling->getFlagField();
+
+         auto xyzSizeWGl = flagField->xyzSizeWithGhostLayer();
+         // reset to domain (fluid)
+         boundaryHandling->forceDomain(xyzSizeWGl);
+      }
+   }
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+};
+
+
+
+/*!\brief Test case for the particle mapping functionality
+ *
+ * The following mapping functions are tested for spheres:
+ *  - RegularParticlesSelector
+ *  - GlobalParticlesSelector
+ *  - FixedParticlesSelector
+ * The mapping inside a block, at a regular block boarder and at a periodic block boarder is tested.
+ * Regular, global and infinite-mass spheres are tested.
+ * After each test, the sphere is destroyed and the mapping is erased from the datastructures.
+ *
+ * The setup is as follows (x marks the different sphere positions, # is a periodic border)
+ * ----------------------------------------------------------------------
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #X         X          X|                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * ----------------------------------------------------------------------
+ *
+ * Furthermore, the mapping of a plane (halfspace) is tested which resides at the bottom of the domain.
+ * It is by definition global and thus uses the GlobalParticlesSelector.
+ *
+ */
+//////////
+// MAIN //
+//////////
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   WALBERLA_CHECK(mpi::MPIManager::instance()->numProcesses()==3, "Due to periodicity, three processes have to be used!");
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   bool writeVTK = false;
+   const real_t omega  = real_t(1);
+   const real_t dx     = real_t(1);
+   const real_t radius = real_t(5);
+
+   ///////////////////////////
+   // DATA STRUCTURES SETUP //
+   ///////////////////////////
+
+   Vector3<uint_t> blocksPerDirection(uint_t(3), uint_t(1), uint_t(1));
+   Vector3<uint_t> cellsPerBlock(uint_t(20), uint_t(20), uint_t(20));
+   Vector3<bool> periodicity(true, false, false);
+
+   auto blocks = blockforest::createUniformBlockGrid( blocksPerDirection[0], blocksPerDirection[1], blocksPerDirection[2],
+                                                      cellsPerBlock[0], cellsPerBlock[1], cellsPerBlock[2],
+                                                      dx,
+                                                      1, false, false,
+                                                      periodicity[0], periodicity[1], periodicity[2],
+                                                      false );
+
+   // create the lattice model
+   LatticeModel_T latticeModel = LatticeModel_T( omega );
+
+   // add PDF field
+   BlockDataID 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 );
+
+   // add boundary handling
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling( flagFieldID, pdfFieldID), "boundary handling" );
+
+   // rpd setup
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+
+   //init data structures
+   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);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+   mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+
+   // coupling
+   const real_t overlap = real_t( 1.5 ) * dx;
+   lbm_mesapd_coupling::ParticleMappingKernel<BoundaryHandling_T> particleMappingKernel(blocks, boundaryHandlingID);
+
+   // vtk
+   auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field" );
+   flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+
+   // testing utilities
+   SphereMappingChecker sphereMappingChecker(blocks, boundaryHandlingID, radius);
+   MappingResetter mappingResetter(blocks, boundaryHandlingID);
+
+   // sphere positions for test scenarios
+   Vector3<real_t> positionInsideBlock(real_t(10), real_t(10), real_t(10));
+   Vector3<real_t> positionAtBlockBoarder(real_t(19), real_t(10), real_t(10));
+   Vector3<real_t> positionAtPeriodicBoarder(real_t(1), real_t(10), real_t(10));
+
+   /////////////////////
+   // NO SLIP MAPPING //
+   /////////////////////
+
+   //////////////////////////////////
+   // TEST SPHERE INSIDE ONE BLOCK //
+   //////////////////////////////////
+
+   ////////////////////
+   // REGULAR SPHERE //
+   ////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere inside block with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionInsideBlock ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionInsideBlock);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      sphereMappingChecker(testIdentifier,positionInsideBlock,false);
+      sphereMappingChecker.checkGhostLayer(testIdentifier,positionInsideBlock,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   ///////////////////
+   // GLOBAL SPHERE //
+   ///////////////////
+   {
+      std::string testIdentifier("Test: global sphere inside block with no slip mapping");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(positionInsideBlock);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      sphereMappingChecker(testIdentifier,positionInsideBlock,false);
+      sphereMappingChecker.checkGhostLayer(testIdentifier,positionInsideBlock,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   //////////////////
+   // FIXED SPHERE //
+   //////////////////
+   {
+      std::string testIdentifier("Test: fixed sphere inside block with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionInsideBlock ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionInsideBlock);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::FixedParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      sphereMappingChecker(testIdentifier,positionInsideBlock,false);
+      sphereMappingChecker.checkGhostLayer(testIdentifier,positionInsideBlock,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   //////////////////////////////////
+   // TEST SPHERE AT BLOCK BOARDER //
+   //////////////////////////////////
+
+   ////////////////////
+   // REGULAR SPHERE //
+   ////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere at block boarder with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtBlockBoarder ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionAtBlockBoarder);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      sphereMappingChecker(testIdentifier,positionAtBlockBoarder,false);
+      sphereMappingChecker.checkGhostLayer(testIdentifier,positionAtBlockBoarder,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   ///////////////////
+   // GLOBAL SPHERE //
+   ///////////////////
+
+   {
+      std::string testIdentifier("Test: global sphere at block boarder with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(positionAtBlockBoarder);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      sphereMappingChecker(testIdentifier,positionAtBlockBoarder,false);
+      sphereMappingChecker.checkGhostLayer(testIdentifier,positionAtBlockBoarder,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+
+   //////////////////
+   // FIXED SPHERE //
+   //////////////////
+   {
+      std::string testIdentifier("Test: fixed sphere at block boarder with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtBlockBoarder ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionAtBlockBoarder);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::FixedParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      sphereMappingChecker(testIdentifier,positionAtBlockBoarder,false);
+      sphereMappingChecker.checkGhostLayer(testIdentifier,positionAtBlockBoarder,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+
+   /////////////////////////////////////
+   // TEST SPHERE AT PERIODIC BOARDER //
+   /////////////////////////////////////
+
+   ////////////////////
+   // REGULAR SPHERE //
+   ////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere at periodic boarder with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtPeriodicBoarder ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionAtPeriodicBoarder);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      sphereMappingChecker(testIdentifier,positionAtPeriodicBoarder,true);
+      sphereMappingChecker.checkGhostLayer(testIdentifier,positionAtPeriodicBoarder,true);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   ///////////////////
+   // GLOBAL SPHERE //
+   ///////////////////
+
+   // left out since periodicity does not affect global particles
+
+   //////////////////
+   // FIXED SPHERE //
+   //////////////////
+   {
+      std::string testIdentifier("Test: fixed sphere at periodic boarder with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtPeriodicBoarder ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionAtPeriodicBoarder);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::FixedParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      sphereMappingChecker(testIdentifier,positionAtPeriodicBoarder,true);
+      sphereMappingChecker.checkGhostLayer(testIdentifier,positionAtPeriodicBoarder,true);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   ///////////////
+   // HALFSPACE //
+   ///////////////
+
+   auto halfSpacePosition = Vector3<real_t>(22.5,0.,3.5);
+   auto halfSpaceNormal = Vector3<real_t>(0.,0.,1.);
+   auto halfSpaceShape = ss->create<mesa_pd::data::HalfSpace>( halfSpaceNormal );
+
+   HalfSpaceMappingChecker halfSpaceMappingChecker(blocks, boundaryHandlingID);
+   {
+      std::string testIdentifier("Test: half space at bottom of domain with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create half space
+      {
+         mesa_pd::data::Particle&& p = *ps->create(true);
+         p.setPosition(halfSpacePosition);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(halfSpaceShape);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+      }
+
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      halfSpaceMappingChecker(testIdentifier,halfSpacePosition,halfSpaceNormal);
+      halfSpaceMappingChecker.checkGhostLayer(testIdentifier,halfSpacePosition,halfSpaceNormal);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   ///////////////////////
+   // ROTATED HALFSPACE //
+   ///////////////////////
+
+   // rotate such that normal is <0,1,0> now
+   Vector3<real_t> rotationAngles( -math::pi / real_t(2), real_t(0), real_t(0));
+   Quaternion<real_t> quat( rotationAngles );
+
+   {
+      std::string testIdentifier("Test: rotated half space with no slip mapping ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create half space
+      {
+         mesa_pd::data::Particle&& p = *ps->create(true);
+         p.setPosition(halfSpacePosition);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(halfSpaceShape);
+         p.setRotation(quat);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), accessor, particleMappingKernel, accessor, NoSlip_Flag );
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      auto rotatedNormal = quat.toRotationMatrix() * halfSpaceNormal;
+
+      // check mapping
+      halfSpaceMappingChecker(testIdentifier,halfSpacePosition,rotatedNormal);
+      halfSpaceMappingChecker.checkGhostLayer(testIdentifier,halfSpacePosition,rotatedNormal);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+
+   return 0;
+
+}
+
+} //namespace particle_mapping
+
+int main( int argc, char **argv ){
+   particle_mapping::main(argc, argv);
+}
diff --git a/tests/lbm_mesapd_coupling/momentum_exchange_method/DragForceSphere.cpp b/tests/lbm_mesapd_coupling/momentum_exchange_method/DragForceSphere.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..18c91bd3ddbdbf45bc5bebc63782a999d9f9f450
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/momentum_exchange_method/DragForceSphere.cpp
@@ -0,0 +1,586 @@
+//======================================================================================================================
+//
+//  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 DragForceSphere.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/Debug.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/logging/Logging.h"
+#include "core/math/all.h"
+#include "core/SharedFunctor.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/mpi/MPIManager.h"
+#include "core/mpi/Reduce.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/communication/PackInfo.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/MacroscopicValueCalculation.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/lattice_model/ForceModel.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.h"
+
+#include "lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.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/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+
+#include "stencil/D3Q27.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include <vector>
+#include <iomanip>
+#include <iostream>
+
+namespace drag_force_sphere_mem
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using ForceModel_T = lbm::force_model::LuoConstant;
+using LatticeModel_T = lbm::D3Q19< lbm::collision_model::TRT, false, ForceModel_T >;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag   ( "fluid" );
+const FlagUID MO_BB_Flag   ( "moving obstacle BB" );
+const FlagUID MO_CLI_Flag  ( "moving obstacle CLI" );
+
+////////////////
+// PARAMETERS //
+////////////////
+
+struct Setup{
+   uint_t checkFrequency;
+   real_t visc;
+   real_t tau;
+   real_t radius;
+   uint_t length;
+   real_t chi;
+   real_t extForce;
+   real_t analyticalDrag;
+};
+
+enum MEMVariant { BB, CLI };
+
+MEMVariant to_MEMVariant( const std::string& s )
+{
+   if( s == "BB"  ) return MEMVariant::BB;
+   if( s == "CLI" ) return MEMVariant::CLI;
+   throw std::runtime_error("invalid conversion from text to MEMVariant");
+}
+
+std::string MEMVariant_to_string ( const MEMVariant& m )
+{
+   if( m == MEMVariant::BB  ) return "BB";
+   if( m == MEMVariant::CLI ) return "CLI";
+   throw std::runtime_error("invalid conversion from MEMVariant to string");
+}
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using SBB_T = lbm_mesapd_coupling::SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using CLI_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, SBB_T, CLI_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  SBB_T("SBB_BB", MO_BB_Flag,  pdfField, flagField, particleField, ac_, fluid, *storage, *block ),
+                                  CLI_T("CLI_BB", MO_CLI_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+
+template< typename ParticleAccessor_T>
+class DragForceEvaluator
+{
+public:
+   DragForceEvaluator( SweepTimeloop* timeloop, Setup* setup, const shared_ptr< StructuredBlockStorage > & blocks,
+                       const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const shared_ptr<ParticleAccessor_T>& ac, walberla::id_t sphereID )
+:     timeloop_( timeloop ), setup_( setup ), blocks_( blocks ),
+      flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ),
+      ac_( ac ), sphereID_( sphereID ),
+      normalizedDragOld_( 0.0 ), normalizedDragNew_( 0.0 )
+   {
+      // calculate the analytical drag force value based on the series expansion of chi
+      // see also Sangani - Slow flow through a periodic array of spheres, IJMF 1982. Eq. 60 and Table 1
+      real_t analyticalDrag = real_c(0);
+      real_t tempChiPowS = real_c(1);
+
+      // coefficients to calculate the drag in a series expansion
+      real_t dragCoefficients[31] = {  real_c(1.000000),  real_c(1.418649),  real_c(2.012564),  real_c(2.331523),  real_c(2.564809),   real_c(2.584787),
+                                       real_c(2.873609),  real_c(3.340163),  real_c(3.536763),  real_c(3.504092),  real_c(3.253622),   real_c(2.689757),
+                                       real_c(2.037769),  real_c(1.809341),  real_c(1.877347),  real_c(1.534685), real_c(0.9034708),  real_c(0.2857896),
+                                       real_c(-0.5512626), real_c(-1.278724),  real_c(1.013350),  real_c(5.492491),  real_c(4.615388), real_c(-0.5736023),
+                                       real_c(-2.865924), real_c(-4.709215), real_c(-6.870076), real_c(0.1455304),  real_c(12.51891),   real_c(9.742811),
+                                       real_c(-5.566269)};
+
+      for(uint_t s = 0; s <= uint_t(30); ++s){
+         analyticalDrag += dragCoefficients[s] * tempChiPowS;
+         tempChiPowS *= setup->chi;
+      }
+      setup_->analyticalDrag = analyticalDrag;
+   }
+
+   // evaluate the acting drag force
+   void operator()()
+   {
+      const uint_t timestep (timeloop_->getCurrentTimeStep()+1);
+
+      if( timestep % setup_->checkFrequency != 0) return;
+
+      // get force in x-direction acting on the sphere
+      real_t forceX = computeDragForce();
+      // get average volumetric flowrate in the domain
+      real_t uBar = computeAverageVel();
+
+      // f_total = f_drag + f_buoyancy
+      real_t totalForce = forceX  + real_c(4.0/3.0) * math::pi * setup_->radius * setup_->radius * setup_->radius * setup_->extForce ;
+
+      real_t normalizedDragForce = totalForce / real_c( 6.0 * math::pi * setup_->visc * setup_->radius * uBar );
+
+      // update drag force values
+      normalizedDragOld_ = normalizedDragNew_;
+      normalizedDragNew_ = normalizedDragForce;
+   }
+
+   // return the relative temporal change in the normalized drag
+   real_t getDragForceDiff() const
+   {
+      return std::fabs( ( normalizedDragNew_ - normalizedDragOld_ ) / normalizedDragNew_ );
+   }
+
+   // return the drag force
+   real_t getDragForce() const
+   {
+      return normalizedDragNew_;
+   }
+
+   void logResultToFile( const std::string & filename ) const
+   {
+      // write to file if desired
+      // format: length tau viscosity simulatedDrag analyticalDrag\n
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( filename.c_str(), std::ofstream::app );
+         file.precision(8);
+         file << setup_->length << " " << setup_->tau << " " << setup_->visc << " " << normalizedDragNew_ << " " << setup_->analyticalDrag << "\n";
+         file.close();
+      }
+   }
+
+private:
+
+   // obtain the drag force acting on the sphere by summing up all the process local parts of fX
+   real_t computeDragForce()
+   {
+      size_t idx = ac_->uidToIdx(sphereID_);
+      real_t force = real_t(0);
+      if( idx!= ac_->getInvalidIdx())
+      {
+         force = ac_->getHydrodynamicForce(idx)[0];
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( force, mpi::SUM );
+      }
+
+      return force;
+   }
+
+   // calculate the average velocity in forcing direction (here: x) inside the domain (assuming dx=1)
+   real_t computeAverageVel()
+   {
+      auto velocity_sum = real_t(0);
+      // iterate all blocks stored locally on this process
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         // retrieve the pdf field and the flag field from the block
+         PdfField_T  *  pdfField = blockIt->getData< PdfField_T >( pdfFieldID_ );
+         FlagField_T * flagField = blockIt->getData< FlagField_T >( flagFieldID_ );
+
+         // get the flag that marks a cell as being fluid
+         auto fluid = flagField->getFlag( Fluid_Flag );
+
+         auto xyzField = pdfField->xyzSize();
+         for (auto cell : xyzField) {
+            if( flagField->isFlagSet( cell.x(), cell.y(), cell.z(), fluid ) )
+            {
+             velocity_sum += pdfField->getVelocity(cell)[0];
+            }
+         }
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( velocity_sum, mpi::SUM );
+      }
+
+      return velocity_sum / real_c( setup_->length * setup_->length * setup_->length );
+   }
+
+   SweepTimeloop* timeloop_;
+
+   Setup* setup_;
+
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+   const walberla::id_t sphereID_;
+
+   // drag coefficient
+   real_t normalizedDragOld_;
+   real_t normalizedDragNew_;
+
+};
+
+//////////
+// MAIN //
+//////////
+
+
+//*******************************************************************************************************************
+/*!\brief Testcase that checks the drag force acting on a fixed sphere in the center of a cubic domain in Stokes flow
+ *
+ * The drag force for this problem (often denoted as Simple Cubic setup) is given by a semi-analytical series expansion.
+ * The cubic domain is periodic in all directions, making it a physically infinite periodic array of spheres.
+ *         _______________
+ *      ->|               |->
+ *      ->|      ___      |->
+ *    W ->|     /   \     |-> E
+ *    E ->|    |  x  |    |-> A
+ *    S ->|     \___/     |-> S
+ *    T ->|               |-> T
+ *      ->|_______________|->
+ *
+ * The collision model used for the LBM is TRT with a relaxation parameter tau=1.5 and the magic parameter 3/16.
+ * The Stokes approximation of the equilibrium PDFs is used.
+ * The flow is driven by a constant body force of 1e-5.
+ * The domain is 32x32x32, and the sphere has a diameter of 16 cells ( chi * domainlength )
+ * The simulation is run until the relative change in the dragforce between 100 time steps is less than 1e-5.
+ * The RPD is not used since the sphere is kept fixed and the force is explicitly reset after each time step.
+ * To avoid periodicity constrain problems, the sphere is set as global.
+ *
+ */
+//*******************************************************************************************************************
+
+
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   auto processes = MPIManager::instance()->numProcesses();
+
+   if( processes != 1 && processes != 2 && processes != 4 && processes != 8)
+   {
+      std::cerr << "Number of processes must be equal to either 1, 2, 4, or 8!" << std::endl;
+      return EXIT_FAILURE;
+   }
+
+   ///////////////////
+   // Customization //
+   ///////////////////
+
+   bool shortrun  = false;
+   bool funcTest  = false;
+   bool logging   = false;
+   MEMVariant method = MEMVariant::BB;
+   real_t tau     = real_c( 1.5 );
+   uint_t length  = uint_c( 32 );
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--shortrun"  ) == 0 ) { shortrun  = true; continue; }
+      if( std::strcmp( argv[i], "--funcTest"  ) == 0 ) { funcTest  = true; continue; }
+      if( std::strcmp( argv[i], "--logging"   ) == 0 ) { logging   = true; continue; }
+      if( std::strcmp( argv[i], "--MEMVariant") == 0 ) { method    = to_MEMVariant( argv[++i] ); continue; }
+      if( std::strcmp( argv[i], "--tau"       ) == 0 ) { tau       = real_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--length"    ) == 0 ) { length    = uint_c( std::atof( argv[++i] ) ); continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   Setup setup;
+
+   setup.length         = length;                            // length of the cubic domain in lattice cells
+   setup.chi            = real_c( 0.5 );                     // porosity parameter: diameter / length
+   setup.tau            = tau;                               // relaxation time
+   setup.extForce       = real_c( 1e-7 );                    // constant body force in lattice units
+   setup.checkFrequency = uint_t( 100 );                     // evaluate the drag force only every checkFrequency time steps
+   setup.radius         = real_c(0.5) * setup.chi * real_c( setup.length );  // sphere radius
+   setup.visc           = ( setup.tau - real_c(0.5) ) / real_c(3);   // viscosity in lattice units
+   const real_t omega      = real_c(1) / setup.tau;          // relaxation rate
+   const real_t dx         = real_c(1);                      // lattice dx
+   const real_t convergenceLimit = real_c( 1e-7 );           // tolerance for relative change in drag force
+   const uint_t timesteps  =  funcTest ? 1 : ( shortrun ? uint_c(150) : uint_c( 50000 ) );  // maximum number of time steps for the whole simulation
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+   const uint_t XBlocks = (processes >= 2) ? uint_t( 2 ) : uint_t( 1 );
+   const uint_t YBlocks = (processes >= 4) ? uint_t( 2 ) : uint_t( 1 );
+   const uint_t ZBlocks = (processes == 8) ? uint_t( 2 ) : uint_t( 1 );
+   const uint_t XCells = setup.length / XBlocks;
+   const uint_t YCells = setup.length / YBlocks;
+   const uint_t ZCells = setup.length / ZBlocks;
+
+   // create fully periodic domain
+   auto blocks = blockforest::createUniformBlockGrid( XBlocks, YBlocks, ZBlocks, XCells, YCells, ZCells, dx, true,
+                                                      true, true, true );
+
+
+   /////////
+   // RPD //
+   /////////
+
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+
+   //init data structures
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = make_shared<ParticleAccessor_T >(ps, ss);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( setup.radius );
+
+   //////////////////
+   // RPD COUPLING //
+   //////////////////
+
+   // connect to pe
+   const real_t overlap = real_t( 1.5 ) * dx;
+
+   if( setup.radius > real_c( setup.length ) * real_t(0.5) - overlap )
+   {
+      std::cerr << "Periodic sphere is too large and would lead to incorrect mapping!" << std::endl;
+      // solution: create the periodic copies explicitly
+      return EXIT_FAILURE;
+   }
+
+   // create the sphere in the middle of the domain
+   // it is global and thus present on all processes
+   Vector3<real_t> position (real_c(setup.length) * real_c(0.5));
+   walberla::id_t sphereID;
+   {
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(position);
+      p.setInteractionRadius(setup.radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+      sphereID = p.getUid();
+   }
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // create the lattice model
+   LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::TRT::constructWithMagicNumber( omega ), ForceModel_T( Vector3<real_t> ( setup.extForce, 0, 0 ) ) );
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (zyxf)", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         uint_t(1), field::zyxf );
+
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::zyxf, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // create the timeloop
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+   blockforest::communication::UniformBufferedScheme< Stencil_T > optimizedPDFCommunicationScheme( blocks );
+   optimizedPDFCommunicationScheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) ); // optimized sync
+
+   // initially map particles into the LBM simulation
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+   if( method == MEMVariant::CLI )
+   {
+      // uses a higher order boundary condition (CLI)
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, movingParticleMappingKernel, *accessor, MO_CLI_Flag);
+   }else{
+      // uses standard bounce back boundary conditions
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, movingParticleMappingKernel, *accessor, MO_BB_Flag);
+   }
+
+   // since external forcing is applied, the evaluation of the velocity has to be carried out directly after the streaming step
+   // however, the default sweep is a  stream - collide step, i.e. after the sweep, the velocity evaluation is not correct
+   // solution: split the sweep explicitly into collide and stream
+   auto sweep = lbm::makeCellwiseSweep< LatticeModel_T, FlagField_T >( pdfFieldID, flagFieldID, Fluid_Flag );
+
+   // collision sweep
+   timeloop.add() << Sweep( lbm::makeCollideSweep( sweep ), "cell-wise LB sweep (collide)" );
+
+   // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+   timeloop.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" )
+                  << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+   // streaming & force evaluation
+   using DragForceEval_T = DragForceEvaluator<ParticleAccessor_T>;
+   auto forceEval = make_shared< DragForceEval_T >( &timeloop, &setup, blocks, flagFieldID, pdfFieldID, accessor, sphereID );
+   timeloop.add() << Sweep( lbm::makeStreamSweep( sweep ), "cell-wise LB sweep (stream)" )
+                  << AfterFunction( SharedFunctor< DragForceEval_T >( forceEval ), "drag force evaluation" );
+
+   // resetting force
+   timeloop.addFuncAfterTimeStep( [ps,accessor](){ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel(), *accessor);}, "reset force on sphere");
+
+   timeloop.addFuncAfterTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+   // time loop
+   for (uint_t i = 0; i < timesteps; ++i )
+   {
+      // perform a single simulation step
+      timeloop.singleStep( timeloopTiming );
+
+      // check if the relative change in the normalized drag force is below the specified convergence criterion
+      if ( i > setup.checkFrequency && forceEval->getDragForceDiff() < convergenceLimit )
+      {
+         // if simulation has converged, terminate simulation
+         break;
+      }
+
+   }
+
+   timeloopTiming.logResultOnRoot();
+
+   if ( !funcTest && !shortrun ){
+      // check the result
+      real_t relErr = std::fabs( ( setup.analyticalDrag - forceEval->getDragForce() ) / setup.analyticalDrag );
+      if ( logging )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::cout << "Analytical drag: " << setup.analyticalDrag << "\n"
+                      << "Simulated drag: " << forceEval->getDragForce() << "\n"
+                      << "Relative error: " << relErr << "\n";
+         }
+         forceEval->logResultToFile( "log_DragForceSphereMEM_"+MEMVariant_to_string(method)+".txt" );
+      }
+      // the relative error has to be below 10%
+      WALBERLA_CHECK_LESS( relErr, real_c(0.1) );
+   }
+
+   return 0;
+
+}
+
+} //namespace drag_force_sphere_mem
+
+int main( int argc, char **argv ){
+   drag_force_sphere_mem::main(argc, argv);
+}
diff --git a/tests/lbm_mesapd_coupling/momentum_exchange_method/ForceBetweenTwoStationaryObjects.cpp b/tests/lbm_mesapd_coupling/momentum_exchange_method/ForceBetweenTwoStationaryObjects.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e244543af014d124057c8b187ef59e7395cb8116
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/momentum_exchange_method/ForceBetweenTwoStationaryObjects.cpp
@@ -0,0 +1,431 @@
+//======================================================================================================================
+//
+//  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 ForceBetweenTwoStationaryObjects.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/Debug.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/logging/Logging.h"
+#include "core/math/all.h"
+#include "core/SharedFunctor.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/mpi/MPIManager.h"
+#include "core/mpi/Reduce.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/communication/PackInfo.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/MacroscopicValueCalculation.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/lattice_model/ForceModel.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.h"
+
+#include "lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.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/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+
+#include "stencil/D3Q27.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include <iomanip>
+#include <iostream>
+
+namespace force_between_two_stationary_objects
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::D3Q19< lbm::collision_model::TRT>;
+using LatticeModelComp_T = lbm::D3Q19< lbm::collision_model::TRT, true>;
+
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag   ( "fluid" );
+const FlagUID BB_Flag   ( "obstacle BB" );
+const FlagUID CLI_Flag  ( "obstacle CLI" );
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+
+template <typename LM_T, typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using PdfField_T = lbm::PdfField<LM_T>;
+   using SBB_T = lbm_mesapd_coupling::SimpleBB< LM_T, FlagField_T, ParticleAccessor_T >;
+   using CLI_T = lbm_mesapd_coupling::CurvedLinear< LM_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, typename LM_T::Stencil, SBB_T, CLI_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  SBB_T("SBB_BB", BB_Flag,  pdfField, flagField, particleField, ac_, fluid, *storage, *block ),
+                                  CLI_T("CLI_BB", CLI_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+
+void createPlaneSetup(const shared_ptr<mesa_pd::data::ParticleStorage> & ps, const shared_ptr<mesa_pd::data::ShapeStorage> & ss,
+                      const math::AABB & simulationDomain, Vector3<real_t> velocity)
+{
+   // create bounding planes
+   mesa_pd::data::Particle p0 = *ps->create(true);
+   p0.setPosition(simulationDomain.minCorner());
+   p0.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,1) ));
+   p0.setOwner(mpi::MPIManager::instance()->rank());
+   p0.setType(0);
+   p0.setLinearVelocity(velocity);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p1 = *ps->create(true);
+   p1.setPosition(simulationDomain.maxCorner());
+   p1.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,-1) ));
+   p1.setOwner(mpi::MPIManager::instance()->rank());
+   p1.setType(0);
+   p1.setLinearVelocity(velocity);
+   mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p2 = *ps->create(true);
+   p2.setPosition(simulationDomain.minCorner());
+   p2.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(1,0,0) ));
+   p2.setOwner(mpi::MPIManager::instance()->rank());
+   p2.setType(0);
+   p2.setLinearVelocity(velocity);
+   mesa_pd::data::particle_flags::set(p2.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p2.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p3 = *ps->create(true);
+   p3.setPosition(simulationDomain.maxCorner());
+   p3.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(-1,0,0) ));
+   p3.setOwner(mpi::MPIManager::instance()->rank());
+   p3.setType(0);
+   p3.setLinearVelocity(velocity);
+   mesa_pd::data::particle_flags::set(p3.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p3.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p4 = *ps->create(true);
+   p4.setPosition(simulationDomain.minCorner());
+   p4.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,1,0) ));
+   p4.setOwner(mpi::MPIManager::instance()->rank());
+   p4.setType(0);
+   p4.setLinearVelocity(velocity);
+   mesa_pd::data::particle_flags::set(p4.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p4.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p5 = *ps->create(true);
+   p5.setPosition(simulationDomain.maxCorner());
+   p5.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,-1,0) ));
+   p5.setOwner(mpi::MPIManager::instance()->rank());
+   p5.setType(0);
+   p5.setLinearVelocity(velocity);
+   mesa_pd::data::particle_flags::set(p5.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p5.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+}
+
+template< typename LM_T, typename ParticleAccessor_T>
+void createSimulationSetup( shared_ptr< StructuredBlockForest > blocks, shared_ptr<ParticleAccessor_T> accessor,
+                            bool useSBB, shared_ptr<mesa_pd::data::ParticleStorage> ps, Vector3<real_t> velocity,
+                            SweepTimeloop & timeloop )
+{
+   real_t omega = real_t(1);
+
+   // create the lattice model
+   LM_T latticeModel = LM_T( lbm::collision_model::TRT::constructWithMagicNumber( omega ) );
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LM_T >( blocks, "pdf field (zyxf)", latticeModel, velocity, real_t(1), uint_t(1), field::zyxf );
+
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::zyxf, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = typename MyBoundaryHandling<LM_T,ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<LM_T,ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+   timeloop.add() << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+   // LBM
+   auto sweep = lbm::makeCellwiseSweep< LM_T, FlagField_T >( pdfFieldID, flagFieldID, Fluid_Flag );
+   timeloop.add() << Sweep( makeSharedSweep(sweep), "cell-wise LB sweep" );
+
+   // map particles
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+   if( useSBB )
+   {
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, movingParticleMappingKernel, *accessor, BB_Flag);
+   }else{
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, movingParticleMappingKernel, *accessor, CLI_Flag);
+   }
+}
+
+
+//*******************************************************************************************************************
+/*!\brief Testcase that checks the force between two stationary objects (sphere-sphere and sphere-wall) in a quiescent fluid.
+ *
+ * Since the fluid has zero velocity, as well as the objects, the force has to be zero!
+ *
+ * The objects have only a small distance.
+ * Thus, not all cells around the sphere contain fluid and are thus skipped by the boundary handling and force computation.
+ * As a result, some force components are missing that balance the total force such that it adds up to zero.
+ * As such, one would need to reconstruct the PDF values inside the missing (from the perspective of one of the obstacles)
+ * fluid cells to then obtain the balance again.
+ * This is e.g. done in
+ *  - Ernst, Dietzel, Sommerfeld - A lattice Boltzmann method for simulating transport and agglomeration of resolved particles
+ *    doi: 10.1007/s00707-013-0923-1 (2013), Section 2.2.4
+ *  - in the work of Simon Bogner
+ *
+ * This is avoided here via the following:
+ * ( also see the docu in the beginning of lbm/PdfField.h)
+ * When using an incompressible LBM model in waLBerla, all stored PDF values are simply the offset from the default weights.
+ * I.e. in a quiescent fluid with density 1, all of them are zero.
+ * Consequently, the force contributions computed by the momentum exchange method are also all zero in that case.
+ * It thus does not matter if some of them are missing due to a nearby obstacle because only zero values would be missing.
+ *
+ * This changes when using a compressible model, as then the stored PDF values are the regular ones in waLBerla.
+ * As such, one would see the missing force contributions in this test.
+ * To compensate this, the PDF values are explicitly normalized like for the incompressible case in the MEM boundary conditions.
+ * Then, the same arguments as for the incompressible flow hold.
+ *
+ * This can also be seen as an implicit reconstruction of the PDFs in the missing cells with feq(0, vec(0)).
+ * As such, it can/will lead to errors if the velocity of the obstacle covering the missing cells is different from the
+ * surrounding flow. In that case, an explicit reconstruction like in Ernst et al. should be applied, which however will
+ * change the algorithm, as the computation of the force components can probbaly no longer be fused with the boundary handling.
+ *
+ * Tested variants:
+ *  - boundary condition: SimpleBB, CLI
+ *  - compressible and incompressible lattice model
+ *  - different surface distances
+ *  - sphere-sphere and sphere-wall
+ *  - different system velocity, i.e. velocity of ALL things is constant but can be non-zero -> zero force expected
+ *
+ */
+//*******************************************************************************************************************
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   ///////////////////
+   // Customization //
+   ///////////////////
+
+   bool useCompressible = false;
+   bool useSBB = false;
+   bool useSphereWallSetup = false;
+   real_t surfaceDistance = real_t(0.5);
+   real_t systemVelocity = real_t(0);
+   uint_t timesteps = uint_t(10);
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--useCompressible"  ) == 0 ) { useCompressible  = true; continue;}
+      if( std::strcmp( argv[i], "--useSBB"  ) == 0 ) { useSBB  = true; continue;}
+      if( std::strcmp( argv[i], "--useSphereWallSetup"  ) == 0 ) { useSphereWallSetup  = true; continue;}
+      if( std::strcmp( argv[i], "--surfaceDistance"    ) == 0 ) { surfaceDistance = real_c(std::atof( argv[++i])); continue;}
+      if( std::strcmp( argv[i], "--systemVelocity"    ) == 0 ) { systemVelocity = real_c(std::atof( argv[++i])); continue;}
+      if( std::strcmp( argv[i], "--timesteps"    ) == 0 ) { timesteps = uint_c(std::atof( argv[++i])); continue;}
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   const uint_t length = uint_t(32);
+   const real_t radius = real_t(5);
+   const Vector3<real_t> velocity(systemVelocity, real_t(0), real_t(0));
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+   const uint_t XBlocks = uint_t( 1 );
+   const uint_t YBlocks = uint_t( 1 );
+   const uint_t ZBlocks = uint_t( 1 );
+   const uint_t XCells = length / XBlocks;
+   const uint_t YCells = length / YBlocks;
+   const uint_t ZCells = length / ZBlocks;
+
+   auto blocks = blockforest::createUniformBlockGrid( XBlocks, YBlocks, ZBlocks, XCells, YCells, ZCells, uint_t(1), true,
+                                                      false, false, false );
+
+
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = make_shared<ParticleAccessor_T >(ps, ss);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+
+   createPlaneSetup(ps, ss, walberla::math::AABB(real_t(0), real_t(0), real_t(0), length, length, length), velocity);
+
+   walberla::id_t sphereID;
+   if(useSphereWallSetup)
+   {
+      // create sphere close to right wall
+      Vector3<real_t> position1 (real_c(length) - radius - surfaceDistance,
+                                 real_c(length) * real_c(0.5),
+                                 real_c(length) * real_c(0.5));
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(position1);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         p.setLinearVelocity(velocity);
+         sphereID = p.getUid();
+      }
+   } else {
+      // create two spheres
+      Vector3<real_t> position1 (real_c(length) * real_c(0.5) - radius - surfaceDistance*real_t(0.5),
+                                 real_c(length) * real_c(0.5),
+                                 real_c(length) * real_c(0.5));
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(position1);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         p.setLinearVelocity(velocity);
+         sphereID = p.getUid();
+      }
+
+
+      Vector3<real_t> position2 (real_c(length) * real_c(0.5) + radius + surfaceDistance*real_t(0.5),
+                                 real_c(length) * real_c(0.5),
+                                 real_c(length) * real_c(0.5));
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(position2);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+         p.setLinearVelocity(velocity);
+      }
+   }
+
+   // create the timeloop
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+   if(useCompressible) createSimulationSetup<LatticeModelComp_T>(blocks, accessor, useSBB, ps, velocity , timeloop);
+   else                createSimulationSetup<LatticeModel_T>(blocks, accessor, useSBB, ps, velocity, timeloop);
+
+
+   for(uint_t t = 0; t < timeloop.getNrOfTimeSteps(); ++t)
+   {
+      // some timesteps
+      timeloop.singleStep();
+
+      // check force
+      size_t idx = accessor->uidToIdx(sphereID);
+      auto hydrodynamicForce = accessor->getHydrodynamicForce(idx);
+
+      //WALBERLA_LOG_INFO(hydrodynamicForce);
+      for(uint_t comp = 0; comp < 3; ++comp)
+      {
+         WALBERLA_CHECK_FLOAT_EQUAL(hydrodynamicForce[comp], real_t(0), "Found non-zero force in component " << comp);
+      }
+
+      lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+      ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor );
+
+   }
+
+   return 0;
+
+}
+
+} //namespace force_between_two_stationary_objects
+
+int main( int argc, char **argv ){
+   force_between_two_stationary_objects::main(argc, argv);
+}
diff --git a/tests/lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.cpp b/tests/lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a390f6feb62535af2d6edf140244649eecf9ba55
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.cpp
@@ -0,0 +1,763 @@
+//======================================================================================================================
+//
+//  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 MovingParticleMapping.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/Debug.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/math/all.h"
+#include "core/mpi/MPIManager.h"
+#include "core/mpi/Reduce.h"
+
+#include "field/AddToStorage.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+
+#include "lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/SimpleBB.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/kernel/SingleCast.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+
+namespace moving_particle_mapping
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::D3Q19<lbm::collision_model::SRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+const uint_t FieldGhostLayers = 1;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag ( "fluid" );
+const FlagUID MO_Flag ( "moving obstacle" );
+const FlagUID FormerMO_Flag ( "former moving obstacle" );
+
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+
+template <typename ParticleAccessor>
+class MyBoundaryHandling
+{
+public:
+
+   using MO_T = lbm_mesapd_coupling::SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, MO_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor>& ac) :
+      flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  MO_T("MO_BB",  MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor> ac_;
+
+};
+
+template <typename BoundaryHandling_T>
+class MappingChecker
+{
+public:
+   MappingChecker(const shared_ptr< StructuredBlockStorage > & blocks,
+                  const BlockDataID & boundaryHandlingID, real_t sphereRadius) :
+         blocks_( blocks ), boundaryHandlingID_( boundaryHandlingID ),
+         sphereRadius_( sphereRadius ), sphereVolume_( math::pi * real_t(4) / real_t(3) * sphereRadius * sphereRadius * sphereRadius )
+   {
+      WALBERLA_ASSERT(blocks->isXPeriodic());
+   }
+
+   // check the mapping in the inner domain of the block and check mapped volume against real sphere volume
+   void operator()(std::string & testIdentifier, const Vector3<real_t> & pos, bool periodic )
+   {
+      uint_t cellCounter( uint_t(0) );
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+
+         auto xyzSize = flagField->xyzSize();
+
+         for (auto cellIt : xyzSize) {
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter(*blockIt, cellIt);
+            real_t distance = (cellCenter - pos).length();
+            if( periodic )
+            {
+               Vector3<real_t> periodicOffset(blocks_->getDomain().xSize(),0,0);
+               // check if other (periodic) copies are closer
+               distance = std::min(distance, (cellCenter - (pos-periodicOffset)).length());
+               distance = std::min(distance, (cellCenter - (pos+periodicOffset)).length());
+            }
+
+            if (distance < sphereRadius_) {
+               WALBERLA_CHECK(boundaryHandling->isBoundary(cellIt),
+                              testIdentifier << "Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected boundary cell. Distance cell center - particle center = "
+                                             << distance << ".");
+               ++cellCounter;
+            } else {
+               WALBERLA_CHECK(boundaryHandling->isDomain(cellIt),
+                              testIdentifier << "Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected domain cell. Distance cell center - particle center = "
+                                             << distance << ".");
+            }
+         }
+      }
+
+      mpi::allReduceInplace(cellCounter, mpi::SUM);
+
+      // mapped volume has to be - approximately - the same as the real volume
+      real_t mappedVolume = real_c(cellCounter); // dx=1
+      WALBERLA_CHECK(std::fabs( mappedVolume - sphereVolume_ ) / sphereVolume_ <= real_t(0.1),
+                     "Mapped volume " << mappedVolume << " does not fit to real sphere volume " << sphereVolume_ << ".");
+   }
+
+   // checks only the mapping in the ghost layers
+   void checkGhostLayer(std::string & testIdentifier, const Vector3<real_t> & pos, bool periodic )
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+
+         auto xyzSizeWGl = flagField->xyzSizeWithGhostLayer();
+
+         for (auto cellIt : xyzSizeWGl) {
+            if( flagField->isInInnerPart(cellIt) ) continue;
+
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter( *blockIt, cellIt);
+            real_t distance = (cellCenter - pos).length();
+            if( periodic )
+            {
+               Vector3<real_t> periodicOffset(blocks_->getDomain().xSize(),0,0);
+               // check if other (periodic) copies are closer
+               distance = std::min(distance, (cellCenter - (pos-periodicOffset)).length());
+               distance = std::min(distance, (cellCenter - (pos+periodicOffset)).length());
+            }
+
+            if( distance < sphereRadius_ )
+            {
+               WALBERLA_CHECK( boundaryHandling->isBoundary(cellIt),
+                               testIdentifier << ": Invalid mapping in ghost layer cell " << cellIt
+                                              << " with center at " << cellCenter
+                                              << " - expected boundary cell. Distance cell center - body center = "
+                                              << distance << ".");
+            }
+            else
+            {
+               WALBERLA_CHECK( boundaryHandling->isDomain(cellIt),
+                               testIdentifier << ": Invalid mapping in ghost layer cell " << cellIt
+                                              << " with center at " << cellCenter
+                                              << " - expected domain cell. Distance cell center - body center = "
+                                              << distance << "." );
+            }
+         }
+      }
+   }
+
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+   real_t sphereRadius_, sphereVolume_;
+
+};
+
+template< typename BoundaryHandling_T>
+class MappingResetter
+{
+public:
+   MappingResetter(const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & boundaryHandlingID, const BlockDataID & particleFieldID, walberla::id_t invalidUID) :
+         blocks_( blocks ), boundaryHandlingID_( boundaryHandlingID ), particleFieldID_( particleFieldID ), invalidUID_( invalidUID )
+   { }
+
+   void operator()()
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+         auto *    particleField = blockIt->getData< lbm_mesapd_coupling::ParticleField_T >( particleFieldID_ );
+
+         auto xyzSizeWGl = flagField->xyzSizeWithGhostLayer();
+         // reset to domain (fluid)
+         boundaryHandling->forceDomain(xyzSizeWGl);
+
+         for( auto cellIt = xyzSizeWGl.begin(); cellIt != xyzSizeWGl.end(); ++cellIt )
+         {
+            // reset body field
+            particleField->get(*cellIt) = invalidUID_;
+         }
+      }
+   }
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+   const BlockDataID particleFieldID_;
+   walberla::id_t invalidUID_;
+};
+
+
+
+/*!\brief Test case for the particle mapping functionality
+ *
+ * The following mapping functions are tested:
+ *  - RegularParticlesSelector
+ *  - GlobalParticlesSelector
+ *  - FixedParticlesSelector
+ * The mapping inside a block, at a regular block boarder and at a periodic block boarder is tested.
+ * Regular, global and infinite-mass spheres are tested.
+ * After each test, the sphere is destroyed and the mapping is erased from the datastructures.
+ *
+ * The setup is as follows (x marks the different sphere positions, # is a periodic border)
+ * ----------------------------------------------------------------------
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #X         X          X|                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * #                      |                      |                      #
+ * ----------------------------------------------------------------------
+ */
+
+
+//////////
+// MAIN //
+//////////
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   WALBERLA_CHECK(mpi::MPIManager::instance()->numProcesses()==3, "Due to periodicity, three processes have to be used!");
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   bool writeVTK = false;
+   const real_t omega  = real_t(1);
+   const real_t dx     = real_t(1);
+   const real_t radius = real_t(5);
+
+   ///////////////////////////
+   // DATA STRUCTURES SETUP //
+   ///////////////////////////
+
+   Vector3<uint_t> blocksPerDirection(uint_t(3), uint_t(1), uint_t(1));
+   Vector3<uint_t> cellsPerBlock(uint_t(20), uint_t(20), uint_t(20));
+   Vector3<bool> periodicity(true, false, false);
+
+   auto blocks = blockforest::createUniformBlockGrid( blocksPerDirection[0], blocksPerDirection[1], blocksPerDirection[2],
+                                                      cellsPerBlock[0], cellsPerBlock[1], cellsPerBlock[2],
+                                                      dx,
+                                                      1, false, false,
+                                                      periodicity[0], periodicity[1], periodicity[2],
+                                                      false );
+
+   // create the lattice model
+   LatticeModel_T latticeModel = LatticeModel_T( omega );
+
+   // add PDF field
+   BlockDataID 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 );
+
+   // rpd setup
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+
+   //init data structures
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   shared_ptr<ParticleAccessor_T> accessor = make_shared<ParticleAccessor_T>(ps, ss);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+   mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+
+   // coupling
+   const real_t overlap = real_t( 1.5 ) * dx;
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::zyxf, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // vtk
+   auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field" );
+   flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+
+   // testing utilities
+   MappingChecker<BoundaryHandling_T> mappingChecker(blocks, boundaryHandlingID, radius);
+   MappingResetter<BoundaryHandling_T> mappingResetter(blocks, boundaryHandlingID, particleFieldID, accessor->getInvalidUid());
+
+   // mapping functors
+   auto regularParticleMapper = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, lbm_mesapd_coupling::RegularParticlesSelector(), true );
+   auto globalParticleMapper = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, lbm_mesapd_coupling::GlobalParticlesSelector(), true );
+
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+
+   // sphere positions for test scenarios
+   Vector3<real_t> positionInsideBlock(real_t(10), real_t(10), real_t(10));
+   Vector3<real_t> positionAtBlockBoarder(real_t(19), real_t(10), real_t(10));
+   Vector3<real_t> positionAtPeriodicBoarder(real_t(1), real_t(10), real_t(10));
+
+   //////////////////////////
+   // MOVING BODY MAPPING //
+   /////////////////////////
+
+   //////////////////////////////////
+   // TEST SPHERE INSIDE ONE BLOCK //
+   //////////////////////////////////
+
+   //////////////////////
+   // REGULAR SPHERE 1 //
+   //////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere inside block with moving particle mapping, case 1 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionInsideBlock ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionInsideBlock);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionInsideBlock,false);
+      mappingChecker.checkGhostLayer(testIdentifier,positionInsideBlock,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   //////////////////////
+   // REGULAR SPHERE 2 //
+   //////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere inside block with moving particle mapping, case 2 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionInsideBlock ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionInsideBlock);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionInsideBlock,false);
+      mappingChecker.checkGhostLayer(testIdentifier,positionInsideBlock,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   /////////////////////
+   // GLOBAL SPHERE 1 //
+   /////////////////////
+   {
+      std::string testIdentifier("Test: global sphere inside block with moving particle mapping, case 1 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(positionInsideBlock);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionInsideBlock,false);
+      mappingChecker.checkGhostLayer(testIdentifier,positionInsideBlock,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   /////////////////////
+   // GLOBAL SPHERE 2 //
+   /////////////////////
+   {
+      std::string testIdentifier("Test: global sphere inside block with moving particle mapping, case 2 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(positionInsideBlock);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) globalParticleMapper(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionInsideBlock,false);
+      mappingChecker.checkGhostLayer(testIdentifier,positionInsideBlock,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   //////////////////////////////////
+   // TEST SPHERE AT BLOCK BOARDER //
+   //////////////////////////////////
+
+   //////////////////////
+   // REGULAR SPHERE 1 //
+   //////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere at block boarder with moving particle mapping, case 1 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtBlockBoarder ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionAtBlockBoarder);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionAtBlockBoarder,false);
+      mappingChecker.checkGhostLayer(testIdentifier,positionAtBlockBoarder,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   //////////////////////
+   // REGULAR SPHERE 2 //
+   //////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere at block boarder with moving particle mapping, case 2 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtBlockBoarder ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionAtBlockBoarder);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionAtBlockBoarder,false);
+      mappingChecker.checkGhostLayer(testIdentifier,positionAtBlockBoarder,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   /////////////////////
+   // GLOBAL SPHERE 1 //
+   /////////////////////
+   {
+      std::string testIdentifier("Test: global sphere at block boarder with moving particle mapping, case 1 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(positionAtBlockBoarder);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionAtBlockBoarder,false);
+      mappingChecker.checkGhostLayer(testIdentifier,positionAtBlockBoarder,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   /////////////////////
+   // GLOBAL SPHERE 2 //
+   /////////////////////
+   {
+      std::string testIdentifier("Test: global sphere at block boarder with moving particle mapping, case 2 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      mesa_pd::data::Particle&& p = *ps->create(true);
+      p.setPosition(positionAtBlockBoarder);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) globalParticleMapper(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionAtBlockBoarder,false);
+      mappingChecker.checkGhostLayer(testIdentifier,positionAtBlockBoarder,false);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   /////////////////////////////////////
+   // TEST SPHERE AT PERIODIC BOARDER //
+   /////////////////////////////////////
+
+   //////////////////////
+   // REGULAR SPHERE 1 //
+   //////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere at periodic boarder with moving particle mapping, case 1 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtPeriodicBoarder ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionAtPeriodicBoarder);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionAtPeriodicBoarder,true);
+      mappingChecker.checkGhostLayer(testIdentifier,positionAtPeriodicBoarder,true);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   //////////////////////
+   // REGULAR SPHERE 2 //
+   //////////////////////
+   {
+      std::string testIdentifier("Test: regular sphere at periodic boarder with moving particle mapping, case 2 ");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      // create sphere
+      if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtPeriodicBoarder ))
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(positionAtPeriodicBoarder);
+         p.setInteractionRadius(radius);
+         p.setOwner(mpi::MPIManager::instance()->rank());
+         p.setShapeID(sphereShape);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // map
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check mapping
+      mappingChecker(testIdentifier,positionAtPeriodicBoarder,true);
+      mappingChecker.checkGhostLayer(testIdentifier,positionAtPeriodicBoarder,true);
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+      syncNextNeighborFunc(*ps, domain, overlap);
+   }
+
+   /////////////////////
+   // GLOBAL SPHERE 1 //
+   /////////////////////
+
+   // left out since periodicity does not affect global particles
+
+   return 0;
+
+}
+
+} //namespace moving_particle_mapping
+
+int main( int argc, char **argv ){
+   moving_particle_mapping::main(argc, argv);
+}
diff --git a/tests/lbm_mesapd_coupling/momentum_exchange_method/PdfReconstruction.cpp b/tests/lbm_mesapd_coupling/momentum_exchange_method/PdfReconstruction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c280a45f84cd5a7f8f79212381591ed732875c1a
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/momentum_exchange_method/PdfReconstruction.cpp
@@ -0,0 +1,922 @@
+//======================================================================================================================
+//
+//  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 PDFReconstruction.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/Debug.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/math/all.h"
+#include "core/mpi/MPIManager.h"
+#include "core/mpi/Reduce.h"
+
+#include "field/AddToStorage.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+
+#include "lbm_mesapd_coupling/momentum_exchange_method/MovingParticleMapping.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/SimpleBB.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/momentum_exchange_method/reconstruction/ExtrapolationDirectionFinder.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.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/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/SingleCast.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+
+namespace pdf_reconstruction
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::D3Q19<lbm::collision_model::SRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+const uint_t FieldGhostLayers = 1;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag ( "fluid" );
+const FlagUID MO_Flag ( "moving obstacle" );
+const FlagUID FormerMO_Flag ( "former moving obstacle" );
+
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+
+template <typename ParticleAccessor>
+class MyBoundaryHandling
+{
+public:
+
+   using MO_T = lbm_mesapd_coupling::SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, MO_T>;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor>& ac) :
+      flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  MO_T("MO_BB",  MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor> ac_;
+
+};
+
+template< typename PdfField_T, typename BoundaryHandling_T>
+class ReconstructionChecker
+{
+public:
+   ReconstructionChecker(const shared_ptr< StructuredBlockStorage > & blocks,const BlockDataID & pdfFieldID,
+                         const BlockDataID & boundaryHandlingID, Vector3<real_t> expectedVelocity, real_t expectedDensity,
+                         const FlagUID formerObstacle) :
+   blocks_( blocks ), pdfFieldID_( pdfFieldID ), boundaryHandlingID_ (boundaryHandlingID ), expectedVelocity_( expectedVelocity ), expectedDensity_( expectedDensity ), formerObstacle_(formerObstacle)
+   { }
+
+   void operator()(std::string testIdentifier)
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt ) {
+         auto *pdfField         = blockIt->getData<PdfField_T>(pdfFieldID_);
+         auto *boundaryHandling = blockIt->getData<BoundaryHandling_T>(boundaryHandlingID_);
+         auto *flagField        = boundaryHandling->getFlagField();
+
+         auto xyzSize = pdfField->xyzSize();
+
+         const flag_t formerObstacle = flagField->getFlag( formerObstacle_ );
+
+         for (auto cellIt : xyzSize) {
+            WALBERLA_CHECK(!isFlagSet(flagField->get(cellIt), formerObstacle),testIdentifier << "Found incorrect formerObstacle flag in cell " << cellIt << ".")
+
+            if(boundaryHandling->isDomain(cellIt))
+            {
+               auto velocity = pdfField->getVelocity(cellIt);
+               WALBERLA_CHECK_FLOAT_EQUAL(velocity[0], expectedVelocity_[0],testIdentifier << "Invalid velocity X in cell " << cellIt << ".");
+               WALBERLA_CHECK_FLOAT_EQUAL(velocity[1], expectedVelocity_[1],testIdentifier << "Invalid velocity Y in cell " << cellIt << ".");
+               WALBERLA_CHECK_FLOAT_EQUAL(velocity[2], expectedVelocity_[2],testIdentifier << "Invalid velocity Z in cell " << cellIt << ".");
+
+               auto density = pdfField->getDensity(cellIt);
+               WALBERLA_CHECK_FLOAT_EQUAL(density, expectedDensity_,testIdentifier << "Invalid density in cell " << cellIt << ".");
+            }
+         }
+      }
+   }
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID boundaryHandlingID_;
+
+   Vector3<real_t> expectedVelocity_;
+   real_t expectedDensity_;
+
+   const FlagUID formerObstacle_;
+
+};
+
+
+
+template <typename BoundaryHandling_T>
+class MappingChecker
+{
+public:
+   MappingChecker(const shared_ptr< StructuredBlockStorage > & blocks,
+                  const BlockDataID & boundaryHandlingID, real_t sphereRadius) :
+         blocks_( blocks ), boundaryHandlingID_( boundaryHandlingID ),
+         sphereRadius_( sphereRadius ), sphereVolume_( math::pi * real_t(4) / real_t(3) * sphereRadius * sphereRadius * sphereRadius )
+   { }
+
+   // check the mapping in the inner domain of the block and check mapped volume against real sphere volume
+   void operator()(std::string testIdentifier, const Vector3<real_t> & pos, bool periodic )
+   {
+      uint_t cellCounter( uint_t(0) );
+
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+
+         auto xyzSize = flagField->xyzSize();
+
+         for (auto cellIt : xyzSize) {
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter(*blockIt, cellIt);
+            real_t distance = (cellCenter - pos).length();
+            if( periodic )
+            {
+               Vector3<real_t> periodicOffset(blocks_->getDomain().xSize(),0,0);
+               // check if other (periodic) copies are closer
+               distance = std::min(distance, (cellCenter - (pos-periodicOffset)).length());
+               distance = std::min(distance, (cellCenter - (pos+periodicOffset)).length());
+            }
+
+            if (distance < sphereRadius_) {
+               WALBERLA_CHECK(boundaryHandling->isBoundary(cellIt),
+                              testIdentifier << "Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected boundary cell. Distance cell center - particle center = "
+                                             << distance << ".");
+               ++cellCounter;
+            } else {
+               WALBERLA_CHECK(boundaryHandling->isDomain(cellIt),
+                              testIdentifier << "Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected domain cell. Distance cell center - particle center = "
+                                             << distance << ".");
+            }
+         }
+      }
+
+      mpi::allReduceInplace(cellCounter, mpi::SUM);
+
+      // mapped volume has to be - approximately - the same as the real volume
+      real_t mappedVolume = real_c(cellCounter); // dx=1
+      WALBERLA_CHECK(std::fabs( mappedVolume - sphereVolume_ ) / sphereVolume_ <= real_t(0.1),
+                     "Mapped volume " << mappedVolume << " does not fit to real sphere volume " << sphereVolume_ << ".");
+   }
+
+   // checks only the mapping in the ghost layers
+   void checkGhostLayer(std::string testIdentifier, const Vector3<real_t> & pos, bool periodic )
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+
+         auto xyzSizeWGl = flagField->xyzSizeWithGhostLayer();
+
+         for (auto cellIt : xyzSizeWGl) {
+            if( flagField->isInInnerPart(cellIt) ) continue;
+
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter( *blockIt, cellIt);
+            real_t distance = (cellCenter - pos).length();
+            if( periodic )
+            {
+               Vector3<real_t> periodicOffset(blocks_->getDomain().xSize(),0,0);
+               // check if other (periodic) copies are closer
+               distance = std::min(distance, (cellCenter - (pos-periodicOffset)).length());
+               distance = std::min(distance, (cellCenter - (pos+periodicOffset)).length());
+            }
+
+            if( distance < sphereRadius_ )
+            {
+               WALBERLA_CHECK( boundaryHandling->isBoundary(cellIt),
+                               testIdentifier << ": Invalid mapping in ghost layer cell " << cellIt
+                                              << " with center at " << cellCenter
+                                              << " - expected boundary cell. Distance cell center - particle center = "
+                                              << distance << ".");
+            }
+            else
+            {
+               WALBERLA_CHECK( boundaryHandling->isDomain(cellIt),
+                               testIdentifier << ": Invalid mapping in ghost layer cell " << cellIt
+                                              << " with center at " << cellCenter
+                                              << " - expected domain cell. Distance cell center - particle center = "
+                                              << distance << "." );
+            }
+         }
+      }
+   }
+
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+   real_t sphereRadius_, sphereVolume_;
+
+};
+
+template< typename BoundaryHandling_T>
+class MappingResetter
+{
+public:
+   MappingResetter(const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & boundaryHandlingID, const BlockDataID & particleFieldID, walberla::id_t invalidUID) :
+         blocks_( blocks ), boundaryHandlingID_( boundaryHandlingID ), particleFieldID_( particleFieldID ), invalidUID_( invalidUID )
+   { }
+
+   void operator()()
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+         auto *    particleField = blockIt->getData< lbm_mesapd_coupling::ParticleField_T >( particleFieldID_ );
+
+         auto xyzSizeWGl = flagField->xyzSizeWithGhostLayer();
+         // reset to domain (fluid)
+         boundaryHandling->forceDomain(xyzSizeWGl);
+
+         for( auto cellIt = xyzSizeWGl.begin(); cellIt != xyzSizeWGl.end(); ++cellIt )
+         {
+            // reset particle field
+            particleField->get(*cellIt) = invalidUID_;
+         }
+      }
+   }
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+   const BlockDataID particleFieldID_;
+   walberla::id_t invalidUID_;
+};
+
+
+template<typename BoundaryHandling_T, typename ParticleAccessor_T, typename ExtrapolationDirectionFinder_T>
+void checkExtrapolationDirectionFinder( std::string identifier,
+                                        shared_ptr< StructuredBlockForest > & blocks, shared_ptr<mesa_pd::data::ParticleStorage> & ps, shared_ptr<mesa_pd::data::ShapeStorage> ss,
+                                        shared_ptr<ParticleAccessor_T> & accessor, BlockDataID pdfFieldID, BlockDataID boundaryHandlingID, BlockDataID particleFieldID,
+                                        ExtrapolationDirectionFinder_T directionFinder, real_t radius, bool conserveMomentum)
+{
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+   mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+   const real_t overlap = real_t( 1.5 );
+
+   MappingResetter<BoundaryHandling_T> mappingResetter(blocks, boundaryHandlingID, particleFieldID, accessor->getInvalidUid());
+   auto regularParticleMapper = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, mesa_pd::kernel::SelectAll(), conserveMomentum);
+
+   std::string testIdentifier(identifier + " Test:");
+   WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+   Vector3<real_t> spherePosition(real_t(9.5), real_t(9.5), real_t(9.5));
+
+   std::array<Vector3<real_t>,3> referenceNormals = {{Vector3<real_t>(1,0,0), Vector3<real_t>(0,-1,0), Vector3<real_t>(-1,0,1)/sqrt(2)}};
+   std::array<Vector3<real_t>,3> evaluationPoints = {{spherePosition + referenceNormals[0] * radius, spherePosition + referenceNormals[1] * radius, spherePosition + referenceNormals[2] * radius}};
+
+   // create sphere
+   walberla::id_t sphereUid = 0;
+   if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), spherePosition ))
+   {
+      mesa_pd::data::Particle&& p = *ps->create();
+      p.setPosition(spherePosition);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+      sphereUid = p.getUid();
+   }
+
+   mpi::allReduceInplace(sphereUid, mpi::SUM);
+
+   syncNextNeighborFunc(*ps, domain, overlap);
+
+   // map
+   for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+   // check normal at evaluation points
+   // since the normal must be aligned with some lattice direction, the normal could be different from the real one
+   for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt )
+   {
+      for( uint_t i = 0; i < evaluationPoints.size(); ++i)
+      {
+         auto evalPoint = evaluationPoints[i];
+         if( blocks->getAABB((*blockIt).getId()).contains(evalPoint))
+         {
+            auto cell = blocks->getBlockLocalCell(*blockIt,evalPoint);
+
+            size_t idx = accessor->uidToIdx(sphereUid);
+            if( idx != accessor->getInvalidIdx())
+            {
+               auto normal = directionFinder(&(*blockIt), cell.x(), cell.y(), cell.z(), idx, *accessor).getNormalized();
+               for(uint_t comp = 0; comp < 3; ++comp) WALBERLA_CHECK_FLOAT_EQUAL(normal[comp], referenceNormals[i][comp], "Non-matching extrapolation direction for normal component " << comp << " in evaluation point " << evalPoint);
+            }
+         }
+      }
+   }
+
+   WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+   // clean up
+   mappingResetter();
+   ps->clear();
+   syncNextNeighborFunc(*ps, domain, overlap);
+
+
+}
+
+// TEST SPHERE INSIDE ONE BLOCK
+template<typename BoundaryHandling_T, typename ParticleAccessor_T, typename ReconstructionManager_T>
+void checkSphereInsideBlock( std::string identifier,
+                             shared_ptr< StructuredBlockForest > & blocks, shared_ptr<mesa_pd::data::ParticleStorage> & ps, shared_ptr<mesa_pd::data::ShapeStorage> ss,
+                             shared_ptr<ParticleAccessor_T> & accessor, BlockDataID pdfFieldID, BlockDataID boundaryHandlingID, BlockDataID particleFieldID,
+                             ReconstructionManager_T reconstructionManager,
+                             real_t radius, Vector3<real_t> velocity, real_t density, bool conserveMomentum )
+{
+
+
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+   mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+   const real_t overlap = real_t( 1.5 );
+
+   MappingChecker<BoundaryHandling_T> mappingChecker(blocks, boundaryHandlingID, radius);
+   MappingResetter<BoundaryHandling_T> mappingResetter(blocks, boundaryHandlingID, particleFieldID, accessor->getInvalidUid());
+   ReconstructionChecker<PdfField_T, BoundaryHandling_T> reconstructionChecker(blocks, pdfFieldID, boundaryHandlingID, velocity, density, FormerMO_Flag );
+
+   auto regularParticleMapper = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, mesa_pd::kernel::SelectAll(), conserveMomentum);
+
+   std::string testIdentifier(identifier + " Test: sphere inside block");
+   WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+   Vector3<real_t> positionInsideBlock(real_t(10), real_t(10), real_t(10));
+
+   // create sphere
+   walberla::id_t sphereUid = 0;
+   if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionInsideBlock ))
+   {
+      mesa_pd::data::Particle&& p = *ps->create();
+      p.setPosition(positionInsideBlock);
+      p.setLinearVelocity(velocity);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+      sphereUid = p.getUid();
+   }
+
+   mpi::allReduceInplace(sphereUid, mpi::SUM);
+
+   syncNextNeighborFunc(*ps, domain, overlap);
+
+   // map
+   for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+   auto currentSpherePosition = positionInsideBlock;
+   // run several "timesteps" and advance the sphere in total one cell
+   for( uint_t t = 0; t < 10; ++t )
+   {
+      currentSpherePosition = currentSpherePosition + velocity;
+
+      // advance sphere
+      size_t idx = accessor->uidToIdx(sphereUid);
+      if( idx != accessor->getInvalidIdx())
+      {
+         accessor->setPosition(idx, currentSpherePosition);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // update mapping
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+      // carry out restore step
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) reconstructionManager(&(*blockIt));
+
+
+      // check mapping
+      mappingChecker(testIdentifier + " (t=" + std::to_string(t)+") ",currentSpherePosition,false);
+      mappingChecker.checkGhostLayer(testIdentifier + " (t=" + std::to_string(t)+") ",currentSpherePosition,false);
+
+      // check reconstruction
+      reconstructionChecker(testIdentifier + " (t=" + std::to_string(t)+") ");
+   }
+
+   WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+   // clean up
+   mappingResetter();
+   ps->clear();
+   syncNextNeighborFunc(*ps, domain, overlap);
+}
+
+// TEST SPHERE ON BLOCK BOARDER
+template<typename BoundaryHandling_T, typename ParticleAccessor_T, typename ReconstructionManager_T>
+void checkSphereOnBlockBoarder( std::string identifier,
+                                shared_ptr< StructuredBlockForest > & blocks, shared_ptr<mesa_pd::data::ParticleStorage> & ps, shared_ptr<mesa_pd::data::ShapeStorage> ss,
+                                shared_ptr<ParticleAccessor_T> & accessor, BlockDataID pdfFieldID, BlockDataID boundaryHandlingID, BlockDataID particleFieldID,
+                                ReconstructionManager_T reconstructionManager,
+                                real_t radius, Vector3<real_t> velocity, real_t density, bool conserveMomentum )
+{
+
+
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+   mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+   const real_t overlap = real_t( 1.5 );
+
+   MappingChecker<BoundaryHandling_T> mappingChecker(blocks, boundaryHandlingID, radius);
+   MappingResetter<BoundaryHandling_T> mappingResetter(blocks, boundaryHandlingID, particleFieldID, accessor->getInvalidUid());
+   ReconstructionChecker<PdfField_T, BoundaryHandling_T> reconstructionChecker(blocks, pdfFieldID, boundaryHandlingID, velocity, density, FormerMO_Flag );
+
+   auto regularParticleMapper = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, mesa_pd::kernel::SelectAll(), conserveMomentum);
+
+   std::string testIdentifier(identifier + " Test: sphere on block boarder");
+   WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+   Vector3<real_t> positionAtBlockBoarder(real_t(19.5), real_t(10), real_t(10));
+
+   // create sphere
+   walberla::id_t sphereUid = 0;
+   if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtBlockBoarder ))
+   {
+      mesa_pd::data::Particle&& p = *ps->create();
+      p.setPosition(positionAtBlockBoarder);
+      p.setLinearVelocity(velocity);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+      sphereUid = p.getUid();
+   }
+   syncNextNeighborFunc(*ps, domain, overlap);
+
+   mpi::allReduceInplace(sphereUid, mpi::SUM);
+
+   // map
+   for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+   auto currentSpherePosition = positionAtBlockBoarder;
+   // run several "timesteps" and advance the sphere in total one cell
+   for( uint_t t = 0; t < 10; ++t )
+   {
+      currentSpherePosition = currentSpherePosition + velocity;
+
+      // advance sphere
+      size_t idx = accessor->uidToIdx(sphereUid);
+      if( idx != accessor->getInvalidIdx())
+      {
+         accessor->setPosition(idx, currentSpherePosition);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // update mapping
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+      // carry out restore step
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) reconstructionManager(&(*blockIt));
+
+
+      // check mapping
+      mappingChecker(testIdentifier + " (t=" + std::to_string(t)+") ",currentSpherePosition,false);
+      mappingChecker.checkGhostLayer(testIdentifier + " (t=" + std::to_string(t)+") ",currentSpherePosition,false);
+
+      // check reconstruction
+      reconstructionChecker(testIdentifier + " (t=" + std::to_string(t)+") ");
+   }
+
+   WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+   // clean up
+   mappingResetter();
+   ps->clear();
+   syncNextNeighborFunc(*ps, domain, overlap);
+}
+
+// TEST SPHERE ON BLOCK BOARDER 2
+template<typename BoundaryHandling_T, typename ParticleAccessor_T, typename ReconstructionManager_T>
+void checkSphereOnBlockBoarder2( std::string identifier,
+                                 shared_ptr< StructuredBlockForest > & blocks, shared_ptr<mesa_pd::data::ParticleStorage> & ps, shared_ptr<mesa_pd::data::ShapeStorage> ss,
+                                 shared_ptr<ParticleAccessor_T> & accessor, BlockDataID pdfFieldID, BlockDataID boundaryHandlingID, BlockDataID particleFieldID,
+                                 ReconstructionManager_T reconstructionManager,
+                                 real_t radius, Vector3<real_t> velocity, real_t density, bool conserveMomentum )
+{
+
+
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+   mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+   const real_t overlap = real_t( 1.5 );
+
+   MappingChecker<BoundaryHandling_T> mappingChecker(blocks, boundaryHandlingID, radius);
+   MappingResetter<BoundaryHandling_T> mappingResetter(blocks, boundaryHandlingID, particleFieldID, accessor->getInvalidUid());
+   ReconstructionChecker<PdfField_T, BoundaryHandling_T> reconstructionChecker(blocks, pdfFieldID, boundaryHandlingID, velocity, density, FormerMO_Flag );
+
+   auto regularParticleMapper = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, mesa_pd::kernel::SelectAll(), conserveMomentum);
+
+   std::string testIdentifier(identifier + " Test: sphere on block boarder 2");
+   WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+   Vector3<real_t> positionAtBlockBoarder2(real_t(20)+radius, real_t(10), real_t(10));
+
+   // create sphere
+   walberla::id_t sphereUid = 0;
+   if (domain.isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), positionAtBlockBoarder2 ))
+   {
+      mesa_pd::data::Particle&& p = *ps->create();
+      p.setPosition(positionAtBlockBoarder2);
+      p.setLinearVelocity(velocity);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+      sphereUid = p.getUid();
+   }
+   syncNextNeighborFunc(*ps, domain, overlap);
+
+   mpi::allReduceInplace(sphereUid, mpi::SUM);
+
+   // map
+   for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+   auto currentSpherePosition = positionAtBlockBoarder2;
+   // run several "timesteps" and advance the sphere in total one cell
+   for( uint_t t = 0; t < 10; ++t )
+   {
+      currentSpherePosition = currentSpherePosition + velocity;
+
+      // advance sphere
+      size_t idx = accessor->uidToIdx(sphereUid);
+      if( idx != accessor->getInvalidIdx())
+      {
+         accessor->setPosition(idx, currentSpherePosition);
+      }
+      syncNextNeighborFunc(*ps, domain, overlap);
+
+      // update mapping
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) regularParticleMapper(&(*blockIt));
+
+      // carry out restore step
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) reconstructionManager(&(*blockIt));
+
+
+      // check mapping
+      mappingChecker(testIdentifier + " (t=" + std::to_string(t)+") ",currentSpherePosition,false);
+      mappingChecker.checkGhostLayer(testIdentifier + " (t=" + std::to_string(t)+") ",currentSpherePosition,false);
+
+      // check reconstruction
+      reconstructionChecker(testIdentifier + " (t=" + std::to_string(t)+") ");
+   }
+
+   WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+   // clean up
+   mappingResetter();
+   ps->clear();
+   syncNextNeighborFunc(*ps, domain, overlap);
+}
+
+
+/*!\brief Test case for the Pdf reconstruction functionality
+ *
+ * It tests these different reconstructors:
+ *  - EquilibriumReconstructor
+ *  - EquilibriumAndNonEquilibriumReconstructor
+ *  - ExtrapolationReconstructor
+ *  - GradsMomentApproximationReconstructor
+ *
+ * The following features of the PdfReconstructionManager are tested as well:
+ *  - reconstruction at block boarder
+ *  - version for small obstacle fraction
+ *
+ * Additionally, the following extrapolation direction finders are tested:
+ *  - SphereNormalExtrapolationFinder
+ *  - FlagFieldExtrapolationDirectionFinder
+ *
+ * -----------------------------------------------
+ * #                      |                      #
+ * #                      |                      #
+ * #                      |                      #
+ * #                      |                      #
+ * #          X          X|  X                   #
+ * #                      |                      #
+ * #                      |                      #
+ * #                      |                      #
+ * #                      |                      #
+ * -----------------------------------------------
+ */
+//////////
+// MAIN //
+//////////
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   const real_t omega  = real_t(1);
+   const real_t dx     = real_t(1);
+   const real_t radius = real_t(5);
+   const Vector3<real_t> velocity( real_t(0.1), real_t(0), real_t(0) );
+   const real_t density = real_t(1);
+
+   bool conserveMomentum = true;
+
+   ///////////////////////////
+   // DATA STRUCTURES SETUP //
+   ///////////////////////////
+
+   Vector3<uint_t> blocksPerDirection(uint_t(2), uint_t(1), uint_t(1));
+   Vector3<uint_t> cellsPerBlock(uint_t(20), uint_t(20), uint_t(20));
+   Vector3<bool> periodicity(false, false, false);
+
+   auto blocks = blockforest::createUniformBlockGrid( blocksPerDirection[0], blocksPerDirection[1], blocksPerDirection[2],
+                                                      cellsPerBlock[0], cellsPerBlock[1], cellsPerBlock[2],
+                                                      dx,
+                                                      0, false, false,
+                                                      periodicity[0], periodicity[1], periodicity[2],
+                                                      false );
+
+   // create the lattice model
+   LatticeModel_T latticeModel = LatticeModel_T( omega );
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (zyxf)", latticeModel,
+                                                                         velocity, density,
+                                                                         FieldGhostLayers, field::zyxf );
+
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field", FieldGhostLayers );
+
+   // rpd setup
+
+   //init data structures
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = make_shared<ParticleAccessor_T>(ps, ss);
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::zyxf, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // vtk
+   //auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field" );
+   //flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+
+
+   /////////////////////////////////////
+   // EQUILIBRIUM RECONSTRUCTOR TESTS //
+   /////////////////////////////////////
+
+   auto equilibriumReconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, conserveMomentum);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Equilibrium Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *equilibriumReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Equilibrium Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *equilibriumReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Equilibrium Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *equilibriumReconstructionManager, radius, velocity, density, conserveMomentum );
+
+
+   /////////////////////////////////////
+   // EQUILIBRIUM RECONSTRUCTOR TESTS //
+   //  for small obstacle fractions   //
+   /////////////////////////////////////
+
+   auto equilibriumReconstructionSmallObstacleFractionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, conserveMomentum, true);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Equilibrium Reconstructor Small Obstacle Fraction", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *equilibriumReconstructionSmallObstacleFractionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Equilibrium Reconstructor Small Obstacle Fraction", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *equilibriumReconstructionSmallObstacleFractionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Equilibrium Reconstructor Small Obstacle Fraction", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *equilibriumReconstructionSmallObstacleFractionManager, radius, velocity, density, conserveMomentum );
+
+   /////////////////////////////////////////////////////////
+   // EQUILIBRIUM AND NON-EQUILIBRIUM RECONSTRUCTOR TESTS //
+   //       with Sphere Normal Extrapolator               //
+   /////////////////////////////////////////////////////////
+
+   auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+
+   checkExtrapolationDirectionFinder<BoundaryHandling_T>( "Sphere Normal Extrapolation Direction Finder", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                          *sphereNormalExtrapolationDirectionFinder, radius, conserveMomentum );
+
+
+   auto equilibriumAndNonEquilibriumSphereNormalReconstructor = lbm_mesapd_coupling::makeEquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, 3);
+   auto equilibriumAndNonEquilibriumSphereNormalReconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, equilibriumAndNonEquilibriumSphereNormalReconstructor, conserveMomentum);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with Sphere Normal", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *equilibriumAndNonEquilibriumSphereNormalReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with Sphere Normal", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *equilibriumAndNonEquilibriumSphereNormalReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with Sphere Normal", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *equilibriumAndNonEquilibriumSphereNormalReconstructionManager, radius, velocity, density, conserveMomentum );
+
+
+   /////////////////////////////////////////////////////////
+   // EQUILIBRIUM AND NON-EQUILIBRIUM RECONSTRUCTOR TESTS //
+   //       with FlagField Normal Extrapolator            //
+   /////////////////////////////////////////////////////////
+
+   auto flagFieldNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::FlagFieldNormalExtrapolationDirectionFinder<BoundaryHandling_T> >(blocks,boundaryHandlingID);
+
+   checkExtrapolationDirectionFinder<BoundaryHandling_T>( "FlagField Normal Extrapolation Direction Finder", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                          *flagFieldNormalExtrapolationDirectionFinder, radius, conserveMomentum );
+
+   auto equilibriumAndNonEquilibriumFlagFieldNormalReconstructor = lbm_mesapd_coupling::makeEquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, flagFieldNormalExtrapolationDirectionFinder, 2);
+   auto equilibriumAndNonEquilibriumFlagFieldNormalReconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, equilibriumAndNonEquilibriumFlagFieldNormalReconstructor, conserveMomentum);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with FlagField Normal", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *equilibriumAndNonEquilibriumFlagFieldNormalReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with FlagField Normal", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *equilibriumAndNonEquilibriumFlagFieldNormalReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with FlagField Normal", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *equilibriumAndNonEquilibriumFlagFieldNormalReconstructionManager, radius, velocity, density, conserveMomentum );
+
+
+   /////////////////////////////////////////////////////////
+   // EQUILIBRIUM AND NON-EQUILIBRIUM RECONSTRUCTOR TESTS //
+   //       with only one extrapolation cell              //
+   /////////////////////////////////////////////////////////
+
+   auto equilibriumAndNonEquilibriumSphereNormal1Reconstructor = lbm_mesapd_coupling::makeEquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, 1);
+   auto equilibriumAndNonEquilibriumSphereNormal1ReconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, equilibriumAndNonEquilibriumSphereNormal1Reconstructor, conserveMomentum);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with one ext cell", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *equilibriumAndNonEquilibriumSphereNormal1ReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with one ext cell", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *equilibriumAndNonEquilibriumSphereNormal1ReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Equilibrium and NonEquilibrium Reconstructor with one ext cell", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *equilibriumAndNonEquilibriumSphereNormal1ReconstructionManager, radius, velocity, density, conserveMomentum );
+
+
+   ///////////////////////////////////////
+   // EXTRAPOLATION RECONSTRUCTOR TESTS //
+   ///////////////////////////////////////
+
+   auto extrapolationReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder);
+   auto extrapolationReconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, extrapolationReconstructor, conserveMomentum);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Extrapolation Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *extrapolationReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Extrapolation Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *extrapolationReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Extrapolation Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *extrapolationReconstructionManager, radius, velocity, density, conserveMomentum );
+
+
+   ///////////////////////////////////////
+   // EXTRAPOLATION RECONSTRUCTOR TESTS //
+   //   with activated constrain        //
+   ///////////////////////////////////////
+
+   auto extrapolationConstrainReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T, lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder, true>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder);
+   auto extrapolationConstrainReconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, extrapolationReconstructor, conserveMomentum);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Extrapolation Reconstructor with Constrain", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *extrapolationConstrainReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Extrapolation Reconstructor with Constrain", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *extrapolationConstrainReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Extrapolation Reconstructor with Constrain", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *extrapolationConstrainReconstructionManager, radius, velocity, density, conserveMomentum );
+
+   //////////////////////////////////////////////
+   // GRADs MOMENT APPROXIMATION RECONSTRUCTOR //
+   //////////////////////////////////////////////
+
+   auto gradReconstructor = lbm_mesapd_coupling::makeGradsMomentApproximationReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, omega, false);
+   auto gradReconstructorManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, gradReconstructor, conserveMomentum);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Grads Moment Approximation Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *gradReconstructorManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Grads Moment Approximation Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *gradReconstructorManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Grads Moment Approximation Reconstructor", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *gradReconstructorManager, radius, velocity, density, conserveMomentum );
+
+   //////////////////////////////////////////////
+   // GRADs MOMENT APPROXIMATION RECONSTRUCTOR //
+   //        with density recomputation        //
+   //////////////////////////////////////////////
+
+   auto gradReconstructorWithRecomp = lbm_mesapd_coupling::makeGradsMomentApproximationReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, omega, true);
+   auto gradReconstructorWithRecompManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, gradReconstructorWithRecomp, conserveMomentum);
+
+   checkSphereInsideBlock<BoundaryHandling_T>( "Grads Moment Approximation Reconstructor with Density Recomputation", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                               *gradReconstructorWithRecompManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder<BoundaryHandling_T>( "Grads Moment Approximation Reconstructor with Density Recomputation", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                  *gradReconstructorWithRecompManager, radius, velocity, density, conserveMomentum );
+
+   checkSphereOnBlockBoarder2<BoundaryHandling_T>( "Grads Moment Approximation Reconstructor with Density Recomputation", blocks, ps, ss, accessor, pdfFieldID, boundaryHandlingID, particleFieldID,
+                                                   *gradReconstructorWithRecompManager, radius, velocity, density, conserveMomentum );
+
+   return 0;
+
+}
+
+} //namespace pdf_reconstruction
+
+int main( int argc, char **argv ){
+   pdf_reconstruction::main(argc, argv);
+}
diff --git a/tests/lbm_mesapd_coupling/momentum_exchange_method/SettlingSphere.cpp b/tests/lbm_mesapd_coupling/momentum_exchange_method/SettlingSphere.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ef8db99fa2e19a58c8c14ac96973722e18cff824
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/momentum_exchange_method/SettlingSphere.cpp
@@ -0,0 +1,849 @@
+//======================================================================================================================
+//
+//  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 SettlingSphere.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.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/math/all.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/logging/all.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+#include "field/communication/PackInfo.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.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/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/boundary/CurvedLinear.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.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/utility/AddForceOnParticlesKernel.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h"
+#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h"
+#include "lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/shape/HalfSpace.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ExplicitEulerWithShape.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+#include "mesa_pd/kernel/SpringDashpot.h"
+#include "mesa_pd/kernel/VelocityVerletWithShape.h"
+#include "mesa_pd/mpi/ContactFilter.h"
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/mpi/ReduceProperty.h"
+#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h"
+#include "mesa_pd/vtk/ParticleVtkOutput.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+#include "lbm/vtk/all.h"
+
+#include <functional>
+
+namespace settling_sphere
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::D3Q19< lbm::collision_model::TRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// 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" );
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using NoSlip_T = lbm::NoSlip< LatticeModel_T, flag_t >;
+   using MO_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, NoSlip_T, MO_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ),
+                                  MO_T( "MO", MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+//*******************************************************************************************************************
+
+
+//*******************************************************************************************************************
+/*!\brief Evaluating the position and velocity of the sphere
+ *
+ */
+//*******************************************************************************************************************
+template< typename ParticleAccessor_T>
+class SpherePropertyLogger
+{
+public:
+   SpherePropertyLogger( const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid,
+         const std::string & fileName, bool fileIO,
+         real_t dx_SI, real_t dt_SI, real_t diameter, real_t gravitationalForceMag) :
+      ac_( ac ), sphereUid_( sphereUid ), fileName_( fileName ), fileIO_(fileIO),
+      dx_SI_( dx_SI ), dt_SI_( dt_SI ), diameter_( diameter ), gravitationalForceMag_( gravitationalForceMag ),
+      position_( real_t(0) ), maxVelocity_( real_t(0) )
+   {
+      if ( fileIO_ )
+      {
+         WALBERLA_ROOT_SECTION()
+         {
+            std::ofstream file;
+            file.open( fileName_.c_str() );
+            file << "#\t t\t posX\t posY\t gapZ\t velX\t velY\t velZ\n";
+            file.close();
+         }
+      }
+   }
+
+   void operator()(const uint_t timestep)
+   {
+      Vector3<real_t> pos(real_t(0));
+      Vector3<real_t> transVel(real_t(0));
+      Vector3<real_t> hydForce(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);
+            transVel = ac_->getLinearVelocity(idx);
+         }
+         hydForce = ac_->getHydrodynamicForce(idx);
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( pos[0], mpi::SUM );
+         mpi::allReduceInplace( pos[1], mpi::SUM );
+         mpi::allReduceInplace( pos[2], mpi::SUM );
+
+         mpi::allReduceInplace( transVel[0], mpi::SUM );
+         mpi::allReduceInplace( transVel[1], mpi::SUM );
+         mpi::allReduceInplace( transVel[2], mpi::SUM );
+
+         mpi::allReduceInplace( hydForce[0], mpi::SUM );
+         mpi::allReduceInplace( hydForce[1], mpi::SUM );
+         mpi::allReduceInplace( hydForce[2], mpi::SUM );
+      }
+
+      position_ = pos[2];
+      maxVelocity_ = std::max(maxVelocity_, -transVel[2]);
+
+      if( fileIO_ )
+         writeToFile( timestep, pos, transVel, hydForce);
+   }
+
+   real_t getPosition() const
+   {
+      return position_;
+   }
+
+   real_t getMaxVelocity() const
+   {
+      return maxVelocity_;
+   }
+
+private:
+   void writeToFile( const uint_t timestep, const Vector3<real_t> & position, const Vector3<real_t> & velocity, const Vector3<real_t> & hydForce )
+   {
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( fileName_.c_str(), std::ofstream::app );
+
+         auto scaledPosition = position / diameter_;
+         auto velocity_SI = velocity * dx_SI_ / dt_SI_;
+         auto normalizedHydForce = hydForce / gravitationalForceMag_;
+
+
+         file << timestep << "\t" << real_c(timestep) * dt_SI_ << "\t"
+              << "\t" << scaledPosition[0] << "\t" << scaledPosition[1] << "\t" << scaledPosition[2] - real_t(0.5)
+              << "\t" << velocity_SI[0] << "\t" << velocity_SI[1] << "\t" << velocity_SI[2]
+              << "\t" << normalizedHydForce[0] << "\t" << normalizedHydForce[1] << "\t" << normalizedHydForce[2]
+              << "\n";
+         file.close();
+      }
+   }
+
+   shared_ptr< ParticleAccessor_T > ac_;
+   const walberla::id_t sphereUid_;
+   std::string fileName_;
+   bool fileIO_;
+   real_t dx_SI_, dt_SI_, diameter_, gravitationalForceMag_;
+
+   real_t position_;
+   real_t maxVelocity_;
+};
+
+
+void createPlaneSetup(const shared_ptr<mesa_pd::data::ParticleStorage> & ps, const shared_ptr<mesa_pd::data::ShapeStorage> & ss, const math::AABB & simulationDomain)
+{
+   // create bounding planes
+   mesa_pd::data::Particle p0 = *ps->create(true);
+   p0.setPosition(simulationDomain.minCorner());
+   p0.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,1) ));
+   p0.setOwner(mpi::MPIManager::instance()->rank());
+   p0.setType(0);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p1 = *ps->create(true);
+   p1.setPosition(simulationDomain.maxCorner());
+   p1.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,0,-1) ));
+   p1.setOwner(mpi::MPIManager::instance()->rank());
+   p1.setType(0);
+   mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p1.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p2 = *ps->create(true);
+   p2.setPosition(simulationDomain.minCorner());
+   p2.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(1,0,0) ));
+   p2.setOwner(mpi::MPIManager::instance()->rank());
+   p2.setType(0);
+   mesa_pd::data::particle_flags::set(p2.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p2.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p3 = *ps->create(true);
+   p3.setPosition(simulationDomain.maxCorner());
+   p3.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(-1,0,0) ));
+   p3.setOwner(mpi::MPIManager::instance()->rank());
+   p3.setType(0);
+   mesa_pd::data::particle_flags::set(p3.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p3.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p4 = *ps->create(true);
+   p4.setPosition(simulationDomain.minCorner());
+   p4.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,1,0) ));
+   p4.setOwner(mpi::MPIManager::instance()->rank());
+   p4.setType(0);
+   mesa_pd::data::particle_flags::set(p4.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p4.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+   mesa_pd::data::Particle p5 = *ps->create(true);
+   p5.setPosition(simulationDomain.maxCorner());
+   p5.setShapeID(ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(0,-1,0) ));
+   p5.setOwner(mpi::MPIManager::instance()->rank());
+   p5.setType(0);
+   mesa_pd::data::particle_flags::set(p5.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+   mesa_pd::data::particle_flags::set(p5.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+
+}
+
+//////////
+// MAIN //
+//////////
+
+//*******************************************************************************************************************
+/*!\brief Testcase that simulates the settling of a sphere inside a rectangular column filled with viscous fluid
+ *
+ * see: ten Cate, Nieuwstad, Derksen, Van den Akker - "Particle imaging velocimetry experiments and lattice-Boltzmann
+ * simulations on a single sphere settling under gravity" (2002), Physics of Fluids, doi: 10.1063/1.1512918
+ */
+//*******************************************************************************************************************
+
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   ///////////////////
+   // Customization //
+   ///////////////////
+
+   // simulation control
+   bool shortrun = false;
+   bool funcTest = false;
+   bool fileIO = false;
+   uint_t vtkIOFreq = 0;
+   std::string baseFolder = "vtk_out_SettlingSphere";
+
+   // physical setup
+   uint_t fluidType = 1;
+
+   //numerical parameters
+   uint_t numberOfCellsInHorizontalDirection = uint_t(135);
+   bool averageForceTorqueOverTwoTimSteps = true;
+   bool conserveMomentum = false;
+   uint_t numRPDSubCycles = uint_t(1);
+   bool useVelocityVerlet = false;
+   bool useEANReconstructor = false;
+   bool useExtReconstructor = false;
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--shortrun" )            == 0 ) { shortrun = true; continue; }
+      if( std::strcmp( argv[i], "--funcTest" )            == 0 ) { funcTest = true; continue; }
+      if( std::strcmp( argv[i], "--fileIO" )              == 0 ) { fileIO = true; continue; }
+      if( std::strcmp( argv[i], "--vtkIOFreq" )           == 0 ) { vtkIOFreq = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--fluidType" )           == 0 ) { fluidType = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--numRPDSubCycles" )     == 0 ) { numRPDSubCycles = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--resolution" )          == 0 ) { numberOfCellsInHorizontalDirection = uint_c( std::atof( argv[++i] ) ); continue; }
+      if( std::strcmp( argv[i], "--noForceAveraging" )    == 0 ) { averageForceTorqueOverTwoTimSteps = false; continue; }
+      if( std::strcmp( argv[i], "--baseFolder" )          == 0 ) { baseFolder = argv[++i]; continue; }
+      if( std::strcmp( argv[i], "--useVV" )               == 0 ) { useVelocityVerlet = true; continue; }
+      if( std::strcmp( argv[i], "--useEANReconstructor" ) == 0 ) { useEANReconstructor = true; continue; }
+      if( std::strcmp( argv[i], "--useExtReconstructor" ) == 0 ) { useExtReconstructor = true; continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   WALBERLA_CHECK(!(useEANReconstructor && useExtReconstructor), "Can not use two reconstructors!");
+
+   if( funcTest )
+   {
+      walberla::logging::Logging::instance()->setLogLevel(logging::Logging::LogLevel::WARNING);
+   }
+
+   if( fileIO )
+   {
+      // create base directory if it does not yet exist
+      filesystem::path tpath( baseFolder );
+      if( !filesystem::exists( tpath ) )
+         filesystem::create_directory( tpath );
+   }
+
+   //////////////////////////////////////
+   // SIMULATION PROPERTIES in SI units//
+   //////////////////////////////////////
+
+   // values are mainly taken from the reference paper
+   const real_t diameter_SI = real_t(15e-3);
+   const real_t densitySphere_SI = real_t(1120);
+
+   real_t densityFluid_SI, dynamicViscosityFluid_SI;
+   real_t expectedSettlingVelocity_SI;
+   switch( fluidType )
+   {
+      case 1:
+         // Re_p around 1.5
+         densityFluid_SI = real_t(970);
+         dynamicViscosityFluid_SI = real_t(373e-3);
+         expectedSettlingVelocity_SI = real_t(0.035986);
+         break;
+      case 2:
+         // Re_p around 4.1
+         densityFluid_SI = real_t(965);
+         dynamicViscosityFluid_SI = real_t(212e-3);
+         expectedSettlingVelocity_SI = real_t(0.05718);
+         break;
+      case 3:
+         // Re_p around 11.6
+         densityFluid_SI = real_t(962);
+         dynamicViscosityFluid_SI = real_t(113e-3);
+         expectedSettlingVelocity_SI = real_t(0.087269);
+         break;
+      case 4:
+         // Re_p around 31.9
+         densityFluid_SI = real_t(960);
+         dynamicViscosityFluid_SI = real_t(58e-3);
+         expectedSettlingVelocity_SI = real_t(0.12224);
+         break;
+      default:
+         WALBERLA_ABORT("Only four different fluids are supported! Choose type between 1 and 4.");
+   }
+   const real_t kinematicViscosityFluid_SI = dynamicViscosityFluid_SI / densityFluid_SI;
+
+   const real_t gravitationalAcceleration_SI = real_t(9.81);
+   Vector3<real_t> domainSize_SI(real_t(100e-3), real_t(100e-3), real_t(160e-3));
+   //shift starting gap a bit upwards to match the reported (plotted) values
+   const real_t startingGapSize_SI = real_t(120e-3) + real_t(0.25) * diameter_SI;
+
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in SI units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid type = " << fluidType );
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter_SI << ", density = " << densitySphere_SI << ", starting gap size = " << startingGapSize_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid_SI << ", dyn. visc = " << dynamicViscosityFluid_SI << ", kin. visc = " << kinematicViscosityFluid_SI );
+   WALBERLA_LOG_INFO_ON_ROOT(" - expected settling velocity = " << expectedSettlingVelocity_SI << " --> Re_p = " << expectedSettlingVelocity_SI * diameter_SI / kinematicViscosityFluid_SI );
+
+
+   //////////////////////////
+   // NUMERICAL PARAMETERS //
+   //////////////////////////
+
+
+   const real_t dx_SI = domainSize_SI[0] / real_c(numberOfCellsInHorizontalDirection);
+   const Vector3<uint_t> domainSize( uint_c(floor(domainSize_SI[0] / dx_SI + real_t(0.5)) ),
+                                     uint_c(floor(domainSize_SI[1] / dx_SI + real_t(0.5)) ),
+                                     uint_c(floor(domainSize_SI[2] / dx_SI + real_t(0.5)) ) );
+   const real_t diameter = diameter_SI / dx_SI;
+   const real_t sphereVolume = math::pi / real_t(6) * diameter * diameter * diameter;
+
+   const real_t expectedSettlingVelocity = real_t(0.01);
+   const real_t dt_SI = expectedSettlingVelocity / expectedSettlingVelocity_SI * dx_SI;
+
+   const real_t viscosity =  kinematicViscosityFluid_SI * dt_SI / ( dx_SI * dx_SI );
+   const real_t relaxationTime = real_t(1) / lbm::collision_model::omegaFromViscosity(viscosity);
+
+   const real_t gravitationalAcceleration = gravitationalAcceleration_SI * dt_SI * dt_SI / dx_SI;
+
+   const real_t densityFluid = real_t(1);
+   const real_t densitySphere = densityFluid * densitySphere_SI / densityFluid_SI;
+
+   const real_t dx = real_t(1);
+
+   const uint_t timesteps = funcTest ? 1 : ( shortrun ? uint_t(200) : uint_t( 250000 ) );
+
+   WALBERLA_LOG_INFO_ON_ROOT(" - dx_SI = " << dx_SI << ", dt_SI = " << dt_SI);
+   WALBERLA_LOG_INFO_ON_ROOT("Setup (in simulation, i.e. lattice, units):");
+   WALBERLA_LOG_INFO_ON_ROOT(" - domain size = " << domainSize);
+   WALBERLA_LOG_INFO_ON_ROOT(" - sphere: diameter = " << diameter << ", density = " << densitySphere );
+   WALBERLA_LOG_INFO_ON_ROOT(" - fluid: density = " << densityFluid << ", relaxation time (tau) = " << relaxationTime << ", kin. visc = " << viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - gravitational acceleration = " << gravitationalAcceleration );
+   WALBERLA_LOG_INFO_ON_ROOT(" - expected settling velocity = " << expectedSettlingVelocity << " --> Re_p = " << expectedSettlingVelocity * diameter / viscosity );
+   WALBERLA_LOG_INFO_ON_ROOT(" - integrator = " << (useVelocityVerlet ? "Velocity Verlet" : "Explicit Euler") );
+
+   if( vtkIOFreq > 0 )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing vtk files to folder \"" << baseFolder << "\" with frequency " << vtkIOFreq);
+   }
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+
+   Vector3<uint_t> numberOfBlocksPerDirection( uint_t(1), uint_t(1), uint_t(4) );
+   Vector3<uint_t> cellsPerBlockPerDirection( domainSize[0] / numberOfBlocksPerDirection[0],
+                                              domainSize[1] / numberOfBlocksPerDirection[1],
+                                              domainSize[2] / numberOfBlocksPerDirection[2] );
+   for( uint_t i = 0; i < 3; ++i ) {
+      WALBERLA_CHECK_EQUAL(cellsPerBlockPerDirection[i] * numberOfBlocksPerDirection[i], domainSize[i],
+                           "Unmatching domain decomposition in direction " << i << "!");
+   }
+
+   auto blocks = blockforest::createUniformBlockGrid( numberOfBlocksPerDirection[0], numberOfBlocksPerDirection[1], numberOfBlocksPerDirection[2],
+                                                      cellsPerBlockPerDirection[0], cellsPerBlockPerDirection[1], cellsPerBlockPerDirection[2], dx,
+                                                      0, false, false,
+                                                      false, false, false, //periodicity
+                                                      false );
+
+   WALBERLA_LOG_INFO_ON_ROOT("Domain decomposition:");
+   WALBERLA_LOG_INFO_ON_ROOT(" - blocks per direction = " << numberOfBlocksPerDirection );
+   WALBERLA_LOG_INFO_ON_ROOT(" - cells per block = " << cellsPerBlockPerDirection );
+
+   //write domain decomposition to file
+   if( vtkIOFreq > 0 )
+   {
+      vtk::writeDomainDecomposition( blocks, "initial_domain_decomposition", baseFolder );
+   }
+
+   //////////////////
+   // RPD COUPLING //
+   //////////////////
+
+   auto rpdDomain = 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>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = walberla::make_shared<ParticleAccessor_T >(ps, ss);
+
+   // bounding planes
+   createPlaneSetup(ps,ss,blocks->getDomain());
+
+   // create sphere and store Uid
+   Vector3<real_t> initialPosition( real_t(0.5) * real_c(domainSize[0]), real_t(0.5) * real_c(domainSize[1]), startingGapSize_SI / dx_SI + real_t(0.5) * diameter);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( diameter * real_t(0.5) );
+   ss->shapes[sphereShape]->updateMassAndInertia(densitySphere);
+
+   walberla::id_t sphereUid = 0;
+   if (rpdDomain->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);
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // create the lattice model
+   LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::TRT::constructWithMagicNumber( real_t(1) / relaxationTime ) );
+
+   // add PDF field
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (zyxf)", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         uint_t(1), field::zyxf );
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::zyxf, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // set up RPD functionality
+   std::function<void(void)> syncCall = [ps,rpdDomain](){
+      const real_t overlap = real_t( 1.5 );
+      mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc;
+      syncNextNeighborFunc(*ps, *rpdDomain, overlap);
+   };
+
+   syncCall();
+
+   mesa_pd::kernel::ExplicitEulerWithShape explEulerIntegrator(real_t(1)/real_t(numRPDSubCycles));
+   mesa_pd::kernel::VelocityVerletWithShapePreForceUpdate  vvIntegratorPreForce(real_t(1)/real_t(numRPDSubCycles));
+   mesa_pd::kernel::VelocityVerletWithShapePostForceUpdate vvIntegratorPostForce(real_t(1)/real_t(numRPDSubCycles));
+
+   mesa_pd::kernel::SpringDashpot collisionResponse(1);
+   mesa_pd::mpi::ReduceProperty reduceProperty;
+
+   // set up coupling functionality
+   lbm_mesapd_coupling::RegularParticlesSelector sphereSelector;
+   Vector3<real_t> gravitationalForce( real_t(0), real_t(0), -(densitySphere - densityFluid) * gravitationalAcceleration * sphereVolume );
+   lbm_mesapd_coupling::AddForceOnParticlesKernel addGravitationalForce(gravitationalForce);
+   lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction;
+   lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque;
+   lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque;
+   lbm_mesapd_coupling::LubricationCorrectionKernel lubricationCorrectionKernel(viscosity, [](real_t r){ return real_t(0.0016) * r;});
+   lbm_mesapd_coupling::ParticleMappingKernel<BoundaryHandling_T> particleMappingKernel(blocks, boundaryHandlingID);
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // map planes into the LBM simulation -> act as no-slip boundaries
+   ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, particleMappingKernel, *accessor, NoSlip_Flag);
+
+   // map particles into the LBM simulation
+   ps->forEachParticle(false, sphereSelector, *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+
+   // 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 );
+
+   auto bhSweep = BoundaryHandling_T::getBlockSweep( boundaryHandlingID );
+   auto lbmSweep = lbm::makeCellwiseSweep< LatticeModel_T, FlagField_T >( pdfFieldID, flagFieldID, Fluid_Flag );
+
+   timeloop.addFuncBeforeTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" );
+
+   // vtk output
+   if( vtkIOFreq != uint_t(0) )
+   {
+      // spheres
+      auto particleVtkOutput = make_shared<mesa_pd::vtk::ParticleVtkOutput>(ps);
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleOwner>("owner");
+      particleVtkOutput->addOutput<mesa_pd::data::SelectParticleLinearVelocity>("velocity");
+      auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "Particles", vtkIOFreq, baseFolder, "simulation_step");
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( particleVtkWriter ), "VTK (sphere data)" );
+
+      // flag field (written only once in the first time step, ghost layers are also written)
+      //auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field", timesteps, FieldGhostLayers, false, baseFolder );
+      //flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+      //timeloop.addFuncBeforeTimeStep( vtk::writeFiles( flagFieldVTK ), "VTK (flag field data)" );
+
+      // pdf field
+      auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", vtkIOFreq, 0, false, baseFolder );
+
+      blockforest::communication::UniformBufferedScheme< stencil::D3Q27 > pdfGhostLayerSync( blocks );
+      pdfGhostLayerSync.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) );
+      pdfFieldVTK->addBeforeFunction( pdfGhostLayerSync );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID );
+      fluidFilter.addFlag( Fluid_Flag );
+      pdfFieldVTK->addCellInclusionFilter( fluidFilter );
+
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTK->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+   }
+
+   // sweep for updating the particle mapping into the LBM simulation
+   timeloop.add() << Sweep( lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, sphereSelector, conserveMomentum), "Particle Mapping" );
+
+
+
+   // sweep for restoring PDFs in cells previously occupied by particles
+   if( useEANReconstructor )
+   {
+
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+      auto equilibriumAndNonEquilibriumSphereNormalReconstructor = lbm_mesapd_coupling::makeEquilibriumAndNonEquilibriumReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder);
+      auto reconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, equilibriumAndNonEquilibriumSphereNormalReconstructor, conserveMomentum);
+
+      timeloop.add() << Sweep( makeSharedSweep(reconstructionManager), "PDF Restore" );
+
+   } else if( useExtReconstructor )
+   {
+      auto sphereNormalExtrapolationDirectionFinder = make_shared<lbm_mesapd_coupling::SphereNormalExtrapolationDirectionFinder>(blocks);
+      auto extrapolationSphereNormalReconstructor = lbm_mesapd_coupling::makeExtrapolationReconstructor<BoundaryHandling_T>(blocks, boundaryHandlingID, sphereNormalExtrapolationDirectionFinder, true);
+      timeloop.add() << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, extrapolationSphereNormalReconstructor, conserveMomentum) ), "PDF Restore" );
+   } else
+   {
+      timeloop.add() << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, conserveMomentum) ), "PDF Restore" );
+   }
+
+
+   bool useStreamCollide = true;
+
+
+   if( useStreamCollide )
+   {
+      // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+      timeloop.add() << BeforeFunction( commFunction, "LBM Communication" )
+                     << Sweep(bhSweep, "Boundary Handling" );
+
+      // stream + collide LBM step
+      timeloop.add() << Sweep( makeSharedSweep( lbmSweep ), "cell-wise LB sweep" );
+   }
+   else
+   {
+      // collide LBM step
+      timeloop.add() << Sweep( makeCollideSweep( lbmSweep ), "cell-wise collide LB sweep" );
+
+      // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment)
+      timeloop.add() << BeforeFunction( commFunction, "LBM Communication" )
+                     << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+      // stream LBM step
+      timeloop.add() << Sweep( makeStreamSweep( lbmSweep ), "cell-wise stream LB sweep" );
+   }
+
+
+   // evaluation functionality
+   std::string loggingFileName( baseFolder + "/LoggingSettlingSphere_");
+   loggingFileName += std::to_string(fluidType);
+   loggingFileName += ".txt";
+   if( fileIO  )
+   {
+      WALBERLA_LOG_INFO_ON_ROOT(" - writing logging output to file \"" << loggingFileName << "\"");
+   }
+   SpherePropertyLogger<ParticleAccessor_T> logger( accessor, sphereUid, loggingFileName, fileIO, dx_SI, dt_SI, diameter, -gravitationalForce[2] );
+
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+
+   real_t terminationPosition = real_t(0.51) * diameter; // right before sphere touches the bottom wall
+
+   const bool useOpenMP = false;
+
+   // time loop
+   for (uint_t i = 0; i < timesteps; ++i )
+   {
+      // perform a single simulation step -> this contains LBM and setting of the hydrodynamic interactions
+      timeloop.singleStep( timeloopTiming );
+
+      timeloopTiming["RPD"].start();
+
+      if( averageForceTorqueOverTwoTimSteps && i!= 0)
+      {
+         ps->forEachParticle(useOpenMP, sphereSelector, *accessor, averageHydrodynamicForceTorque, *accessor );
+      }
+
+      for(auto subCycle = uint_t(0); subCycle < numRPDSubCycles; ++subCycle )
+      {
+
+         if( useVelocityVerlet )
+         {
+            ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPreForce, *accessor);
+            syncCall();
+         }
+
+         ps->forEachParticle(useOpenMP, sphereSelector, *accessor, addHydrodynamicInteraction, *accessor );
+         ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addGravitationalForce, *accessor );
+
+         // lubrication correction
+         ps->forEachParticlePairHalf(useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor,
+                                     [&lubricationCorrectionKernel,rpdDomain](const size_t idx1, const size_t idx2, auto& ac)
+                                     {
+            //TODO change this to storing copy, not reference
+                                        mesa_pd::collision_detection::AnalyticContactDetection acd;
+                                        acd.getContactThreshold() = lubricationCorrectionKernel.getNormalCutOffDistance();
+                                        mesa_pd::kernel::DoubleCast double_cast;
+                                        mesa_pd::mpi::ContactFilter contact_filter;
+                                        if (double_cast(idx1, idx2, ac, acd, ac ))
+                                        {
+                                           if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain))
+                                           {
+                                              double_cast(idx1, idx2, ac, lubricationCorrectionKernel, ac, acd.getContactNormal(), acd.getPenetrationDepth());
+                                           }
+                                        }
+                                     },
+                                     *accessor );
+
+         // one could add linked cells here
+
+         // collision response
+         ps->forEachParticlePairHalf(useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor,
+                                     [collisionResponse,rpdDomain](const size_t idx1, const size_t idx2, auto& ac)
+                                     {
+                                        mesa_pd::collision_detection::AnalyticContactDetection acd;
+                                        mesa_pd::kernel::DoubleCast double_cast;
+                                        mesa_pd::mpi::ContactFilter contact_filter;
+                                        if (double_cast(idx1, idx2, ac, acd, ac ))
+                                        {
+                                           if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain))
+                                           {
+                                              collisionResponse(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), acd.getContactNormal(), acd.getPenetrationDepth());
+                                           }
+                                        }
+                                     },
+                                     *accessor );
+
+
+         reduceProperty.operator()<mesa_pd::ForceTorqueNotification>(*ps);
+
+
+         if( useVelocityVerlet ) ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPostForce, *accessor);
+         else ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, explEulerIntegrator, *accessor);
+
+         syncCall();
+      }
+
+      timeloopTiming["RPD"].end();
+
+      // evaluation
+      timeloopTiming["Logging"].start();
+      logger(i);
+      timeloopTiming["Logging"].end();
+
+      // reset after logging here
+      ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor );
+
+      // check for termination
+      if ( logger.getPosition() < terminationPosition )
+      {
+         WALBERLA_LOG_INFO_ON_ROOT("Sphere reached terminal position " << logger.getPosition() << " after " << i << " timesteps!");
+         break;
+      }
+   }
+
+   timeloopTiming.logResultOnRoot();
+
+   // check the result
+   if ( !funcTest && !shortrun )
+   {
+      real_t relErr = std::fabs( expectedSettlingVelocity - logger.getMaxVelocity()) / expectedSettlingVelocity;
+      WALBERLA_LOG_INFO_ON_ROOT( "Expected maximum settling velocity: " << expectedSettlingVelocity );
+      WALBERLA_LOG_INFO_ON_ROOT( "Simulated maximum settling velocity: " << logger.getMaxVelocity() );
+      WALBERLA_LOG_INFO_ON_ROOT( "Relative error: " << relErr );
+
+      // the relative error has to be below 10%
+      WALBERLA_CHECK_LESS( relErr, real_t(0.1) );
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace settling_sphere_mem
+
+int main( int argc, char **argv ){
+   settling_sphere::main(argc, argv);
+}
diff --git a/tests/lbm_mesapd_coupling/momentum_exchange_method/UpdateParticleMapping.cpp b/tests/lbm_mesapd_coupling/momentum_exchange_method/UpdateParticleMapping.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6ac2b87c83547c946730b5c929809fb2af4f8b38
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/momentum_exchange_method/UpdateParticleMapping.cpp
@@ -0,0 +1,625 @@
+//======================================================================================================================
+//
+//  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 UpdateParticleMapping.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "boundary/all.h"
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/Debug.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/math/all.h"
+
+#include "field/AddToStorage.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.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/SimpleBB.h"
+#include "lbm_mesapd_coupling/momentum_exchange_method/reconstruction/PdfReconstructionManager.h"
+#include "lbm_mesapd_coupling/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+
+#include "mesa_pd/mpi/SyncNextNeighbors.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/kernel/SingleCast.h"
+
+#include "vtk/all.h"
+#include "field/vtk/all.h"
+
+namespace update_particle_mapping
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::D3Q19<lbm::collision_model::SRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+const uint_t FieldGhostLayers = 1;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag ( "fluid" );
+const FlagUID MO_Flag ( "moving obstacle" );
+const FlagUID FormerMO_Flag ( "former moving obstacle" );
+const FlagUID NoSlip_Flag( "no slip" );
+
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+
+template <typename ParticleAccessor>
+class MyBoundaryHandling
+{
+public:
+
+   using NoSlip_T = lbm::NoSlip< LatticeModel_T, flag_t >;
+   using MO_T = lbm_mesapd_coupling::SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, NoSlip_T, MO_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor>& ac) :
+      flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ),
+                                  MO_T("MO_BB",  MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor> ac_;
+
+};
+
+template <typename BoundaryHandling_T>
+class MappingChecker
+{
+public:
+   MappingChecker(const shared_ptr< StructuredBlockStorage > & blocks,
+                  const BlockDataID & boundaryHandlingID) :
+         blocks_( blocks ), boundaryHandlingID_( boundaryHandlingID )
+   { }
+
+   // check the mapping and check mapped volume against real sphere volume located at position pos
+   void operator()(std::string testIdentifier, const Vector3<real_t> & pos, real_t sphereRadius )
+   {
+      real_t sphereVolume = math::pi * real_t(4) / real_t(3) * sphereRadius * sphereRadius * sphereRadius;
+      uint_t cellCounter( uint_t(0) );
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+
+         auto xyzSize = flagField->xyzSize();
+
+         for (auto cellIt : xyzSize) {
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter(*blockIt, cellIt);
+            real_t distance = (cellCenter - pos).length();
+
+            if (distance < sphereRadius) {
+               WALBERLA_CHECK(boundaryHandling->isBoundary(cellIt),
+                              testIdentifier << " Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected boundary cell. Distance cell center - particle center = "
+                                             << distance << ".");
+               ++cellCounter;
+            }
+         }
+      }
+      // mapped volume has to be - approximately - the same as the real volume
+      real_t mappedVolume = real_c(cellCounter); // dx=1
+      WALBERLA_CHECK(std::fabs( mappedVolume - sphereVolume ) / sphereVolume <= real_t(0.1),
+                     testIdentifier << " Mapped volume " << mappedVolume << " does not fit to real sphere volume " << sphereVolume << ".");
+   }
+
+   // check the mapping of a plane for a given AABB
+   void operator()(std::string testIdentifier, const math::AABB & planeAABB )
+   {
+      uint_t cellCounter( uint_t(0) );
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+
+         auto xyzSize = flagField->xyzSize();
+
+         for (auto cellIt : xyzSize) {
+            Vector3<real_t> cellCenter = blocks_->getBlockLocalCellCenter(*blockIt, cellIt);
+
+            if (planeAABB.contains(cellCenter)) {
+               WALBERLA_CHECK(boundaryHandling->isBoundary(cellIt),
+                              testIdentifier << " Invalid mapping in cell " << cellIt
+                                             << " with center at " << cellCenter
+                                             << " - expected boundary cell since inside AABB " << planeAABB);
+               ++cellCounter;
+            }
+         }
+      }
+   }
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+
+};
+
+template< typename BoundaryHandling_T>
+class MappingResetter
+{
+public:
+   MappingResetter(const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & boundaryHandlingID, const BlockDataID & particleFieldID, walberla::id_t invalidUID) :
+         blocks_( blocks ), boundaryHandlingID_( boundaryHandlingID ), particleFieldID_( particleFieldID ), invalidUID_( invalidUID )
+   { }
+
+   void operator()()
+   {
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         auto * boundaryHandling = blockIt->getData< BoundaryHandling_T >( boundaryHandlingID_ );
+         auto *        flagField = boundaryHandling->getFlagField();
+         auto *    particleField = blockIt->getData< lbm_mesapd_coupling::ParticleField_T >( particleFieldID_ );
+
+         auto xyzSizeWGl = flagField->xyzSizeWithGhostLayer();
+         // reset to domain (fluid)
+         boundaryHandling->forceDomain(xyzSizeWGl);
+
+         for( auto cellIt = xyzSizeWGl.begin(); cellIt != xyzSizeWGl.end(); ++cellIt )
+         {
+            // reset body field
+            particleField->get(*cellIt) = invalidUID_;
+         }
+      }
+   }
+
+private:
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID boundaryHandlingID_;
+   const BlockDataID particleFieldID_;
+   walberla::id_t invalidUID_;
+};
+
+
+
+/*!\brief Test case for the update particle mapping functionality
+ *
+ * The following scenarios are tested:
+ *  - two spheres in contact (i.e. overlapping the same cell), then one moves away
+ *  - sphere-plane in contact, then sphere moves away
+ *  - sphere in contact with a no-slip boundary condition, then moves away
+ *
+ *  The expected behavior in all cases is that the mapping afterwards is still valid and existing boundary conditions are not overwritten.
+ */
+
+
+//////////
+// MAIN //
+//////////
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   bool writeVTK = true;
+   const real_t omega  = real_t(1);
+   const real_t dx     = real_t(1);
+   const real_t radius = real_t(5);
+
+   ///////////////////////////
+   // DATA STRUCTURES SETUP //
+   ///////////////////////////
+
+   Vector3<uint_t> blocksPerDirection(uint_t(1), uint_t(1), uint_t(1));
+   Vector3<uint_t> cellsPerBlock(uint_t(30), uint_t(30), uint_t(30));
+   Vector3<bool> periodicity(false, false, false);
+
+   auto blocks = blockforest::createUniformBlockGrid( blocksPerDirection[0], blocksPerDirection[1], blocksPerDirection[2],
+                                                      cellsPerBlock[0], cellsPerBlock[1], cellsPerBlock[2],
+                                                      dx,
+                                                      1, false, false,
+                                                      periodicity[0], periodicity[1], periodicity[2],
+                                                      false );
+
+   // create the lattice model
+   LatticeModel_T latticeModel = LatticeModel_T( omega );
+
+   // add PDF field
+   BlockDataID 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 );
+
+   //init data structures
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   shared_ptr<ParticleAccessor_T> accessor = make_shared<ParticleAccessor_T>(ps, ss);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+   auto planeShape = ss->create<mesa_pd::data::HalfSpace>( Vector3<real_t>(real_t(1), real_t(0), real_t(0)) );
+
+   // add particle field
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::zyxf, FieldGhostLayers );
+
+   // add boundary handling
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+   auto bhBlockSweep = BoundaryHandling_T::getBlockSweep( boundaryHandlingID );
+
+   // reconstructor
+   auto reconstructionManager = lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMO_Flag, Fluid_Flag, false);
+
+   // vtk
+   auto flagFieldVTK = vtk::createVTKOutput_BlockData( blocks, "flag_field" );
+   flagFieldVTK->addCellDataWriter( make_shared< field::VTKWriter< FlagField_T > >( flagFieldID, "FlagField" ) );
+
+   // testing utilities
+   MappingChecker<BoundaryHandling_T> mappingChecker(blocks, boundaryHandlingID);
+   MappingResetter<BoundaryHandling_T> mappingResetter(blocks, boundaryHandlingID, particleFieldID, accessor->getInvalidUid());
+
+   // mapping functors
+   auto regularParticleMapper = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, lbm_mesapd_coupling::RegularParticlesSelector(), true );
+   auto globalParticleMapper = lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MO_Flag, FormerMO_Flag, lbm_mesapd_coupling::GlobalParticlesSelector(), true );
+
+   /////////////////////////////
+   // SPHERE - SPHERE MAPPING //
+   /////////////////////////////
+   {
+      std::string testIdentifier("Test: two spheres single moving");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      Vector3<real_t> pos1(10, 10, 9.5);
+      Vector3<real_t> pos2(19, 10, 9.5);
+
+      walberla::id_t sphere2Uid;
+
+      // create sphere 1
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos1);
+         p.setInteractionRadius(radius);
+         p.setShapeID(sphereShape);
+      }
+
+      // create sphere 2
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos2);
+         p.setInteractionRadius(radius);
+         p.setShapeID(sphereShape);
+         sphere2Uid = p.getUid();
+      }
+
+      // map
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (regularParticleMapper)(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check initial mapping
+      mappingChecker(testIdentifier + " mapping check 1, sphere 1", pos1, radius);
+      mappingChecker(testIdentifier + " mapping check 1, sphere 2", pos2, radius);
+
+      // carry out boundary handling (sets forces)
+      for(auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) bhBlockSweep(&(*blockIt));
+
+      // check force on particles (should be zero)
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, [](const size_t idx, const ParticleAccessor_T& ac){ WALBERLA_CHECK_EQUAL(ac.getHydrodynamicForce(idx), Vector3<real_t>(real_t(0)));}, *accessor);
+
+      // update position
+      auto updatedPos2 = pos2 + Vector3<real_t>(real_t(1), real_t(0), real_t(0));
+      accessor->setPosition(accessor->uidToIdx(sphere2Uid),updatedPos2 );
+
+      // update mapping
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (regularParticleMapper)(&(*blockIt));
+
+      // PDF reconstruction
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (*reconstructionManager)(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check updated mapping
+      mappingChecker(testIdentifier + " mapping check 2, sphere 1", pos1, radius);
+      mappingChecker(testIdentifier + " mapping check 2, sphere 2", updatedPos2, radius);
+
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+   }
+
+
+   /////////////////////////////
+   // SPHERE - SPHERE MAPPING //
+   /////////////////////////////
+   {
+      std::string testIdentifier("Test: two spheres both moving");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      Vector3<real_t> pos1(10, 10, 9.5);
+      Vector3<real_t> pos2(19, 10, 9.5);
+
+      walberla::id_t sphere1Uid;
+      walberla::id_t sphere2Uid;
+
+      // create sphere 1
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos1);
+         p.setInteractionRadius(radius);
+         p.setShapeID(sphereShape);
+         sphere1Uid = p.getUid();
+      }
+
+      // create sphere 2
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos2);
+         p.setInteractionRadius(radius);
+         p.setShapeID(sphereShape);
+         sphere2Uid = p.getUid();
+      }
+
+      // map
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (regularParticleMapper)(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check initial mapping
+      mappingChecker(testIdentifier + " mapping check 1, sphere 1", pos1, radius);
+      mappingChecker(testIdentifier + " mapping check 1, sphere 2", pos2, radius);
+
+      // carry out boundary handling (sets forces)
+      for(auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) bhBlockSweep(&(*blockIt));
+
+      // check force on particles (should be zero)
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, [](const size_t idx, const ParticleAccessor_T& ac){ WALBERLA_CHECK_EQUAL(ac.getHydrodynamicForce(idx), Vector3<real_t>(real_t(0)));}, *accessor);
+
+      // update position
+      auto updatedPos1 = pos1 + Vector3<real_t>(-real_t(1), real_t(0), real_t(0));
+      accessor->setPosition(accessor->uidToIdx(sphere1Uid),updatedPos1 );
+      auto updatedPos2 = pos2 + Vector3<real_t>(real_t(1), real_t(0), real_t(0));
+      accessor->setPosition(accessor->uidToIdx(sphere2Uid),updatedPos2 );
+
+      // update mapping
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (regularParticleMapper)(&(*blockIt));
+
+      // PDF reconstruction
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (*reconstructionManager)(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check updated mapping
+      mappingChecker(testIdentifier + " mapping check 2, sphere 1", updatedPos1, radius);
+      mappingChecker(testIdentifier + " mapping check 2, sphere 2", updatedPos2, radius);
+
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+   }
+
+
+   /////////////////////////////
+   // SPHERE - PLANE MAPPING //
+   /////////////////////////////
+   {
+      std::string testIdentifier("Test: sphere and plane");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      Vector3<real_t> pos1(1, 10, 9.5);
+      Vector3<real_t> pos2(5, 10, 9.5);
+
+      walberla::id_t sphere2Uid;
+
+      // create plane
+      {
+         mesa_pd::data::Particle&& p = *ps->create(true);
+         p.setPosition(pos1);
+         p.setShapeID(planeShape);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+      }
+
+      // create sphere 2
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos2);
+         p.setInteractionRadius(radius);
+         p.setShapeID(sphereShape);
+         sphere2Uid = p.getUid();
+      }
+
+      // map
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (regularParticleMapper)(&(*blockIt));
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (globalParticleMapper)(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check initial mapping
+      math::AABB planeAABB(real_t(0), real_t(0), real_t(0), pos1[0], real_c(cellsPerBlock[1]), real_c(cellsPerBlock[2]));
+      mappingChecker(testIdentifier + " mapping check 1, plane", planeAABB);
+      mappingChecker(testIdentifier + " mapping check 1, sphere", pos2, radius);
+
+      // carry out boundary handling (sets forces)
+      for(auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) bhBlockSweep(&(*blockIt));
+
+      // check force on particles (should be zero)
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, [](const size_t idx, const ParticleAccessor_T& ac){ WALBERLA_CHECK_EQUAL(ac.getHydrodynamicForce(idx), Vector3<real_t>(real_t(0)));}, *accessor);
+
+      // update position
+      auto updatedPos2 = pos2 + Vector3<real_t>(real_t(1), real_t(0), real_t(0));
+      accessor->setPosition(accessor->uidToIdx(sphere2Uid),updatedPos2 );
+
+      // update mapping
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (regularParticleMapper)(&(*blockIt));
+
+      // PDF reconstruction
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (*reconstructionManager)(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check updated mapping
+      mappingChecker(testIdentifier + " mapping check 2, plane", planeAABB);
+      mappingChecker(testIdentifier + " mapping check 2, sphere", updatedPos2, radius);
+
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+   }
+
+   ///////////////////////////////
+   // SPHERE - BOUNDARY MAPPING //
+   ///////////////////////////////
+   // here, a plane is used to map the no slip boundary condition into the domain
+   {
+      std::string testIdentifier("Test: sphere and boundary");
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - started");
+
+      Vector3<real_t> pos1(1, 10, 9.5);
+      Vector3<real_t> pos2(5, 10, 9.5);
+
+      walberla::id_t sphere2Uid;
+
+      // create plane
+      {
+         mesa_pd::data::Particle&& p = *ps->create(true);
+         p.setPosition(pos1);
+         p.setShapeID(planeShape);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE);
+         mesa_pd::data::particle_flags::set(p.getFlagsRef(), mesa_pd::data::particle_flags::FIXED);
+      }
+
+      // create sphere 2
+      {
+         mesa_pd::data::Particle&& p = *ps->create();
+         p.setPosition(pos2);
+         p.setInteractionRadius(radius);
+         p.setShapeID(sphereShape);
+         sphere2Uid = p.getUid();
+      }
+
+      // map
+      lbm_mesapd_coupling::ParticleMappingKernel<BoundaryHandling_T> particleMappingKernel(blocks, boundaryHandlingID);
+      ps->forEachParticle(false, lbm_mesapd_coupling::GlobalParticlesSelector(), *accessor, particleMappingKernel, *accessor, NoSlip_Flag);
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (regularParticleMapper)(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check initial mapping
+      math::AABB planeAABB(real_t(0), real_t(0), real_t(0), pos1[0], real_c(cellsPerBlock[1]), real_c(cellsPerBlock[2]));
+      mappingChecker(testIdentifier + " mapping check 1, boundary", planeAABB);
+      mappingChecker(testIdentifier + " mapping check 1, sphere", pos2, radius);
+
+      // carry out boundary handling (sets forces)
+      for(auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) bhBlockSweep(&(*blockIt));
+
+      // check force on particles (should be zero)
+      ps->forEachParticle(false, lbm_mesapd_coupling::RegularParticlesSelector(), *accessor, [](const size_t idx, const ParticleAccessor_T& ac){ WALBERLA_CHECK_EQUAL(ac.getHydrodynamicForce(idx), Vector3<real_t>(real_t(0)));}, *accessor);
+
+      // update position
+      auto updatedPos2 = pos2 + Vector3<real_t>(real_t(1), real_t(0), real_t(0));
+      accessor->setPosition(accessor->uidToIdx(sphere2Uid),updatedPos2 );
+
+      // update mapping
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (regularParticleMapper)(&(*blockIt));
+
+      // PDF reconstruction
+      for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (*reconstructionManager)(&(*blockIt));
+
+      if( writeVTK ) flagFieldVTK->write();
+
+      // check updated mapping
+      mappingChecker(testIdentifier + " mapping check 2, boundary", planeAABB);
+      mappingChecker(testIdentifier + " mapping check 2, sphere", updatedPos2, radius);
+
+      mappingResetter();
+
+      WALBERLA_LOG_DEVEL_ON_ROOT(testIdentifier << " - ended successfully");
+
+      // clean up
+      ps->clear();
+   }
+
+   return 0;
+
+}
+
+} //namespace update_particle_mapping
+
+int main( int argc, char **argv ){
+   update_particle_mapping::main(argc, argv);
+}
diff --git a/tests/lbm_mesapd_coupling/utility/InspectionProbe.cpp b/tests/lbm_mesapd_coupling/utility/InspectionProbe.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c8c4d173e82be628dc3f79aadea30539f3fdf9bf
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/utility/InspectionProbe.cpp
@@ -0,0 +1,222 @@
+//======================================================================================================================
+//
+//  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 InspectionProbe.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.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/math/all.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/logging/all.h"
+
+#include "domain_decomposition/SharedSweep.h"
+
+#include "field/AddToStorage.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.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/utility/ParticleSelector.h"
+#include "lbm_mesapd_coupling/utility/InspectionProbe.h"
+#include "lbm_mesapd_coupling/DataTypes.h"
+
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/shape/Sphere.h"
+#include "mesa_pd/domain/BlockForestDomain.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+namespace inspection_probe
+{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+using LatticeModel_T = lbm::D3Q19< lbm::collision_model::TRT>;
+
+using Stencil_T = LatticeModel_T::Stencil;
+using PdfField_T = lbm::PdfField<LatticeModel_T>;
+
+using flag_t = walberla::uint8_t;
+using FlagField_T = FlagField<flag_t>;
+
+const uint_t FieldGhostLayers = 1;
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag( "fluid" );
+const FlagUID MO_Flag( "moving obstacle" );
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+template <typename ParticleAccessor_T>
+class MyBoundaryHandling
+{
+public:
+
+   using MO_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >;
+   using Type = BoundaryHandling< FlagField_T, Stencil_T, MO_T >;
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID,
+                       const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac) :
+         flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), ac_( ac ) {}
+
+   Type * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const
+   {
+      WALBERLA_ASSERT_NOT_NULLPTR( block );
+      WALBERLA_ASSERT_NOT_NULLPTR( storage );
+
+      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 );
+
+      Type * handling = new Type( "moving obstacle boundary handling", flagField, fluid,
+                                  MO_T( "MO", MO_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) );
+
+      handling->fillWithDomain( FieldGhostLayers );
+
+      return handling;
+   }
+
+private:
+
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID particleFieldID_;
+
+   shared_ptr<ParticleAccessor_T> ac_;
+};
+
+
+//////////
+// MAIN //
+//////////
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   bool printToScreen = false;
+   bool printSurroundingState = false;
+   std::string outputFileName = "";
+
+   for( int i = 1; i < argc; ++i )
+   {
+      if( std::strcmp( argv[i], "--printToScreen"  ) == 0 ) { printToScreen  = true; continue; }
+      if( std::strcmp( argv[i], "--printSurroundingState"  ) == 0 ) { printSurroundingState  = true; continue; }
+      if( std::strcmp( argv[i], "--outputFileName" ) == 0 ) { outputFileName = argv[++i]; continue; }
+      WALBERLA_ABORT("Unrecognized command line argument found: " << argv[i]);
+   }
+
+   const uint_t length = uint_t(32);
+   const real_t radius = real_t(5);
+
+   const uint_t XBlocks = uint_t( 1 );
+   const uint_t YBlocks = uint_t( 1 );
+   const uint_t ZBlocks = uint_t( 1 );
+   const uint_t XCells = length / XBlocks;
+   const uint_t YCells = length / YBlocks;
+   const uint_t ZCells = length / ZBlocks;
+
+   auto blocks = blockforest::createUniformBlockGrid( XBlocks, YBlocks, ZBlocks, XCells, YCells, ZCells, uint_t(1), true,
+                                                      true, true, true );
+
+
+   mesa_pd::domain::BlockForestDomain domain(blocks->getBlockForestPointer());
+
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto ss = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor_T = mesa_pd::data::ParticleAccessorWithShape;
+   auto accessor = make_shared<ParticleAccessor_T >(ps, ss);
+   auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius );
+
+   Vector3<real_t> centerPosition (real_c(length) * real_c(0.5));
+   {
+      mesa_pd::data::Particle&& p = *ps->create();
+      p.setPosition(centerPosition);
+      p.setInteractionRadius(radius);
+      p.setOwner(mpi::MPIManager::instance()->rank());
+      p.setShapeID(sphereShape);
+
+   }
+
+   LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::TRT::constructWithMagicNumber( real_t(1) ) );
+
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (zyxf)", latticeModel,
+                                                                         Vector3< real_t >( real_t(0) ), real_t(1),
+                                                                         uint_t(1), field::zyxf );
+
+   BlockDataID flagFieldID = field::addFlagFieldToStorage<FlagField_T>( blocks, "flag field" );
+
+   BlockDataID particleFieldID = field::addToStorage<lbm_mesapd_coupling::ParticleField_T>( blocks, "particle field", accessor->getInvalidUid(), field::zyxf, FieldGhostLayers );
+
+   using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type;
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor), "boundary handling" );
+
+   // map sphere
+   lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID);
+   ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, movingParticleMappingKernel, *accessor, MO_Flag);
+
+   Vector3<real_t> probeLocation = centerPosition - Vector3<real_t>(radius + real_t(1), real_t(0), real_t(0));
+   lbm_mesapd_coupling::InspectionProbe<PdfField_T, BoundaryHandling_T, ParticleAccessor_T> probe(probeLocation, blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, printToScreen, printSurroundingState, outputFileName );
+
+   real_t rhoAtLocation;
+   Vector3<real_t> velocityAtLocation;
+   probe(rhoAtLocation, velocityAtLocation);
+
+   WALBERLA_ASSERT_FLOAT_EQUAL(rhoAtLocation, real_t(1));
+   WALBERLA_ASSERT_FLOAT_EQUAL(velocityAtLocation[0], real_t(0));
+   WALBERLA_ASSERT_FLOAT_EQUAL(velocityAtLocation[1], real_t(0));
+   WALBERLA_ASSERT_FLOAT_EQUAL(velocityAtLocation[2], real_t(0));
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace inspection_probe
+
+int main( int argc, char **argv ){
+   inspection_probe::main(argc, argv);
+}
diff --git a/tests/lbm_mesapd_coupling/utility/LubricationCorrection.cpp b/tests/lbm_mesapd_coupling/utility/LubricationCorrection.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ce4be90eb90c37e26e93ff86058a4a3f1784e391
--- /dev/null
+++ b/tests/lbm_mesapd_coupling/utility/LubricationCorrection.cpp
@@ -0,0 +1,427 @@
+//======================================================================================================================
+//
+//  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 LubricationCorrection.cpp
+//! \ingroup lbm_mesapd_coupling
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "core/DataTypes.h"
+#include "core/Environment.h"
+#include "core/debug/TestSubsystem.h"
+#include "core/math/all.h"
+
+#include "lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h"
+
+#include "mesa_pd/collision_detection/AnalyticContactDetection.h"
+#include "mesa_pd/data/DataTypes.h"
+#include "mesa_pd/data/LinkedCells.h"
+#include "mesa_pd/data/ParticleAccessorWithShape.h"
+#include "mesa_pd/data/ParticleStorage.h"
+#include "mesa_pd/data/ShapeStorage.h"
+#include "mesa_pd/kernel/DoubleCast.h"
+#include "mesa_pd/kernel/InsertParticleIntoLinkedCells.h"
+#include "mesa_pd/kernel/ParticleSelector.h"
+
+namespace lubrication_correction_test
+{
+using namespace walberla;
+
+
+// from Brenner - "The slow motion of a sphere through a viscous fluid towards a plane surface" (1961), Gl. 3.13
+// from Cooley & O'Neill - "On the slow motion generated in a viscous fluid by the approach of a sphere to a plane wall or stationary sphere" (1969) -> Brenners work for free surface is identical to two spheres
+//NOTE: Brenner uses a sphere with velocity U and thus calculates the force as F = 6 PI visc R U beta
+// thus, the normalization to get beta is F / (..U..). However, we have two approaching spheres here with U and -U as velocities, thus relative velocity of 2U. Therefore, we have to divide this value by 2.
+// see also Tab.2 of Cooley & O'Neill and discussion above.
+real_t analyticalNonDimForceSphSph( real_t radius, real_t gap )
+{
+   uint_t numberOfAdditions = 50;
+
+   real_t h = radius + real_t(0.5)*gap; // half gap because the formula was derived for sph-free surface where the gap is half of the gap here
+   real_t alpha = std::acosh(h/radius);
+
+   real_t temp = real_t(0);
+
+   for( uint_t n = 1; n < numberOfAdditions; ++n )
+   {
+      real_t nr = real_t(n);
+      real_t temp1 = real_t(4) * std::cosh( ( nr + real_t(0.5) ) * alpha ) * std::cosh( ( nr + real_t(0.5) ) * alpha ) +
+                     ( real_t(2) * nr + real_t(1) ) * ( real_t(2) * nr + real_t(1) ) * std::sinh( alpha ) * std::sinh( alpha );
+      real_t temp2 = real_t(2) * std::sinh( ( real_t(2) * nr + real_t(1) ) * alpha) -
+                     (real_t(2) * nr + real_t(1) ) * std::sinh( real_t(2) * alpha );
+      temp += nr * ( nr + real_t(1) ) / ( (real_t(2) * nr - real_t(1) ) * ( real_t(2) * nr + real_t(3) ) )  * ( temp1 / temp2 - real_t(1));
+   }
+
+   real_t beta = real_t(4)/real_t(3) * std::sinh(alpha) * temp;
+
+   return beta * real_t(0.5);
+}
+
+// from Brenner - "The slow motion of a sphere through a viscous fluid towards a plane surface" (1961), Gl. 2.19
+real_t analyticalNonDimForceSphWall( real_t radius, real_t gap )
+{
+   uint_t numberOfAdditions = 50;
+
+   real_t h = radius + gap;
+   real_t alpha = std::acosh(h/radius);
+
+   real_t temp = real_t(0);
+
+   for( uint_t n = 1; n < numberOfAdditions; ++n )
+   {
+      real_t nr = real_t(n);
+      real_t temp1 = real_t(2) * std::sinh( ( real_t(2) * nr + real_t(1) ) * alpha ) + ( real_t(2) * nr + real_t(1) ) * std::sinh(real_t(2) * alpha );
+      real_t temp2 = real_t(4) * std::sinh( ( nr + real_t(0.5) ) * alpha) * std::sinh( ( nr + real_t(0.5) ) * alpha) - (real_t(2) * nr + real_t(1)) * (real_t(2) * nr + real_t(1)) * std::sinh(alpha) * std::sinh(alpha);
+      temp += nr * ( nr + real_t(1) ) / ( (real_t(2) * nr - real_t(1) ) * ( real_t(2) * nr + real_t(3) ) )  * ( temp1 / temp2 - real_t(1));
+   }
+
+   real_t lambda = real_t(4)/real_t(3) * std::sinh(alpha) * temp;
+
+   return lambda;
+
+}
+
+/*!\brief Checks the implementation of the lubrication correction
+ *
+ * the resulting forces of various scenarios are checked for plausibility (sign, fi = -fj, etc) and compared to the analytical function
+ * note that the lubrication formula of the correction is typically only a simple approximation of the analytical value (an infinite series) which is why differences will be present
+ *
+ * Currently, only the normal force is checked
+ */
+
+//////////
+// MAIN //
+//////////
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   auto ps = std::make_shared<mesa_pd::data::ParticleStorage>(1);
+   auto shapeStorage = std::make_shared<mesa_pd::data::ShapeStorage>();
+   using ParticleAccessor = mesa_pd::data::ParticleAccessorWithShape;
+   ParticleAccessor accessor(ps, shapeStorage);
+
+   // note: these are just arbitrary test values and are not necessarily physically meaningful!
+   real_t cutOffDistance = real_t(0.8);
+   real_t minimalGapSize = real_t(0.1);
+   real_t dynamicViscosity = real_t(1.7);
+
+   mesa_pd::kernel::DoubleCast doubleCast;
+   mesa_pd::collision_detection::AnalyticContactDetection acd;
+   acd.getContactThreshold() = cutOffDistance;
+
+
+   /////////////////////
+   // SPHERE - SPHERE //
+   /////////////////////
+   {
+      real_t sphereRadius = real_t(2);
+      auto sphereShape = shapeStorage->create<mesa_pd::data::Sphere>( sphereRadius );
+
+      real_t sphereGapSize = real_t(0.3);
+      real_t relativeVelocity = real_t(0.7);
+
+      real_t normalizationFactor = real_t(6) * math::pi * dynamicViscosity * sphereRadius * relativeVelocity;
+
+      real_t analyticalLubForce = (analyticalNonDimForceSphSph(sphereRadius, sphereGapSize) - analyticalNonDimForceSphSph(sphereRadius, cutOffDistance)) * normalizationFactor;
+
+      Vector3<real_t> position1(real_t(0), real_t(0), real_t(0));
+      Vector3<real_t> position2(real_t(2) * sphereRadius + sphereGapSize, real_t(0), real_t(0));
+      Vector3<real_t> position3(real_t(0), real_t(2) * sphereRadius + sphereGapSize, real_t(0));
+      Vector3<real_t> position4(real_t(0), real_t(0), real_t(2) * sphereRadius + sphereGapSize);
+
+      mesa_pd::data::Particle&& p1 = *ps->create();
+      p1.setPosition(position1);
+      p1.setShapeID(sphereShape);
+      p1.setLinearVelocity(Vector3<real_t>(relativeVelocity,-relativeVelocity,real_t(0.5)*relativeVelocity));
+      auto idxSph1 = p1.getIdx();
+
+      mesa_pd::data::Particle&& p2 = *ps->create();
+      p2.setPosition(position2);
+      p2.setShapeID(sphereShape);
+      p2.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(2),real_t(0)));
+      auto idxSph2 = p2.getIdx();
+
+      mesa_pd::data::Particle&& p3 = *ps->create();
+      p3.setPosition(position3);
+      p3.setShapeID(sphereShape);
+      p3.setLinearVelocity(Vector3<real_t>(real_t(2),real_t(0),real_t(2)));
+      auto idxSph3 = p3.getIdx();
+
+      mesa_pd::data::Particle&& p4 = *ps->create();
+      p4.setPosition(position4);
+      p4.setShapeID(sphereShape);
+      p4.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(0),real_t(-0.5)*relativeVelocity));
+      auto idxSph4 = p4.getIdx();
+
+      lbm_mesapd_coupling::LubricationCorrectionKernel lubCorrFctr(dynamicViscosity, [minimalGapSize](real_t /*r*/){return minimalGapSize;}, cutOffDistance, real_t(0), real_t(0));
+
+      ps->forEachParticlePairHalf(false, mesa_pd::kernel::ExcludeInfiniteInfinite(), accessor,
+                                 [&](const size_t idx1, const size_t idx2)
+                                 {
+                                    if (doubleCast(idx1, idx2, accessor, acd, accessor ))
+                                    {
+                                       //note: in parallel simulations, first use the contact filter to assign the pairs to a single process
+                                       doubleCast(idx1, idx2, accessor, lubCorrFctr, accessor, acd.getContactNormal(), acd.getPenetrationDepth());
+                                    }
+                                 }
+      );
+
+      auto force1 = accessor.getForce(idxSph1);
+      auto force2 = accessor.getForce(idxSph2);
+      auto force3 = accessor.getForce(idxSph3);
+      auto force4 = accessor.getForce(idxSph4);
+
+      WALBERLA_CHECK_LESS(force1[0], real_t(0), "Sph1, dir 0, sign check");
+      WALBERLA_CHECK_LESS((std::abs(force1[0]) - std::abs(analyticalLubForce))/ std::abs(analyticalLubForce), real_t(0.2), "Sph1, dir 0, rel error check");
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[0], -force2[0], "Sph1 - Sph2, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force2[1], real_t(0), "Sph2, dir 1");
+      WALBERLA_CHECK_FLOAT_EQUAL(force2[2], real_t(0), "Sph2, dir 2");
+
+      WALBERLA_CHECK_GREATER(force1[1], real_t(0), "Sph1, dir 1, sign check");
+      WALBERLA_CHECK_LESS((std::abs(force1[1]) - std::abs(analyticalLubForce))/ std::abs(analyticalLubForce), real_t(0.2), "Sph1, dir 1, rel error check");
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[1], -force3[1], "Sph1 - Sph3, dir 1");
+      WALBERLA_CHECK_FLOAT_EQUAL(force3[0], real_t(0), "Sph3, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force3[2], real_t(0), "Sph3, dir 2");
+
+      WALBERLA_CHECK_LESS(force1[2], real_t(0), "Sph1, dir 2, sign check");
+      WALBERLA_CHECK_LESS((std::abs(force1[2]) - std::abs(analyticalLubForce))/ std::abs(analyticalLubForce), real_t(0.2), "Sph1, dir 2, rel error check");
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[2], -force4[2], "Sph1 - Sph4, dir 2");
+      WALBERLA_CHECK_FLOAT_EQUAL(force4[0], real_t(0), "Sph4, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force4[1], real_t(0), "Sph4, dir 1");
+
+      // clean up
+      ps->clear();
+
+   }
+
+   ///////////////////////////////////
+   // SPHERE - SPHERE Special Cases //
+   ///////////////////////////////////
+   {
+      real_t sphereRadius = real_t(2);
+      auto sphereShape = shapeStorage->create<mesa_pd::data::Sphere>( sphereRadius );
+
+      real_t relativeVelocity = real_t(0.7);
+
+      real_t normalizationFactor = real_t(6) * math::pi * dynamicViscosity * sphereRadius * relativeVelocity;
+
+      Vector3<real_t> position1(real_t(0), real_t(0), real_t(0));
+      Vector3<real_t> position2(real_t(2) * sphereRadius - real_t(0.1), real_t(0), real_t(0)); // with overlap
+      Vector3<real_t> position3(real_t(0), real_t(2) * sphereRadius + real_t(0.3) * minimalGapSize, real_t(0)); // below minimal gap size
+      Vector3<real_t> position4(real_t(0), real_t(0), real_t(2) * sphereRadius + real_t(1.2) * cutOffDistance); // larger than cutoff distance
+
+      mesa_pd::data::Particle&& p1 = *ps->create();
+      p1.setPosition(position1);
+      p1.setShapeID(sphereShape);
+      p1.setLinearVelocity(Vector3<real_t>(relativeVelocity,-relativeVelocity,real_t(0.5)*relativeVelocity));
+      auto idxSph1 = p1.getIdx();
+
+      mesa_pd::data::Particle&& p2 = *ps->create();
+      p2.setPosition(position2);
+      p2.setShapeID(sphereShape);
+      p2.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(2),real_t(0)));
+      auto idxSph2 = p2.getIdx();
+
+      mesa_pd::data::Particle&& p3 = *ps->create();
+      p3.setPosition(position3);
+      p3.setShapeID(sphereShape);
+      p3.setLinearVelocity(Vector3<real_t>(real_t(2),real_t(0),real_t(2)));
+      auto idxSph3 = p3.getIdx();
+
+      mesa_pd::data::Particle&& p4 = *ps->create();
+      p4.setPosition(position4);
+      p4.setShapeID(sphereShape);
+      p4.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(0),real_t(-0.5)*relativeVelocity));
+      auto idxSph4 = p4.getIdx();
+
+      // variant with linked cells
+      // they can be used to break the O(N^2) complexity
+
+      math::AABB domain(real_t(0), real_t(0), real_t(0), real_t(10), real_t(10), real_t(10));
+      real_t spacing = real_t(1.1) * ( sphereRadius + cutOffDistance );
+      mesa_pd::data::LinkedCells lc(domain.getExtended(spacing), spacing );
+      mesa_pd::kernel::InsertParticleIntoLinkedCells ipilc;
+
+      lc.clear();
+      ps->forEachParticle( false, mesa_pd::kernel::SelectAll(), accessor, ipilc, accessor, lc);
+
+      lbm_mesapd_coupling::LubricationCorrectionKernel lubCorrFctr(dynamicViscosity, [minimalGapSize](real_t /*r*/){return minimalGapSize;}, cutOffDistance, real_t(0), real_t(0));
+
+      lc.forEachParticlePairHalf( false, mesa_pd::kernel::ExcludeInfiniteInfinite(), accessor,
+                                  [&](const size_t idx1, const size_t idx2, auto& ac)
+                                  {
+                                     if (doubleCast(idx1, idx2, ac, acd, ac ))
+                                     {
+                                        //note: in parallel simulations, first use the contact filter to assign the pairs to a single process
+                                        doubleCast(idx1, idx2, ac, lubCorrFctr, ac, acd.getContactNormal(), acd.getPenetrationDepth());
+                                     }
+                                  },
+      accessor
+      );
+
+      auto force1 = accessor.getForce(idxSph1);
+      auto force2 = accessor.getForce(idxSph2);
+      auto force3 = accessor.getForce(idxSph3);
+      auto force4 = accessor.getForce(idxSph4);
+
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[0], real_t(0), "SpecialCase: Sph1, dir 0, sign check");
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[0], -force2[0], "SpecialCase:  Sph1 - Sph2, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force2[1], real_t(0), "SpecialCase:  Sph2, dir 1");
+      WALBERLA_CHECK_FLOAT_EQUAL(force2[2], real_t(0), "SpecialCase:  Sph2, dir 2");
+
+      real_t analyticalLubForce = (analyticalNonDimForceSphSph(sphereRadius, minimalGapSize) - analyticalNonDimForceSphSph(sphereRadius, cutOffDistance)) * normalizationFactor;
+      WALBERLA_CHECK_GREATER(force1[1], real_t(0), "SpecialCase: Sph1, dir 1, sign check");
+      WALBERLA_CHECK_LESS((std::abs(force1[1]) - std::abs(analyticalLubForce))/ std::abs(analyticalLubForce), real_t(0.2), "SpecialCase: Sph1, dir 1, rel error check");
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[1], -force3[1], "SpecialCase: Sph1 - Sph3, dir 1");
+      WALBERLA_CHECK_FLOAT_EQUAL(force3[0], real_t(0), "SpecialCase: Sph3, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force3[2], real_t(0), "SpecialCase: Sph3, dir 2");
+
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[2], real_t(0), "SpecialCase:  Sph1, dir 2, sign check");
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[2], -force4[2], "SpecialCase:  Sph1 - Sph4, dir 2");
+      WALBERLA_CHECK_FLOAT_EQUAL(force4[0], real_t(0), "SpecialCase:  Sph4, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force4[1], real_t(0), "SpecialCase:  Sph4, dir 1");
+
+      // clean up
+      ps->clear();
+
+   }
+
+   ///////////////////
+   // SPHERE - WALL //
+   ///////////////////
+
+   {
+      Vector3<real_t> wallPosition(real_t(0), real_t(0), real_t(0));
+      Vector3<real_t> wallNormal(real_t(0), real_t(0), real_t(1));
+
+      auto planeShape = shapeStorage->create<mesa_pd::data::HalfSpace>( wallNormal.getNormalized() );
+
+      real_t sphereRadius = real_t(2);
+      auto sphereShape = shapeStorage->create<mesa_pd::data::Sphere>( sphereRadius );
+
+      real_t relativeVelocity = real_t(0.7);
+
+      real_t gapSize = real_t(0.3);
+      real_t normalizationFactor = real_t(6) * math::pi * dynamicViscosity * sphereRadius * relativeVelocity;
+
+      Vector3<real_t> position1(real_t(0), real_t(0), sphereRadius + gapSize);
+      Vector3<real_t> position2(real_t( 3) * sphereRadius, real_t(0), sphereRadius + gapSize);
+      Vector3<real_t> position3(real_t( 6) * sphereRadius, real_t(0), sphereRadius - real_t(0.1));
+      Vector3<real_t> position4(real_t( 9) * sphereRadius, real_t(0), sphereRadius + real_t(0.3) * minimalGapSize);
+      Vector3<real_t> position5(real_t(12) * sphereRadius, real_t(0), sphereRadius + real_t(1.1) * cutOffDistance);
+
+      mesa_pd::data::Particle&& p1 = *ps->create();
+      p1.setPosition(position1);
+      p1.setShapeID(sphereShape);
+      p1.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(0),relativeVelocity));
+      auto idxSph1 = p1.getIdx();
+
+      // mix up order to test Sph - HSp and HSp - Sph variants
+      mesa_pd::data::Particle&& pW = *ps->create(true);
+      pW.setPosition(wallPosition);
+      pW.setShapeID(planeShape);
+      auto idxWall = pW.getIdx();
+
+      mesa_pd::data::Particle&& p2 = *ps->create();
+      p2.setPosition(position2);
+      p2.setShapeID(sphereShape);
+      p2.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(0),-relativeVelocity));
+      auto idxSph2 = p2.getIdx();
+
+      mesa_pd::data::Particle&& p3 = *ps->create();
+      p3.setPosition(position3);
+      p3.setShapeID(sphereShape);
+      p3.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(0),relativeVelocity));
+      auto idxSph3 = p3.getIdx();
+
+      mesa_pd::data::Particle&& p4 = *ps->create();
+      p4.setPosition(position4);
+      p4.setShapeID(sphereShape);
+      p4.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(0),relativeVelocity));
+      auto idxSph4 = p4.getIdx();
+
+      mesa_pd::data::Particle&& p5 = *ps->create();
+      p5.setPosition(position5);
+      p5.setShapeID(sphereShape);
+      p5.setLinearVelocity(Vector3<real_t>(real_t(0),real_t(0),relativeVelocity));
+      auto idxSph5 = p5.getIdx();
+
+      lbm_mesapd_coupling::LubricationCorrectionKernel lubCorrFctr(dynamicViscosity, [minimalGapSize](real_t /*r*/){return minimalGapSize;}, cutOffDistance, real_t(0), real_t(0));
+
+      ps->forEachParticlePairHalf(false, mesa_pd::kernel::ExcludeInfiniteInfinite(), accessor,
+                                  [&](const size_t idx1, const size_t idx2)
+                                  {
+                                     if (doubleCast(idx1, idx2, accessor, acd, accessor ))
+                                     {
+                                        //note: in parallel simulations, first use the contact filter to assign the pairs to a single CPU
+                                        doubleCast(idx1, idx2, accessor, lubCorrFctr, accessor, acd.getContactNormal(), acd.getPenetrationDepth());
+                                     }
+                                  }
+      );
+
+      auto force1 = accessor.getForce(idxSph1);
+      auto force2 = accessor.getForce(idxSph2);
+      auto force3 = accessor.getForce(idxSph3);
+      auto force4 = accessor.getForce(idxSph4);
+      auto force5 = accessor.getForce(idxSph5);
+      auto forceW = accessor.getForce(idxWall);
+
+      WALBERLA_CHECK_FLOAT_EQUAL(forceW[0], real_t(0), "Wall, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(forceW[1], real_t(0), "Wall, dir 1");
+      WALBERLA_CHECK_FLOAT_EQUAL(forceW[2], real_t(0), "Wall, dir 2");
+
+      real_t analyticalLubForceRegular = (analyticalNonDimForceSphWall(sphereRadius, gapSize) - analyticalNonDimForceSphWall(sphereRadius, cutOffDistance)) * normalizationFactor;
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[0], real_t(0), "Wall-Sph1, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force1[1], real_t(0), "Wall-Sph1, dir 1");
+      WALBERLA_CHECK_LESS(force1[2], real_t(0), "Wall-Sph1, dir 2, sign check");
+      WALBERLA_CHECK_LESS((std::abs(force1[2]) - std::abs(analyticalLubForceRegular))/ std::abs(analyticalLubForceRegular), real_t(0.2), "Wall-Sph1, dir 2, rel error check");
+
+      WALBERLA_CHECK_FLOAT_EQUAL(force2[0], real_t(0), "Wall-Sph2, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force2[1], real_t(0), "Wall-Sph2, dir 1");
+      WALBERLA_CHECK_GREATER(force2[2], real_t(0), "Wall-Sph2, dir 2, sign check");
+      WALBERLA_CHECK_LESS((std::abs(force2[2]) - std::abs(analyticalLubForceRegular))/ std::abs(analyticalLubForceRegular), real_t(0.2), "Wall-Sph2, dir 2, rel error check");
+
+      WALBERLA_CHECK_FLOAT_EQUAL(force3[0], real_t(0), "Wall-Sph3, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force3[1], real_t(0), "Wall-Sph3, dir 1");
+      WALBERLA_CHECK_FLOAT_EQUAL(force3[2], real_t(0), "Wall-Sph3, dir 2");
+
+      real_t analyticalLubForceMin = (analyticalNonDimForceSphWall(sphereRadius, minimalGapSize) - analyticalNonDimForceSphWall(sphereRadius, cutOffDistance)) * normalizationFactor;
+      WALBERLA_CHECK_FLOAT_EQUAL(force4[0], real_t(0), "Wall-Sph4, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force4[1], real_t(0), "Wall-Sph4, dir 1");
+      WALBERLA_CHECK_LESS(force4[2], real_t(0), "Wall-Sph4, dir 2, sign check");
+      WALBERLA_CHECK_LESS((std::abs(force4[2]) - std::abs(analyticalLubForceMin))/ std::abs(analyticalLubForceMin), real_t(0.2), "Wall-Sph4, dir 2, rel error check");
+
+      WALBERLA_CHECK_FLOAT_EQUAL(force5[0], real_t(0), "Wall-Sph5, dir 0");
+      WALBERLA_CHECK_FLOAT_EQUAL(force5[1], real_t(0), "Wall-Sph5, dir 1");
+      WALBERLA_CHECK_FLOAT_EQUAL(force5[2], real_t(0), "Wall-Sph5, dir 2");
+
+      // clean up
+      ps->clear();
+
+   }
+
+   return 0;
+
+}
+
+} //namespace lubrication_correction_test
+
+int main( int argc, char **argv ){
+   lubrication_correction_test::main(argc, argv);
+}