From 0ba7bf900d3530d4c63a2b49fd8cc164aa45c5f7 Mon Sep 17 00:00:00 2001
From: Christoph Rettinger <christoph.rettinger@fau.de>
Date: Thu, 18 May 2017 17:51:33 +0200
Subject: [PATCH] Added motion single heavy sphere benchmark

---
 apps/benchmarks/CMakeLists.txt                |    1 +
 .../MotionSingleHeavySphere/CMakeLists.txt    |    2 +
 .../MotionSingleHeavySphere.cpp               | 1402 +++++++++++++++++
 3 files changed, 1405 insertions(+)
 create mode 100644 apps/benchmarks/MotionSingleHeavySphere/CMakeLists.txt
 create mode 100644 apps/benchmarks/MotionSingleHeavySphere/MotionSingleHeavySphere.cpp

diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt
index bad57adab..02a864055 100644
--- a/apps/benchmarks/CMakeLists.txt
+++ b/apps/benchmarks/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_subdirectory( CouetteFlow )
 add_subdirectory( NonUniformGrid )
+add_subdirectory( MotionSingleHeavySphere )
 add_subdirectory( PoiseuilleChannel )
 add_subdirectory( SchaeferTurek )
 add_subdirectory( UniformGrid )
\ No newline at end of file
diff --git a/apps/benchmarks/MotionSingleHeavySphere/CMakeLists.txt b/apps/benchmarks/MotionSingleHeavySphere/CMakeLists.txt
new file mode 100644
index 000000000..5fd40d16f
--- /dev/null
+++ b/apps/benchmarks/MotionSingleHeavySphere/CMakeLists.txt
@@ -0,0 +1,2 @@
+waLBerla_add_executable ( NAME MotionSingleHeavySphere 
+                          DEPENDS blockforest boundary core domain_decomposition field lbm pe pe_coupling postprocessing timeloop vtk )
\ No newline at end of file
diff --git a/apps/benchmarks/MotionSingleHeavySphere/MotionSingleHeavySphere.cpp b/apps/benchmarks/MotionSingleHeavySphere/MotionSingleHeavySphere.cpp
new file mode 100644
index 000000000..29aba5b38
--- /dev/null
+++ b/apps/benchmarks/MotionSingleHeavySphere/MotionSingleHeavySphere.cpp
@@ -0,0 +1,1402 @@
+//======================================================================================================================
+//
+//  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 MotionSingleHeavySphere.cpp
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#include "boundary/all.h"
+
+#include "blockforest/all.h"
+
+#include "core/all.h"
+
+#include "domain_decomposition/all.h"
+
+#include "field/AddToStorage.h"
+#include "field/communication/PackInfo.h"
+#include "field/vtk/all.h"
+
+#include "lbm/boundary/all.h"
+#include "lbm/communication/PdfFieldPackInfo.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/D3Q19.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.h"
+#include "lbm/vtk/all.h"
+
+#include "pe/basic.h"
+#include "pe/vtk/BodyVtkOutput.h"
+#include "pe/vtk/SphereVtkOutput.h"
+#include "pe/cr/ICR.h"
+#include "pe/Types.h"
+
+#include "pe_coupling/mapping/all.h"
+#include "pe_coupling/momentum_exchange_method/all.h"
+#include "pe_coupling/partially_saturated_cells_method/all.h"
+#include "pe_coupling/utility/all.h"
+
+#include "stencil/D3Q27.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "vtk/BlockCellDataWriter.h"
+#include "vtk/Initialization.h"
+#include "vtk/VTKOutput.h"
+
+#include <memory>
+
+
+namespace motion_single_heavy_sphere{
+
+///////////
+// USING //
+///////////
+
+using namespace walberla;
+using walberla::uint_t;
+
+//////////////
+// TYPEDEFS //
+//////////////
+
+// PDF field, flag field & body field
+typedef lbm::D3Q19< lbm::collision_model::TRT, false >  LatticeModel_T;
+
+typedef LatticeModel_T::Stencil                         Stencil_T;
+typedef lbm::PdfField< LatticeModel_T >                 PdfField_T;
+
+typedef walberla::uint8_t                 flag_t;
+typedef FlagField< flag_t >               FlagField_T;
+typedef GhostLayerField< pe::BodyID, 1 >  BodyField_T;
+
+typedef std::pair< pe::BodyID, real_t >                              BodyAndVolumeFraction_T;
+typedef GhostLayerField< std::vector< BodyAndVolumeFraction_T >, 1 > BodyAndVolumeFractionField_T;
+
+const uint_t FieldGhostLayers = 1;
+
+// boundary handling
+typedef lbm::SimpleUBB< LatticeModel_T, flag_t >           UBB_T;
+typedef lbm::SimplePressure< LatticeModel_T, flag_t >      Outlet_T;
+
+typedef pe_coupling::SimpleBB< LatticeModel_T, FlagField_T >        MEM_BB_T;
+typedef pe_coupling::CurvedLinear< LatticeModel_T, FlagField_T >    MEM_CLI_T;
+typedef pe_coupling::CurvedQuadratic< LatticeModel_T, FlagField_T > MEM_MR_T;
+
+typedef boost::tuples::tuple< UBB_T, Outlet_T, MEM_BB_T, MEM_CLI_T, MEM_MR_T > BoundaryConditions_T;
+typedef BoundaryHandling< FlagField_T, Stencil_T, BoundaryConditions_T > BoundaryHandling_T;
+
+typedef boost::tuple<pe::Sphere> BodyTypeTuple;
+
+///////////
+// FLAGS //
+///////////
+
+const FlagUID Fluid_Flag   ( "fluid" );
+
+const FlagUID UBB_Flag     ( "velocity bounce back" );
+const FlagUID Outlet_Flag  ( "outlet" );
+
+const FlagUID MEM_BB_Flag   ( "moving obstacle BB" );
+const FlagUID MEM_CLI_Flag  ( "moving obstacle CLI" );
+const FlagUID MEM_MR_Flag   ( "moving obstacle MR" );
+const FlagUID FormerMEM_Flag( "former moving obstacle" );
+
+
+//////////////////////////////
+// Coupling algorithm enums //
+//////////////////////////////
+enum MEMVariant { BB, CLI, MR };
+
+MEMVariant to_MEMVariant( const std::string& s )
+{
+   if( s == "BB"  ) return MEMVariant::BB;
+   if( s == "CLI" ) return MEMVariant::CLI;
+   if( s == "MR"  ) return MEMVariant::MR;
+   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";
+   if( m == MEMVariant::MR  ) return "MR";
+   throw std::runtime_error("invalid conversion from MEMVariant to string");
+}
+
+enum PSMVariant { SC1W1, SC2W1, SC3W1, SC1W2, SC2W2, SC3W2 };
+
+PSMVariant to_PSMVariant( const std::string& s )
+{
+   if( s == "SC1W1"  ) return PSMVariant::SC1W1;
+   if( s == "SC2W1"  ) return PSMVariant::SC2W1;
+   if( s == "SC3W1"  ) return PSMVariant::SC3W1;
+   if( s == "SC1W2"  ) return PSMVariant::SC1W2;
+   if( s == "SC2W2"  ) return PSMVariant::SC2W2;
+   if( s == "SC3W2"  ) return PSMVariant::SC3W2;
+   throw std::runtime_error("invalid conversion from text to PSMVariant");
+}
+
+std::string PSMVariant_to_string ( const PSMVariant& m )
+{
+   if( m == PSMVariant::SC1W1 ) return "SC1W1";
+   if( m == PSMVariant::SC2W1 ) return "SC2W1";
+   if( m == PSMVariant::SC3W1 ) return "SC3W1";
+   if( m == PSMVariant::SC1W2 ) return "SC1W2";
+   if( m == PSMVariant::SC2W2 ) return "SC2W2";
+   if( m == PSMVariant::SC3W2 ) return "SC3W2";
+   throw std::runtime_error("invalid conversion from PSMVariant to string");
+}
+
+/////////////////////////////////////
+// BOUNDARY HANDLING CUSTOMIZATION //
+/////////////////////////////////////
+class MyBoundaryHandling
+{
+public:
+
+   MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID, const BlockDataID & bodyFieldID, const BlockDataID & pdfFieldPreColID, const Vector3<real_t> & uInfty) :
+      flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), bodyFieldID_( bodyFieldID ), pdfFieldPreColID_( pdfFieldPreColID ), velocity_( uInfty )
+   {}
+
+   BoundaryHandling_T * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const;
+
+private:
+   const BlockDataID flagFieldID_;
+   const BlockDataID pdfFieldID_;
+   const BlockDataID bodyFieldID_;
+   const BlockDataID pdfFieldPreColID_;
+   const Vector3<real_t> & velocity_;
+
+}; // 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_ );
+   BodyField_T * bodyField = block->getData< BodyField_T >( bodyFieldID_ );
+   PdfField_T *  pdfFieldPreCol  = block->getData< PdfField_T > ( pdfFieldPreColID_ );
+
+   const auto fluid = flagField->flagExists( Fluid_Flag ) ? flagField->getFlag( Fluid_Flag ) : flagField->registerFlag( Fluid_Flag );
+
+   BoundaryHandling_T * handling = new BoundaryHandling_T( "moving obstacle boundary handling", flagField, fluid,
+         boost::tuples::make_tuple( UBB_T( "UBB", UBB_Flag, pdfField, velocity_),
+                                    Outlet_T( "Outlet", Outlet_Flag, pdfField, real_t(1) ),
+                                    MEM_BB_T (  "MEM_BB",  MEM_BB_Flag, pdfField, flagField, bodyField, fluid, *storage, *block ),
+                                    MEM_CLI_T( "MEM_CLI", MEM_CLI_Flag, pdfField, flagField, bodyField, fluid, *storage, *block ),
+                                    MEM_MR_T (  "MEM_MR",  MEM_MR_Flag, pdfField, flagField, bodyField, fluid, *storage, *block, pdfFieldPreCol ) )  );
+
+   const auto ubb = flagField->getFlag( UBB_Flag );
+   const auto outlet = flagField->getFlag( Outlet_Flag );
+
+   CellInterval domainBB = storage->getDomainCellBB();
+
+   domainBB.xMin() -= cell_idx_c( FieldGhostLayers ); // -1
+   domainBB.xMax() += cell_idx_c( FieldGhostLayers ); // cellsX
+
+   domainBB.yMin() -= cell_idx_c( FieldGhostLayers ); // 0
+   domainBB.yMax() += cell_idx_c( FieldGhostLayers ); // cellsY+1
+
+   domainBB.zMin() -= cell_idx_c( FieldGhostLayers ); // 0
+   domainBB.zMax() += cell_idx_c( FieldGhostLayers ); // cellsZ+1
+
+   // BOTTOM
+   // -1........-1........-1
+   // cellsX+1..cellsY+1..-1
+   CellInterval bottom( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMin() );
+   storage->transformGlobalToBlockLocalCellInterval( bottom, *block );
+   handling->forceBoundary( ubb, bottom );
+
+   // TOP
+   // -1........-1........cellsZ+1
+   // cellsX+1..cellsY+1..cellsZ+1
+   CellInterval top( domainBB.xMin(), domainBB.yMin(), domainBB.zMax(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() );
+   storage->transformGlobalToBlockLocalCellInterval( top, *block );
+   handling->forceBoundary( outlet, top );
+
+   handling->fillWithDomain( FieldGhostLayers );
+
+   return handling;
+}
+
+
+class ForceEval
+{
+public:
+   ForceEval( SweepTimeloop* timeloop,
+              const shared_ptr< StructuredBlockStorage > & blocks, const ConstBlockDataID & bodyStorageID,
+              uint_t averageFrequency, const std::string & basefolder ) :
+      timeloop_( timeloop ), blocks_( blocks ), bodyStorageID_( bodyStorageID ), averageFrequency_( averageFrequency )
+   {
+      // initially write header in file
+      std::ofstream file;
+      filename_ = basefolder;
+      filename_ += "/log_init.txt";
+      file.open( filename_.c_str() );
+      file << "# fDrag_current fDrag_average\n";
+      file.close();
+   }
+
+   // evaluate the acting drag force on a fixed sphere
+   void operator()()
+   {
+      const uint_t timestep (timeloop_->getCurrentTimeStep()+1);
+
+      // average the force over averageFrequency consecutive timesteps since there are small fluctuations in the force
+      real_t currentForce = calcForce();
+      currentAverage_ += currentForce;
+
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+         file.open( filename_.c_str(), std::ofstream::app );
+         file.precision(8);
+         file << timestep << "\t" << currentForce << "\t" << dragForceNew_ << std::endl;
+         file.close();
+      }
+
+      if( timestep % averageFrequency_ != 0) return;
+
+      dragForceOld_ = dragForceNew_;
+      dragForceNew_ = currentAverage_ / real_c( averageFrequency_ );
+      currentAverage_ = real_t(0);
+
+   }
+
+   // Return the relative temporal change in the drag force
+   real_t getForceDiff() const
+   {
+      return std::fabs( ( dragForceNew_ - dragForceOld_ ) / dragForceNew_ );
+   }
+
+   real_t getForce() const
+   {
+      return dragForceNew_;
+   }
+
+private:
+
+   // Obtain the drag force acting on the sphere by summing up all the process local parts of fZ
+   real_t calcForce()
+   {
+      real_t forceZ = real_t( 0 );
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         for( auto bodyIt = pe::BodyIterator::begin<pe::Sphere>(*blockIt, bodyStorageID_ ); bodyIt != pe::BodyIterator::end<pe::Sphere>(); ++bodyIt )
+         {
+            forceZ += bodyIt->getForce()[2];
+         }
+      }
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( forceZ, mpi::SUM );
+      }
+      return forceZ;
+   }
+
+   SweepTimeloop* timeloop_;
+
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID bodyStorageID_;
+
+   real_t currentAverage_ = real_t(0);
+   uint_t averageFrequency_;
+   std::string filename_;
+   real_t dragForceOld_ = real_t(0);
+   real_t dragForceNew_ = real_t(0);
+
+};
+
+class SphereEval
+{
+public:
+   SphereEval( SweepTimeloop* timeloop,
+               const shared_ptr< StructuredBlockStorage > & blocks, const ConstBlockDataID & bodyStorageID,
+               const std::string & basefolder,
+               const Vector3<real_t> & u_infty, const real_t Galileo, const real_t GalileoSim,
+               const real_t gravity, const real_t viscosity, const real_t diameter, const real_t densityRatio,
+               const uint_t numLBMSubCycles ) :
+      timeloop_( timeloop ), blocks_( blocks ), bodyStorageID_( bodyStorageID ),
+      u_infty_( u_infty ), gravity_( gravity ), viscosity_( viscosity ),
+      diameter_( diameter ), densityRatio_( densityRatio ), numLBMSubCycles_( numLBMSubCycles )
+   {
+
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream fileSetup;
+         std::string filenameSetup = basefolder;
+         filenameSetup += "/setup.txt";
+         fileSetup.open( filenameSetup.c_str() );
+
+         fileSetup << "# setup parameters: \n";
+         fileSetup << "processes = " << MPIManager::instance()->numProcesses() << "\n";
+         fileSetup << "Galileo number (targeted) = " << Galileo << "\n";
+         fileSetup << "Galileo number (simulated) = " << GalileoSim << "\n";
+         fileSetup << "gravity = " << gravity << "\n";
+         fileSetup << "viscosity = " << viscosity << "\n";
+         fileSetup << "diameter = " << diameter << "\n";
+         fileSetup << "uInfty = " << u_infty_[2] << "\n";
+         real_t u_ref = std::sqrt( std::fabs(densityRatio - real_t(1)) * gravity * diameter );
+         fileSetup << "u_{ref} = " << u_ref << "\n";
+         fileSetup << "numLBMSubCycles = " << numLBMSubCycles << "\n";
+         fileSetup.close();
+      }
+
+      filenameRes_ = basefolder;
+      filenameRes_ += "/log_results.txt";
+      filenameAdd_ = basefolder;
+      filenameAdd_ += "/log_additional.txt";
+
+      WALBERLA_ROOT_SECTION()
+      {
+         // initially write header in file
+         std::ofstream fileRes;
+         fileRes.open( filenameRes_.c_str() );
+         // raw data
+         fileRes << "#\t x_p_x\t x_p_y\t x_p_z\t u_p_x\t u_p_y\t u_p_z\t omega_p_x\t omega_p_y\t omega_p_z\t u_{pr}_x\t u_{pr}_y\t u_{pr}_z\t ";
+         // processed data, 14 - 18
+         fileRes << "Re\t u_{pV}\t u_{pH}\t omega_{pV}\t omega_{pH}\t alpha\n";
+         fileRes.close();
+
+         std::ofstream fileAdd;
+         fileAdd.open( filenameAdd_.c_str() );
+         fileAdd << "# forceX forceY forceZ torqueX torqueY torqueZ\n";
+         fileAdd.close();
+      }
+   }
+
+   // evaluate and write the sphere properties
+   void operator()()
+   {
+      const uint_t timestep ( timeloop_->getCurrentTimeStep() * numLBMSubCycles_ + 1 );
+
+      Vector3<real_t> transVel( real_t(0) );
+      Vector3<real_t> angularVel( real_t(0) );
+      Vector3<real_t> pos( real_t(0) );
+
+      Vector3<real_t> force( real_t(0) );
+      Vector3<real_t> torque( real_t(0) );
+
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         for( auto bodyIt = pe::LocalBodyIterator::begin<pe::Sphere>(*blockIt, bodyStorageID_ ); bodyIt != pe::LocalBodyIterator::end<pe::Sphere>(); ++bodyIt )
+         {
+            pos= bodyIt->getPosition();
+            transVel = bodyIt->getLinearVel();
+            angularVel = bodyIt->getAngularVel();
+         }
+         for( auto bodyIt = pe::BodyIterator::begin<pe::Sphere>(*blockIt, bodyStorageID_ ); bodyIt != pe::BodyIterator::end<pe::Sphere>(); ++bodyIt )
+         {
+            force += bodyIt->getForce();
+            torque += bodyIt->getTorque();
+         }
+      }
+
+      std::vector<real_t> particlePos(3);
+      particlePos[0]=pos[0]; particlePos[1]=pos[1]; particlePos[2]=pos[2];
+
+      std::vector<real_t> particleTransVel(3);
+      particleTransVel[0]=transVel[0]; particleTransVel[1]=transVel[1]; particleTransVel[2]=transVel[2];
+
+      std::vector<real_t> particleAngularVel(3);
+      particleAngularVel[0]=angularVel[0]; particleAngularVel[1]=angularVel[1]; particleAngularVel[2]=angularVel[2];
+
+      std::vector<real_t> particleForce(3);
+      particleForce[0]=force[0]; particleForce[1]=force[1]; particleForce[2]=force[2];
+
+      std::vector<real_t> particleTorque(3);
+      particleTorque[0]=torque[0]; particleTorque[1]=torque[1]; particleTorque[2]=torque[2];
+
+      // reduce to root
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::reduceInplace( particlePos, mpi::SUM );
+         mpi::reduceInplace( particleTransVel, mpi::SUM );
+         mpi::reduceInplace( particleAngularVel, mpi::SUM );
+         mpi::reduceInplace( particleForce, mpi::SUM );
+         mpi::reduceInplace( particleTorque, mpi::SUM );
+      }
+
+      //only root process has all the data
+      WALBERLA_ROOT_SECTION()
+      {
+
+         Vector3<real_t> u_p_r( particleTransVel[0] - u_infty_[0], particleTransVel[1] - u_infty_[1], particleTransVel[2] - u_infty_[2]);
+         real_t u_p_H = std::sqrt( u_p_r[0] * u_p_r[0] + u_p_r[1] * u_p_r[1]);
+         real_t u_p_V = u_p_r[2];
+
+         real_t omega_p_H = std::sqrt( particleAngularVel[0] * particleAngularVel[0] + particleAngularVel[1] * particleAngularVel[1] );
+         real_t omega_p_V = particleAngularVel[2];
+
+         real_t u_ref = std::sqrt( std::fabs(densityRatio_ - real_t(1)) * gravity_ * diameter_ );
+         real_t Reynolds = u_p_r.length() * diameter_ / viscosity_;
+
+         // results
+         std::ofstream fileRes;
+         fileRes.open( filenameRes_.c_str(), std::ofstream::app );
+         fileRes.precision(8);
+         fileRes << timestep
+                 << "\t" << particlePos[0] << "\t" << particlePos[1] << "\t" << particlePos[2]
+                 << "\t" << particleTransVel[0] << "\t" << particleTransVel[1] << "\t" << particleTransVel[2]
+                 << "\t" << particleAngularVel[0] << "\t" << particleAngularVel[1] << "\t" << particleAngularVel[2]
+                 << "\t" << u_p_r[0] << "\t" << u_p_r[1] << "\t" << u_p_r[2]
+                 << "\t" << Reynolds << "\t" << u_p_V/u_ref << "\t" << u_p_H/u_ref
+                 << "\t" << omega_p_V*(diameter_/u_ref) << "\t" << omega_p_H*(diameter_/u_ref)
+                 << "\t" << std::atan( u_p_H/std::fabs(u_p_V) )
+                 << std::endl;
+         fileRes.close();
+
+         // additional
+         std::ofstream fileAdd;
+         fileAdd.open( filenameAdd_.c_str(), std::ofstream::app );
+         fileAdd.precision(8);
+         fileAdd << timestep
+                 << "\t" << particleForce[0] << "\t" << particleForce[1] << "\t" << particleForce[2]
+                 << "\t" << particleTorque[0] << "\t" << particleTorque[1] << "\t" << particleTorque[2]
+                 << std::endl;
+         fileAdd.close();
+      }
+   }
+
+private:
+   SweepTimeloop* timeloop_;
+
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID bodyStorageID_;
+
+   std::string filenameRes_;
+   std::string filenameAdd_;
+
+   Vector3<real_t> u_infty_;
+   real_t gravity_;
+   real_t viscosity_;
+   real_t diameter_;
+   real_t densityRatio_;
+   uint_t numLBMSubCycles_;
+
+};
+
+class ResetForce
+{
+   public:
+   ResetForce( const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & bodyStorageID )
+      : blocks_( blocks ), bodyStorageID_( bodyStorageID ) { }
+
+      void operator()()
+      {
+
+         for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+         {
+            for( auto bodyIt = pe::BodyIterator::begin< pe::Sphere >( *blockIt, bodyStorageID_); bodyIt != pe::BodyIterator::end<pe::Sphere>(); ++bodyIt )
+            {
+               bodyIt->resetForceAndTorque();
+            }
+         }
+      }
+   private:
+      const shared_ptr< StructuredBlockStorage > blocks_;
+      const BlockDataID bodyStorageID_;
+};
+
+class GravitationalForce
+{
+   public:
+   GravitationalForce( const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & bodyStorageID,
+                       real_t force )
+      : blocks_( blocks ), bodyStorageID_( bodyStorageID ), force_( force ) { }
+
+      void operator()()
+      {
+
+         for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+         {
+            for( auto bodyIt = pe::LocalBodyIterator::begin< pe::Sphere >( *blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end<pe::Sphere>(); ++bodyIt )
+            {
+               bodyIt->addForce ( real_t(0), real_t(0), force_ );
+            }
+         }
+      }
+   private:
+      const shared_ptr< StructuredBlockStorage > blocks_;
+      const BlockDataID bodyStorageID_;
+      const real_t force_;
+};
+
+
+
+/*
+ * When using N LBM steps and one (larger) pe step in a single simulation step, the force applied on the pe bodies in each LBM sep has to be scaled
+ * by a factor of 1/N before running the pe simulation. This corresponds to an averaging of the force and torque over the N LBM steps and is said to
+ * damp oscillations. Usually, N = 2.
+ * See Ladd - "Numerical simulations of particulate suspensions via a discretized Boltzmann equation. Part 1. Theoretical foundation", 1994, p. 302
+ */
+class AverageForce
+{
+   public:
+      AverageForce( const shared_ptr< StructuredBlockStorage > & blocks, const BlockDataID & bodyStorageID,
+                    const uint_t lbmSubCycles )
+      : blocks_( blocks ), bodyStorageID_( bodyStorageID ), invLbmSubCycles_( real_t(1) / real_c( lbmSubCycles ) ) { }
+
+      void operator()()
+      {
+         Vector3<real_t> force( real_t(0) );
+         Vector3<real_t> torque( real_t(0) );
+
+         for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+         {
+            for( auto bodyIt = pe::BodyIterator::begin( *blockIt, bodyStorageID_); bodyIt != pe::BodyIterator::end(); ++bodyIt )
+            {
+               force  = invLbmSubCycles_ * bodyIt->getForce();
+               torque = invLbmSubCycles_ * bodyIt->getTorque();
+
+               bodyIt->resetForceAndTorque();
+
+               bodyIt->setForce ( force );
+               bodyIt->setTorque( torque );
+            }
+         }
+      }
+   private:
+      const shared_ptr< StructuredBlockStorage > blocks_;
+      const BlockDataID bodyStorageID_;
+      real_t invLbmSubCycles_;
+};
+
+
+/*
+ * Result evaluation
+ * input: vel (fluid velocity), u_p (particle velocity), u_infty (inflow velocity)
+ *
+ * needed data:
+ * relative flow velocity: vel_r = vel - u_p
+ * relative particle velocity: u_p_r = u_p - u_infty
+ *
+ * magnitude of relative particle velocity in horizontal plane: u_p_H = sqrt( (u_p_r)_x^2 + (u_p_r)_y^2 )
+ * unit vector of particle motion in horizontal plane: e_p_H = ( (u_p_r)_x, (u_p_r)_y, 0) / u_p_H
+ * unit vector perpendicular to e_p_H and e_z:  e_p_Hz_perp = ( -(u_p_r)_y, (u_p_r)_x, 0) / u_p_H
+ * unit vector of particle motion: e_p_parallel = u_p_r / ||u_p_r||
+ * unit vector perpendicular to e_p_parallel and e_p_Hz_perp: e_p_perp = e_p_Hz_perp x e_p_parallel
+ * projected fluid velocities: vel_r_parallel = vel_r * (-e_p_parallel)
+ *                             vel_r_perp     = vel_r * e_p_perp
+ *                             vel_r_Hz_perp  = vel_r * e_p_Hz_perp
+ */
+class LogVTKInfo
+{
+public:
+
+   LogVTKInfo( SweepTimeloop* timeloop,
+               const shared_ptr< StructuredBlockStorage > & blocks, const ConstBlockDataID & bodyStorageID,
+               const std::string & baseFolder,
+               const Vector3<real_t> u_infty, const real_t gravity,
+               const real_t diameter, const real_t densityRatio ) :
+   timeloop_( timeloop ), blocks_( blocks ), bodyStorageID_( bodyStorageID ), baseFolder_( baseFolder ), u_infty_( u_infty )
+   {
+      u_p_r_sqr_mag_ = real_t(0);
+      u_ref_ = std::sqrt( std::fabs(densityRatio - real_t(1)) * gravity * diameter );
+   }
+
+   void operator()()
+   {
+      Vector3<real_t> transVel( real_t(0) );
+      for( auto blockIt = blocks_->begin(); blockIt != blocks_->end(); ++blockIt )
+      {
+         for( auto bodyIt = pe::LocalBodyIterator::begin<pe::Sphere>(*blockIt, bodyStorageID_ ); bodyIt != pe::LocalBodyIterator::end<pe::Sphere>(); ++bodyIt )
+         {
+            transVel = bodyIt->getLinearVel();
+         }
+      }
+
+      WALBERLA_MPI_SECTION()
+      {
+         mpi::allReduceInplace( transVel[0], mpi::SUM );
+         mpi::allReduceInplace( transVel[1], mpi::SUM );
+         mpi::allReduceInplace( transVel[2], mpi::SUM );
+
+      }
+
+      // store particle velocity
+      u_p_ = transVel;
+
+      Vector3<real_t> u_p_r = u_p_ - u_infty_;
+      u_p_r_sqr_mag_ = u_p_r.sqrLength();
+
+      real_t u_p_H = std::sqrt( u_p_r[0] * u_p_r[0] + u_p_r[1] * u_p_r[1]);
+
+      Vector3<real_t> e_p_H (u_p_r[0], u_p_r[1], real_t(0));
+      e_p_H /= u_p_H;
+      Vector3<real_t> e_p_Hz_perp (-u_p_r[1], u_p_r[0], real_t(0));
+      e_p_Hz_perp /= u_p_H;
+
+      Vector3<real_t> e_p_parallel = u_p_r / u_p_r.length();
+      Vector3<real_t> e_p_perp     = e_p_Hz_perp%e_p_parallel;
+
+      WALBERLA_ROOT_SECTION()
+      {
+         std::ofstream file;
+
+         std::string filename(baseFolder_);
+         filename += "/log_vtk/log_vtk_";
+         filename += std::to_string( timeloop_->getCurrentTimeStep() );
+         filename += ".txt";
+
+         file.open( filename.c_str() );
+         file.precision(8);
+
+         file << "u_p = "           << u_p_ << "\n";
+         file << "u_p_r_2 = "       << u_p_r_sqr_mag_ << "\n\n";
+
+         file << "e_{pH} = "        << e_p_H        << "\n";
+         file << "e_{pparallel} = " << e_p_parallel << "\n";
+         file << "e_{pperp} =  "    << e_p_perp     << "\n";
+         file << "e_{pHzperp} = "   << e_p_Hz_perp  << "\n";
+
+         file.close();
+      }
+
+   }
+
+   Vector3<real_t> getParticleVel() const
+   {
+      return u_p_;
+   }
+
+   real_t getURef() const
+   {
+      return u_ref_;
+   }
+
+   real_t getUPRSqrMag() const
+   {
+      return u_p_r_sqr_mag_;
+   }
+
+private:
+
+   SweepTimeloop* timeloop_;
+
+   shared_ptr< StructuredBlockStorage > blocks_;
+   const BlockDataID bodyStorageID_;
+
+   std::string baseFolder_;
+   Vector3<real_t> u_infty_;
+
+   Vector3< real_t > u_p_;
+   real_t u_p_r_sqr_mag_;
+   real_t u_ref_;
+
+};
+
+class PDFFieldCopy
+{
+public:
+   PDFFieldCopy( const BlockDataID & pdfFieldID, const BlockDataID & pdfFieldPreColID )
+      : pdfFieldID_( pdfFieldID ), pdfFieldPreColID_( pdfFieldPreColID )
+   {}
+   void operator()( IBlock * block )
+   {
+      auto pdfField = block->getData< PdfField_T>( pdfFieldID_ );
+      auto pdfFieldPreCol = block->getData< PdfField_T>( pdfFieldPreColID_ );
+      std::copy( pdfField->data(), pdfField->data() + pdfField->allocSize(), pdfFieldPreCol->data() );
+      pdfFieldPreCol->resetLatticeModel( pdfField->latticeModel() );
+
+   }
+private:
+   BlockDataID pdfFieldID_;
+   BlockDataID pdfFieldPreColID_;
+};
+
+/*
+ * This extensive, physical test case simulates a single, heavy sphere in ambient fluid flow.
+ * It is based on the benchmark proposed in
+ * Uhlman, Dusek - "The motion of a single heavy sphere in ambient fluid: A benchmark for interface-resolved
+ *                  particulate flow simulations with significant relative velocities" IJMF (2014),
+ *                  doi: 10.1016/j.ijmultiphaseflow.2013.10.010
+ * Results for LBM done with waLBerla are published in
+ * Rettinger, Ruede - "A comparative study of fluid-particle coupling methods for fully resolved
+ *                     lattice Boltzmann simulations" CaF (2017),
+ *                     doi: 10.1016/j.compfluid.2017.05.033
+ * The setup and the benchmark itself are explained in detail in these two papers.
+ * Several logging files are written to evaluate the benchmark quantities.
+ *
+ * It is used to compare different (LBM-specific) fluid-particle coupling approaches:
+ *  - momentum exchange method, with different boundary conditions:
+ *    - BB (simple bounce-back)
+ *    - CLI (central linear interpolated)
+ *    - MR (multi reflection)
+ *  - partially saturated cells method (i.e. Noble-Torczynski method), with different algorithmic variances:
+ *    - solid collision operator variant: 1, 2, or 3
+ *    - weighting factor variant: 1, or 2
+ *
+ * The results obtained by this benchmark should be compared to the reference spectral method results from Uhlman & Dusek.
+ * Furthermore, comparisons to the CFD-IBM (classical Navier-Stokes solver with immersed boundary method)
+ * results from Uhlman & Dusek are available in their paper.
+ * New coupling algorithms or algorithmic changes to the exiting approaches should also be cross-checked with the
+ * values in Rettinger & Ruede.
+ *
+ * The test case is split into two parts, since a certain, specified Galileo number should be simulated
+ * but can not be chosen a-priori:
+ * simulation is started with a fixed viscosity, and an inflow velocity estimated from the reference Reynolds number
+ * initial simulation:
+ *    run simulation with fixed sphere until force on sphere is converged
+ *    chose gravity to balance this interaction force
+ *    calculate Galileo number based on this gravitational acceleration
+ *    if simulated Galileo number is close enough to targeted Galileo number, terminate, else adapt viscosity and repeat
+ *
+ * moving simulation:
+ *    sphere is allowed to move freely with acting gravity, given by initial simulation
+ *
+ */
+int main( int argc, char **argv )
+{
+   debug::enterTestMode();
+
+   mpi::Environment env( argc, argv );
+
+   bool noViscosityIterations  = false;
+   bool vtkIOInit = false;
+   bool longDom   = false;
+
+   bool useMEM = false;
+   MEMVariant memVariant = MEMVariant::BB;
+   bool usePSM = false;
+   PSMVariant psmVariant = PSMVariant::SC3W2;
+
+   real_t Galileo = real_t(144);
+   real_t diameter = real_t(18);
+
+   ////////////////////////////
+   // COMMAND LINE ARGUMENTS //
+   ////////////////////////////
+
+   for( int i = 1; i < argc; ++i )
+   {
+           if( std::strcmp( argv[i], "--Galileo"   ) == 0 ) Galileo   = real_c( std::atof( argv[++i] ) ); // targeted Galileo number
+      else if( std::strcmp( argv[i], "--diameter"  ) == 0 ) diameter  = real_c( std::atof( argv[++i] ) ); // diameter of the spheres, in cells per diameter
+      else if( std::strcmp( argv[i], "--noViscosityIterations" ) == 0 ) noViscosityIterations   = true; // keep viscosity unchanged in initial simulation
+      else if( std::strcmp( argv[i], "--vtkIOInit" ) == 0 ) vtkIOInit = true; // write vtk IO for initial simulation
+      else if( std::strcmp( argv[i], "--longDom"   ) == 0 ) longDom   = true; // use a domain that is extended by the original domain length in positive and negative streamwise direction
+      else if( std::strcmp( argv[i], "--useMEM"    ) == 0 ) // use momentum exchange coupling and the given MEM variant (BB, CLI, or MR)
+           { useMEM = true; memVariant = to_MEMVariant( argv[++i] ); }
+      else if( std::strcmp( argv[i], "--usePSM"    ) == 0 ) // use partially saturated cells method and the given PSM variant (SC1W1, SC1W2, SC2W1, SC2W2, SC3W1, or SC3W2)
+           { usePSM = true; psmVariant = to_PSMVariant( argv[++i] ); }
+      else WALBERLA_ABORT("command line argument unknown: " << argv[i] );
+   }
+   if( !useMEM && !usePSM ) WALBERLA_ABORT("No coupling method chosen via command line!\nChose either \"--useMEM MEMVARIANT\" or \"--usePSM PSMVARIANT\"!");
+
+   ///////////////////////////
+   // SIMULATION PROPERTIES //
+   ///////////////////////////
+
+   const real_t radius = real_t(0.5) * diameter;
+   const uint_t xlength = uint_c( diameter * real_t(5.34) );
+   const uint_t ylength = xlength;
+   uint_t zlength = uint_c( diameter * real_t(16) );
+
+   if (longDom)
+   {
+      zlength *= uint_t(3);
+   }
+
+   real_t viscosity = real_t(0.01);
+   real_t Re_target = real_t(1);
+   real_t timestepsNonDim = real_t(1);
+
+   // estimate Reynolds number (i.e. inflow velocity) based on Galileo number
+   // values are taken from the original simulation of Uhlmann, Dusek
+   switch( int(Galileo) )
+   {
+      case 144:
+         Re_target = real_t(185.08);
+         timestepsNonDim = real_t(100);
+         break;
+      case 178:
+         Re_target = real_t(243.01);
+         timestepsNonDim = real_t(250);
+         break;
+      case 190:
+         Re_target = real_t(262.71);
+         timestepsNonDim = real_t(250);
+         break;
+      case 250:
+         Re_target = real_t(365.10);
+         timestepsNonDim = real_t(510);
+         break;
+      default:
+         WALBERLA_ABORT("Galileo number is different from the usual ones (144, 178, 190, or 250). No estimate of the Reynolds number available. Add this case manually!");
+   }
+
+   // estimate fitting inflow velocity (diffusive scaling, viscosity is fixed)
+   real_t uIn = Re_target * viscosity / diameter;
+
+   real_t omega = lbm::collision_model::omegaFromViscosity(viscosity);
+
+   Vector3<real_t> uInfty = Vector3<real_t>( real_t(0), real_t(0), uIn );
+
+   const real_t densityRatio = real_t(1.5);
+
+   const uint_t averageFrequency = uint_c( ( ( uint_c(Galileo) >= 200) ? real_t(500) : real_t(2) ) * diameter / uIn ); // for initial simulation
+   const real_t convergenceLimit = real_t(1e-4);
+   const real_t convergenceLimitGalileo = real_t(1e-4);
+   const real_t dx = real_t(1);
+   const real_t magicNumberTRT = lbm::collision_model::TRT::threeSixteenth;
+
+   const uint_t numLBMSubCycles = ( useMEM ) ? 2 : 1;
+   const uint_t numPeSubCycles  = uint_c(numLBMSubCycles); // dtPE = dtLBM
+
+   const uint_t timestepsInit = uint_c( ( ( uint_c(Galileo) >= 200) ? real_t(3000) : real_t(100) ) * diameter / uIn ); // maximum number of time steps for the initial simulation
+   const uint_t writeFrequencyInit = uint_t(1000); // vtk write frequency init
+
+   ///////////////////////////
+   // BLOCK STRUCTURE SETUP //
+   ///////////////////////////
+   const int numProcs = MPIManager::instance()->numProcesses();
+
+   if( numProcs < 16 ) WALBERLA_ABORT("At least 16 MPI ranks have to be used due to horizontal periodicity requirements!");
+
+   uint_t XBlocks, YBlocks, ZBlocks;
+   if( numProcs >= 192 )
+   {
+      XBlocks = uint_t(6);
+      YBlocks = uint_t(6);
+      ZBlocks = uint_c(numProcs) / ( XBlocks * YBlocks );
+   } else {
+      XBlocks = uint_t(4);
+      YBlocks = uint_t(4);
+      ZBlocks = uint_c(MPIManager::instance()->numProcesses()) / ( XBlocks * YBlocks );
+   }
+   const uint_t XCells = xlength / XBlocks;
+   const uint_t YCells = ylength / YBlocks;
+   const uint_t ZCells = zlength / ZBlocks;
+
+   if( (xlength != XCells * XBlocks) && (ylength != YCells * YBlocks) && (zlength != ZCells * ZBlocks) )
+   {
+      WALBERLA_ABORT("Domain decomposition does not fit to total domain size!");
+   }
+
+   WALBERLA_LOG_INFO_ON_ROOT("Diameter: " << diameter);
+   WALBERLA_LOG_INFO_ON_ROOT("Domain: " << xlength << " x " << ylength << " x " << zlength);
+   WALBERLA_LOG_INFO_ON_ROOT("Processes: " << XBlocks << " x " << YBlocks << " x " << ZBlocks);
+   WALBERLA_LOG_INFO_ON_ROOT("Subdomains: " << XCells << " x " << YCells << " x " << ZCells);
+   WALBERLA_LOG_INFO_ON_ROOT("Tau: " << real_t(1)/omega);
+   WALBERLA_LOG_INFO_ON_ROOT("Viscosity: " << viscosity);
+   WALBERLA_LOG_INFO_ON_ROOT("uIn: " << uIn);
+   WALBERLA_LOG_INFO_ON_ROOT("Re_infty: " << uIn * diameter / viscosity);
+
+   auto blocks = blockforest::createUniformBlockGrid( XBlocks, YBlocks, ZBlocks, XCells, YCells, ZCells, dx, true,
+                                                      true, true, false );
+
+   /////////////////
+   // PE COUPLING //
+   /////////////////
+
+   // set up pe functionality
+   shared_ptr<pe::BodyStorage>  globalBodyStorage = make_shared<pe::BodyStorage>();
+   pe::SetBodyTypeIDs<BodyTypeTuple>::execute();
+   auto bodyStorageID  = blocks->addBlockData(pe::createStorageDataHandling<BodyTypeTuple>(), "pe Body Storage");
+   auto ccdID          = blocks->addBlockData(pe::ccd::createHashGridsDataHandling( globalBodyStorage, bodyStorageID ), "CCD");
+   auto fcdID          = blocks->addBlockData(pe::fcd::createGenericFCDDataHandling<BodyTypeTuple, pe::fcd::AnalyticCollideFunctor>(), "FCD");
+
+   // set up collision response, here DEM solver
+   // in this test case, it is only used for the time integration
+   pe::cr::DEM cr(globalBodyStorage, blocks->getBlockStoragePointer(), bodyStorageID, ccdID, fcdID, NULL);
+
+   // set up synchronization procedure
+   const real_t overlap = real_t( 1.5 ) * dx;
+   boost::function<void(void)> syncCall;
+   if( XBlocks <= uint_t(4) )
+      syncCall = boost::bind( pe::syncNextNeighbors<BodyTypeTuple>, boost::ref(blocks->getBlockForest()), bodyStorageID, static_cast<WcTimingTree*>(NULL), overlap, false );
+   else
+      syncCall = boost::bind( pe::syncShadowOwners<BodyTypeTuple>, boost::ref(blocks->getBlockForest()), bodyStorageID, static_cast<WcTimingTree*>(NULL), overlap, false );
+
+
+   real_t xParticle = real_t(0);
+   real_t yParticle = real_t(0);
+   real_t zParticle = real_t(0);
+
+   // root determines particle position, then broadcasts it
+   WALBERLA_ROOT_SECTION()
+   {
+      if( int(Galileo) == 144 )
+      {
+         xParticle = real_c( xlength ) * real_t(0.5);
+         yParticle = real_c( ylength ) * real_t(0.5);
+      }
+      else if( int(Galileo) == 250 )
+      {
+         // add random perturbance for chaotic regime
+         walberla::math::seedRandomGenerator( boost::mt19937::result_type(std::time(0)) );
+         xParticle = real_c( xlength ) * real_t(0.5) + walberla::math::realRandom( real_t(-0.5), real_t(0.5) );
+         yParticle = real_c( ylength ) * real_t(0.5) + walberla::math::realRandom( real_t(-0.5), real_t(0.5) );
+
+      }
+      else
+      {
+         //add small perturbance to sphere position to break stability due to numerical symmetry
+         real_t perturbance = real_t(0.35);
+
+         xParticle = real_c( xlength ) * real_t(0.5) + perturbance;
+         yParticle = real_c( ylength ) * real_t(0.5);
+      }
+
+      zParticle = (longDom) ? ( diameter * real_t(16) + real_c( xlength ) ) : real_c( xlength );
+   }
+
+   // broadcast to other ranks
+   WALBERLA_MPI_SECTION()
+   {
+      mpi::broadcastObject( xParticle );
+      mpi::broadcastObject( yParticle );
+      mpi::broadcastObject( zParticle );
+   }
+
+   // add sphere
+   const auto sphereMaterial = pe::createMaterial( "mySphereMat", densityRatio , real_t(0.5), real_t(0.1), real_t(0.1), real_t(0.24), real_t(200), real_t(200), real_t(0), real_t(0) );
+   Vector3<real_t> position( xParticle, yParticle, zParticle );
+   pe::createSphere( *globalBodyStorage, blocks->getBlockStorage(), bodyStorageID, 0, position, radius, sphereMaterial );
+   syncCall();
+
+   WALBERLA_LOG_INFO_ON_ROOT("Initial sphere position: " << position);
+
+   ///////////////////////
+   // ADD DATA TO BLOCKS //
+   ////////////////////////
+
+   // create the lattice model
+   LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::TRT::constructWithMagicNumber( omega, magicNumberTRT ) );
+
+   // add PDF field
+   // initial velocity in domain = inflow velocity
+   BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field (zyxf)", latticeModel, uInfty, real_t(1), uint_t(1), field::zyxf );
+
+   // add PDF field (needed to store pre collision values for MEM_MR scheme)
+   BlockDataID pdfFieldPreColID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "nqOdd field (zyxf)", latticeModel, uInfty, real_t(1), uint_t(1), field::zyxf );
+
+   // add flag field
+   BlockDataID flagFieldID = field::addFlagFieldToStorage< FlagField_T >( blocks, "flag field" );
+
+   // add body field
+   BlockDataID bodyFieldID = field::addToStorage<BodyField_T>( blocks, "body field", NULL, field::zyxf );
+
+   // add body and volume fraction field
+   BlockDataID bodyAndVolumeFractionFieldID = field::addToStorage< BodyAndVolumeFractionField_T >( blocks, "body and volume fraction field",
+                                                                                                   std::vector<BodyAndVolumeFraction_T>(), field::zyxf, 0 );
+
+   // add boundary handling & initialize outer domain boundaries
+   BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(
+                                    MyBoundaryHandling( flagFieldID, pdfFieldID, bodyFieldID, pdfFieldPreColID, uInfty ), "boundary handling" );
+
+   if( usePSM ){
+      // mapping of sphere required by PSM methods
+
+      pe_coupling::BodyAndVolumeFractionMapping bodyMapping( blocks, globalBodyStorage, bodyStorageID, bodyAndVolumeFractionFieldID );
+      bodyMapping();
+
+      //initialization of the PDFs inside the particles, important for PSM M3
+      if( psmVariant == PSMVariant::SC1W1 || psmVariant == PSMVariant::SC2W1 || psmVariant == PSMVariant::SC3W1 )
+         pe_coupling::initializeDomainForPSM< LatticeModel_T, 1 >( blocks, pdfFieldID, bodyAndVolumeFractionFieldID );
+      else
+         pe_coupling::initializeDomainForPSM< LatticeModel_T, 2 >( blocks, pdfFieldID, bodyAndVolumeFractionFieldID );
+
+   }
+   else
+   {
+      // mapping of sphere required by MEM variants
+      // sets the correct flags
+
+      if( memVariant == MEMVariant::CLI )
+      {
+         pe_coupling::mapMovingBodies< BoundaryHandling_T >( blocks, boundaryHandlingID, bodyStorageID, bodyFieldID, MEM_CLI_Flag );
+      }else if ( memVariant == MEMVariant::MR ){
+         pe_coupling::mapMovingBodies< BoundaryHandling_T >( blocks, boundaryHandlingID, bodyStorageID, bodyFieldID, MEM_MR_Flag );
+      }else{
+         pe_coupling::mapMovingBodies< BoundaryHandling_T >( blocks, boundaryHandlingID, bodyStorageID, bodyFieldID, MEM_BB_Flag );
+      }
+   }
+
+
+   // base folder to store all logs and vtk output
+   std::string basefolder ("MSHS_");
+
+   basefolder += std::to_string( uint_c( Galileo ) );
+   basefolder += "_";
+   basefolder += std::to_string( uint_c( diameter ) );
+   if ( usePSM )
+   {
+      basefolder += "_PSM_";
+      basefolder += PSMVariant_to_string( psmVariant );
+   }
+   else
+   {
+      basefolder += "_MEM_";
+      basefolder += MEMVariant_to_string( memVariant );
+   }
+
+   if( longDom )
+      basefolder += "_longDom";
+
+   WALBERLA_LOG_INFO_ON_ROOT("Basefolder for simulation results: " << basefolder);
+
+   // create base directory if it does not yet exist
+   boost::filesystem::path tpath( basefolder );
+   if( !boost::filesystem::exists( tpath ) )
+      boost::filesystem::create_directory( tpath );
+
+   // setup of the LBM communication for synchronizing the pdf field between neighboring blocks
+   boost::function< void () > commFunction;
+
+   blockforest::communication::UniformBufferedScheme< Stencil_T > scheme( blocks );
+   scheme.addPackInfo( make_shared< lbm::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldID ) );
+   commFunction = scheme;
+
+
+   //////////////////////////////////////
+   // TIME LOOP FOR INITIAL SIMULATION //
+   //////////////////////////////////////
+
+   // Initialization simulation: fixed sphere and simulate until convergence (of drag force)
+
+   // create the timeloop
+   SweepTimeloop timeloopInit( blocks->getBlockStorage(), timestepsInit );
+
+   if( usePSM ){
+
+      // add LBM communication function and boundary handling sweep
+      timeloopInit.add()
+         << BeforeFunction( commFunction, "LBM Communication" )
+         << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+      // LBM stream collide sweep
+      if( psmVariant == PSMVariant::SC1W1 )
+      {
+         auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,1,1>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+         timeloopInit.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+      } else if( psmVariant == PSMVariant::SC2W1 )
+      {
+         auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,2,1>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+         timeloopInit.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+      } else if( psmVariant == PSMVariant::SC3W1 )
+      {
+         auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,3,1>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+         timeloopInit.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+      } else if( psmVariant == PSMVariant::SC1W2 )
+      {
+         auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,1,2>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+         timeloopInit.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+      } else if( psmVariant == PSMVariant::SC2W2 )
+      {
+         auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,2,2>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+         timeloopInit.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+      } else if( psmVariant == PSMVariant::SC3W2 )
+      {
+         auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,3,2>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+         timeloopInit.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+      }
+
+   } else {
+
+      if( memVariant == MEMVariant::MR )
+         timeloopInit.add() << Sweep( PDFFieldCopy( pdfFieldID, pdfFieldPreColID ), "pdf field copy" );
+
+      auto sweep = lbm::makeCellwiseSweep< LatticeModel_T, FlagField_T >( pdfFieldID, flagFieldID, Fluid_Flag );
+
+      // collision sweep
+      timeloopInit.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)
+      timeloopInit.add() << BeforeFunction( commFunction, "LBM Communication" )
+                         << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+      // streaming & force evaluation
+      timeloopInit.add() << Sweep( lbm::makeStreamSweep( sweep ), "cell-wise LB sweep (stream)" );
+
+   }
+
+   // evaluate the drag force acting on the sphere...
+   shared_ptr< ForceEval > forceEval = walberla::make_shared< ForceEval >( &timeloopInit, blocks, bodyStorageID, averageFrequency, basefolder );
+   timeloopInit.addFuncAfterTimeStep( SharedFunctor< ForceEval >( forceEval ), "Evaluating drag force" );
+
+   // ...then reset the force
+   timeloopInit.addFuncAfterTimeStep( ResetForce( blocks, bodyStorageID ), "Resetting force on body");
+
+   if( vtkIOInit )
+   {
+      auto pdfFieldVTKInit = vtk::createVTKOutput_BlockData( blocks, "fluid_field_init", writeFrequencyInit, 0, false, basefolder );
+
+      field::FlagFieldCellFilter< FlagField_T > fluidFilterInit( flagFieldID );
+      fluidFilterInit.addFlag( Fluid_Flag );
+      pdfFieldVTKInit->addCellInclusionFilter( fluidFilterInit );
+
+      pdfFieldVTKInit->addCellDataWriter( make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "VelocityFromPDF" ) );
+      pdfFieldVTKInit->addCellDataWriter( make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "DensityFromPDF" ) );
+
+      timeloopInit.addFuncAfterTimeStep( vtk::writeFiles( pdfFieldVTKInit ), "VTK (fluid field data)" );
+   }
+
+   timeloopInit.addFuncAfterTimeStep( RemainingTimeLogger( timeloopInit.getNrOfTimeSteps(), real_t(120) ), "Remaining Time Logger" );
+
+   ////////////////////////////////
+   // EXECUTE INITIAL SIMULATION //
+   ////////////////////////////////
+
+   real_t gravity = real_t(1);
+   real_t GalileoSim = real_t(1);
+   real_t ReynoldsSim = real_t(1);
+   real_t u_ref = real_t(1);
+
+   WALBERLA_LOG_INFO_ON_ROOT("Starting initialization phase (sphere is kept fixed).");
+   WALBERLA_LOG_INFO_ON_ROOT("Iterating, and adapting the viscosity, until the targeted Galileo number is set.");
+
+   while (true) {
+      WcTimingPool timeloopInitTiming;
+
+      WALBERLA_LOG_INFO_ON_ROOT("(Re-)Starting initial simulation.");
+      for( uint_t i = 1; i < timestepsInit; ++i ){
+         timeloopInit.singleStep( timeloopInitTiming );
+         // check if the relative change in the average drag force is below the specified convergence criterion
+         if (forceEval->getForceDiff() < convergenceLimit && i > 2 * std::max(averageFrequency, zlength) )
+         {
+            // conditions to break:
+            // - force diff sufficiently small
+            // - AND more time steps than twice the domain size in z direction to ensure new information due
+            //   to possible viscosity change has traveled twice through domain,
+            //   or twice the average frequency to have converged averages
+
+            WALBERLA_LOG_INFO_ON_ROOT("Drag force converged at time " << timeloopInit.getCurrentTimeStep());
+            break;
+         }
+         // if simulation gets unstable
+         if( std::isnan(forceEval->getForce()) )
+         {
+            WALBERLA_ABORT("NAN value detected in drag force during initial simulation, exiting....");
+         }
+      }
+      WALBERLA_LOG_INFO_ON_ROOT("Initial simulation has ended.")
+
+      //evaluate the gravitational force necessary to keep the sphere at a approximately fixed position
+      gravity = forceEval->getForce() / ( (densityRatio - real_t(1) ) * diameter * diameter * diameter * math::PI / real_t(6) );
+      GalileoSim = std::sqrt( ( densityRatio - real_t(1) ) * gravity * diameter * diameter * diameter ) / viscosity;
+      ReynoldsSim = uIn * diameter / viscosity;
+      u_ref = std::sqrt( std::fabs(densityRatio - real_t(1)) * gravity * diameter );
+
+      WALBERLA_LOG_INFO_ON_ROOT("Acting gravity (= interaction force) = " << gravity );
+      WALBERLA_LOG_INFO_ON_ROOT("Simulated Galileo number = " << GalileoSim );
+      WALBERLA_LOG_INFO_ON_ROOT("Targeted Galileo number = " << Galileo );
+      WALBERLA_LOG_INFO_ON_ROOT("Reynolds number infty = " << ReynoldsSim );
+
+      if ( noViscosityIterations )
+      {
+         timeloopInitTiming.logResultOnRoot();
+         WALBERLA_LOG_INFO_ON_ROOT("Terminate iterations since viscosity should not be change, flag \"--noViscosityIterations\"");
+         break;
+      }
+
+      // if simulated Galileo number is close enough to targeted Galileo number, stop the initial simulation
+      if( std::abs( GalileoSim - Galileo ) / Galileo < convergenceLimitGalileo )
+      {
+         timeloopInitTiming.logResultOnRoot();
+         WALBERLA_LOG_INFO_ON_ROOT("Iterations converged, simulated Galileo number is close enough to targeted one");
+         break;
+      }
+
+      // else update the simulation parameter accordingly and resume simulation
+      real_t diff = GalileoSim/Galileo;
+      viscosity = diff * viscosity;
+      real_t newOmega = lbm::collision_model::omegaFromViscosity( viscosity );
+
+      // iterate all blocks with an iterator 'block' and change the collision model
+      for( auto block = blocks->begin(); block != blocks->end(); ++block )
+      {
+          // get the field data out of the block
+          auto pdf = block->getData< PdfField_T > ( pdfFieldID );
+          pdf->latticeModel().collisionModel().resetWithMagicNumber( newOmega, magicNumberTRT );
+      }
+
+      WALBERLA_LOG_INFO_ON_ROOT("==> Adapting viscosity:");
+      WALBERLA_LOG_INFO_ON_ROOT("New viscosity = " << viscosity );
+      WALBERLA_LOG_INFO_ON_ROOT("New omega = " << newOmega );
+
+   }
+
+
+
+   ///////////////
+   // TIME LOOP //
+   ///////////////
+
+   // actual simulation: freely moving sphere with acting gravity
+
+   // calculate the number of timesteps
+   const real_t t_ref = ( diameter / u_ref );
+   const uint_t timestepsLBM = uint_c( timestepsNonDim * t_ref );
+   const uint_t timesteps = timestepsLBM / numLBMSubCycles + 1; // total number of time steps for the whole simulation
+
+   // set vtk write frequency accordingly
+   const real_t dtWriteNonDim = real_t(3); // write every 3 non-dim timesteps
+   const uint_t nVTK = ( int(Galileo) != 178 ) ? 2 : 10; // write only 10 vtk files: the 10 final ones
+
+   const uint_t writeFrequency = uint_c( dtWriteNonDim * t_ref ) / numLBMSubCycles; // vtk write frequency
+   const uint_t initWriteCallsToSkip = uint_c(std::max( 0, int( timesteps - ( nVTK - uint_t(1) ) * writeFrequency - uint_t(1) ) ) ); // write calls to be skipped
+
+   WALBERLA_LOG_INFO_ON_ROOT("Starting simulation timeloop with  " << timesteps << " timesteps!");
+   WALBERLA_LOG_INFO_ON_ROOT("Sphere is allowed to move freely under action of gravity");
+
+   SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps );
+
+   if( usePSM ){
+
+      timeloop.addFuncBeforeTimeStep( pe_coupling::BodyAndVolumeFractionMapping( blocks, globalBodyStorage, bodyStorageID, bodyAndVolumeFractionFieldID ), "volume fraction mapping" );
+
+      for( uint_t lbmcycle = 0; lbmcycle < numLBMSubCycles; ++lbmcycle ){
+
+         // 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" );
+
+         // LBM stream collide sweep
+         if( psmVariant == PSMVariant::SC1W1 )
+         {
+            auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,1,1>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+            timeloop.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+         } else if( psmVariant == PSMVariant::SC2W1 )
+         {
+            auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,2,1>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+            timeloop.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+         } else if( psmVariant == PSMVariant::SC3W1 )
+         {
+            auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,3,1>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+            timeloop.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+         } else if( psmVariant == PSMVariant::SC1W2 )
+         {
+            auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,1,2>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+            timeloop.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+         } else if( psmVariant == PSMVariant::SC2W2 )
+         {
+            auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,2,2>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+            timeloop.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+         } else if( psmVariant == PSMVariant::SC3W2 )
+         {
+            auto sweep = pe_coupling::makePSMSweep<LatticeModel_T,FlagField_T,3,2>( pdfFieldID, bodyAndVolumeFractionFieldID, blocks, flagFieldID, Fluid_Flag );
+            timeloop.add() << Sweep( makeSharedSweep( sweep ), "cell-wise LB sweep" );
+         }
+      }
+
+      if( numLBMSubCycles != uint_t(1) )
+         timeloop.addFuncAfterTimeStep( AverageForce( blocks, bodyStorageID, numLBMSubCycles ), "Force averaging for several LBM steps" );
+
+
+   }else{
+
+      // sweep for updating the pe body mapping into the LBM simulation
+      if( memVariant == MEMVariant::CLI )
+         timeloop.add() << Sweep( pe_coupling::BodyMapping< BoundaryHandling_T >( blocks, boundaryHandlingID, bodyStorageID, bodyFieldID,  MEM_CLI_Flag, FormerMEM_Flag ), "Body Mapping" );
+      else if ( memVariant == MEMVariant::MR )
+         timeloop.add() << Sweep( pe_coupling::BodyMapping< BoundaryHandling_T >( blocks, boundaryHandlingID, bodyStorageID, bodyFieldID,  MEM_MR_Flag, FormerMEM_Flag ), "Body Mapping" );
+      else
+         timeloop.add() << Sweep( pe_coupling::BodyMapping< BoundaryHandling_T >( blocks, boundaryHandlingID, bodyStorageID, bodyFieldID,  MEM_BB_Flag, FormerMEM_Flag ), "Body Mapping" );
+
+
+      // reconstruct missing PDFs
+      typedef pe_coupling::SphereNormalExtrapolationDirectionFinder ExtrapolationFinder_T;
+      ExtrapolationFinder_T extrapolationFinder( blocks, bodyFieldID );
+      typedef pe_coupling::ExtrapolationReconstructor< LatticeModel_T, BoundaryHandling_T, ExtrapolationFinder_T > Reconstructor_T;
+      Reconstructor_T reconstructor( blocks, boundaryHandlingID, pdfFieldID, bodyFieldID, extrapolationFinder, true );
+      timeloop.add() << Sweep( pe_coupling::PDFReconstruction< LatticeModel_T, BoundaryHandling_T, Reconstructor_T >
+                               ( blocks, boundaryHandlingID, bodyStorageID, bodyFieldID, reconstructor, FormerMEM_Flag, Fluid_Flag  ), "PDF Restore" );
+
+      for( uint_t lbmcycle = 0; lbmcycle < numLBMSubCycles; ++lbmcycle ){
+
+         if( memVariant == MEMVariant::MR )
+            timeloop.add() << Sweep( PDFFieldCopy( pdfFieldID, pdfFieldPreColID ), "pdf field copy" );
+
+         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( commFunction, "LBM Communication" )
+                        << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" );
+
+         // streaming
+         timeloop.add() << Sweep( lbm::makeStreamSweep( sweep ), "cell-wise LB sweep (stream)" );
+      }
+
+      if( numLBMSubCycles != uint_t(1) )
+         timeloop.addFuncAfterTimeStep( AverageForce( blocks, bodyStorageID, numLBMSubCycles ), "Force averaging for several LBM steps" );
+
+   }
+
+   // add gravity
+   timeloop.addFuncAfterTimeStep( GravitationalForce( blocks, bodyStorageID, - gravity * ( densityRatio - real_t(1) ) * diameter * diameter * diameter * math::PI / real_t(6) ), "Add gravitational force" );
+
+   // evaluate the sphere properties
+   timeloop.addFuncAfterTimeStep( SphereEval( &timeloop, blocks, bodyStorageID, basefolder, uInfty, Galileo, GalileoSim, gravity, viscosity, diameter, densityRatio, numLBMSubCycles ), "Evaluate sphere");
+
+   // advance pe rigid body simulation
+   timeloop.addFuncAfterTimeStep( pe_coupling::TimeStep( blocks, bodyStorageID, cr, syncCall, real_c(numLBMSubCycles), numPeSubCycles ), "pe Time Step" );
+
+   // vtk output
+   auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", writeFrequency, FieldGhostLayers, false, basefolder );
+   pdfFieldVTK->setInitialWriteCallsToSkip( initWriteCallsToSkip );
+
+   blockforest::communication::UniformBufferedScheme< stencil::D3Q27 > pdfGhostLayerSync( blocks );
+   pdfGhostLayerSync.addPackInfo( make_shared< field::communication::PackInfo< PdfField_T > >( pdfFieldID ) );
+   pdfFieldVTK->addBeforeFunction( pdfGhostLayerSync );
+
+   // function to output plane infos for vtk output
+   shared_ptr< LogVTKInfo > logVTKInfo = walberla::make_shared< LogVTKInfo >( &timeloop, blocks, bodyStorageID, basefolder, uInfty, gravity, diameter, densityRatio );
+   pdfFieldVTK->addBeforeFunction( SharedFunctor< LogVTKInfo >( logVTKInfo ) );
+
+   // create folder for log_vtk files to not pollute the basefolder
+   boost::filesystem::path tpath2( basefolder+"/log_vtk" );
+   if( !boost::filesystem::exists( tpath2 ) )
+      boost::filesystem::create_directory( tpath2 );
+
+
+   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.addFuncAfterTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" );
+
+
+   timeloop.addFuncAfterTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps(), real_t(120) ), "Remaining Time Logger" );
+
+   ////////////////////////
+   // EXECUTE SIMULATION //
+   ////////////////////////
+
+   WcTimingPool timeloopTiming;
+   timeloop.run( timeloopTiming );
+   timeloopTiming.logResultOnRoot();
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace motion_single_heavy_sphere
+
+int main( int argc, char **argv ){
+   motion_single_heavy_sphere::main(argc, argv);
+}
-- 
GitLab