From 527f387e8e14c5586b967bd85ed2f81b05e3f6d6 Mon Sep 17 00:00:00 2001 From: Christoph Rettinger <christoph.rettinger@fau.de> Date: Tue, 23 Feb 2021 11:38:09 +0100 Subject: [PATCH] Added new benchmark scenario for fp coupling --- .../FluidParticleCoupling/CMakeLists.txt | 4 + .../MotionSettlingSphere.cpp | 1378 +++++++++++++++++ 2 files changed, 1382 insertions(+) create mode 100644 apps/benchmarks/FluidParticleCoupling/MotionSettlingSphere.cpp diff --git a/apps/benchmarks/FluidParticleCoupling/CMakeLists.txt b/apps/benchmarks/FluidParticleCoupling/CMakeLists.txt index a9b6e43d8..898352998 100644 --- a/apps/benchmarks/FluidParticleCoupling/CMakeLists.txt +++ b/apps/benchmarks/FluidParticleCoupling/CMakeLists.txt @@ -38,6 +38,10 @@ if( WALBERLA_BUILD_WITH_CODEGEN ) DEPENDS blockforest boundary core domain_decomposition field lbm lbm_mesapd_coupling mesa_pd postprocessing timeloop vtk FluidParticleCouplingGeneratedLBM) + waLBerla_add_executable(NAME MotionSettlingSphere FILES MotionSettlingSphere.cpp + DEPENDS blockforest boundary core domain_decomposition field lbm lbm_mesapd_coupling mesa_pd + postprocessing timeloop vtk FluidParticleCouplingGeneratedLBM) + else() waLBerla_add_executable ( NAME SphereWallCollision FILES SphereWallCollision.cpp diff --git a/apps/benchmarks/FluidParticleCoupling/MotionSettlingSphere.cpp b/apps/benchmarks/FluidParticleCoupling/MotionSettlingSphere.cpp new file mode 100644 index 000000000..69f6a438e --- /dev/null +++ b/apps/benchmarks/FluidParticleCoupling/MotionSettlingSphere.cpp @@ -0,0 +1,1378 @@ +//====================================================================================================================== +// +// 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 MotionSettlingSphere.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 "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 "lbm_mesapd_coupling/utility/InspectionProbe.h" +#include "lbm_mesapd_coupling/utility/InitializeHydrodynamicForceTorqueForAveragingKernel.h" + +#include "mesa_pd/data/ParticleStorage.h" +#include "mesa_pd/data/ParticleAccessorWithShape.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/domain/BlockForestDataHandling.h" +#include "mesa_pd/kernel/DoubleCast.h" +#include "mesa_pd/kernel/ParticleSelector.h" +#include "mesa_pd/kernel/VelocityVerlet.h" +#include "mesa_pd/kernel/ExplicitEuler.h" +#include "mesa_pd/mpi/SyncNextNeighbors.h" +#include "mesa_pd/mpi/SyncGhostOwners.h" +#include "mesa_pd/mpi/ReduceProperty.h" +#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h" +#include "mesa_pd/mpi/notifications/HydrodynamicForceTorqueNotification.h" +#include "mesa_pd/vtk/ParticleVtkOutput.h" + +#include "stencil/D3Q27.h" + +#include "timeloop/SweepTimeloop.h" + +#include "vtk/BlockCellDataWriter.h" +#include "vtk/Initialization.h" +#include "vtk/VTKOutput.h" + +#include <functional> +#include <memory> + +#ifdef WALBERLA_BUILD_WITH_CODEGEN +#include "GeneratedLBM.h" +#endif + +namespace motion_settling_sphere{ + +/////////// +// USING // +/////////// + +using namespace walberla; +using walberla::uint_t; + +////////////// +// TYPEDEFS // +////////////// + +using namespace walberla; +using walberla::uint_t; + +#ifdef WALBERLA_BUILD_WITH_CODEGEN +using LatticeModel_T = lbm::GeneratedLBM; +#else +using LatticeModel_T = lbm::D3Q19< lbm::collision_model::D3Q19MRT>; +#endif + +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 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 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; + 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 UBB_T = lbm::SimpleUBB< LatticeModel_T, flag_t >; + using Outlet_T = lbm::SimplePressure< LatticeModel_T, flag_t >; + using MO_SBB_T = lbm_mesapd_coupling::SimpleBB< LatticeModel_T, FlagField_T, ParticleAccessor_T >; + using MO_CLI_T = lbm_mesapd_coupling::CurvedLinear< LatticeModel_T, FlagField_T, ParticleAccessor_T >; + using Type = BoundaryHandling< FlagField_T, Stencil_T, UBB_T, Outlet_T, MO_SBB_T, MO_CLI_T >; + + + MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID, + const BlockDataID & particleFieldID, const shared_ptr<ParticleAccessor_T>& ac, + Vector3<real_t> uInfty) : + flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), particleFieldID_( particleFieldID ), + ac_( ac ), velocity_( uInfty ) + {} + + Type * 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_ ); + 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, + UBB_T( "UBB", UBB_Flag, pdfField, velocity_), + Outlet_T( "Outlet", Outlet_Flag, pdfField, real_t(1) ), + MO_SBB_T ( "MEM_BB", MEM_BB_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ), + MO_CLI_T( "MEM_CLI", MEM_CLI_Flag, pdfField, flagField, particleField, ac_, fluid, *storage, *block ) ); + + 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; + } + +private: + const BlockDataID flagFieldID_; + const BlockDataID pdfFieldID_; + const BlockDataID particleFieldID_; + + shared_ptr<ParticleAccessor_T> ac_; + const Vector3<real_t> velocity_; + +}; // class MyBoundaryHandling + +/* + * Functionality for continuous logging of sphere properties during initial (resting) simulation + */ +template< typename ParticleAccessor_T> +class RestingSphereForceEvaluator +{ +public: + RestingSphereForceEvaluator( const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid, + uint_t averageFrequency, const std::string & basefolder ) : + ac_( ac ), sphereUid_( sphereUid ), averageFrequency_( averageFrequency ) + { + WALBERLA_ROOT_SECTION() { + // initially write header in file + std::ofstream file; + filename_ = basefolder; + filename_ += "/log_init.txt"; + file.open(filename_.c_str()); + file << "# f_z_current f_z_average f_x f_y\n"; + file.close(); + } + } + + // evaluate the acting drag force on a fixed sphere + void operator()(const uint_t timestep) + { + + // average the force over averageFrequency consecutive timesteps since there are small fluctuations in the force + auto currentForce = getForce(); + currentAverage_ += currentForce[2]; + + WALBERLA_ROOT_SECTION() + { + std::ofstream file; + file.open( filename_.c_str(), std::ofstream::app ); + file.precision(8); + file << timestep << "\t" << currentForce[2] << "\t" << dragForceNew_ + << "\t" << currentForce[0] << "\t" << currentForce[1] << 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 getDragForce() const + { + return dragForceNew_; + } + +private: + + // Obtain the drag force acting on the sphere + Vector3<real_t> getForce() + { + Vector3<real_t> force(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)) + { + force = ac_->getHydrodynamicForce(idx); + } + } + + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( force[0], mpi::SUM ); + mpi::allReduceInplace( force[1], mpi::SUM ); + mpi::allReduceInplace( force[2], mpi::SUM ); + } + return force; + } + + shared_ptr< ParticleAccessor_T > ac_; + const walberla::id_t sphereUid_; + + real_t currentAverage_ = real_t(0); + uint_t averageFrequency_; + std::string filename_; + real_t dragForceOld_ = real_t(0); + real_t dragForceNew_ = real_t(0); + +}; + +/* + * Functionality for continuous logging of sphere properties during moving simulation + */ +template< typename ParticleAccessor_T> +class MovingSpherePropertyEvaluator +{ +public: + MovingSpherePropertyEvaluator( const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid, 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 ) : + ac_( ac ), sphereUid_( sphereUid ), + u_infty_( u_infty ), gravity_( gravity ), viscosity_( viscosity ), + diameter_( diameter ), densityRatio_( densityRatio ) + { + + 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.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) + { + 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) ); + + 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); + angularVel = ac_->getAngularVelocity(idx); + force = ac_->getHydrodynamicForce(idx); + torque = ac_->getHydrodynamicTorque(idx); + } + } + + 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::allReduceInplace( particlePos, mpi::SUM ); + mpi::reduceInplace( particleTransVel, mpi::SUM ); + mpi::reduceInplace( particleAngularVel, mpi::SUM ); + mpi::reduceInplace( particleForce, mpi::SUM ); + mpi::reduceInplace( particleTorque, mpi::SUM ); + } + + particleHeight_ = particlePos[2]; + + //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(); + } + } + + real_t getParticleHeight() const + { + return particleHeight_; + } + +private: + + shared_ptr< ParticleAccessor_T > ac_; + const walberla::id_t sphereUid_; + + std::string filenameRes_; + std::string filenameAdd_; + + Vector3<real_t> u_infty_; + real_t gravity_; + real_t viscosity_; + real_t diameter_; + real_t densityRatio_; + + real_t particleHeight_; + +}; + +/* + * Result evaluation for the written VTK files + * + * 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 + */ +template< typename ParticleAccessor_T> +class VTKInfoLogger +{ +public: + + VTKInfoLogger( SweepTimeloop* timeloop, const shared_ptr< ParticleAccessor_T > & ac, walberla::id_t sphereUid, + const std::string & baseFolder, const Vector3<real_t>& u_infty ) : + timeloop_( timeloop ), ac_( ac ), sphereUid_( sphereUid ), baseFolder_( baseFolder ), u_infty_( u_infty ) + { } + + void operator()() + { + Vector3<real_t> u_p( 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)) + { + u_p = ac_->getLinearVelocity(idx); + } + } + + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( u_p[0], mpi::SUM ); + mpi::allReduceInplace( u_p[1], mpi::SUM ); + mpi::allReduceInplace( u_p[2], mpi::SUM ); + + } + + Vector3<real_t> u_p_r = u_p - u_infty_; + real_t 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(); + } + } + +private: + + SweepTimeloop* timeloop_; + + shared_ptr< ParticleAccessor_T > ac_; + const walberla::id_t sphereUid_; + + std::string baseFolder_; + Vector3<real_t> u_infty_; + +}; + + +/* + * 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. + * + * This case is the same as can be found in apps/benchmarks/MotionSingleHeavySphere + * but using the lbm_mesapd_coupling, instead of the pe_coupling. + * Compared to this previous work, significantly different outcomes can be observed when using the CLI boundary condition. + * This can be traced back to the distinct way of force averaging. + * The force averaging applied in the previous study (two LBM steps, then average) introduces a error in the Galilean invariance. + * Surprisingly, this error can result in better agreement to the reference solutions. + * + */ +int main( int argc, char **argv ) +{ + debug::enterTestMode(); + + mpi::Environment env( argc, argv ); + + bool noViscosityIterations = false; + bool vtkIOInit = false; + bool longDom = false; + bool broadDom = false; + bool useOmegaBulkAdaption = false; + real_t bulkViscRateFactor = real_t(1); + uint_t averageForceTorqueOverTwoTimeStepsMode = 1; // 0: no, 1: running, 2: 2LBM + bool conserveMomentum = false; + bool useDiffusiveScaling = true; + bool useVelocityVerlet = true; + bool initFluidVelocity = true; + bool vtkOutputGhostlayers = false; + + MEMVariant memVariant = MEMVariant::BB; + std::string reconstructorType = "Grad"; // Eq, EAN, Ext, Grad + + real_t diameter = real_t(18); + uint_t XBlocks = uint_t(4); + uint_t YBlocks = uint_t(4); + + real_t viscosity = real_t(0.01); // used in diffusive scaling + real_t uIn = real_t(0.02); // used for acoustic scaling + + /* + * 0: custom + * 1: Ga = 144 + * 2: Ga = 178.46 + * 3: Ga = 190 + * 4: Ga = 250 + */ + uint_t simulationCase = 1; + real_t Galileo = real_t(1); + real_t Re_target = real_t(1); + real_t timestepsNonDim = real_t(1); + + + real_t vtkWriteSpacingNonDim = real_t(3); // write every 3 non-dim timesteps + uint_t vtkNumOutputFiles = 10; // write only 10 vtk files: the 10 final ones + + std::string folderEnding = ""; + + //////////////////////////// + // COMMAND LINE ARGUMENTS // + //////////////////////////// + + for( int i = 1; i < argc; ++i ) + { + if( std::strcmp( argv[i], "--case" ) == 0 ) {simulationCase = uint_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--Ga" ) == 0 ){Galileo = real_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--Re" ) == 0 ){Re_target = real_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--timestepsNonDim" ) == 0 ){timestepsNonDim = 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], "--noViscosityIterations" ) == 0 ){noViscosityIterations = true; continue;} + if( std::strcmp( argv[i], "--viscosity" ) == 0 ){viscosity = real_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--uIn" ) == 0 ){uIn = real_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--vtkIOInit" ) == 0 ){vtkIOInit = true; continue;} + if( std::strcmp( argv[i], "--longDom" ) == 0 ){longDom = true; continue;} + if( std::strcmp( argv[i], "--broadDom" ) == 0 ){broadDom = true; continue;} + if( std::strcmp( argv[i], "--variant" ) == 0 ){memVariant = to_MEMVariant( argv[++i] ); continue;} + if( std::strcmp( argv[i], "--useOmegaBulkAdaption" ) == 0 ){useOmegaBulkAdaption = true; continue;} + if( std::strcmp( argv[i], "--bvrf" ) == 0 ){bulkViscRateFactor = real_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--forceTorqueAverageMode" ) == 0 ){averageForceTorqueOverTwoTimeStepsMode = uint_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--conserveMomentum" ) == 0 ){conserveMomentum = true; continue;} + if( std::strcmp( argv[i], "--useDiffusiveScaling" ) == 0 ){useDiffusiveScaling = true; continue;} + if( std::strcmp( argv[i], "--XBlocks" ) == 0 ){XBlocks = uint_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--YBlocks" ) == 0 ){YBlocks = uint_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--folderEnding" ) == 0 ){folderEnding = argv[++i]; continue;} + if( std::strcmp( argv[i], "--reconstructorType" ) == 0 ) {reconstructorType = argv[++i]; continue;} + if( std::strcmp( argv[i], "--useEuler" ) == 0 ) {useVelocityVerlet = false; continue;} + if( std::strcmp( argv[i], "--noFluidVelocityInit" ) == 0 ) {initFluidVelocity = false; continue;} + if( std::strcmp( argv[i], "--vtkOutputGhostlayers" ) == 0 ) {vtkOutputGhostlayers = true; continue;} + if( std::strcmp( argv[i], "--vtkNumOutputFiles" ) == 0 ){vtkNumOutputFiles = uint_c( std::atof( argv[++i] ) ); continue;} + if( std::strcmp( argv[i], "--vtkWriteSpacingNonDim" ) == 0 ){vtkWriteSpacingNonDim = real_c( std::atof( argv[++i] ) ); continue;} + WALBERLA_ABORT("command line argument unknown: " << argv[i] ); + } + + /////////////////////////// + // SIMULATION PROPERTIES // + /////////////////////////// + + const real_t radius = real_t(0.5) * diameter; + uint_t xlength = uint_c( diameter * real_t(5.34) ); + uint_t ylength = xlength; + uint_t zlength = uint_c( diameter * real_t(16) ); + + if(broadDom) + { + xlength *= uint_t(2); + ylength *= uint_t(2); + } + + if(longDom) + { + zlength *= uint_t(3); + } + + + // estimate Reynolds number (i.e. inflow velocity) based on Galileo number + // values are taken from the original simulation of Uhlmann, Dusek + switch( simulationCase ) + { + case 0: + WALBERLA_LOG_INFO_ON_ROOT("Using custom simulation case -> you have to provide Ga, Re, and time steps via command line arguments!"); + break; + case 1: + Galileo = real_t(144); + Re_target = real_t(185.08); + timestepsNonDim = real_t(100); + break; + case 2: + Galileo = real_t(178.46); + Re_target = real_t(243.01); + timestepsNonDim = real_t(250); + break; + case 3: + Galileo = real_t(190); + Re_target = real_t(262.71); + timestepsNonDim = real_t(250); + break; + case 4: + Galileo = real_t(250); + Re_target = real_t(365.10); + timestepsNonDim = real_t(510); + break; + default: + WALBERLA_ABORT("Simulation case is not supported!"); + } + + if( useDiffusiveScaling) + { + // estimate fitting inflow velocity (diffusive scaling, viscosity is fixed) + // attention: might lead to large velocities for small diameters + uIn = Re_target * viscosity / diameter; + } else { + // estimate fitting viscosity (acoustic scaling: velocity is fixed) + // note that this is different to the approach taking in the paper, where an diffusive scaling with visc = 0.01 is taken + // attention: might lead to small viscosities for small diameters + viscosity = uIn * diameter / Re_target; + } + + + real_t omega = lbm::collision_model::omegaFromViscosity(viscosity); + real_t omegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor); + + 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 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(); + uint_t ZBlocks = uint_c(numProcs) / ( 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 partitioning does not fit to total domain size!"); + } + + WALBERLA_LOG_INFO_ON_ROOT("Motion settling sphere simulation of case " << simulationCase << " -> Ga = " << Galileo); + 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 ); + + ////////////////// + // 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); + + + + 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( simulationCase == 1 ) + { + xParticle = real_c( xlength ) * real_t(0.5); + yParticle = real_c( ylength ) * real_t(0.5); + } + else if( simulationCase == 4 ) + { + // add random perturbance for chaotic regime + walberla::math::seedRandomGenerator( std::mt19937::result_type(std::time(nullptr)) ); + 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 + Vector3<real_t> position( xParticle, yParticle, zParticle ); + auto sphereShape = ss->create<mesa_pd::data::Sphere>( radius ); + ss->shapes[sphereShape]->updateMassAndInertia(densityRatio); + + walberla::id_t sphereUid = 0; + if (rpdDomain->isContainedInProcessSubdomain( uint_c(mpi::MPIManager::instance()->rank()), position )) + { + mesa_pd::data::Particle&& p = *ps->create(); + p.setPosition(position); + p.setInteractionRadius(radius); + p.setOwner(mpi::MPIManager::instance()->rank()); + p.setShapeID(sphereShape); + sphereUid = p.getUid(); + } + mpi::allReduceInplace(sphereUid, mpi::SUM); + + // set up synchronization procedure + const real_t overlap = real_t( 1.5 ) * dx; + std::function<void(void)> syncCall; + if( XBlocks <= uint_t(4) ) + { + WALBERLA_LOG_INFO_ON_ROOT("Using next neighbor sync!") + syncCall = [&ps,&rpdDomain,overlap](){ + mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc; + syncNextNeighborFunc(*ps, *rpdDomain, overlap); + }; + syncCall(); + } + else + { + WALBERLA_LOG_INFO_ON_ROOT("Using ghost owner sync!") + syncCall = [&ps,&rpdDomain,overlap](){ + mesa_pd::mpi::SyncGhostOwners syncGhostOwnersFunc; + syncGhostOwnersFunc(*ps, *rpdDomain, overlap); + }; + for(uint_t i = 0; i < uint_c(XBlocks/2); ++i) syncCall(); + } + + + WALBERLA_LOG_INFO_ON_ROOT("Initial sphere position: " << position); + + /////////////////////// + // ADD DATA TO BLOCKS // + //////////////////////// + + // add omega bulk field + BlockDataID omegaBulkFieldID = field::addToStorage<ScalarField_T>( blocks, "omega bulk field", omegaBulk, field::fzyx); + + + // create the lattice model + real_t lambda_e = lbm::collision_model::TRT::lambda_e( omega ); + real_t lambda_d = lbm::collision_model::TRT::lambda_d( omega, magicNumberTRT ); +#ifdef WALBERLA_BUILD_WITH_CODEGEN + WALBERLA_LOG_INFO_ON_ROOT("Using generated TRT-like lattice model!"); + LatticeModel_T latticeModel = LatticeModel_T(omegaBulkFieldID, lambda_d, lambda_e); +#else + WALBERLA_LOG_INFO_ON_ROOT("Using waLBerla built-in TRT lattice model and ignoring omega bulk!"); + LatticeModel_T latticeModel = LatticeModel_T( lbm::collision_model::TRT::constructWithMagicNumber( omega, magicNumberTRT ) ); +#endif + + // add PDF field + // initial velocity in domain = inflow velocity + BlockDataID pdfFieldID = lbm::addPdfFieldToStorage< LatticeModel_T >( blocks, "pdf field", latticeModel, initFluidVelocity ? uInfty : Vector3<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 & initialize outer domain boundaries + using BoundaryHandling_T = MyBoundaryHandling<ParticleAccessor_T>::Type; + BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >(MyBoundaryHandling<ParticleAccessor_T>( flagFieldID, pdfFieldID, particleFieldID, accessor, uInfty), "boundary handling" ); + + // kernels + mesa_pd::mpi::ReduceProperty reduceProperty; + + lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction; + lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque; + lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque; + lbm_mesapd_coupling::InitializeHydrodynamicForceTorqueForAveragingKernel initializeHydrodynamicForceTorqueForAveragingKernel; + + lbm_mesapd_coupling::RegularParticlesSelector sphereSelector; + + lbm_mesapd_coupling::MovingParticleMappingKernel<BoundaryHandling_T> movingParticleMappingKernel(blocks, boundaryHandlingID, particleFieldID); + + // mapping of sphere required by MEM variants + // sets the correct flags + if( memVariant == MEMVariant::CLI ) + { + ps->forEachParticle(false, sphereSelector, *accessor, movingParticleMappingKernel, *accessor, MEM_CLI_Flag); + }else { + ps->forEachParticle(false, sphereSelector, *accessor, movingParticleMappingKernel, *accessor, 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 ) ); + basefolder += "_MEM_"; + basefolder += MEMVariant_to_string( memVariant ); + basefolder += "_bvrf"; + basefolder += std::to_string(int(bulkViscRateFactor)); + if( useOmegaBulkAdaption ) basefolder += "Adapt"; + + basefolder += "_" + reconstructorType; + + if( longDom ) basefolder += "_longDom"; + if( broadDom ) basefolder += "_broadDom"; + if( conserveMomentum ) basefolder += "_conserveMomentum"; + if( !folderEnding.empty() ) basefolder += "_" + folderEnding; + + WALBERLA_LOG_INFO_ON_ROOT("Basefolder for simulation results: " << basefolder); + + // create base directory if it does not yet exist + filesystem::path tpath( basefolder ); + if( !filesystem::exists( tpath ) ) + filesystem::create_directory( tpath ); + + // 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 + + // omega bulk adaption + using OmegaBulkAdapter_T = lbm_mesapd_coupling::OmegaBulkAdapter<ParticleAccessor_T, decltype(sphereSelector)>; + 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, sphereSelector); + for (auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) { + (*omegaBulkAdapter)(blockIt.get()); + } + + + ////////////////////////////////////// + // TIME LOOP FOR INITIAL SIMULATION // + ////////////////////////////////////// + + // Initialization simulation: fixed sphere and simulate until convergence (of drag force) + + // create the timeloop + SweepTimeloop timeloopInit( blocks->getBlockStorage(), timestepsInit ); + + // add LBM communication function and boundary handling sweep (does the hydro force calculations and the no-slip treatment) + auto bhSweep = BoundaryHandling_T::getBlockSweep( boundaryHandlingID ); + timeloopInit.add() << BeforeFunction( optimizedPDFCommunicationScheme, "LBM Communication" ) + << Sweep(bhSweep, "Boundary Handling" ); + + // stream + collide LBM step +#ifdef WALBERLA_BUILD_WITH_CODEGEN + auto lbmSweep = LatticeModel_T::Sweep( pdfFieldID ); + timeloopInit.add() << Sweep( lbmSweep, "LB sweep" ); +#else + auto lbmSweep = lbm::makeCellwiseSweep< LatticeModel_T, FlagField_T >( pdfFieldID, flagFieldID, Fluid_Flag ); + timeloopInit.add() << Sweep( makeSharedSweep( lbmSweep ), "cell-wise LB sweep" ); +#endif + + 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(30) ), "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."); + + RestingSphereForceEvaluator<ParticleAccessor_T> forceEval( accessor, sphereUid, averageFrequency, basefolder ); + + while (true) { + WcTimingPool timeloopInitTiming; + + WALBERLA_LOG_INFO_ON_ROOT("(Re-)Starting initial simulation."); + for( uint_t i = 1; i < timestepsInit; ++i ){ + + ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor ); + + timeloopInit.singleStep( timeloopInitTiming ); + + reduceProperty.operator()<mesa_pd::HydrodynamicForceTorqueNotification>(*ps); + forceEval(timeloopInit.getCurrentTimeStep()+1); + + // 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.getDragForce()) ) + { + 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.getDragForce() / ( (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 = " << 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 changed, 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; + omega = lbm::collision_model::omegaFromViscosity( viscosity ); + + lambda_e = lbm::collision_model::TRT::lambda_e( omega ); + lambda_d = lbm::collision_model::TRT::lambda_d( omega, magicNumberTRT ); + + // also adapt omega bulk + omegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, bulkViscRateFactor); + defaultOmegaBulk = lbm_mesapd_coupling::omegaBulkFromOmega(omega, real_t(1)); + omegaBulkAdapter->setDefaultOmegaBulk(defaultOmegaBulk); + omegaBulkAdapter->setAdaptedOmegaBulk(omegaBulk); + + // iterate all blocks with an iterator 'block' and change the collision model + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) + { + // get the field data out of the block + auto pdf = blockIt->getData< PdfField_T > ( pdfFieldID ); +#ifdef WALBERLA_BUILD_WITH_CODEGEN + pdf->latticeModel().omega_magic_ = lambda_d; + pdf->latticeModel().omega_visc_ = lambda_e; +#else + pdf->latticeModel().collisionModel().resetWithMagicNumber( omega, magicNumberTRT ); +#endif + (*omegaBulkAdapter)(blockIt.get()); + } + + + WALBERLA_LOG_INFO_ON_ROOT("==> Adapting viscosity:"); + WALBERLA_LOG_INFO_ON_ROOT("New viscosity = " << viscosity ); + WALBERLA_LOG_INFO_ON_ROOT("New omega = " << omega ); + WALBERLA_LOG_INFO_ON_ROOT("New omega bulk = " << omegaBulk ); + + } + + if( averageForceTorqueOverTwoTimeStepsMode == 1 ) + { + // maintain a good initial guess for averaging + ps->forEachParticle(false, mesa_pd::kernel::SelectLocal(), *accessor, initializeHydrodynamicForceTorqueForAveragingKernel, *accessor ); + } + ps->forEachParticle(false, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor ); + + /////////////// + // TIME LOOP // + /////////////// + + // actual simulation: freely moving sphere with acting gravity + + // calculate the number of timesteps + + real_t dtSim = (averageForceTorqueOverTwoTimeStepsMode == 2) ? real_t(2) : real_t(1); + const real_t t_ref = ( diameter / u_ref ); + const uint_t timesteps = uint_c( timestepsNonDim * t_ref / dtSim ); + + // set vtk write frequency accordingly + + const uint_t writeFrequency = uint_c( vtkWriteSpacingNonDim * t_ref ); // vtk write frequency + const uint_t initWriteCallsToSkip = uint_c(std::max( 0, int( timesteps - ( vtkNumOutputFiles - 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(), uint_c(dtSim) * timesteps ); + + + timeloop.addFuncBeforeTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps(), real_t(30) ), "Remaining Time Logger" ); + + // vtk output + auto pdfFieldVTK = vtk::createVTKOutput_BlockData( blocks, "fluid_field", writeFrequency, (vtkOutputGhostlayers) ? FieldGhostLayers : 0, false, basefolder ); + pdfFieldVTK->setInitialWriteCallsToSkip( initWriteCallsToSkip ); + + pdfFieldVTK->addBeforeFunction( fullPDFCommunicationScheme ); + + // function to output plane infos for vtk output + pdfFieldVTK->addBeforeFunction(VTKInfoLogger<ParticleAccessor_T>( &timeloop, accessor, sphereUid, basefolder, uInfty )); + + // create folder for log_vtk files to not pollute the basefolder + filesystem::path tpath2( basefolder+"/log_vtk" ); + if( !filesystem::exists( tpath2 ) ) + 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.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTK ), "VTK (fluid field data)" ); + + + // 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 +#ifdef WALBERLA_BUILD_WITH_CODEGEN + timeloop.add() << Sweep( lbmSweep, "LB sweep" ); +#else + timeloop.add() << Sweep( makeSharedSweep( lbmSweep ), "cell-wise LB sweep" ); +#endif + + + SweepTimeloop timeloopAfterParticles( blocks->getBlockStorage(), timesteps ); + + // sweep for updating the pe body mapping into the LBM simulation + if( memVariant == MEMVariant::CLI ) + timeloopAfterParticles.add() << Sweep( lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MEM_CLI_Flag, FormerMEM_Flag, sphereSelector, conserveMomentum), "Particle Mapping" ); + else + timeloopAfterParticles.add() << Sweep( lbm_mesapd_coupling::makeMovingParticleMapping<PdfField_T, BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, MEM_BB_Flag, FormerMEM_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, FormerMEM_Flag, Fluid_Flag, equilibriumAndNonEquilibriumSphereNormalReconstructor, conserveMomentum); + + timeloopAfterParticles.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); + + timeloopAfterParticles.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" ) + << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMEM_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); + + timeloopAfterParticles.add() << BeforeFunction( fullPDFCommunicationScheme, "PDF Communication" ) + << Sweep( makeSharedSweep(lbm_mesapd_coupling::makePdfReconstructionManager<PdfField_T,BoundaryHandling_T>(blocks, pdfFieldID, boundaryHandlingID, particleFieldID, accessor, FormerMEM_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, FormerMEM_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 ) + { + timeloopAfterParticles.add() << Sweep( makeSharedSweep(omegaBulkAdapter), "Omega Bulk Adapter"); + } + + Vector3<real_t> extForcesOnSphere( real_t(0), real_t(0), - gravity * ( densityRatio - real_t(1) ) * diameter * diameter * diameter * math::pi / real_t(6)); + lbm_mesapd_coupling::AddForceOnParticlesKernel addGravitationalForce(extForcesOnSphere); + + + mesa_pd::kernel::VelocityVerletPreForceUpdate vvIntegratorPreForce(dtSim); + mesa_pd::kernel::VelocityVerletPostForceUpdate vvIntegratorPostForce(dtSim); + mesa_pd::kernel::ExplicitEuler explicitEulerIntegrator(dtSim); + + MovingSpherePropertyEvaluator<ParticleAccessor_T> movingSpherePropertyEvaluator( accessor, sphereUid, basefolder, uInfty, Galileo, GalileoSim, gravity, viscosity, diameter, densityRatio ); + + + //////////////////////// + // EXECUTE SIMULATION // + //////////////////////// + + WcTimingPool timeloopTiming; + + 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 ); + + if( averageForceTorqueOverTwoTimeStepsMode == 2) + { + // store current force and torque in fOld and tOld + ps->forEachParticle(useOpenMP, sphereSelector, *accessor, initializeHydrodynamicForceTorqueForAveragingKernel, *accessor ); + + // reset f and t + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor ); + + timeloop.singleStep( timeloopTiming ); + + + // f = (f+fOld)/2 + ps->forEachParticle(useOpenMP, sphereSelector, *accessor, averageHydrodynamicForceTorque, *accessor ); + + reduceProperty.operator()<mesa_pd::HydrodynamicForceTorqueNotification>(*ps); + } + else + { + reduceProperty.operator()<mesa_pd::HydrodynamicForceTorqueNotification>(*ps); + + if( averageForceTorqueOverTwoTimeStepsMode == 1 ) + { + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, averageHydrodynamicForceTorque, *accessor ); + } + } + + + timeloopTiming["RPD"].start(); + + if( useVelocityVerlet) + { + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPreForce, *accessor); + syncCall(); + } + + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addHydrodynamicInteraction, *accessor ); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addGravitationalForce, *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, explicitEulerIntegrator, *accessor); + } + syncCall(); + + timeloopTiming["RPD"].end(); + + // evaluation + timeloopTiming["Logging"].start(); + movingSpherePropertyEvaluator((i+1)*uint_c(dtSim)); + timeloopTiming["Logging"].end(); + + // reset after logging here + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, *accessor ); + + timeloopAfterParticles.singleStep( timeloopTiming ); + + if(movingSpherePropertyEvaluator.getParticleHeight() > real_c(zlength) - real_t(3) * diameter) + { + pdfFieldVTK->forceWrite(timeloop.getCurrentTimeStep()); + WALBERLA_LOG_WARNING_ON_ROOT("Sphere is too close to outflow, stopping simulation"); + break; + } + + } + + timeloopTiming.logResultOnRoot(); + + return EXIT_SUCCESS; +} + +} // namespace motion_settling_sphere + +int main( int argc, char **argv ){ + motion_settling_sphere::main(argc, argv); +} -- GitLab