From 3b5f6763346585dbd5a0f175cd4c57ff173ef171 Mon Sep 17 00:00:00 2001 From: Christoph Rettinger <christoph.rettinger@fau.de> Date: Wed, 20 Dec 2017 09:27:48 +0100 Subject: [PATCH] Added functionality, tests and showcase for discrete particle method coupling --- .../BidisperseFluidizedBedDPM.cpp | 1622 +++++++++++++++ .../BidisperseFluidizedBed/CMakeLists.txt | 2 + apps/showcases/CMakeLists.txt | 1 + src/pe_coupling/all.h | 1 + .../discrete_particle_methods/all.h | 30 + .../correlations/AddedMassForceCorrelations.h | 54 + .../correlations/DragForceCorrelations.h | 222 +++ .../correlations/LiftForceCorrelations.h | 63 + .../correlations/all.h | 29 + .../evaluators/AddedMassForceEvaluator.h | 145 ++ .../BodyVelocityTimeDerivativeEvaluator.h | 88 + .../EffectiveViscosityFieldEvaluator.h | 106 + .../evaluators/InteractionForceEvaluator.h | 180 ++ .../evaluators/LiftForceEvaluator.h | 145 ++ .../evaluators/LubricationForceEvaluator.h | 326 +++ .../evaluators/PressureFieldEvaluator.h | 75 + .../PressureGradientFieldEvaluator.h | 120 ++ .../SolidVolumeFractionFieldEvaluator.h | 102 + .../StressTensorGradientFieldEvaluator.h | 137 ++ .../evaluators/VelocityCurlFieldEvaluator.h | 112 ++ .../evaluators/VelocityFieldEvaluator.h | 81 + .../VelocityGradientFieldEvaluator.h | 144 ++ ...elocityTotalTimeDerivativeFieldEvaluator.h | 103 + .../evaluators/all.h | 39 + .../gns_lbm/GNSSweep.h | 381 ++++ .../discrete_particle_methods/gns_lbm/all.h | 30 + .../GNSExternalForceToForceFieldAdder.h | 77 + .../utility/GNSPressureFieldEvaluator.h | 81 + .../gns_lbm/utility/GNSSmagorinskyLESField.h | 189 ++ .../utility/GNSVelocityFieldEvaluator.h | 88 + .../gns_lbm/utility/all.h | 29 + ...edInteractionForceFieldToForceFieldAdder.h | 100 + .../utility/BodyVelocityInitializer.h | 110 + .../CombinedReductionFieldCommunication.h | 75 + .../utility/ExternalForceToForceFieldAdder.h | 72 + .../utility/FieldDataSwapper.h | 63 + .../utility/ForceFieldResetter.h | 61 + .../utility/SubgridTimeStep.h | 164 ++ .../discrete_particle_methods/utility/all.h | 32 + .../geometry/SphereEquivalentDiameter.h | 47 + src/pe_coupling/geometry/all.h | 1 + tests/pe_coupling/CMakeLists.txt | 10 + .../HinderedSettlingDynamicsDPM.cpp | 1771 +++++++++++++++++ .../SphereWallCollisionBehaviorDPM.cpp | 1401 +++++++++++++ 44 files changed, 8709 insertions(+) create mode 100644 apps/showcases/BidisperseFluidizedBed/BidisperseFluidizedBedDPM.cpp create mode 100644 apps/showcases/BidisperseFluidizedBed/CMakeLists.txt create mode 100644 src/pe_coupling/discrete_particle_methods/all.h create mode 100644 src/pe_coupling/discrete_particle_methods/correlations/AddedMassForceCorrelations.h create mode 100644 src/pe_coupling/discrete_particle_methods/correlations/DragForceCorrelations.h create mode 100644 src/pe_coupling/discrete_particle_methods/correlations/LiftForceCorrelations.h create mode 100644 src/pe_coupling/discrete_particle_methods/correlations/all.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/AddedMassForceEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/BodyVelocityTimeDerivativeEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/EffectiveViscosityFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/InteractionForceEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/LiftForceEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/LubricationForceEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/PressureFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/PressureGradientFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/SolidVolumeFractionFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/StressTensorGradientFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/VelocityCurlFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/VelocityFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/VelocityGradientFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/VelocityTotalTimeDerivativeFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/evaluators/all.h create mode 100644 src/pe_coupling/discrete_particle_methods/gns_lbm/GNSSweep.h create mode 100644 src/pe_coupling/discrete_particle_methods/gns_lbm/all.h create mode 100644 src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSExternalForceToForceFieldAdder.h create mode 100644 src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSPressureFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSSmagorinskyLESField.h create mode 100644 src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSVelocityFieldEvaluator.h create mode 100644 src/pe_coupling/discrete_particle_methods/gns_lbm/utility/all.h create mode 100644 src/pe_coupling/discrete_particle_methods/utility/AveragedInteractionForceFieldToForceFieldAdder.h create mode 100644 src/pe_coupling/discrete_particle_methods/utility/BodyVelocityInitializer.h create mode 100644 src/pe_coupling/discrete_particle_methods/utility/CombinedReductionFieldCommunication.h create mode 100644 src/pe_coupling/discrete_particle_methods/utility/ExternalForceToForceFieldAdder.h create mode 100644 src/pe_coupling/discrete_particle_methods/utility/FieldDataSwapper.h create mode 100644 src/pe_coupling/discrete_particle_methods/utility/ForceFieldResetter.h create mode 100644 src/pe_coupling/discrete_particle_methods/utility/SubgridTimeStep.h create mode 100644 src/pe_coupling/discrete_particle_methods/utility/all.h create mode 100644 src/pe_coupling/geometry/SphereEquivalentDiameter.h create mode 100644 tests/pe_coupling/discrete_particle_methods/HinderedSettlingDynamicsDPM.cpp create mode 100644 tests/pe_coupling/discrete_particle_methods/SphereWallCollisionBehaviorDPM.cpp diff --git a/apps/showcases/BidisperseFluidizedBed/BidisperseFluidizedBedDPM.cpp b/apps/showcases/BidisperseFluidizedBed/BidisperseFluidizedBedDPM.cpp new file mode 100644 index 000000000..1d65e3987 --- /dev/null +++ b/apps/showcases/BidisperseFluidizedBed/BidisperseFluidizedBedDPM.cpp @@ -0,0 +1,1622 @@ +//====================================================================================================================== +// +// 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 BiDisperseFluidizedBedDPM.cpp +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#include "core/all.h" +#include "boundary/all.h" +#include "lbm/all.h" +#include "pe_coupling/all.h" + +#include "blockforest/Initialization.h" +#include "blockforest/communication/all.h" + +#include "core/grid_generator/SCIterator.h" +#include "core/logging/all.h" + +#include "field/AddToStorage.h" +#include "field/communication/PackInfo.h" +#include "field/distributors/all.h" +#include "field/interpolators/all.h" + +#include "pe/basic.h" +#include "pe/vtk/SphereVtkOutput.h" +#include "pe/Types.h" +#include "pe/utility/DestroyBody.h" + +#include "stencil/D3Q27.h" + +#include "timeloop/SweepTimeloop.h" + +#include "vtk/VTKOutput.h" + +#include <vector> +#include <iomanip> +#include <iostream> +#include <random> + +namespace bidisperse_fluidized_bed_dpm +{ + +/////////// +// USING // +/////////// + +using namespace walberla; +using walberla::uint_t; + + +/////////////// +// CONSTANTS // +/////////////// + +const uint_t FieldGhostLayers( 1 ); + +////////////// +// TYPEDEFS // +////////////// + +//#define OutletBC + +// PDF field, flag field & body field +typedef GhostLayerField< Matrix3<real_t>, 1 > TensorField_T; +typedef GhostLayerField< Vector3<real_t>, 1 > Vec3Field_T; +typedef GhostLayerField< real_t, 1 > ScalarField_T; +typedef lbm::force_model::GuoField< Vec3Field_T > ForceModel_T; + +typedef lbm::D3Q19< lbm::collision_model::SRTField<ScalarField_T>, false, ForceModel_T > 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; + +// boundary handling +typedef lbm::NoSlip< LatticeModel_T, flag_t > NoSlip_T; +typedef lbm::SimpleUBB< LatticeModel_T, flag_t > Inflow_T; + +#ifdef OutletBC +typedef lbm::Outlet< LatticeModel_T, FlagField_T > Outflow_T; +#else +typedef lbm::SimplePressure< LatticeModel_T, flag_t > Outflow_T; +#endif + +typedef boost::tuples::tuple< NoSlip_T, Inflow_T, Outflow_T > BoundaryConditions_T; +typedef BoundaryHandling<FlagField_T, Stencil_T, BoundaryConditions_T> BoundaryHandling_T; + +typedef boost::tuple<pe::Plane, pe::Sphere> BodyTypeTuple ; + +/////////// +// FLAGS // +/////////// + +const FlagUID Fluid_Flag ( "fluid" ); +const FlagUID NoSlip_Flag ( "no slip flag" ); +const FlagUID Inflow_Flag ( "inflow flag" ); +const FlagUID Outflow_Flag ( "outflow flag" ); + +// coupling methods +enum DPMethod { GNS }; + +// interpolation (for PDFs, velocity and/or solid volume fraction) +enum Interpolation { INearestNeighbor, IKernel, ITrilinear }; + +// force distribution +enum Distribution { DNearestNeighbor, DKernel }; + +//drag correlation +enum DragCorrelation { ErgunWenYu, Tang, Stokes, Felice, Tenneti, NoDrag }; + +//lift correlation +enum LiftCorrelation { NoLift, Saffman }; + +//added mass correlation +enum AddedMassCorrelation { NoAM, Finn }; + +//effective viscosity +enum EffectiveViscosity { None, Rescaled, Eilers }; + +///////////////////////////////////// +// BOUNDARY HANDLING CUSTOMIZATION // +///////////////////////////////////// +class MyBoundaryHandling +{ +public: + + MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID, real_t uInflow ) : + flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ), uInflow_( uInflow ) {} + + BoundaryHandling_T * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const; + +private: + + const BlockDataID flagFieldID_; + const BlockDataID pdfFieldID_; + real_t uInflow_; + +}; // class MyBoundaryHandling + + +BoundaryHandling_T * MyBoundaryHandling::operator()( IBlock * const block, const StructuredBlockStorage * const storage ) const +{ + WALBERLA_ASSERT_NOT_NULLPTR( block ); + + FlagField_T * flagField = block->getData< FlagField_T >( flagFieldID_ ); + PdfField_T * pdfField = block->getData< PdfField_T > ( pdfFieldID_ ); + + const auto fluid = flagField->flagExists( Fluid_Flag ) ? flagField->getFlag( Fluid_Flag ) : flagField->registerFlag( Fluid_Flag ); + +#ifdef OutletBC + BoundaryHandling_T * handling = new BoundaryHandling_T( "Boundary handling", flagField, fluid, + boost::tuples::make_tuple( NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ), + Inflow_T( "Inflow", Inflow_Flag, pdfField, Vector3<real_t>(real_t(0),real_t(0),uInflow_) ), + Outflow_T( "Outflow", Outflow_Flag, pdfField, flagField, fluid ) ) ); +#else + BoundaryHandling_T * handling = new BoundaryHandling_T( "Boundary handling", flagField, fluid, + boost::tuples::make_tuple( NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ), + Inflow_T( "Inflow", Inflow_Flag, pdfField, Vector3<real_t>(real_t(0),real_t(0),uInflow_) ), + Outflow_T( "Outflow", Outflow_Flag, pdfField, real_t(1) ) ) ); +#endif + + const auto noslip = flagField->getFlag( NoSlip_Flag ); + const auto inflow = flagField->getFlag( Inflow_Flag ); + const auto outflow = flagField->getFlag( Outflow_Flag ); + + CellInterval domainBB = storage->getDomainCellBB(); + + domainBB.zMin() -= cell_idx_c( FieldGhostLayers ); // 0 + domainBB.zMax() += cell_idx_c( FieldGhostLayers ); // cellsZ+1 + + // BOTTOM + CellInterval bottom( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMin() ); + storage->transformGlobalToBlockLocalCellInterval( bottom, *block ); + handling->forceBoundary( inflow, bottom ); + + // TOP + CellInterval top( domainBB.xMin(), domainBB.yMin(), domainBB.zMax(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( top, *block ); + handling->forceBoundary( outflow, top ); + + domainBB.xMin() -= cell_idx_c( FieldGhostLayers ); // -1 + domainBB.xMax() += cell_idx_c( FieldGhostLayers ); // cellsX + + // LEFT + CellInterval left( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMin(), domainBB.yMax(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( left, *block ); + handling->forceBoundary( noslip, left ); + + // RIGHT + CellInterval right( domainBB.xMax(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( right, *block ); + handling->forceBoundary( noslip, right ); + + domainBB.yMin() -= cell_idx_c( FieldGhostLayers ); // 0 + domainBB.yMax() += cell_idx_c( FieldGhostLayers ); // cellsY+1 + + // FRONT + CellInterval front( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMin(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( front, *block ); + handling->forceBoundary( noslip, front ); + + // BACK + CellInterval back( domainBB.xMin(), domainBB.yMax(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( back, *block ); + handling->forceBoundary( noslip, back ); + + + + handling->fillWithDomain( FieldGhostLayers ); + + return handling; +} +//******************************************************************************************************************* + + +// VTK Output +shared_ptr<vtk::VTKOutput> createFluidFieldVTKWriter( shared_ptr< StructuredBlockForest > & blocks, + const BlockDataID & pdfFieldID, const BlockDataID & flagFieldID, uint_t writeFrequency, + const std::string & vtkBaseFolder ) +{ + auto pdfFieldVTKWriter = vtk::createVTKOutput_BlockData( blocks, "fluid_field", writeFrequency, uint_t(1), false, vtkBaseFolder ); + + blockforest::communication::UniformBufferedScheme<stencil::D3Q27> pdfGhostLayerSync( blocks ); + pdfGhostLayerSync.addPackInfo( make_shared< field::communication::PackInfo<PdfField_T> >( pdfFieldID ) ); + pdfFieldVTKWriter->addBeforeFunction( pdfGhostLayerSync ); + + field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID ); + fluidFilter.addFlag( Fluid_Flag ); + pdfFieldVTKWriter->addCellInclusionFilter( fluidFilter ); + + auto velocityWriter = make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "Velocity (Lattice)" ); + auto densityWriter = make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "Density (Lattice)" ); + pdfFieldVTKWriter->addCellDataWriter( velocityWriter ); + pdfFieldVTKWriter->addCellDataWriter( densityWriter ); + + return pdfFieldVTKWriter; +} + +DPMethod to_DPMethod( const std::string& s ) +{ + if( s == "GNS" ) return DPMethod::GNS; + throw std::runtime_error("invalid conversion from " + s + " to DPMethod"); +} + +Interpolation to_interpolation( const std::string& s ) +{ + if( s == "nearest" ) return Interpolation::INearestNeighbor; + if( s == "kernel" ) return Interpolation::IKernel; + if( s == "trilinear" ) return Interpolation::ITrilinear; + throw std::runtime_error("invalid conversion from " + s + " to Interpolation"); +} + +Distribution to_distribution( const std::string& s ) +{ + if( s == "nearest" ) return Distribution::DNearestNeighbor; + if( s == "kernel" ) return Distribution::DKernel; + throw std::runtime_error("invalid conversion from " + s + " to Distribution"); +} + +DragCorrelation to_dragCorrelation( const std::string& s ) +{ + if( s == "ergun" ) return DragCorrelation::ErgunWenYu; + if( s == "tang" ) return DragCorrelation::Tang; + if( s == "stokes" ) return DragCorrelation::Stokes; + if( s == "felice" ) return DragCorrelation::Felice; + if( s == "tenneti" ) return DragCorrelation::Tenneti; + if( s == "none" ) return DragCorrelation::NoDrag; + throw std::runtime_error("invalid conversion from " + s + " to DragCorrelation"); +} + +LiftCorrelation to_liftCorrelation( const std::string& s ) +{ + if( s == "none" ) return LiftCorrelation::NoLift; + if( s == "saffman" ) return LiftCorrelation::Saffman; + throw std::runtime_error("invalid conversion from " + s + " to LiftCorrelation"); +} + +AddedMassCorrelation to_addedMassCorrelation( const std::string& s ) +{ + if( s == "none" ) return AddedMassCorrelation::NoAM; + if( s == "finn" ) return AddedMassCorrelation::Finn; + throw std::runtime_error("invalid conversion from " + s + " to AddedMassCorrelation"); +} + +EffectiveViscosity to_effvisc( const std::string& s ) +{ + if( s == "none" ) return EffectiveViscosity::None; + if( s == "rescaled" ) return EffectiveViscosity::Rescaled; + if( s == "eilers" ) return EffectiveViscosity::Eilers; + throw std::runtime_error("invalid conversion from " + s + " to effective viscosity"); +} + +std::string dpm_to_string( const DPMethod& dpm ) +{ + if( dpm == DPMethod::GNS ) return "GNS"; + throw std::runtime_error("invalid conversion from DPMethod to string"); +} + +uint_t createSpheresRandomly( StructuredBlockForest & forest, pe::BodyStorage & globalBodyStorage, + const BlockDataID & bodyStorageID, const AABB & generationDomain, + real_t diameter1, real_t diameter2, real_t diameterAvg, + real_t solidVolumeFraction, pe::MaterialID & material ) +{ + real_t domainVolume = generationDomain.volume(); + real_t totalSphereVolume = domainVolume * solidVolumeFraction; + + real_t percentageOfSpecies1 = real_t(1); + if( diameter1 < diameter2 || diameter1 > diameter2 ) + { + real_t effectiveMass1 = diameter1 * diameter1 * diameter1; + real_t effectiveMass2 = diameter2 * diameter2 * diameter2; + real_t effectiveMassAvg = diameterAvg * diameterAvg * diameterAvg; + + percentageOfSpecies1 = (effectiveMassAvg - effectiveMass2) / (effectiveMass1 - effectiveMass2); + } + + WALBERLA_LOG_INFO_ON_ROOT("Creating "<< percentageOfSpecies1 * real_t(100) << "% of sphere type 1 with diameter = " << diameter1 << + " and " << (real_t(1) - percentageOfSpecies1) * real_t(100) << "% of sphere type 2 with diameter = " << diameter2); + + real_t xParticle = real_t(0); + real_t yParticle = real_t(0); + real_t zParticle = real_t(0); + real_t creationDiameter = real_t(0); + + real_t currentSphereVolume = real_t(0); + uint_t numberOfSpheres = uint_t(0); + + while( currentSphereVolume < totalSphereVolume ) + { + + WALBERLA_ROOT_SECTION() + { + xParticle = math::realRandom<real_t>(generationDomain.xMin(), generationDomain.xMax()); + yParticle = math::realRandom<real_t>(generationDomain.yMin(), generationDomain.yMax()); + zParticle = math::realRandom<real_t>(generationDomain.zMin(), generationDomain.zMax()); + + real_t speciesDecider = math::realRandom<real_t>(real_t(0), real_t(1)); + // if decider is in [0,...,percentageOfSpecies1], then species 1 is created, else species 2 + if( percentageOfSpecies1 > speciesDecider ) + { + creationDiameter = diameter1; + } + else + { + creationDiameter = diameter2; + } + } + + WALBERLA_MPI_SECTION() + { + mpi::broadcastObject( xParticle ); + mpi::broadcastObject( yParticle ); + mpi::broadcastObject( zParticle ); + mpi::broadcastObject( creationDiameter ); + } + + pe::createSphere( globalBodyStorage, forest.getBlockStorage(), bodyStorageID, 0, Vector3<real_t>( xParticle, yParticle, zParticle ), creationDiameter * real_t(0.5), material ); + + currentSphereVolume += math::M_PI / real_t(6) * creationDiameter * creationDiameter * creationDiameter; + + ++numberOfSpheres; + } + + return numberOfSpheres; +} + +class ForceOnBodiesAdder +{ +public: + + ForceOnBodiesAdder( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & bodyStorageID, + const Vector3<real_t> & forcePerVolume ) + : blockStorage_( blockStorage ), bodyStorageID_( bodyStorageID ), forcePerVolume_( forcePerVolume ) + { } + + // set a force on all (only local, to avoid force duplication) bodies + void operator()() + { + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + for( auto bodyIt = pe::LocalBodyIterator::begin( *blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end(); ++bodyIt ) + { + real_t volume = bodyIt->getVolume(); + bodyIt->addForce ( forcePerVolume_ * volume ); + } + } + } + +private: + + shared_ptr<StructuredBlockStorage> blockStorage_; + const BlockDataID bodyStorageID_; + Vector3<real_t> forcePerVolume_; +}; + +class BodiesQuantityEvaluator +{ +public: + + BodiesQuantityEvaluator( SweepTimeloop* timeloop, const shared_ptr<StructuredBlockStorage> & blockStorage, + const BlockDataID & bodyStorageID, const std::string & fileName, uint_t numSpheres, real_t totalMass ) : + timeloop_( timeloop ), blockStorage_( blockStorage ), bodyStorageID_( bodyStorageID ), fileName_( fileName ), + numSpheres_( numSpheres ), totalMass_( totalMass ) + { + std::ofstream file; + file.open( fileName_.c_str() ); + file << "#t \tfX \tfY \tfZ \tvelX \tvelY \tvelZ \tCoMX \tCoMY \tCoMZ\n"; + file.close(); + } + + void operator()() + { + // get mean particle velocity + Vector3<real_t> particleVelocity = getMeanParticleVelocity(); + // get force on particles + Vector3<real_t> particleForce = getParticleForce(); + // get center of mass (average position weighted by mass proportion) + Vector3<real_t> centerOfMass = getCenterOfMass(); + + WALBERLA_ROOT_SECTION() + { + writeToFile( particleForce, particleVelocity, centerOfMass ); + } + } + +private: + + Vector3<real_t> getMeanParticleVelocity() + { + Vector3<real_t> velocity(0.); + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + for( auto bodyIt = pe::LocalBodyIterator::begin< pe::Sphere >( *blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end<pe::Sphere>(); ++bodyIt ) + { + auto bodyVelocity = bodyIt->getLinearVel(); + velocity[0] += std::fabs(bodyVelocity[0]); + velocity[1] += std::fabs(bodyVelocity[1]); + velocity[2] += std::fabs(bodyVelocity[2]); + } + } + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( velocity[0], mpi::SUM ); + mpi::allReduceInplace( velocity[1], mpi::SUM ); + mpi::allReduceInplace( velocity[2], mpi::SUM ); + } + return velocity / real_c(numSpheres_); + } + + Vector3<real_t> getParticleForce() + { + Vector3<real_t> force(0.); + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + for( auto bodyIt = pe::BodyIterator::begin< pe::Sphere >( *blockIt, bodyStorageID_); bodyIt != pe::BodyIterator::end<pe::Sphere>(); ++bodyIt ) + { + force += bodyIt->getForce(); + } + } + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( force[0], mpi::SUM ); + mpi::allReduceInplace( force[1], mpi::SUM ); + mpi::allReduceInplace( force[2], mpi::SUM ); + } + return force; + } + + Vector3<real_t> getCenterOfMass() + { + Vector3<real_t> centerOfMass(0.); + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + for( auto bodyIt = pe::LocalBodyIterator::begin< pe::Sphere >( *blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end<pe::Sphere>(); ++bodyIt ) + { + auto bodyMass = bodyIt->getMass(); + auto bodyPosition = bodyIt->getPosition(); + centerOfMass += ( bodyMass / totalMass_ ) * bodyPosition; + } + } + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( centerOfMass[0], mpi::SUM ); + mpi::allReduceInplace( centerOfMass[1], mpi::SUM ); + mpi::allReduceInplace( centerOfMass[2], mpi::SUM ); + } + return centerOfMass; + } + + void writeToFile( const Vector3<real_t> & particleForce, const Vector3<real_t> & meanParticleVel, const Vector3<real_t> & centerOfMass ) + { + std::ofstream file; + file.open( fileName_.c_str(), std::ofstream::app ); + file.precision(8); + + file << timeloop_->getCurrentTimeStep() << "\t " + << particleForce[0] << "\t " << particleForce[1] << "\t " << particleForce[2] << "\t " + << meanParticleVel[0] << "\t " << meanParticleVel[1] << "\t " << meanParticleVel[2] << "\t" + << centerOfMass[0] << "\t " << centerOfMass[1] << "\t " << centerOfMass[2] << "\n"; + file.close(); + } + + SweepTimeloop* timeloop_; + shared_ptr<StructuredBlockStorage> blockStorage_; + const BlockDataID bodyStorageID_; + std::string fileName_; + const uint_t numSpheres_; + const real_t totalMass_; +}; + +class CollisionPropertiesEvaluator +{ +public: + CollisionPropertiesEvaluator( pe::cr::ICR & collisionResponse ) : collisionResponse_( collisionResponse ), maximumPenetration_(real_t(0)) + {} + + void operator()() + { + real_t maxPen = collisionResponse_.getMaximumPenetration(); + maximumPenetration_ = std::max( maximumPenetration_, maxPen ); + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( maximumPenetration_, mpi::MAX ); + } + } + real_t getMaximumPenetrationInSimulation() + { + return maximumPenetration_; + } + void resetMaximumPenetration() + { + maximumPenetration_ = real_t(0); + } +private: + pe::cr::ICR & collisionResponse_; + real_t maximumPenetration_; +}; + +class SphereCounter +{ +public: + SphereCounter( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & bodyStorageID, uint_t numSpheres) + : blockStorage_( blockStorage ), bodyStorageID_( bodyStorageID ), numSpheres_( numSpheres ) + {} + void operator()() + { + uint_t curNumSpheres = getNumSpheres(); + if( curNumSpheres != numSpheres_) + { + WALBERLA_LOG_INFO_ON_ROOT("Warning: Number of spheres has changed: " << numSpheres_ << " -> " << curNumSpheres ); + numSpheres_ = curNumSpheres; + } + } + + void updateNumSpheres() + { + numSpheres_ = getNumSpheres(); + } + + uint_t getNumSpheres() + { + uint_t curNumSpheres = uint_t(0); + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + for( auto bodyIt = pe::LocalBodyIterator::begin( *blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end(); ++bodyIt ) + { + ++curNumSpheres; + } + } + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( curNumSpheres, mpi::SUM ); + } + return curNumSpheres; + } + +private: + shared_ptr<StructuredBlockStorage> blockStorage_; + const BlockDataID bodyStorageID_; + uint_t numSpheres_; +}; + +void setBodyVelocities(const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & bodyStorageID, Vector3<real_t> velocity ) +{ + for( auto blockIt = blockStorage->begin(); blockIt != blockStorage->end(); ++blockIt ) + { + for( auto bodyIt = pe::LocalBodyIterator::begin( *blockIt, bodyStorageID); bodyIt != pe::LocalBodyIterator::end(); ++bodyIt ) + { + bodyIt->setLinearVel(velocity); + } + } + +} + +class DummySweep +{ +public: + DummySweep( ) + {} + + void operator()(IBlock * const /*block*/) + {} +}; + +void emptyFunction(){} + +//******************************************************************************************************************* +/*!\brief Simulation of a bidisperse fluidized bed with the discrete particle method. + * + * The simulation features a fluidized bed with spherical particles inside a rectangular column. + * The domain size is [32 x 16 x 256] * d_avg + * Since a bidisperse setup is used, i.e. the spheres have either diameter d1 or d2, one can specify d1, d2, and d_avg. + * The volume-average diameter d_avg determines the fractional distribution of spheres with d1 and d2. + * A monodisperse setting is obtained if d1 = d2 = d_avg. + * + * The fluidization is driven by a constant and uniform inflow from the bottom plane in positive z-direction. + * A plane, not visible to the fluid, is placed above this inlet to prevent particles from directly interfering + * with the inlet. + * The Reynolds number and the Galileo number characterize the setup. + * + * Command line customization allows to switch on/off different parts of the DPS (discrete particle simulation) + * algorithm. + * + * In a first step, the spherical particles are created with either d1 or d2 at random positions in the lower part of + * the domain. Possible overlaps are resolved, artificially bounded by a horizontal plane at the top of the generation + * domain, by carrying out PE-only steps. + * + * Afterwards, an initial, fluid-only, simulation is possible to equilibrate the fluid- and solid phase. + * Since this is not needed for the default case, it is not used by default (numberOfInitialTimeSteps=0). + * + * Finally, the fully coupled simulation is carried out with the option to write vtk files. + * + * The algorithm, as well as the setup and the outcome are described in detail in + * Rettinger, Ruede - "A Coupled Lattice Boltzmann Method and Discrete Element Method for Discrete Particle Simulations + * of Particulate Flows" to be published + * + */ +//******************************************************************************************************************* +int main( int argc, char **argv ) { + debug::enterTestMode(); + + mpi::Environment env(argc, argv); + + + ///////////////////////////////////////////// + // // + // Command Line Argument Customization // + // // + ///////////////////////////////////////////// + + bool funcTest = false; + bool vtkIO = false; + uint_t vtkWriteFrequency = uint_t(0); + uint_t numberOfTimeSteps = uint_t(50000); + uint_t numberOfInitialTimeSteps = uint_t(0); + std::string vtkBaseFolder = "vtk_out_BiDisperseFluidizedBedDPM"; + + real_t densityRatio = real_t(2500) / real_t(1000); + real_t ReynoldsNumber = real_t(10); // = rho_f * diameterAvg * uInflow / visc_f + real_t GalileoNumber = real_t(28); // = sqrt((densityRatio - 1) * g * diameterAvg ) * diameterAvg / visc_f + + real_t diameter1 = real_t(0.7); // diameter of species 1 + real_t diameter2 = real_t(0.8); // diameter of species 2 + real_t diameterAvg = real_t(0.75); // (mass) average diameter of all particles + + uint_t interactionSubCycles = uint_t(2); // number of subcycles that involve evaluation of the interaction force + uint_t peSubSteps = uint_t(10); // number of pe only calls in each subcycle + real_t solidVolumeFraction = real_t(0.2); // in the generation domain (fraction of the whole domain) + + DPMethod dpm = DPMethod::GNS; + Interpolation interpol = Interpolation::IKernel; + Distribution dist = Distribution::DKernel; + DragCorrelation dragCorr = DragCorrelation::Tenneti; + LiftCorrelation liftCorr = LiftCorrelation::Saffman; + AddedMassCorrelation addedMassCorr = AddedMassCorrelation::Finn; + EffectiveViscosity effVisc = EffectiveViscosity::Eilers; + + bool useTurbulenceModel = true; + const real_t smagorinskyConstant = real_t(0.1); //for turbulence model + + real_t lubricationCutOffDistance = diameterAvg; //0 switches it off + + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--funcTest") == 0) funcTest = true; + else if (std::strcmp(argv[i], "--vtkIOFreq") == 0) vtkWriteFrequency = uint_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--numberOfTimeSteps") == 0) numberOfTimeSteps = uint_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--numberOfInitialTimeSteps") == 0) numberOfInitialTimeSteps = uint_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--densityRatio") == 0) densityRatio = real_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--Ga") == 0) GalileoNumber = real_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--Re") == 0) ReynoldsNumber = real_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--diameter1") == 0) diameter1 = real_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--diameter2") == 0) diameter2 = real_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--diameterAvg") == 0) diameterAvg = real_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--solidVolumeFraction") == 0) solidVolumeFraction = real_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--interactionSubCycles") == 0) interactionSubCycles = uint_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--peSubSteps") == 0) peSubSteps = uint_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--DPMethod") == 0) dpm = to_DPMethod(argv[++i]); + else if (std::strcmp(argv[i], "--interpolation") == 0) interpol = to_interpolation(argv[++i]); + else if (std::strcmp(argv[i], "--distribution") == 0) dist = to_distribution(argv[++i]); + else if (std::strcmp(argv[i], "--dragCorrelation") == 0) dragCorr = to_dragCorrelation(argv[++i]); + else if (std::strcmp(argv[i], "--liftCorrelation") == 0) liftCorr = to_liftCorrelation(argv[++i]); + else if (std::strcmp(argv[i], "--addedMassCorrelation") == 0) addedMassCorr = to_addedMassCorrelation(argv[++i]); + else if (std::strcmp(argv[i], "--effectiveViscosity") == 0) effVisc = to_effvisc(argv[++i]); + else if (std::strcmp(argv[i], "--disableTurbulenceModel") == 0) useTurbulenceModel = false; + else if (std::strcmp(argv[i], "--lubricationCutOff") == 0) lubricationCutOffDistance = real_c(std::atof(argv[++i])); + else if (std::strcmp(argv[i], "--vtkBaseFolder") == 0) vtkBaseFolder = argv[++i]; + else WALBERLA_ABORT("Found invalid command line argument \"" << argv[i] << "\" - aborting..."); + } + + if (vtkWriteFrequency > 0) vtkIO = true; + + WALBERLA_CHECK(diameter1 <= real_t(1), "Diameter is not allowed to be > 1!"); + WALBERLA_CHECK(diameter2 <= real_t(1), "Diameter is not allowed to be > 1!"); + WALBERLA_CHECK((diameter1 <= diameterAvg && diameterAvg <= diameter2) || + (diameter2 <= diameterAvg && diameterAvg <= diameter1), + "Average diameter has to be between diameter 1 and 2!"); + WALBERLA_CHECK(solidVolumeFraction > real_t(0), "Solid volume fraction has to be > 0!"); + WALBERLA_CHECK(solidVolumeFraction <= real_t(0.65), "Solid volume fraction is not allowed to be > 0.65!"); + WALBERLA_CHECK(interactionSubCycles > uint_t(0), "Number of interaction sub cycles has to be at least 1!"); + WALBERLA_CHECK(peSubSteps > uint_t(0), "Number of pe sub steps has to be at least 1!"); + WALBERLA_CHECK(lubricationCutOffDistance >= real_t(0), "Lubrication cut off distance has to be non-negative!"); + + if (funcTest) { + walberla::logging::Logging::instance()->setLogLevel(logging::Logging::LogLevel::WARNING); + } + if( !funcTest ) + { + // create base directory if it does not yet exist + boost::filesystem::path tpath( vtkBaseFolder ); + if( !boost::filesystem::exists( tpath ) ) + boost::filesystem::create_directory( tpath ); + } + + + /////////////////////////////// + // // + // SIMULATION PROPERTIES // + // // + /////////////////////////////// + + const uint_t xlength = uint_c(real_t(32) * diameterAvg); + const uint_t ylength = uint_c(real_t(16) * diameterAvg); + const uint_t zlength = uint_c(real_t(256) * diameterAvg); + + const real_t uInflow = real_t(0.01); + const real_t viscosity = diameterAvg * uInflow / ReynoldsNumber; + + const real_t ug = GalileoNumber * viscosity / diameterAvg; + const real_t gravitationalAcc = ug * ug / ((densityRatio - real_t(1)) * diameterAvg); + + const real_t omega = lbm::collision_model::omegaFromViscosity(viscosity); + const real_t tau = real_t(1) / omega; + + const real_t dx = real_t(1); + + const real_t dt_DEM = real_t(1) / real_c(interactionSubCycles * peSubSteps); + + const real_t dt = real_t(1); + const real_t dtInteractionSubCycle = dt / real_c(interactionSubCycles); + const real_t dtBodyVelocityTimeDerivativeEvaluation = dtInteractionSubCycle; + + + const uint_t timesteps = (funcTest) ? uint_t(3) + : numberOfTimeSteps; // total number of time steps for the whole simulation + const uint_t initialTimesteps = (funcTest) ? uint_t(0) + : numberOfInitialTimeSteps; // total number of time steps for the initial simulation + + const Vector3<real_t> initialFluidVelocity(real_t(0), real_t(0), uInflow); + + + if (!funcTest) { + WALBERLA_LOG_INFO_ON_ROOT("Lx x Ly x Lz = " << xlength << " x " << ylength << " x " << zlength); + WALBERLA_LOG_INFO_ON_ROOT("diameter1 = " << diameter1 << ", diameter2 = " << diameter2 << ", diameterAvg = " << diameterAvg); + WALBERLA_LOG_INFO_ON_ROOT("Re = " << ReynoldsNumber << ", Ga = " << GalileoNumber); + WALBERLA_LOG_INFO_ON_ROOT("tau = " << tau << ", omega = " << omega); + WALBERLA_LOG_INFO_ON_ROOT("viscosity = " << viscosity); + WALBERLA_LOG_INFO_ON_ROOT("gravity = " << gravitationalAcc); + WALBERLA_LOG_INFO_ON_ROOT("input solid volume fraction = " << solidVolumeFraction); + WALBERLA_LOG_INFO_ON_ROOT("discrete particle method = " << dpm_to_string(dpm)); + WALBERLA_LOG_INFO_ON_ROOT("interpolator = " << interpol); + WALBERLA_LOG_INFO_ON_ROOT("distribution = " << dist); + WALBERLA_LOG_INFO_ON_ROOT("dragCorrelation = " << dragCorr); + WALBERLA_LOG_INFO_ON_ROOT("addedMassCorrelation = " << addedMassCorr); + WALBERLA_LOG_INFO_ON_ROOT("liftCorrelation = " << liftCorr); + WALBERLA_LOG_INFO_ON_ROOT("effective viscosity = " << effVisc); + WALBERLA_LOG_INFO_ON_ROOT("turbulence model = " << (useTurbulenceModel ? "yes" : "no")); + WALBERLA_LOG_INFO_ON_ROOT("interaction sub cycles = " << interactionSubCycles); + WALBERLA_LOG_INFO_ON_ROOT("pe sub steps = " << peSubSteps); + WALBERLA_LOG_INFO_ON_ROOT("lubrication cut off distance = " << lubricationCutOffDistance); + WALBERLA_LOG_INFO_ON_ROOT("dt_DEM = " << dt_DEM); + } + + /////////////////////////// + // BLOCK STRUCTURE SETUP // + /////////////////////////// + + const uint_t XBlocks = uint_t(4); + const uint_t YBlocks = uint_t(1); + const uint_t ZBlocks = uint_t(8); + + const uint_t XCells = xlength / XBlocks; + const uint_t YCells = ylength / YBlocks; + const uint_t ZCells = zlength / ZBlocks; + + if (XBlocks * XCells != xlength || + YBlocks * YCells != ylength || + ZBlocks * ZCells != zlength) WALBERLA_ABORT("Domain decomposition failed!"); + + auto blocks = blockforest::createUniformBlockGrid(XBlocks, YBlocks, ZBlocks, XCells, YCells, ZCells, + dx, 0, false, false, + false, false, false, + false); + + + //write domain decomposition to file + if( vtkIO ) + { + vtk::writeDomainDecomposition( blocks, "domain_decomposition", vtkBaseFolder ); + } + + //////// + // PE // + //////// + + 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"); + + pe::cr::ICR *cr; + pe::cr::DEM cr_dem(globalBodyStorage, blocks->getBlockStoragePointer(), bodyStorageID, ccdID, fcdID); + cr = &cr_dem; + + const real_t restitutionCoeff = real_t(0.88); + const real_t frictionCoeff = real_t(0.25); + + real_t sphereVolume = diameterAvg * diameterAvg * diameterAvg * math::M_PI / real_t(6); // based on avg. diameter + const real_t particleMass = densityRatio * sphereVolume; + const real_t Mij = particleMass * particleMass / (real_t(2) * particleMass); + const real_t lnDryResCoeff = std::log(restitutionCoeff); + const real_t collisionTime = real_t(0.5); + const real_t stiffnessCoeff = math::M_PI * math::M_PI * Mij / (collisionTime * collisionTime * + (real_t(1) - lnDryResCoeff * lnDryResCoeff / (math::M_PI * math::M_PI + lnDryResCoeff * lnDryResCoeff))); + const real_t dampingCoeff = -real_t(2) * std::sqrt(Mij * stiffnessCoeff) * + (std::log(restitutionCoeff) / std::sqrt(math::M_PI * math::M_PI + (std::log(restitutionCoeff) * std::log(restitutionCoeff)))); + const real_t contactDuration = real_t(2) * math::M_PI * Mij / (std::sqrt(real_t(4) * Mij * stiffnessCoeff - dampingCoeff * dampingCoeff)); //formula from Uhlman + + WALBERLA_LOG_INFO_ON_ROOT("Created particle material with:\n" + << " - coefficient of restitution = " << restitutionCoeff << "\n" + << " - coefficient of friction = " << frictionCoeff << "\n" + << " - stiffness coefficient kn = " << stiffnessCoeff << "\n" + << " - damping coefficient cdn = " << dampingCoeff << "\n" + << " - contact time Tc = " << contactDuration); + + auto peMaterial = pe::createMaterial("particleMat", densityRatio, restitutionCoeff, frictionCoeff, frictionCoeff, + real_t(0), real_t(200), stiffnessCoeff, dampingCoeff, dampingCoeff); + + // create bounding planes + pe::createPlane(*globalBodyStorage, 0, Vector3<real_t>(1, 0, 0), Vector3<real_t>(0, 0, 0), peMaterial); + pe::createPlane(*globalBodyStorage, 0, Vector3<real_t>(-1, 0, 0), Vector3<real_t>(real_c(xlength), 0, 0), peMaterial); + pe::createPlane(*globalBodyStorage, 0, Vector3<real_t>(0, 1, 0), Vector3<real_t>(0, 0, 0), peMaterial); + pe::createPlane(*globalBodyStorage, 0, Vector3<real_t>(0, -1, 0), Vector3<real_t>(0, real_c(ylength), 0), peMaterial); + + // set the planes in z-direction slightly inwards the simulation domain to avoid direct interaction of the spheres with the in- and outflow BC + real_t zOffset = real_t(4); + pe::createPlane(*globalBodyStorage, 0, Vector3<real_t>(0, 0, 1), Vector3<real_t>(0, 0, zOffset), peMaterial); + pe::createPlane(*globalBodyStorage, 0, Vector3<real_t>(0, 0, -1), Vector3<real_t>(0, 0, real_c(zlength) - zOffset), peMaterial); + + + ///////////////// + // PE COUPLING // + ///////////////// + + // connect to pe + const real_t overlap = real_t(1.5) * dx; + auto syncCall = boost::bind(pe::syncNextNeighbors<BodyTypeTuple>, boost::ref(blocks->getBlockForest()), + bodyStorageID, static_cast<WcTimingTree *>(NULL), overlap, false); + shared_ptr<CollisionPropertiesEvaluator> collisionPropertiesEvaluator = walberla::make_shared<CollisionPropertiesEvaluator>(*cr); + + // create the spheres + uint_t numSpheres = 0; + { + real_t radiusMax = real_t(0.5) * std::max(diameter1, diameter2); + AABB generationDomain(radiusMax, radiusMax, radiusMax + zOffset, + real_c(xlength) - radiusMax, real_c(ylength) - radiusMax, real_t(64) * diameterAvg + zOffset); + numSpheres = createSpheresRandomly(*blocks, *globalBodyStorage, bodyStorageID, generationDomain, + diameter1, diameter2, diameterAvg, solidVolumeFraction, peMaterial); + syncCall(); + + const uint_t initialPeSteps = uint_t(50000); + const real_t dt_DEM_init = collisionTime / real_t(uint_t(10) * peSubSteps); + const real_t overlapLimit = real_t(0.05) * diameterAvg; + + WALBERLA_LOG_INFO_ON_ROOT("Sphere creation done -- " << numSpheres << " spheres created"); + WALBERLA_LOG_INFO_ON_ROOT( + "resolving overlaps with goal all < " << overlapLimit / diameterAvg * real_t(100) << "%"); + + auto boundingPlaneForInit = pe::createPlane(*globalBodyStorage, 0, Vector3<real_t>(0, 0, -1), + Vector3<real_t>(0, 0, generationDomain.zMax() + radiusMax), + peMaterial); + + for (uint_t pet = uint_t(1); pet <= initialPeSteps; ++pet) { + cr->timestep(dt_DEM_init); + syncCall(); + (*collisionPropertiesEvaluator)(); + real_t maxPen = collisionPropertiesEvaluator->getMaximumPenetrationInSimulation(); + if (maxPen < overlapLimit) { + WALBERLA_LOG_INFO_ON_ROOT("Carried out " << pet << " DEM-only time steps to resolve initial overlaps"); + break; + } else { + if (pet % uint_t(200) == uint_t(0)) { + WALBERLA_LOG_INFO_ON_ROOT( + pet << " - current max overlap = " << maxPen / diameterAvg * real_t(100) << "%"); + } + } + collisionPropertiesEvaluator->resetMaximumPenetration(); + } + pe::destroyBodyBySID(*globalBodyStorage, blocks->getBlockStorage(), bodyStorageID, + boundingPlaneForInit->getSystemID()); + + // set body velocities to zero + setBodyVelocities(blocks, bodyStorageID, Vector3<real_t>(real_t(0))); + + // some spheres might have gone lost due to unfortunate random generation + SphereCounter sphereNumberChecker(blocks, bodyStorageID, numSpheres); + sphereNumberChecker.updateNumSpheres(); + numSpheres = sphereNumberChecker.getNumSpheres(); + + } + + + + // log setup to file + if (!funcTest && vtkIO) { + WALBERLA_ROOT_SECTION() { + std::string fileName = vtkBaseFolder + "/setup.txt"; + std::ofstream file; + file.open(fileName.c_str()); + file.precision(8); + + file << "Lx x Ly x Lz = " << xlength << " x " << ylength << " x " << zlength << "\n" + << "diameter1 = " << diameter1 << ", diameter2 = " << diameter2 << ", diameterAvg = " << diameterAvg << "\n" + << "Re = " << ReynoldsNumber << ", Ga = " << GalileoNumber << "\n" + << "tau = " << tau << ", omega = " << omega << "\n" + << "viscosity = " << viscosity << "\n" + << "gravity = " << gravitationalAcc << "\n" + << "input solid volume fraction = " << solidVolumeFraction << "\n" + << "discrete particle method = " << dpm_to_string(dpm) << "\n" + << "interpolator = " << interpol << "\n" + << "distribution = " << dist << "\n" + << "dragCorrelation = " << dragCorr << "\n" + << "addedMassCorrelation = " << addedMassCorr << "\n" + << "liftCorrelation = " << liftCorr << "\n" + << "effective viscosity = " << effVisc << "\n" + << "turbulence model = " << (useTurbulenceModel ? "yes" : "no") << "\n" + << "interaction sub cycles = " << interactionSubCycles << "\n" + << "pe sub steps = " << peSubSteps << "\n" + << "lubrication cut off distance = " << lubricationCutOffDistance << "\n" + << "dt_DEM = " << dt_DEM << "\n" + << "Material:\n" + << " - coefficient of restitution = " << restitutionCoeff << "\n" + << " - coefficient of friction = " << frictionCoeff << "\n" + << " - stiffness coefficient kn = " << stiffnessCoeff << "\n" + << " - damping coefficient cdn = " << dampingCoeff << "\n" + << " - contact time Tc = " << contactDuration << "\n" + << "number of spheres = " << numSpheres << "\n"; + } + } + + + ////////////////////// + // // + // BLOCK DATA // + // // + ////////////////////// + + // create force field + BlockDataID forceFieldID = field::addToStorage< Vec3Field_T >( blocks, "force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + + BlockDataID dragForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "drag force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + BlockDataID amForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "am force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + BlockDataID liftForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "lift force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + + // create omega field + BlockDataID omegaFieldID = field::addToStorage< ScalarField_T >( blocks, "omega field", omega, field::zyxf, FieldGhostLayers ); + + // create the lattice model + LatticeModel_T latticeModel = LatticeModel_T( omegaFieldID, ForceModel_T( forceFieldID ) ); + + // add PDF field + BlockDataID pdfFieldID = lbm::addPdfFieldToStorage( blocks, "pdf field (zyxf)", latticeModel, initialFluidVelocity, real_t(1), FieldGhostLayers, field::zyxf ); + + // add flag field + BlockDataID flagFieldID = field::addFlagFieldToStorage< FlagField_T >( blocks, "flag field" ); + + // add boundary handling & initialize outer domain boundaries + BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >( MyBoundaryHandling( flagFieldID, pdfFieldID, uInflow ), "boundary handling" ); + + // field to store fluid velolcity + BlockDataID velocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "velocity field", initialFluidVelocity, field::zyxf, FieldGhostLayers ); + BlockDataID oldVelocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "old velocity field", initialFluidVelocity, field::zyxf, FieldGhostLayers ); + BlockDataID swappedOldVelocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "swapped old velocity field", initialFluidVelocity, field::zyxf, FieldGhostLayers ); + + // create pressure field + BlockDataID pressureFieldID = field::addToStorage< ScalarField_T >( blocks, "pressure field", real_t(0), field::zyxf, FieldGhostLayers ); + + // create solid volume fraction field + BlockDataID svfFieldID = field::addToStorage< ScalarField_T >( blocks, "svf field", real_t(0), field::zyxf, FieldGhostLayers ); + + // field to store pressure gradient + BlockDataID pressureGradientFieldID = field::addToStorage< Vec3Field_T >( blocks, "pressure gradient field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store curl of fluid velocity + BlockDataID velocityCurlFieldID = field::addToStorage< Vec3Field_T >( blocks, "velocity curl field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store velocity gradient + BlockDataID velocityGradientFieldID = field::addToStorage< TensorField_T >( blocks, "velocity gradient field", Matrix3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store gradient of stress tensor + BlockDataID stressTensorGradientFieldID = field::addToStorage< Vec3Field_T >( blocks, "stress tensor gradient field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store time derivative of fluid velocity + BlockDataID timeDerivativeVelocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "time derivative velocity field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // communication schemes + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> forceComm( blocks, forceFieldID ); + + blockforest::communication::UniformBufferedScheme<stencil::D3Q27> pdfScheme( blocks ); + pdfScheme.addPackInfo( make_shared< field::communication::PackInfo<PdfField_T> >( pdfFieldID ) ); + + // setup of the communication for synchronizing the velocity field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( velocityFieldID ) ); + + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > oldVelocityCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + oldVelocityCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( oldVelocityFieldID ) ); + + // setup of the communication for synchronizing the solid volume fraction field between neighboring blocks which takes into account the svf values inside the ghost layers + shared_ptr<pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >svfCommunicationScheme = make_shared<pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >( blocks, svfFieldID ); + + // setup of the communication for synchronizing the pressure field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > pressureCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + pressureCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<ScalarField_T> >( pressureFieldID ) ); + + // setup of the communication for synchronizing the pressure gradient field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > pressureGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + pressureGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( pressureGradientFieldID ) ); + + // setup of the communication for synchronizing the velocity curl field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityCurlCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityCurlCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( velocityCurlFieldID ) ); + + // communication for synchronizing the velocity gradient values + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<TensorField_T> >( velocityGradientFieldID ) ); + + // communication for synchronizing the stress tensor gradient values + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > stressTensorGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + stressTensorGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( stressTensorGradientFieldID ) ); + + // communication for synchronizing the interaction force field + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> dragForceComm( blocks, dragForceFieldID ); + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> amForceComm( blocks, amForceFieldID ); + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> liftForceComm( blocks, liftForceFieldID ); + + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> dragForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, dragForceFieldID, uint_t(1) ); + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> amForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, amForceFieldID, uint_t(1) ); + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> liftForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, liftForceFieldID, uint_t(1) ); + + ///////////////////////////////// + // // + // CORRELATION FUNCTIONS // + // // + ///////////////////////////////// + + // drag correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t>&, const Vector3<real_t> &, real_t, real_t, real_t, real_t)> dragCorrelationFunction; + if( dragCorr == DragCorrelation::ErgunWenYu ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceErgunWenYu; + } + else if( dragCorr == DragCorrelation::Tang ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceTang; + } + else if( dragCorr == DragCorrelation::Stokes ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceStokes; + } + else if( dragCorr == DragCorrelation::Felice ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceFelice; + } + else if( dragCorr == DragCorrelation::Tenneti ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceTenneti; + } + else if( dragCorr == DragCorrelation::NoDrag ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::noDragForce; + } + else + { + WALBERLA_ABORT("Drag correlation not yet implemented!"); + } + + // lift correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t, real_t )> liftCorrelationFunction; + if( liftCorr == LiftCorrelation::NoLift ) + { + liftCorrelationFunction = pe_coupling::discrete_particle_methods::noLiftForce; + } + else if( liftCorr == LiftCorrelation::Saffman ) + { + liftCorrelationFunction = pe_coupling::discrete_particle_methods::liftForceSaffman; + } + else + { + WALBERLA_ABORT("Lift correlation not yet implemented!"); + } + + // added mass correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t )> addedMassCorrelationFunction; + if( addedMassCorr == AddedMassCorrelation::NoAM ) + { + addedMassCorrelationFunction = pe_coupling::discrete_particle_methods::noAddedMassForce; + } + else if( addedMassCorr == AddedMassCorrelation::Finn ) + { + addedMassCorrelationFunction = pe_coupling::discrete_particle_methods::addedMassForceFinn; + } + else + { + WALBERLA_ABORT("Added mass correlation not yet implemented!"); + } + + // set up effective viscosity calculation + boost::function<real_t ( real_t, real_t)> effectiveViscosityFunction; + if( effVisc == EffectiveViscosity::None ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateUnchangedEffectiveViscosity; + } + else if( effVisc == EffectiveViscosity::Rescaled ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateRescaledEffectiveViscosity; + } + else if( effVisc == EffectiveViscosity::Eilers ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateEilersEffectiveViscosity; + } + else + { + WALBERLA_ABORT("Effective viscosity not yet implemented!"); + } + shared_ptr<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator> effectiveViscosityEvaluator = + walberla::make_shared<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator>(omegaFieldID, svfFieldID, viscosity, effectiveViscosityFunction ); + + + //////////////////////////////// + // // + // EVALUATION FUNCTIONS // + // // + //////////////////////////////// + + // evaluator for bodies' velocity time derivative + shared_ptr<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator> bodyVelocityTimeDerivativeEvaluator = make_shared<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator>( blocks, bodyStorageID, dtBodyVelocityTimeDerivativeEvaluation ); + (*bodyVelocityTimeDerivativeEvaluator)(); + + // function used to evaluate the interaction force between fluid and particles + boost::function<void(void)> dragAndPressureForceEvaluationFunction; + if( dpm == DPMethod::GNS ) { + if (interpol == Interpolation::INearestNeighbor) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } else if (interpol == Interpolation::IKernel) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } else if (interpol == Interpolation::ITrilinear) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + } + else + { + WALBERLA_ABORT("Discrete particle method not yet implemented!"); + } + + + // function to evaluate the lift force contribution + boost::function<void(void)> liftForceEvaluationFunction; + if( interpol == Interpolation::INearestNeighbor ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::IKernel ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::ITrilinear ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + + // function to evaluate the added mass contribution + boost::function<void(void)> addedMassEvaluationFunction; + if( interpol == Interpolation::INearestNeighbor ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::IKernel ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::ITrilinear ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + + // function to evaluate lubrication forces + boost::function<void(void)> lubricationEvaluationFunction; + if( lubricationCutOffDistance > real_t(0) ) + { + typedef pe_coupling::discrete_particle_methods::LubricationForceEvaluator LE_T; + shared_ptr<LE_T> lubEval = make_shared<LE_T>( blocks, globalBodyStorage, bodyStorageID, viscosity, lubricationCutOffDistance ); + lubricationEvaluationFunction = boost::bind(&LE_T::operator(), lubEval); + } + else + { + lubricationEvaluationFunction = emptyFunction; + } + + + ////////////////////////////// + // // + // INITIAL SIMULATION // + // // + ////////////////////////////// + + { + // init solid volume fraction field + pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::KernelDistributor> svfEvaluator( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) svfEvaluator( &(*blockIt) ); + + (*svfCommunicationScheme)(); + + pe_coupling::discrete_particle_methods::ForceFieldResetter forceFieldResetter( forceFieldID ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) forceFieldResetter( &(*blockIt) ); + } + + if( initialTimesteps > uint_t(0) ) + { + + SweepTimeloop timeloop( blocks->getBlockStorage(), initialTimesteps ); + + ///////// begin of GNS timeloop /////////////////////// + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSPressureFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( pressureFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Pressure Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureCommunicationScheme), "Pressure Field Communication" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::PressureGradientFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( pressureGradientFieldID, pressureFieldID, boundaryHandlingID ), "Pressure Gradient Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureGradientCommunicationScheme), "Pressure Gradient Field Communication" ); + + + // subcycling loop begin + for( uint_t subcycle = 1; subcycle <= interactionSubCycles; ++subcycle ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCommunicationScheme), "Velocity Field Communication" ); + + // evaluate Fdrag + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( dragForceFieldID ), "Drag Force Field Reset" ) + << AfterFunction( dragAndPressureForceEvaluationFunction, "Fluid-Particle Interaction Force Evaluation" ) + << AfterFunction( dragForceComm, "Drag Force Field Communication" ); + + // ext forces on bodies + timeloop.add() << Sweep( DummySweep(), "Dummy Sweep ") + << AfterFunction( pe_coupling::ForceTorqueOnBodiesResetter( blocks, bodyStorageID ), "Force on Bodies Reset" ); + + } + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( makeSharedSweep<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator>( effectiveViscosityEvaluator ), "Effective Viscosity Evaluation"); + if( useTurbulenceModel ) timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSSmagorinskyLESField<LatticeModel_T>(blocks, omegaFieldID, pdfFieldID, svfFieldID, smagorinskyConstant), "Turbulence Model" ); + + // execute GNS-LBM sweep, boundary handling, PDF communication + auto sweep = pe_coupling::discrete_particle_methods::makeGNSSweep< LatticeModel_T, FlagField_T >( pdfFieldID, svfFieldID, flagFieldID, Fluid_Flag ); + + timeloop.add() << Sweep( lbm::makeCollideSweep( sweep ), "GNS-LBM sweep (collide)" ); + + timeloop.add() << BeforeFunction( pdfScheme, "LBM Communication" ) + << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" ); + + timeloop.add() << Sweep( lbm::makeStreamSweep( sweep ), "GNS-LBM sweep (stream)" ); + + + timeloop.addFuncAfterTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" ); + + WALBERLA_LOG_INFO_ON_ROOT("Starting initial (GNS-LBM only) simulation with " << initialTimesteps << " time steps.") + timeloop.run(); + + } + + + ///////////////////////////// + // // + // ACTUAL SIMULATION // + // // + ///////////////////////////// + + // create the timeloop + SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps ); + + { + pe_coupling::discrete_particle_methods::ForceFieldResetter forceFieldResetter( forceFieldID ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) forceFieldResetter( &(*blockIt) ); + + // evaluate fluid velocity field + pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> velocityEvaluator( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) velocityEvaluator( &(*blockIt) ); + + } + + // configure vtk output + if( vtkIO ) + { + shared_ptr<vtk::VTKOutput> pdfFieldVTKWriter = createFluidFieldVTKWriter( blocks, pdfFieldID, flagFieldID, vtkWriteFrequency, vtkBaseFolder ); + timeloop.addFuncBeforeTimeStep( vtk::writeFiles( pdfFieldVTKWriter ), "VTK (fluid field data)" ); + + auto bodyVtkOutput = make_shared<pe::SphereVtkOutput>( bodyStorageID, blocks->getBlockStorage() ); + auto bodyVTK = vtk::createVTKOutput_PointData( bodyVtkOutput, "bodies", vtkWriteFrequency, vtkBaseFolder ); + timeloop.addFuncBeforeTimeStep( vtk::writeFiles( bodyVTK ), "VTK (sphere data)" ); + + } + + + ///////// begin of GNS timeloop /////////////////////// + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::FieldDataSwapper<Vec3Field_T>( velocityFieldID, swappedOldVelocityFieldID ), "Velocity Field Swap" ); + } + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( oldVelocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Old Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(oldVelocityCommunicationScheme), "Old Velocity Field Communication" ); + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityGradientFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( velocityGradientFieldID, oldVelocityFieldID, boundaryHandlingID ), "Velocity Gradient Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityGradientCommunicationScheme), "Velocity Gradient Field Communication" ); + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityTotalTimeDerivativeFieldEvaluator( timeDerivativeVelocityFieldID, oldVelocityFieldID, swappedOldVelocityFieldID, velocityGradientFieldID, dt ), "Velocity Time Derivative Field Evaluation" ); + } + + // subcycling loop begin + for( uint_t subcycle = 1; subcycle <= interactionSubCycles; ++subcycle ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCommunicationScheme), "Velocity Field Communication" ); + + if( liftCorr != LiftCorrelation::NoLift ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityCurlFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( velocityCurlFieldID, velocityFieldID, boundaryHandlingID ), "Velocity Curl Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCurlCommunicationScheme), "Velocity Curl Field Communication" ); + } + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSPressureFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( pressureFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Pressure Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureCommunicationScheme), "Pressure Field Communication" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::PressureGradientFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( pressureGradientFieldID, pressureFieldID, boundaryHandlingID ), "Pressure Gradient Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureGradientCommunicationScheme), "Pressure Gradient Field Communication" ); + + if( liftCorr != LiftCorrelation::NoLift ) + { + // evaluate Flift + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( liftForceFieldID ), "Lift Force Field Reset" ) + << AfterFunction( liftForceEvaluationFunction, "Lift Force Evaluation") + << AfterFunction( liftForceComm, "Lift Force Field Communication" ); + } + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + // evaluate Fam + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( amForceFieldID ), "AM Force Field Reset" ) + << AfterFunction( addedMassEvaluationFunction, "Added Mass Force Evaluation") + << AfterFunction( amForceComm, "Force Field Communication" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator>( bodyVelocityTimeDerivativeEvaluator ), "Body Velocity Time Derivative Evaluation" ); + } + + if( liftCorr != LiftCorrelation::NoLift || addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCommunicationScheme), "Velocity Field Communication" ); + } + + // evaluate Fdrag + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( dragForceFieldID ), "Drag Force Field Reset" ) + << AfterFunction( dragAndPressureForceEvaluationFunction, "Fluid-Particle Interaction Force Evaluation" ) + << AfterFunction( dragForceComm, "Drag Force Field Communication" ); + + // ext forces on bodies and PE step(s) + timeloop.add() << Sweep( DummySweep(), "Dummy Sweep ") + << AfterFunction( BodiesQuantityEvaluator(&timeloop, blocks, bodyStorageID, vtkBaseFolder+"/quantityLogging.txt", numSpheres, real_t(numSpheres) * particleMass ), "Body Quantity Evaluator") + << AfterFunction( ForceOnBodiesAdder( blocks, bodyStorageID, Vector3<real_t>(0,0,- gravitationalAcc * ( densityRatio - real_t(1) ) ) ), "Gravitational and Buoyancy Force Add" ) + << AfterFunction( pe_coupling::discrete_particle_methods::SubgridTimeStep( blocks, bodyStorageID, *cr, syncCall, lubricationEvaluationFunction, dtInteractionSubCycle, peSubSteps ), "Pe Time Step" ); + + // update solid volume fraction field + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::KernelDistributor> ( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ), "Solid Volume Fraction Field Evaluation" ) + << AfterFunction( SharedFunctor< pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >(svfCommunicationScheme), "Solid Volume Fraction Field Communication" ); + + } + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( makeSharedSweep<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator>( effectiveViscosityEvaluator ), "Effective Viscosity Evaluation"); + if( useTurbulenceModel ) timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSSmagorinskyLESField<LatticeModel_T>(blocks, omegaFieldID, pdfFieldID, svfFieldID, smagorinskyConstant), "Turbulence Model" ); + + // execute GNS-LBM sweep, boundary handling, PDF communication + auto sweep = pe_coupling::discrete_particle_methods::makeGNSSweep< LatticeModel_T, FlagField_T >( pdfFieldID, svfFieldID, flagFieldID, Fluid_Flag ); + + timeloop.add() << Sweep( lbm::makeCollideSweep( sweep ), "GNS-LBM sweep (collide)" ); + + timeloop.add() << BeforeFunction( pdfScheme, "LBM Communication" ) + << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" ); + + timeloop.add() << Sweep( lbm::makeStreamSweep( sweep ), "GNS-LBM sweep (stream)" ); + + timeloop.addFuncAfterTimeStep( SphereCounter(blocks, bodyStorageID, numSpheres ), "Sphere Counter" ); + + timeloop.addFuncAfterTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" ); + + + WALBERLA_LOG_INFO_ON_ROOT("Starting actual (fully coupled) simulation with " << timesteps << " time steps.") + + // execute simulation + WcTimingPool timeloopTiming; + + for( uint_t t = 0; t < timesteps; ++t ) + { + timeloop.singleStep( timeloopTiming ); + } + + timeloopTiming.logResultOnRoot(); + + return 0; +} + +} //namespace bidisperse_fluidized_bed_dpm + +int main( int argc, char **argv ){ + bidisperse_fluidized_bed_dpm::main(argc, argv); +} + diff --git a/apps/showcases/BidisperseFluidizedBed/CMakeLists.txt b/apps/showcases/BidisperseFluidizedBed/CMakeLists.txt new file mode 100644 index 000000000..51318ee88 --- /dev/null +++ b/apps/showcases/BidisperseFluidizedBed/CMakeLists.txt @@ -0,0 +1,2 @@ +waLBerla_add_executable ( NAME BidisperseFluidizedBedDPM + DEPENDS blockforest boundary core domain_decomposition field lbm pe pe_coupling postprocessing timeloop vtk ) \ No newline at end of file diff --git a/apps/showcases/CMakeLists.txt b/apps/showcases/CMakeLists.txt index e69de29bb..9220ea4c9 100644 --- a/apps/showcases/CMakeLists.txt +++ b/apps/showcases/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( BidisperseFluidizedBed ) \ No newline at end of file diff --git a/src/pe_coupling/all.h b/src/pe_coupling/all.h index 39c4362a3..2dfc25e12 100644 --- a/src/pe_coupling/all.h +++ b/src/pe_coupling/all.h @@ -24,6 +24,7 @@ #pragma once +#include "discrete_particle_methods/all.h" #include "mapping/all.h" #include "geometry/all.h" #include "partially_saturated_cells_method/all.h" diff --git a/src/pe_coupling/discrete_particle_methods/all.h b/src/pe_coupling/discrete_particle_methods/all.h new file mode 100644 index 000000000..c00728f82 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/all.h @@ -0,0 +1,30 @@ +//====================================================================================================================== +// +// 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 all.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + + +#include "waLBerlaDefinitions.h" + +#include "correlations/all.h" +#include "evaluators/all.h" +#include "gns_lbm/all.h" +#include "utility/all.h" diff --git a/src/pe_coupling/discrete_particle_methods/correlations/AddedMassForceCorrelations.h b/src/pe_coupling/discrete_particle_methods/correlations/AddedMassForceCorrelations.h new file mode 100644 index 000000000..4574cbc19 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/correlations/AddedMassForceCorrelations.h @@ -0,0 +1,54 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file AddedMassForceCorrelations.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" + +#include <cmath> + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Correlation functions for the added mass force. + * + * To be compatible with the interface of AddedMassForceEvaluator, all functions use the following signature: + * Vector3<real_t> ( const Vector3<real_t> & timeDerivativeFluidVel, const Vector3<real_t> & timeDerivativeBodyVel, + * const real_t & bodyVolume, const real_t & fluidDensity ) + */ + +Vector3<real_t> addedMassForceFinn( const Vector3<real_t> & timeDerivativeFluidVel, const Vector3<real_t> & timeDerivativeBodyVel, + const real_t & bodyVolume, const real_t & fluidDensity ) +{ + // formula from Finn et al(2016) + const real_t Coeffam = real_t(0.5); + return bodyVolume * fluidDensity * Coeffam * ( timeDerivativeFluidVel - timeDerivativeBodyVel ); +} + +Vector3<real_t> noAddedMassForce( const Vector3<real_t> &, const Vector3<real_t> &, const real_t &, const real_t & ) +{ + return Vector3<real_t>(real_t(0)); +} + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/correlations/DragForceCorrelations.h b/src/pe_coupling/discrete_particle_methods/correlations/DragForceCorrelations.h new file mode 100644 index 000000000..91ae0da40 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/correlations/DragForceCorrelations.h @@ -0,0 +1,222 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file DragForceCorrelations.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Constants.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + + +/*!\brief Various correlation functions for the drag force. + * + * These functions calculate the drag force for fluid-particle interactions based on different empirical correlations + * from literature. + * Always be aware that those empirical formulas were obtained by different setups and are thus not generally applicable! + * + * To be compatible with the interface of InteractionForceEvaluator, all functions use the following signature: + * Vector3<real_t> ( const Vector3<real_t> & fluidVel, const Vector3<real_t> & particleVel, real_t solidVolumeFraction, + * real_t diameter, real_t fluidDynamicViscosity, real_t fluidDensity ) + * + */ + + +// helper functions -> often used in more advanced drag correlations + +// equation to calculate the drag coefficient on isolated spherical particle +// Schiller, L., Naumann, A., 1935. A drag coefficient correlation. Vdi Zeitung 77, 318-320. +real_t dragCoeffSchillerNaumann( real_t reynoldsNumber ) +{ + WALBERLA_ASSERT_GREATER_EQUAL( reynoldsNumber, real_t(0) ); + + return ( reynoldsNumber < real_t(1000) ) ? real_t(24) * ( real_t(1) + real_t(0.15) * std::pow(reynoldsNumber, real_t(0.687) ) ) / reynoldsNumber + : real_t(0.44); +} + +// Coefficient from Stokes' law for drag, only valid for Stokes regime (low Reynolds numbers) +// = 3 * PI * mu * D * fluidVolumeFraction +real_t dragCoeffStokes ( real_t fluidVolumeFraction, real_t diameter, real_t fluidDynamicViscosity ) +{ + return real_t(3) * math::M_PI * diameter * fluidDynamicViscosity * fluidVolumeFraction; +} + +// threshold value for absolute relative velocity +// if it is below this value, a drag force of 0 is set, to avoid instabilities stemming from divisions by this small value +const real_t thresholdAbsoluteVelocityDifference = real_t(1e-10); + + +////////////////////// +// // +// CORRELATIONS // +// // +////////////////////// + +// Stokes drag law +Vector3<real_t> dragForceStokes( const Vector3<real_t> & fluidVel, const Vector3<real_t> & particleVel, + real_t solidVolumeFraction, real_t diameter, real_t fluidDynamicViscosity, real_t /*fluidDensity*/ ) +{ + WALBERLA_ASSERT_GREATER_EQUAL( solidVolumeFraction, real_t(0) ); + WALBERLA_ASSERT_LESS_EQUAL( solidVolumeFraction, real_t(1) ); + + Vector3<real_t> velDiff = fluidVel - particleVel; + real_t absVelDiff = velDiff.length(); + + if( absVelDiff < thresholdAbsoluteVelocityDifference ) return Vector3<real_t>(real_t(0)); + + real_t fluidVolumeFraction = real_t(1) - solidVolumeFraction; + + return dragCoeffStokes( fluidVolumeFraction, diameter, fluidDynamicViscosity ) * velDiff; +} + + +// S. Ergun, Fluid flow through packed columns. Chemical Engineering Progress 48 (1952), 89-94. +// Y. C. Wen, Y.H. Yu, Mechanics of fluidization. Chemical Engineering Progress Symposium Series 62 (1966), 100-111. +// see also Beetstra, van der Hoef, Kuipers, "Drag Force of Intermediate Reynolds Number Flow Past Mono- and Bidisperse Arrays of Spheres" (2007) +Vector3<real_t> dragForceErgunWenYu( const Vector3<real_t> & fluidVel, const Vector3<real_t> & particleVel, + real_t solidVolumeFraction, real_t diameter, real_t fluidDynamicViscosity, real_t fluidDensity ) +{ + WALBERLA_ASSERT_GREATER_EQUAL( solidVolumeFraction, real_t(0) ); + WALBERLA_ASSERT_LESS_EQUAL( solidVolumeFraction, real_t(1) ); + + Vector3<real_t> velDiff = fluidVel - particleVel; + real_t absVelDiff = velDiff.length(); + + if( absVelDiff < thresholdAbsoluteVelocityDifference ) return Vector3<real_t>(real_t(0)); + + real_t fluidVolumeFraction = real_t(1) - solidVolumeFraction; + + if( fluidVolumeFraction < real_t(0.8) ) + { + // Ergun relation + real_t reynoldsNumber = fluidVolumeFraction * fluidDensity * absVelDiff * diameter / fluidDynamicViscosity; + real_t fDrag = real_t(150) * solidVolumeFraction / ( real_t(18) * fluidVolumeFraction * fluidVolumeFraction ) + + real_t(1.75) / ( real_t(18) * fluidVolumeFraction * fluidVolumeFraction ) * reynoldsNumber; + return fDrag * dragCoeffStokes( fluidVolumeFraction, diameter, fluidDynamicViscosity ) * velDiff; + } else + { + // Wen & Yu correlation + real_t reynoldsNumber = fluidVolumeFraction * fluidDensity * absVelDiff * diameter / fluidDynamicViscosity; + real_t fDrag = dragCoeffSchillerNaumann( reynoldsNumber ) * reynoldsNumber / real_t(24) * std::pow( fluidVolumeFraction, real_t(-3.7) ); + return fDrag * dragCoeffStokes( fluidVolumeFraction, diameter, fluidDynamicViscosity ) * velDiff; + } +} + +// drag correlation proposed by Tang et al. - "A New Drag Correlation from Fully Resolved Simulations of Flow Past +// Monodisperse Static Arrays of Spheres", AiChE, 2014 +Vector3<real_t> dragForceTang( const Vector3<real_t> & fluidVel, const Vector3<real_t> & particleVel, + real_t solidVolumeFraction, real_t diameter, real_t fluidDynamicViscosity, real_t fluidDensity ) +{ + WALBERLA_ASSERT_GREATER_EQUAL( solidVolumeFraction, real_t(0) ); + WALBERLA_ASSERT_LESS_EQUAL( solidVolumeFraction, real_t(1) ); + + Vector3<real_t> velDiff = fluidVel - particleVel; + real_t absVelDiff = velDiff.length(); + + if( absVelDiff < thresholdAbsoluteVelocityDifference ) return Vector3<real_t>(real_t(0)); + + real_t fluidVolumeFraction = real_t(1) - solidVolumeFraction; + real_t fluidVolumeFractionP2 = fluidVolumeFraction * fluidVolumeFraction; + real_t inv_fluidVolumeFractionP4 = real_t(1) / (fluidVolumeFractionP2 * fluidVolumeFractionP2); + real_t reynoldsNumber = fluidVolumeFraction * fluidDensity * absVelDiff * diameter / fluidDynamicViscosity; + + // Eq.21 from the paper + real_t fDrag = real_t(10) * solidVolumeFraction / fluidVolumeFractionP2 + fluidVolumeFractionP2 * ( real_t(1) + real_t(1.5) * std::sqrt(solidVolumeFraction) ) + + ( real_t(0.11) * solidVolumeFraction * ( real_t(1) + solidVolumeFraction ) - real_t(0.00456) * inv_fluidVolumeFractionP4 + + ( real_t(0.169) * fluidVolumeFraction + real_t(0.0644) * inv_fluidVolumeFractionP4 ) * std::pow( reynoldsNumber, -real_t(0.343) ) ) * reynoldsNumber; + + return fDrag * dragCoeffStokes( fluidVolumeFraction, diameter, fluidDynamicViscosity ) * velDiff; + +} + + +// drag correlation based on findings from Felice (1994) +// used e.g. in Kafui et al (2002) +Vector3<real_t> dragForceFelice( const Vector3<real_t> & fluidVel, const Vector3<real_t> & particleVel, + real_t solidVolumeFraction, real_t diameter, real_t fluidDynamicViscosity, real_t fluidDensity ) +{ + WALBERLA_ASSERT_GREATER_EQUAL( solidVolumeFraction, real_t(0) ); + WALBERLA_ASSERT_LESS_EQUAL( solidVolumeFraction, real_t(1) ); + + Vector3<real_t> velDiff = fluidVel - particleVel; + real_t absVelDiff = velDiff.length(); + + if( absVelDiff < thresholdAbsoluteVelocityDifference ) return Vector3<real_t>(real_t(0)); + + real_t fluidVolumeFraction = real_t(1) - solidVolumeFraction; + + real_t reynoldsNumber = fluidVolumeFraction * fluidDensity * absVelDiff * diameter / fluidDynamicViscosity; + + real_t temp1 = ( real_t(0.63) + real_t(4.8) / std::sqrt( reynoldsNumber ) ); + real_t dragCoeff = temp1 * temp1; + + real_t temp2 = real_t(1.5) - std::log10( reynoldsNumber ); + real_t chi = real_t(3.7) - std::pow( real_t(0.65), (- real_t(0.5) * temp2 * temp2 ) ); + + return real_t(0.125) * dragCoeff * fluidDensity * math::M_PI * diameter * diameter * absVelDiff * + std::pow( fluidVolumeFraction, real_t(2) - chi) * velDiff; + +} + +// drag correlation based on findings from Tenneti, Garg, Subramaniam (2011) +// used e.g. in Finn, Li, Apte - Particle based modelling and simulation of natural sand dynamics in the wave bottom boundary layer (2016) +// could be generalized also for non-spherical particles, see Finn et al (2016) +Vector3<real_t> dragForceTenneti( const Vector3<real_t> & fluidVel, const Vector3<real_t> & particleVel, + real_t solidVolumeFraction, real_t diameter, real_t fluidDynamicViscosity, real_t fluidDensity ) +{ + WALBERLA_ASSERT_GREATER_EQUAL( solidVolumeFraction, real_t(0) ); + WALBERLA_ASSERT_LESS_EQUAL( solidVolumeFraction, real_t(1) ); + + Vector3<real_t> velDiff = fluidVel - particleVel; + const real_t absVelDiff = velDiff.length(); + + if( absVelDiff < thresholdAbsoluteVelocityDifference ) return Vector3<real_t>(real_t(0)); + + const real_t fluidVolumeFraction = real_t(1) - solidVolumeFraction; + + const real_t reynoldsNumber = fluidVolumeFraction * fluidDensity * absVelDiff * diameter / fluidDynamicViscosity; + + const real_t fvfCubed = fluidVolumeFraction * fluidVolumeFraction * fluidVolumeFraction; + const real_t A = real_t(5.81) * solidVolumeFraction / fvfCubed + real_t(0.48) * std::cbrt( solidVolumeFraction ) / ( fvfCubed * fluidVolumeFraction ); + + const real_t svfCubed = solidVolumeFraction * solidVolumeFraction * solidVolumeFraction; + const real_t B = svfCubed * reynoldsNumber * ( real_t(0.95) + real_t(0.61) * svfCubed / ( fluidVolumeFraction * fluidVolumeFraction ) ); + + // version from Finn et al. + const real_t CdRe0Sphere = real_t(1) + real_t(0.15) * std::pow( reynoldsNumber, real_t(0.687) ); + + const real_t CdRe = fluidVolumeFraction * ( CdRe0Sphere / fvfCubed + A + B ); + + return real_t(3) * math::M_PI * diameter * fluidDynamicViscosity * fluidVolumeFraction * CdRe * velDiff; + +} + + +Vector3<real_t> noDragForce( const Vector3<real_t> & /*fluidVel*/, const Vector3<real_t> & /*particleVel*/, + real_t /*solidVolumeFraction*/, real_t /*diameter*/, real_t /*fluidDynamicViscosity*/, real_t /*fluidDensity*/ ) +{ + return Vector3<real_t>(real_t(0)); +} + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/correlations/LiftForceCorrelations.h b/src/pe_coupling/discrete_particle_methods/correlations/LiftForceCorrelations.h new file mode 100644 index 000000000..8e66b93da --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/correlations/LiftForceCorrelations.h @@ -0,0 +1,63 @@ +//====================================================================================================================== +// +// 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 LiftForceCorrelations.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" + +#include <cmath> + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Correlation functions for the lift force. + * + * To be compatible with the interface of LiftForceEvaluator, all functions use the following signature: + * Vector3<real_t> ( const Vector3<real_t> & fluidVel, const Vector3<real_t> & curlFluidVel, const Vector3<real_t> & particleVel, + * real_t diameter, real_t fluidDynamicViscosity, real_t fluidDensity ) + */ + +// Saffman lift force +Vector3<real_t> liftForceSaffman ( const Vector3<real_t> & fluidVel, const Vector3<real_t> & curlFluidVel, const Vector3<real_t> & particleVel, + real_t diameter, real_t fluidDynamicViscosity, real_t fluidDensity ) +{ + const real_t absCurlVel = curlFluidVel.length(); + if( absCurlVel < real_t(1e-10) ) return Vector3<real_t>(real_t(0)); + + // Finn et al (2016) for spheres + const real_t Cl = real_t(1.61) * std::sqrt( ( fluidDynamicViscosity * fluidDensity) / absCurlVel ); + return Cl * diameter * diameter * ( ( fluidVel - particleVel ) % curlFluidVel ); + + // Sun, Xiao (2016) + //const real_t Cl = real_t(1.6); + //return Cl * fluidDensity * std::sqrt( fluidDynamicViscosity / fluidDensity ) * diameter * diameter * ( ( fluidVel - particleVel ) % curlFluidVel ); + +} + +Vector3<real_t> noLiftForce ( const Vector3<real_t> &, const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t, real_t ) +{ + return Vector3<real_t>(real_t(0)); +} + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/correlations/all.h b/src/pe_coupling/discrete_particle_methods/correlations/all.h new file mode 100644 index 000000000..472e1d9b6 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/correlations/all.h @@ -0,0 +1,29 @@ +//====================================================================================================================== +// +// 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 all.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + + +#include "waLBerlaDefinitions.h" + +#include "AddedMassForceCorrelations.h" +#include "DragForceCorrelations.h" +#include "LiftForceCorrelations.h" diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/AddedMassForceEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/AddedMassForceEvaluator.h new file mode 100644 index 000000000..4daed731d --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/AddedMassForceEvaluator.h @@ -0,0 +1,145 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file AddedMassForceEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/debug/Debug.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/interpolators/all.h" +#include "field/distributors/all.h" +#include "field/GhostLayerField.h" + +#include "lbm/field/MacroscopicValueCalculation.h" +#include "lbm/field/PdfField.h" +#include "lbm/lattice_model/ForceModel.h" + +#include "pe/rigidbody/BodyIterators.h" +#include "pe/rigidbody/RigidBody.h" +#include "pe/Types.h" +#include "pe/Materials.h" + +#include "BodyVelocityTimeDerivativeEvaluator.h" + +#include "stencil/Directions.h" + +#include <boost/function.hpp> + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Evaluator of the added (virtual) mass force based on the given added mass correlation. + * + * The added mass force is evaluated for each body (requires interpolation of several quantities from the fluid fields) + * and then applied onto them. + * The corresponding reaction force is added to the fluid via the given distributor. + * + * Note that the fluid velocity (and its derivatives) has to be the fluid-phase velocity (and not the volume-averaged velocity). + * + * For more infos on interpolators, see field interpolators in src/field/interpolators. + * For more infos on distributors, see src/field/distributors. + */ +template< typename FlagField_T, template<typename,typename> class FieldInterpolator_T, template<typename,typename> class Distributor_T > +class AddedMassForceEvaluator +{ + +public: + + typedef GhostLayerField< Vector3<real_t>, 1> Vec3Field_T; + typedef FieldInterpolator_T<Vec3Field_T, FlagField_T> Vec3FieldInterpolator_T; + typedef Distributor_T<Vec3Field_T, FlagField_T> ForceDistributor_T; + + AddedMassForceEvaluator( const shared_ptr<StructuredBlockStorage> & blockStorage, + const BlockDataID & forceFieldID, const BlockDataID & bodyStorageID, + const BlockDataID & flagFieldID, const Set< FlagUID > & domainFlags, + const BlockDataID & velocityTimeDerivativeFieldID, + const boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t )> & addedMassForceCorrelationFunction, + const shared_ptr< BodyVelocityTimeDerivativeEvaluator > & bodyVelocityTimeDerivativeEvaluator ) + : blockStorage_( blockStorage ), bodyStorageID_( bodyStorageID ), + addedMassForceCorrelationFunction_( addedMassForceCorrelationFunction ), bodyVelocityTimeDerivativeEvaluator_( bodyVelocityTimeDerivativeEvaluator ) + { + velocityTimeDerivativeFieldInterpolatorID_ = field::addFieldInterpolator< Vec3FieldInterpolator_T, FlagField_T >( blockStorage, velocityTimeDerivativeFieldID, flagFieldID, domainFlags ); + forceDistributorID_ = field::addDistributor< ForceDistributor_T, FlagField_T >( blockStorage, forceFieldID, flagFieldID, domainFlags ); + } + + void operator()(); + +private: + + shared_ptr<StructuredBlockStorage> blockStorage_; + const BlockDataID bodyStorageID_; + + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t )> addedMassForceCorrelationFunction_; + + shared_ptr< BodyVelocityTimeDerivativeEvaluator > bodyVelocityTimeDerivativeEvaluator_; + + BlockDataID velocityTimeDerivativeFieldInterpolatorID_; + BlockDataID forceDistributorID_; + +}; + +template< typename FlagField_T, template<typename,typename> class FieldInterpolator_T, template<typename,typename> class Distributor_T > +void AddedMassForceEvaluator< FlagField_T, FieldInterpolator_T, Distributor_T > +::operator()() +{ + + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + + Vec3FieldInterpolator_T * velocityTimeDerivativeInterpolator = blockIt->getData< Vec3FieldInterpolator_T >( velocityTimeDerivativeFieldInterpolatorID_ ); + ForceDistributor_T * forceDistributor = blockIt->getData< ForceDistributor_T >( forceDistributorID_ ); + + for( auto bodyIt = pe::LocalBodyIterator::begin(*blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end(); ++bodyIt ) + { + //TODO check if body should be treated by DPM + + Vector3<real_t> forceOnFluid( real_t(0) ); + + Vector3<real_t> bodyPosition = bodyIt->getPosition(); + Vector3<real_t> bodyVelocity = bodyIt->getLinearVel(); + real_t bodyVolume = bodyIt->getVolume(); + real_t fluidDensity( real_t(1) ); + + // evaluate added (virtual) mass force + Vector3<real_t> timeDerivativeFluidVelocity( real_t(0) ); + Vector3<real_t> timeDerivativeBodyVelocity( real_t(0) ); + bodyVelocityTimeDerivativeEvaluator_->get(timeDerivativeBodyVelocity, bodyVelocity, bodyIt->getSystemID() ); + velocityTimeDerivativeInterpolator->get( bodyPosition, &timeDerivativeFluidVelocity ); + Vector3<real_t> addedMassForce = addedMassForceCorrelationFunction_( timeDerivativeFluidVelocity, timeDerivativeBodyVelocity, bodyVolume, fluidDensity ); + WALBERLA_ASSERT( !math::isnan(addedMassForce[0]) && !math::isnan(addedMassForce[1]) && !math::isnan(addedMassForce[2]), + "NaN found in added mass force for body at position " << bodyPosition ); + + bodyIt->addForce( addedMassForce ); + forceOnFluid += ( -addedMassForce ); + + // set/distribute force on fluid + forceDistributor->distribute( bodyPosition, &forceOnFluid ); + } + } + +} + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/BodyVelocityTimeDerivativeEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/BodyVelocityTimeDerivativeEvaluator.h new file mode 100644 index 000000000..d9eadc43c --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/BodyVelocityTimeDerivativeEvaluator.h @@ -0,0 +1,88 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file BodyVelocityTimeDerivativeEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "pe/rigidbody/BodyIterators.h" + +#include <map> + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Evaluates the time derivative of the body velocity + * + * Using the operator()(), an internal map is filled with pairs of the body ID and its current velocity, + * after clearing the old map. + * Calling get(..), the body's former velocity is fetched from the map and used to calculate a simple approximation of the + * body velocity time derivative (du/dt = ( u_new - u_old ) / deltaT ) + * + */ +class BodyVelocityTimeDerivativeEvaluator +{ +public: + + BodyVelocityTimeDerivativeEvaluator( const shared_ptr<StructuredBlockStorage> & blockStorage, + const BlockDataID & bodyStorageID, const real_t & deltaT = real_t(1) ) + : blockStorage_( blockStorage ), bodyStorageID_( bodyStorageID ), deltaTinv_( real_t(1) / deltaT ) + { } + + void operator()() + { + // rebuild velocity map for all known bodies (process local) + bodyVelocityMap_.clear(); + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + for( auto bodyIt = pe::BodyIterator::begin(*blockIt, bodyStorageID_); bodyIt != pe::BodyIterator::end(); ++bodyIt ) + { + bodyVelocityMap_.insert( std::pair<walberla::id_t, Vector3< real_t > >( bodyIt->getSystemID(), bodyIt->getLinearVel() ) ); + } + } + } + + void resetDeltaT( const real_t & deltaT ) + { + deltaTinv_ = real_t(1) / deltaT; + } + + void get( Vector3<real_t> & particleVelocityTimeDerivative, const Vector3<real_t> currentParticleVelocity, const walberla::id_t & bodySystemID ) + { + auto it = bodyVelocityMap_.find( bodySystemID ); + WALBERLA_ASSERT( it != bodyVelocityMap_.end(), "body with ID " << bodySystemID << " not found in body velocity map!" ); + particleVelocityTimeDerivative = ( currentParticleVelocity - it->second ) * deltaTinv_; + } + +private: + + shared_ptr<StructuredBlockStorage> blockStorage_; + const BlockDataID bodyStorageID_; + std::map< walberla::id_t, Vector3< real_t > > bodyVelocityMap_; + real_t deltaTinv_; + +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/EffectiveViscosityFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/EffectiveViscosityFieldEvaluator.h new file mode 100644 index 000000000..c052b6059 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/EffectiveViscosityFieldEvaluator.h @@ -0,0 +1,106 @@ +//====================================================================================================================== +// +// 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 EffectiveViscosityFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "lbm/lattice_model/CollisionModel.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +// correlations for the viscosity + +// no change in viscosity +real_t calculateUnchangedEffectiveViscosity( real_t fluidViscosity, real_t /*porosity*/ ) +{ + return fluidViscosity; +} + +// see: Fattahi, Waluga, Wohlmuth - "Large scale lattice Boltzmann simulation for the coupling of free and porous media flow" +real_t calculateRescaledEffectiveViscosity( real_t fluidViscosity, real_t porosity ) +{ + return fluidViscosity / porosity; +} + +// see: J. R. Finn, M. Li, S. V. Apte - "Particle based modelling and simulation of natural sand dynamics in the wave +// bottom boundary layer", Journal of Fluid Mechanics 796 (2016) 340–385. doi:10.1017/jfm.2016.246. +real_t calculateEilersEffectiveViscosity( real_t fluidViscosity, real_t porosity ) +{ + const real_t closePackingFraction = real_t(0.64); + const real_t intrinsicViscosity = real_t(2.5); //for monosized spheres + const real_t temp = real_t(1) + real_t(0.5) * intrinsicViscosity * ( real_t(1) - porosity ) / ( porosity / closePackingFraction ); + return fluidViscosity * temp * temp; +} + +/*!\brief Evaluates the (local) effective viscosity. + * + * The effective viscosity (using the provided correlation) is evaluated for each cell and stored, + * as the respective omega value, in a field, that can be given to a suitable LBM sweep. + * + */ +class EffectiveViscosityFieldEvaluator +{ +public: + typedef GhostLayerField< real_t, 1 > ScalarField_T; + + EffectiveViscosityFieldEvaluator( const BlockDataID & omegaFieldID, const ConstBlockDataID & solidVolumeFractionFieldID, + const real_t & fluidViscosity, + const boost::function<real_t (real_t, real_t)> & effectiveViscosityFunc ) + : omegaFieldID_( omegaFieldID ), solidVolumeFractionFieldID_( solidVolumeFractionFieldID ), fluidViscosity_( fluidViscosity ), + effectiveViscosityFunc_( effectiveViscosityFunc ) + { + } + + void operator()( IBlock * const block ) + { + ScalarField_T* omegaField = block->getData<ScalarField_T>(omegaFieldID_); + const ScalarField_T* svfField = block->getData<ScalarField_T>(solidVolumeFractionFieldID_); + + WALBERLA_FOR_ALL_CELLS_XYZ(omegaField, + const real_t porosity = real_t(1) - svfField->get(x,y,z); + WALBERLA_ASSERT_FLOAT_UNEQUAL(porosity, real_t(0)); + + real_t effectiveViscosity = effectiveViscosityFunc_(fluidViscosity_, porosity); + + //TODO: add level dependence for omega calculation + omegaField->get(x,y,z) = lbm::collision_model::omegaFromViscosity(effectiveViscosity); + ) + } + + void resetFluidViscosity( real_t newFluidViscosity ) + { + fluidViscosity_ = newFluidViscosity; + } + +private: + const BlockDataID omegaFieldID_; + const ConstBlockDataID solidVolumeFractionFieldID_; + real_t fluidViscosity_; + boost::function<real_t (real_t, real_t)> effectiveViscosityFunc_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/InteractionForceEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/InteractionForceEvaluator.h new file mode 100644 index 000000000..c913952e3 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/InteractionForceEvaluator.h @@ -0,0 +1,180 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file InteractionForceEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/debug/Debug.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/interpolators/all.h" +#include "field/distributors/all.h" +#include "field/GhostLayerField.h" + +#include "lbm/field/MacroscopicValueCalculation.h" +#include "lbm/field/PdfField.h" +#include "lbm/lattice_model/ForceModel.h" + +#include "pe/rigidbody/BodyIterators.h" +#include "pe/rigidbody/RigidBody.h" +#include "pe/rigidbody/Sphere.h" +#include "pe/Types.h" +#include "pe/Materials.h" + +#include "pe_coupling/geometry/SphereEquivalentDiameter.h" + +#include "stencil/Directions.h" + +#include <boost/function.hpp> + +#include <vector> + +#include <cmath> + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Evaluator of the two most important interaction forces: drag and pressure gradient + * + * The evaluation of the drag force is based on the given drag correlation. + * The pressure gradient force requires a valid pressure gradient field. + * These two forces are evaluated for each body (requires interpolation of several quantities from the fluid fields) + * and then applied onto them. + * However, only the drag force (with negative sign) is applied (i.e. distributed) onto the fluid since it is assumed that + * the pressure force is already implicitly included in the formulation of the equations that describe the fluid flow. + * + * Note that the fluid velocity, contained in the velocityField, has to be the fluid-phase velocity (and not the volume-averaged velocity). + * + * For more infos on interpolators, see field interpolators in src/field/interpolators. + * For more infos on distributors, see src/field/distributors. + */ + +template< typename FlagField_T, template<typename,typename> class FieldInterpolator_T, template<typename,typename> class Distributor_T > +class InteractionForceEvaluator +{ + +public: + + typedef GhostLayerField< Vector3<real_t>, 1> Vec3Field_T; + typedef GhostLayerField< real_t, 1> ScalarField_T; + typedef FieldInterpolator_T<Vec3Field_T, FlagField_T> Vec3FieldInterpolator_T; + typedef FieldInterpolator_T<ScalarField_T, FlagField_T> ScalarFieldInterpolator_T; + typedef Distributor_T<Vec3Field_T, FlagField_T> ForceDistributor_T; + + InteractionForceEvaluator( const shared_ptr<StructuredBlockStorage> & blockStorage, + const BlockDataID & forceFieldID, const BlockDataID & bodyStorageID, + const BlockDataID & flagFieldID, const Set< FlagUID > & domainFlags, + const BlockDataID & velocityFieldID, const BlockDataID & solidVolumeFractionFieldID, const BlockDataID & pressureGradientFieldID, + const boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t, real_t, real_t )> & dragForceCorrelationFunction, + real_t fluidDynamicViscosity ) + : blockStorage_( blockStorage ), bodyStorageID_( bodyStorageID ), + dragForceCorrelationFunction_( dragForceCorrelationFunction ), fluidDynamicViscosity_( fluidDynamicViscosity ) + { + velocityFieldInterpolatorID_ = field::addFieldInterpolator< Vec3FieldInterpolator_T, FlagField_T >( blockStorage, velocityFieldID, flagFieldID, domainFlags ); + solidVolumeFractionFieldInterpolatorID_ = field::addFieldInterpolator< ScalarFieldInterpolator_T, FlagField_T >( blockStorage, solidVolumeFractionFieldID, flagFieldID, domainFlags ); + pressureGradientFieldInterpolatorID_ = field::addFieldInterpolator< Vec3FieldInterpolator_T, FlagField_T >( blockStorage, pressureGradientFieldID, flagFieldID, domainFlags ); + forceDistributorID_ = field::addDistributor< ForceDistributor_T, FlagField_T >( blockStorage, forceFieldID, flagFieldID, domainFlags ); + } + + void operator()(); + +private: + + shared_ptr<StructuredBlockStorage> blockStorage_; + const BlockDataID bodyStorageID_; + const BlockDataID pdfFieldID_; + + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t, real_t, real_t )> dragForceCorrelationFunction_; + + BlockDataID velocityFieldInterpolatorID_; + BlockDataID solidVolumeFractionFieldInterpolatorID_; + BlockDataID pressureGradientFieldInterpolatorID_; + BlockDataID forceDistributorID_; + + real_t fluidDynamicViscosity_; +}; + +template< typename FlagField_T, template<typename,typename> class FieldInterpolator_T, template<typename,typename> class Distributor_T > +void InteractionForceEvaluator< FlagField_T, FieldInterpolator_T, Distributor_T > +::operator()() +{ + + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + + Vec3FieldInterpolator_T * velocityInterpolator = blockIt->getData< Vec3FieldInterpolator_T >( velocityFieldInterpolatorID_ ); + ScalarFieldInterpolator_T * solidVolumeFractionInterpolator = blockIt->getData< ScalarFieldInterpolator_T >( solidVolumeFractionFieldInterpolatorID_ ); + Vec3FieldInterpolator_T * pressureGradientInterpolator = blockIt->getData< Vec3FieldInterpolator_T >( pressureGradientFieldInterpolatorID_ ); + ForceDistributor_T * forceDistributor = blockIt->getData< ForceDistributor_T >( forceDistributorID_ ); + + for( auto bodyIt = pe::LocalBodyIterator::begin(*blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end(); ++bodyIt ) + { + //TODO check if body should be treated by DPM + + Vector3<real_t> forceOnFluid( real_t(0) ); + + Vector3<real_t> bodyPosition = bodyIt->getPosition(); + + // interpolate fluid velocity to body position + Vector3<real_t> fluidVelocity( real_t(0) ); + velocityInterpolator->get( bodyPosition, &fluidVelocity ); + + // interpolate solid volume fraction to body position + real_t solidVolumeFraction( real_t(0) ); + solidVolumeFractionInterpolator->get( bodyPosition, &solidVolumeFraction ); + + WALBERLA_ASSERT_GREATER( solidVolumeFraction, real_t(0) ); + + // evaluate drag force + Vector3<real_t> bodyVelocity = bodyIt->getLinearVel(); + real_t bodyDiameter = getSphereEquivalentDiameter( *(*bodyIt) ); + real_t bodyVolume = bodyIt->getVolume(); + real_t fluidDensity( real_t(1) ); + + Vector3<real_t> dragForce = dragForceCorrelationFunction_( fluidVelocity, bodyVelocity, solidVolumeFraction, bodyDiameter, fluidDynamicViscosity_, fluidDensity ); + + WALBERLA_ASSERT( !math::isnan(dragForce[0]) && !math::isnan(dragForce[1]) && !math::isnan(dragForce[2]), + "NaN found in drag force " << dragForce << " for body at position " << bodyPosition ); + + bodyIt->addForce( dragForce ); + + forceOnFluid += ( -dragForce ); + + // evaluate pressure gradient force = - V * grad(p) + Vector3<real_t> pressureGradient( real_t(0) ); + pressureGradientInterpolator->get( bodyPosition, &pressureGradient ); + Vector3<real_t> pressureGradientForce = -bodyVolume * pressureGradient; + WALBERLA_ASSERT( !math::isnan(pressureGradientForce[0]) && !math::isnan(pressureGradientForce[1]) && !math::isnan(pressureGradientForce[2]), + "NaN found in pressure gradient force " << pressureGradientForce << " for body at position " << bodyPosition ); + bodyIt->addForce( pressureGradientForce ); + + // set/distribute force on fluid + forceDistributor->distribute( bodyPosition, &forceOnFluid ); + } + } + +} + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/LiftForceEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/LiftForceEvaluator.h new file mode 100644 index 000000000..5ad9c9659 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/LiftForceEvaluator.h @@ -0,0 +1,145 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file LiftForceEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/debug/Debug.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/interpolators/all.h" +#include "field/distributors/all.h" +#include "field/GhostLayerField.h" + +#include "pe/rigidbody/BodyIterators.h" +#include "pe/rigidbody/RigidBody.h" +#include "pe/Types.h" +#include "pe/Materials.h" + +#include "pe_coupling/geometry/SphereEquivalentDiameter.h" + +#include <boost/function.hpp> + +#include <cmath> + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Evaluator of the lift force based on the given lift correlation. + * + * The lift force is evaluated for each body (requires interpolation of several quantities from the fluid fields) + * and then applied onto them. + * The corresponding reaction force is added to the fluid via the given distributor. + * + * Note that the fluid velocity, contained in the velocityField, has to be the fluid-phase velocity (and not the volume-averaged velocity). + * + * For more infos on interpolators, see field interpolators in src/field/interpolators. + * For more infos on distributors, see src/field/distributors. + */ +template< typename FlagField_T, template<typename,typename> class FieldInterpolator_T, template<typename,typename> class Distributor_T > +class LiftForceEvaluator +{ + +public: + + typedef GhostLayerField< Vector3<real_t>, 1> Vec3Field_T; + typedef FieldInterpolator_T<Vec3Field_T, FlagField_T> Vec3FieldInterpolator_T; + typedef Distributor_T<Vec3Field_T, FlagField_T> ForceDistributor_T; + + LiftForceEvaluator( const shared_ptr<StructuredBlockStorage> & blockStorage, + const BlockDataID & forceFieldID, const BlockDataID & bodyStorageID, const BlockDataID & flagFieldID, const Set< FlagUID > & domainFlags, + const BlockDataID & velocityFieldID, const BlockDataID & velocityCurlFieldID, + const boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t, real_t )> & liftForceCorrelationFunction, + real_t fluidDynamicViscosity ) + : blockStorage_( blockStorage ), bodyStorageID_( bodyStorageID ), liftForceCorrelationFunction_( liftForceCorrelationFunction ), fluidDynamicViscosity_( fluidDynamicViscosity ) + { + velocityFieldInterpolatorID_ = field::addFieldInterpolator< Vec3FieldInterpolator_T, FlagField_T >( blockStorage, velocityFieldID, flagFieldID, domainFlags ); + velocityCurlFieldInterpolatorID_ = field::addFieldInterpolator< Vec3FieldInterpolator_T, FlagField_T >( blockStorage, velocityCurlFieldID, flagFieldID, domainFlags ); + forceDistributorID_ = field::addDistributor< ForceDistributor_T, FlagField_T >( blockStorage, forceFieldID, flagFieldID, domainFlags ); + } + + void operator()(); + +private: + + shared_ptr<StructuredBlockStorage> blockStorage_; + BlockDataID bodyStorageID_; + + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t, real_t )> liftForceCorrelationFunction_; + + real_t fluidDynamicViscosity_; + + BlockDataID velocityFieldInterpolatorID_; + BlockDataID velocityCurlFieldInterpolatorID_; + BlockDataID forceDistributorID_; +}; + +template< typename FlagField_T, template<typename,typename> class FieldInterpolator_T, template<typename,typename> class Distributor_T > +void LiftForceEvaluator< FlagField_T, FieldInterpolator_T, Distributor_T > +::operator()() +{ + + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + + Vec3FieldInterpolator_T * velocityInterpolator = blockIt->getData< Vec3FieldInterpolator_T >( velocityFieldInterpolatorID_ ); + Vec3FieldInterpolator_T * velocityCurlInterpolator = blockIt->getData< Vec3FieldInterpolator_T >( velocityCurlFieldInterpolatorID_ ); + ForceDistributor_T * forceDistributor = blockIt->getData< ForceDistributor_T >( forceDistributorID_ ); + + for( auto bodyIt = pe::LocalBodyIterator::begin(*blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end(); ++bodyIt ) + { + //TODO check if body should be treated by DPM + + Vector3<real_t> forceOnFluid( real_t(0) ); + + Vector3<real_t> bodyPosition = bodyIt->getPosition(); + Vector3<real_t> bodyVelocity = bodyIt->getLinearVel(); + + real_t fluidDensity( real_t(1) ); + real_t bodyDiameter = getSphereEquivalentDiameter( *(*bodyIt) ); + + // interpolate fluid velocity and fluid curl to body position + Vector3<real_t> fluidVelocity( real_t(0) ); + velocityInterpolator->get( bodyPosition, &fluidVelocity ); + + Vector3<real_t> velocityCurl( real_t(0) ); + velocityCurlInterpolator->get( bodyPosition, &velocityCurl ); + + // evaluate lift force according to empirical model + Vector3<real_t> liftForce = liftForceCorrelationFunction_(fluidVelocity, velocityCurl, bodyVelocity, bodyDiameter, fluidDynamicViscosity_, fluidDensity ); + WALBERLA_ASSERT( !math::isnan(liftForce[0]) && !math::isnan(liftForce[1]) && !math::isnan(liftForce[2]), + "NaN found in lift force for body at position " << bodyPosition ); + bodyIt->addForce( liftForce ); + forceOnFluid += ( -liftForce ); + + // set/distribute force on fluid + forceDistributor->distribute( bodyPosition, &forceOnFluid ); + } + } + +} + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/LubricationForceEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/LubricationForceEvaluator.h new file mode 100644 index 000000000..d69046b3e --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/LubricationForceEvaluator.h @@ -0,0 +1,326 @@ +//====================================================================================================================== +// +// 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 LubricationForceEvaluator.h +//! \ingroup pe_coupling +//! \author Dominik Bartuschat +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/logging/all.h" +#include "core/debug/Debug.h" +#include "domain_decomposition/BlockStorage.h" +#include "lbm/field/PdfField.h" + +#include "pe/rigidbody/BodyIterators.h" +#include "pe/rigidbody/Plane.h" +#include "pe/rigidbody/Sphere.h" +#include "pe/utility/Distance.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Evaluates the lubrication force between for a sphere-sphere or a sphere-plane pair. + * + * The force is calculated as given in: + * R. Ball, J. Melrose, A simulation technique for many spheres in quasi–static motion under frame–invariant pair drag + * and Brownian forces, Physica A: Statistical Mechanics and its Applications 247 (1) (1997) 444–472. + * doi:10.1016/S0378-4371(97)00412-3. + * + * However, currently only the normal component is included. + * + * These formulas contain more components than the version from "pe_coupling/utility/LubricationCorrection.h" + * + */ +class LubricationForceEvaluator +{ +public: + + LubricationForceEvaluator ( const shared_ptr<StructuredBlockStorage> & blockStorage, + const shared_ptr<pe::BodyStorage> & globalBodyStorage, const BlockDataID & bodyStorageID, + real_t dynamicViscosity, real_t cutOffDistance = real_t(2) / real_t(3) ) + : blockStorage_ ( blockStorage ) + , globalBodyStorage_( globalBodyStorage ) + , bodyStorageID_( bodyStorageID ) + , dynamicViscosity_( dynamicViscosity ) + , cutOffDistance_( cutOffDistance ) + { } + + void operator()( ); + +private: + + // helper functions + void treatLubricationSphrSphr ( real_t nu_Lattice, real_t cutOff, real_t minimalGap, const pe::SphereID sphereI, const pe::SphereID sphereJ, const math::AABB & blockAABB ); + void treatLubricationSphrPlane( real_t nu_Lattice, real_t cutOff, real_t minimalGap, const pe::SphereID sphereI, const pe::ConstPlaneID planeJ ); + + pe::Vec3 compLubricationSphrSphr ( real_t nu_Lattice, real_t gap, real_t cutOff, const pe::SphereID sphereI, const pe::SphereID sphereJ ) const; + pe::Vec3 compLubricationSphrPlane( real_t nu_Lattice, real_t gap, real_t cutOff, const pe::SphereID sphereI, const pe::ConstPlaneID planeJ) const; + + // member variables + shared_ptr<StructuredBlockStorage> blockStorage_; + shared_ptr<pe::BodyStorage> globalBodyStorage_; + const BlockDataID pdfFieldID_; + const BlockDataID bodyStorageID_; + + real_t dynamicViscosity_; + real_t cutOffDistance_; + +}; // class LubricationForceEvaluator + + +void LubricationForceEvaluator::operator ()( ) +{ + WALBERLA_LOG_PROGRESS( "Calculating Lubrication Force" ); + + // cut off distance for lubrication force + const real_t cutOff = cutOffDistance_; + + for (auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt) + { + // minimal gap used to limit the lubrication force + const real_t minimalGap = real_t(1e-5) * blockStorage_->dx( blockStorage_->getLevel( *blockIt ) ); + + real_t nu_L = dynamicViscosity_; + + // loop over all rigid bodies + for( auto body1It = pe::BodyIterator::begin( *blockIt, bodyStorageID_ ); body1It != pe::BodyIterator::end(); ++body1It ) + { + // lubrication forces for spheres + if ( body1It->getTypeID() == pe::Sphere::getStaticTypeID() ) + { + pe::SphereID sphereI = static_cast<pe::SphereID> ( *body1It ); + + // only moving spheres are considered + if ( sphereI->isFixed() ) + continue; + + auto copyBody1It = body1It; + // loop over all rigid bodies after current body1 to avoid double forces + for( auto body2It = (++copyBody1It); body2It != pe::BodyIterator::end(); ++body2It ) + { + // sphere-sphere lubrication + if ( body2It->getTypeID() == pe::Sphere::getStaticTypeID() ) + { + pe::SphereID sphereJ = static_cast<pe::SphereID>( *body2It ); + treatLubricationSphrSphr( nu_L, cutOff, minimalGap, sphereI, sphereJ, blockIt->getAABB() ); + } + } + } + } + + // lubrication correction for local bodies with global bodies (for example sphere-plane) + for( auto body1It = pe::LocalBodyIterator::begin( *blockIt, bodyStorageID_ ); body1It != pe::LocalBodyIterator::end(); ++body1It ) + { + if ( body1It->getTypeID() == pe::Sphere::getStaticTypeID() ) + { + pe::SphereID sphereI = static_cast<pe::SphereID> ( *body1It ); + + // only moving spheres are considered + if ( sphereI->isFixed() ) + continue; + + for (auto body2It = globalBodyStorage_->begin(); body2It != globalBodyStorage_->end(); ++body2It) + { + if ( body2It->getTypeID() == pe::Plane::getStaticTypeID() ) + { + // sphere-plane lubrication + pe::PlaneID planeJ = static_cast<pe::PlaneID>( *body2It ); + treatLubricationSphrPlane( nu_L, cutOff, minimalGap, sphereI, planeJ ); + } else if ( body2It->getTypeID() == pe::Sphere::getStaticTypeID() ) + { + // sphere-sphere lubrication + pe::SphereID sphereJ = static_cast<pe::SphereID>( *body2It ); + treatLubricationSphrSphr( nu_L, cutOff, minimalGap, sphereI, sphereJ, blockIt->getAABB() ); + } + } + } + } + } +} + +void LubricationForceEvaluator::treatLubricationSphrSphr( real_t nu_Lattice, real_t cutOff, real_t minimalGap, + const pe::SphereID sphereI, const pe::SphereID sphereJ, const math::AABB & blockAABB ) +{ + + WALBERLA_ASSERT_UNEQUAL( sphereI->getSystemID(), sphereJ->getSystemID() ); + + real_t gap = pe::getSurfaceDistance( sphereI, sphereJ ); + + if ( gap > cutOff || gap < real_t(0) ) + { + WALBERLA_LOG_DETAIL("gap " << gap << " larger than cutOff - continue"); + return; + } + + if ( gap < minimalGap ) + { + WALBERLA_LOG_DETAIL("gap " << gap << " smaller than minimal gap " << minimalGap << " - using minimal gap"); + gap = minimalGap; + } + + const pe::Vec3 &posSphereI = sphereI->getPosition(); + const pe::Vec3 &posSphereJ = sphereJ->getPosition(); + pe::Vec3 fLub(0); + + // compute (global) coordinate between spheres' centers of gravity + pe::Vec3 midPoint( (posSphereI + posSphereJ ) * real_c(0.5) ); + + // Let process on which midPoint lies do the lubrication correction + // or the local process of sphereI if sphereJ is global + if ( blockAABB.contains(midPoint) || sphereJ->isGlobal() ) + { + fLub = compLubricationSphrSphr(nu_Lattice, gap, cutOff, sphereI, sphereJ); + sphereI->addForce(fLub); + if( !sphereJ->isFixed() ) + sphereJ->addForce( -fLub); + + WALBERLA_LOG_DETAIL( "Lubrication force on sphere " << sphereI->getID() << " from sphere " << sphereJ->getID() << " is:" << fLub); + WALBERLA_LOG_DETAIL( "Lubrication force on sphere " << sphereJ->getID() << " from sphere " << sphereI->getID() << " is:" << -fLub << "\n"); + } + +} + +void LubricationForceEvaluator::treatLubricationSphrPlane( real_t nu_Lattice, real_t cutOff, real_t minimalGap, + const pe::SphereID sphereI, const pe::ConstPlaneID planeJ ) +{ + + real_t gap = pe::getSurfaceDistance( sphereI, planeJ ); + + if ( gap > cutOff || gap < real_t(0) ) + { + WALBERLA_LOG_DETAIL("gap " << gap << " larger than cutOff - continue"); + return; + } + + if ( gap < minimalGap ) + { + WALBERLA_LOG_DETAIL("gap " << gap << " smaller than minimal gap " << minimalGap << " - using minimal gap"); + gap = minimalGap; + } + + pe::Vec3 fLub = compLubricationSphrPlane( nu_Lattice, gap, cutOff, sphereI, planeJ); + + WALBERLA_LOG_DETAIL( "Lubrication force on sphere " << sphereI->getID() << " to plane with id " << planeJ->getID() << " is:" << fLub << std::endl ); + sphereI->addForce( fLub ); + +} + + +pe::Vec3 LubricationForceEvaluator::compLubricationSphrSphr( real_t nu_Lattice, real_t gap, real_t cutOff, + const pe::SphereID sphereI, const pe::SphereID sphereJ) const +{ + const pe::Vec3 &posSphereI = sphereI->getPosition(); + const pe::Vec3 &posSphereJ = sphereJ->getPosition(); + + const pe::Vec3 &tmpVec = posSphereJ - posSphereI; + const pe::Vec3 &rIJ = tmpVec.getNormalized(); + + real_t diameterSphereI = real_t(2) * sphereI->getRadius(); + real_t diameterSphereJ = real_t(2) * sphereJ->getRadius(); + + pe::Vec3 velDiff(sphereI->getLinearVel() - sphereJ->getLinearVel()); + + real_t length = velDiff * rIJ; + + real_t d = real_t(2) * diameterSphereI * diameterSphereJ / ( diameterSphereI + diameterSphereJ ); + real_t h = gap; + real_t r = d + h; + real_t a_sq = ( real_t(3) * nu_Lattice * walberla::math::PI * d / real_t(2) ) * ( d / ( real_t(4) * h ) + ( real_t(18) / real_t(40) ) * std::log( d / ( real_t(2) * h ) ) + + ( real_t(9)/real_t(84) ) * ( h / d ) * std::log( d/( real_t(2)*h ) ) ); + real_t a_sh = ( nu_Lattice * walberla::math::PI * d / real_t(2) ) * std::log( d / ( real_t(2) * h ) ) * ( d + h ) * ( d + h ) / real_t(4); + pe::Vec3 fLub( - a_sq * length * rIJ - a_sh * ( real_t(2) / r ) * ( real_t(2) / r ) * ( velDiff - length * rIJ ) ); + + WALBERLA_LOG_DETAIL_SECTION() + { + std::stringstream ss; + ss << "Sphere I: \n uid:" << sphereI->getID() << "\n"; + ss << "vel: " << sphereI->getLinearVel() << "\n"; + ss << "rad: " << diameterSphereI * real_t(0.5) << "\n"; + ss << "pos: " << posSphereI << "\n\n"; + + ss << "Sphere J: \n uid:" << sphereJ->getID() << "\n"; + ss << "vel: " << sphereJ->getLinearVel() << "\n"; + ss << "rad: " << diameterSphereJ * real_t(0.5) << "\n"; + ss << "pos: " << posSphereJ << "\n\n"; + + real_t distance = gap + diameterSphereI * real_t(0.5) + diameterSphereJ * real_t(0.5); + ss << "distance: " << distance << "\n"; + ss << "nu: " << nu_Lattice << "\n"; + + ss << "gap: " << gap << "\n"; + ss << "cutOff: " << cutOff << "\n"; + ss << "velDiff " << velDiff << "\n"; + ss << "rIJ " << rIJ << "\n\n"; + + ss << "Resulting lubrication force: " << fLub << "\n"; + + WALBERLA_LOG_DETAIL( ss.str() ); + } + + return fLub; +} + +pe::Vec3 LubricationForceEvaluator::compLubricationSphrPlane( real_t nu_Lattice, real_t gap, real_t cutOff, + const pe::SphereID sphereI, const pe::ConstPlaneID planeJ) const +{ + const pe::Vec3 &posSphereI( sphereI->getPosition() ); + real_t radiusSphereI = sphereI->getRadius(); + + const pe::Vec3 &planeNormal( planeJ->getNormal() ); // took negative of normal from sphere to plane (plane's normal) - sign cancels out anyway + pe::Vec3 rIJ( planeNormal.getNormalized() ); // for safety reasons, normalize normal + + real_t length = sphereI->getLinearVel() * rIJ; + + pe::Vec3 v1 = sphereI->getLinearVel(); + real_t d = real_t(4) * radiusSphereI; + real_t h = gap; + real_t r = d + h; + real_t a_sq = ( real_t(3) * nu_Lattice * walberla::math::PI * d / real_t(2) ) * ( d / ( real_t(4) * h ) + ( real_t(18) / real_t(40) ) * std::log( d / ( real_t(2) * h ) ) + + ( real_t(9)/real_t(84) ) * ( h / d ) * std::log( d/( real_t(2)*h ) ) ); + real_t a_sh = ( nu_Lattice * walberla::math::PI * d / real_t(2) ) * std::log( d / ( real_t(2) * h ) ) * ( d + h ) * ( d + h ) / real_t(4); + pe::Vec3 fLub( - a_sq * length * rIJ - a_sh * ( real_t(2) / r ) * ( real_t(2) / r ) * ( v1 - length * rIJ ) ); + + WALBERLA_LOG_DETAIL_SECTION() { + std::stringstream ss; + ss << "Sphere I: \n uid:" << sphereI->getID() << "\n"; + ss << "vel: " << sphereI->getLinearVel() << "\n"; + ss << "rad: " << radiusSphereI << "\n"; + ss << "pos: " << posSphereI << "\n\n"; + + real_t distance = gap + radiusSphereI; + ss << "distance: " << distance << "\n"; + ss << "nu: " << nu_Lattice << "\n"; + + ss << "gap: " << gap << "\n"; + ss << "cutOff: " << cutOff << "\n"; + ss << "velDiff " << sphereI->getLinearVel() << "\n"; + ss << "rIJ " << -rIJ << "\n\n"; + + ss << "Resulting lubrication force: " << fLub << "\n"; + + WALBERLA_LOG_DETAIL( ss.str() ); + } + + return fLub; +} + +} // discrete_particle_methods +} // pe_coupling +} // walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/PressureFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/PressureFieldEvaluator.h new file mode 100644 index 000000000..61998ae6d --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/PressureFieldEvaluator.h @@ -0,0 +1,75 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file PressureFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Evaluator of the pressure field, given a PDF field. + * + * Uses p = rho / 3 = sum_q f_q / 3, to compute the pressure field from a given PDF field. + */ +template <typename LatticeModel_T, typename BoundaryHandling_T> +class PressureFieldEvaluator +{ +public: + typedef lbm::PdfField< LatticeModel_T > PdfField_T; + typedef GhostLayerField< real_t, 1 > ScalarField_T; + + PressureFieldEvaluator( const BlockDataID & pressureFieldID, const ConstBlockDataID & pdfFieldID, const ConstBlockDataID & boundaryHandlingID ) + : pressureFieldID_( pressureFieldID ), pdfFieldID_( pdfFieldID ), boundaryHandlingID_( boundaryHandlingID ) + { } + + void operator()(IBlock * const block) + { + const PdfField_T* pdfField = block->getData< PdfField_T >( pdfFieldID_ ); + ScalarField_T* pressureField = block->getData< ScalarField_T >( pressureFieldID_ ); + const BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ ); + + const real_t c_s_sqr = real_t(1)/real_t(3); + WALBERLA_FOR_ALL_CELLS_XYZ( pdfField, + if( boundaryHandling->isDomain(x,y,z) ) + { + real_t density = pdfField->getDensity(x,y,z); + WALBERLA_ASSERT( !math::isnan(density), "NaN found when evaluating density in cell " << Cell(x,y,z) ); + pressureField->get(x,y,z) = c_s_sqr * density; + } + ); + } + +private: + const BlockDataID pressureFieldID_; + const ConstBlockDataID pdfFieldID_; + const ConstBlockDataID boundaryHandlingID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/PressureGradientFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/PressureGradientFieldEvaluator.h new file mode 100644 index 000000000..2e8511fca --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/PressureGradientFieldEvaluator.h @@ -0,0 +1,120 @@ +//====================================================================================================================== +// +// 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 PressureGradientFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +#include "stencil/Directions.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Evaluator of the pressure gradient field, given a pressure field. + * + */ +template <typename LatticeModel_T, typename BoundaryHandling_T> +class PressureGradientFieldEvaluator +{ +public: + typedef GhostLayerField< Vector3<real_t>, 1 > VectorField_T; + typedef GhostLayerField< real_t, 1> ScalarField_T; + typedef typename LatticeModel_T::Stencil Stencil_T; + + PressureGradientFieldEvaluator( const BlockDataID & pressureGradientFieldID, const ConstBlockDataID & pressureFieldID, + const ConstBlockDataID & boundaryHandlingID ) + : pressureGradientFieldID_( pressureGradientFieldID ), pressureFieldID_( pressureFieldID ), + boundaryHandlingID_( boundaryHandlingID ) + { } + + void operator()(IBlock * const block) + { + VectorField_T* pressureGradientField = block->getData< VectorField_T >( pressureGradientFieldID_ ); + const ScalarField_T* pressureField = block->getData< ScalarField_T >( pressureFieldID_ ); + const BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ ); + + WALBERLA_FOR_ALL_CELLS_XYZ( pressureGradientField, + if( boundaryHandling->isDomain(x,y,z) ) + { + pressureGradientField->get(x,y,z) = getPressureGradient( Cell(x,y,z), pressureField, boundaryHandling ); + } + ); + } + +private: + + Vector3<real_t> getPressureGradient( const Cell & cell, const ScalarField_T * pressureField, const BoundaryHandling_T * boundaryHandling ) + { + + // temporarily store pressure values of surrounding cells + std::vector<real_t> pressureValues( Stencil_T::Size, real_t(0) ); + + // store pressure value in center cell to potentially apply Neumann like boundary conditions + real_t pressureInCenterCell = pressureField->get( cell ); + + for( auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + // check if boundary treatment is necessary + if( !boundaryHandling->isDomain( cell + *dir ) ) + { + // copy from center cell + pressureValues[ *dir ] = pressureInCenterCell; + } else + { + pressureValues[ *dir ] = pressureField->get( cell + *dir ); + + } + } + + // apply gradient formula + // uses LBM weighting to determine gradient + // See: Ramadugu et al - "Lattice differential operators for computational physics" (2013) + // with T = c_s**2 + const real_t inv_c_s_sqr = real_c(3); + Vector3<real_t> gradient( real_c(0) ); + for( auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + real_t pressure = pressureValues[ *dir ]; + gradient[0] += LatticeModel_T::w[ dir.toIdx() ] * real_c(dir.cx()) * pressure; + gradient[1] += LatticeModel_T::w[ dir.toIdx() ] * real_c(dir.cy()) * pressure; + gradient[2] += LatticeModel_T::w[ dir.toIdx() ] * real_c(dir.cz()) * pressure; + } + gradient *= inv_c_s_sqr; + + return gradient; + } + + const BlockDataID pressureGradientFieldID_; + const ConstBlockDataID pressureFieldID_; + const ConstBlockDataID boundaryHandlingID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/SolidVolumeFractionFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/SolidVolumeFractionFieldEvaluator.h new file mode 100644 index 000000000..776aba229 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/SolidVolumeFractionFieldEvaluator.h @@ -0,0 +1,102 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file SolidVolumeFractionFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/distributors/all.h" +#include "field/GhostLayerField.h" + +#include "pe/rigidbody/BodyIterators.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Evalutor of the solid volume fraction field. + * + * Updates the solid volume fraction field. Includes firstly removing all old entries from the field, then remapping + * the local bodies' volumes to the cells. + * Potentially writes to the ghost layer of the solid volume fraction field which thus requires a special + * PullReducion communication afterwards. + * + * Depending on the used Distributor_T, the resulting solid volume fraction field will vary: + * - NearestNeighborDistributor: + * Corresponds to simply assigning the total body volume to the cell containing the body center. + * - KernelDistributor: + * The body's volume is not directly assigned to one cell only but spread over the neighboring cells as well. + * See Finn, Li, Apte - "Particle based modelling and simulation of natural sand dynamics in the wave bottom boundary layer" (2016) + * for the application, even though different kernel was used there. + * + * For more infos on distributors, see src/field/distributors. + */ +template< typename FlagField_T, template <typename,typename> class Distributor_T > +class SolidVolumeFractionFieldEvaluator +{ +public: + + typedef GhostLayerField< real_t, 1 > ScalarField_T; + typedef Distributor_T<ScalarField_T, FlagField_T> ScalarDistributor_T; + + SolidVolumeFractionFieldEvaluator( const shared_ptr<StructuredBlockStorage> & blockStorage, + const BlockDataID & solidVolumeFractionFieldID, const BlockDataID & bodyStorageID, + const BlockDataID & flagFieldID, const Set< FlagUID > & domainFlags ) + : blockStorage_( blockStorage ), solidVolumeFractionFieldID_( solidVolumeFractionFieldID ), bodyStorageID_( bodyStorageID ) + { + scalarDistributorID_ = field::addDistributor< ScalarDistributor_T, FlagField_T >( blockStorage, solidVolumeFractionFieldID, flagFieldID, domainFlags ); + } + + void operator()( IBlock * const block ) + { + ScalarField_T* svfField = block->getData< ScalarField_T >( solidVolumeFractionFieldID_ ); + + // reset field + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ( svfField, + svfField->get(x,y,z) = real_c(0); + ) + + ScalarDistributor_T * scalarDistributor = block->getData< ScalarDistributor_T >( scalarDistributorID_ ); + + // assign the local bodies' volume to the cell, depending on the chosen Distributor_T + for( auto bodyIt = pe::LocalBodyIterator::begin(*block, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end(); ++bodyIt ) + { + real_t bodyVolume = bodyIt->getVolume(); + const Vector3<real_t> bodyPosition = bodyIt->getPosition(); + + scalarDistributor->distribute( bodyPosition, &bodyVolume ); + } + } + +private: + + shared_ptr<StructuredBlockStorage> blockStorage_; + BlockDataID solidVolumeFractionFieldID_; + BlockDataID bodyStorageID_; + BlockDataID scalarDistributorID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/StressTensorGradientFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/StressTensorGradientFieldEvaluator.h new file mode 100644 index 000000000..29dce0a61 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/StressTensorGradientFieldEvaluator.h @@ -0,0 +1,137 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file StressTensorGradientFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" +#include "core/math/Matrix3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +#include "stencil/Directions.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Evaluator of the stress tensor gradient field, given a velocity field. + * + */ +template <typename LatticeModel_T, typename BoundaryHandling_T> +class StressTensorGradientFieldEvaluator +{ +public: + typedef GhostLayerField< Matrix3< real_t >, 1> TensorField_T; + typedef GhostLayerField< Vector3<real_t>, 1 > VectorField_T; + typedef typename LatticeModel_T::Stencil Stencil_T; + + StressTensorGradientFieldEvaluator( const BlockDataID & stressTensorGradientFieldID, + const ConstBlockDataID & velocityGradientFieldID, + const ConstBlockDataID & boundaryHandlingID, + const real_t & dynamicFluidViscosity ) + : stressTensorGradientFieldID_( stressTensorGradientFieldID ), + velocityGradientFieldID_( velocityGradientFieldID ), + boundaryHandlingID_( boundaryHandlingID ), + dynamicFluidViscosity_( dynamicFluidViscosity ) + { } + + void operator()(IBlock * const block) + { + VectorField_T* stressTensorGradientField = block->getData< VectorField_T >( stressTensorGradientFieldID_ ); + const TensorField_T* velocityGradientField = block->getData< TensorField_T >( velocityGradientFieldID_ ); + const BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ ); + + WALBERLA_FOR_ALL_CELLS_XYZ( stressTensorGradientField, + if( boundaryHandling->isDomain(x,y,z) ) + { + stressTensorGradientField->get(x,y,z) = getStressTensorGradient( Cell(x,y,z), velocityGradientField, boundaryHandling ); + } + ); + } + + void resetViscosity( real_t newDynamicFluidViscosity ) + { + dynamicFluidViscosity_ = newDynamicFluidViscosity; + } + +private: + + // calculates grad( nu * ( ( grad(u) ) + (grad(u))**T ) ), i.e. the gradient of the stress tensor + Vector3<real_t> getStressTensorGradient( const Cell & cell, const TensorField_T* velocityGradientField, const BoundaryHandling_T * boundaryHandling ) + { + + std::vector< Matrix3< real_t > > stressTensorValues( Stencil_T::Size, Matrix3< real_t >(real_t(0)) ); + + + Matrix3< real_t > velGradientInCenterCell = velocityGradientField->get( cell ); + Matrix3< real_t > stressTensorInCenterCell = ( velGradientInCenterCell + velGradientInCenterCell.getTranspose() ) * dynamicFluidViscosity_; + + for( auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + // check if boundary treatment is necessary + if( !boundaryHandling->isDomain( cell + *dir ) ) + { + // copy from center cell + stressTensorValues[ *dir ] = stressTensorInCenterCell; + } else { + Matrix3< real_t > velGradient = velocityGradientField->get( cell + *dir ); + stressTensorValues[ *dir ] = ( velGradient + velGradient.getTranspose() ) * dynamicFluidViscosity_; + } + } + + // obtain the gradient of the tensor using the gradient formula + // See: Ramadugu et al - "Lattice differential operators for computational physics" (2013) + // with T = c_s**2 + const real_t inv_c_s_sqr = real_t(3); + Vector3<real_t> gradStressTensor( real_t(0) ); + for( auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + Vector3<real_t> latticeVel( real_c(dir.cx()), real_c(dir.cy()), real_c(dir.cz()) ); + Matrix3<real_t> & tempTau = stressTensorValues[ *dir ]; + Vector3<real_t> tau1( tempTau[0], tempTau[3], tempTau[6] ); + Vector3<real_t> tau2( tempTau[1], tempTau[4], tempTau[7] ); + Vector3<real_t> tau3( tempTau[2], tempTau[5], tempTau[8] ); + + gradStressTensor[0] += LatticeModel_T::w[ dir.toIdx() ] * latticeVel * tau1; + gradStressTensor[1] += LatticeModel_T::w[ dir.toIdx() ] * latticeVel * tau2; + gradStressTensor[2] += LatticeModel_T::w[ dir.toIdx() ] * latticeVel * tau3; + + } + gradStressTensor *= inv_c_s_sqr; + + return gradStressTensor; + + } + + const BlockDataID stressTensorGradientFieldID_; + const ConstBlockDataID velocityGradientFieldID_; + const ConstBlockDataID boundaryHandlingID_; + real_t dynamicFluidViscosity_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/VelocityCurlFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/VelocityCurlFieldEvaluator.h new file mode 100644 index 000000000..0442814db --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/VelocityCurlFieldEvaluator.h @@ -0,0 +1,112 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file VelocityCurlFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +#include "stencil/Directions.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Evaluator of the curl of a given velocity field. + * + */ +template <typename LatticeModel_T, typename BoundaryHandling_T> +class VelocityCurlFieldEvaluator +{ +public: + typedef GhostLayerField< Vector3<real_t>, 1 > VectorField_T; + typedef typename LatticeModel_T::Stencil Stencil_T; + + VelocityCurlFieldEvaluator( const BlockDataID & velocityCurlFieldID, const ConstBlockDataID & velocityFieldID, + const ConstBlockDataID & boundaryHandlingID ) + : velocityCurlFieldID_( velocityCurlFieldID ), velocityFieldID_( velocityFieldID ), + boundaryHandlingID_( boundaryHandlingID ) + { } + + void operator()(IBlock * const block) + { + const VectorField_T* velocityField = block->getData< VectorField_T >( velocityFieldID_ ); + VectorField_T* velocityCurlField = block->getData< VectorField_T >( velocityCurlFieldID_ ); + const BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ ); + + WALBERLA_FOR_ALL_CELLS_XYZ( velocityCurlField, + if( boundaryHandling->isDomain(x,y,z) ) + { + velocityCurlField->get(x,y,z) = getVelocityCurl( Cell(x,y,z), velocityField, boundaryHandling ); + } + ); + } + +private: + + Vector3<real_t> getVelocityCurl( const Cell & cell, const VectorField_T * velocityField, const BoundaryHandling_T * boundaryHandling ) + { + + std::vector< Vector3<real_t> > velocityValues( Stencil_T::Size, Vector3<real_t>(real_t(0)) ); + + Vector3<real_t> velocityInCenterCell = velocityField->get( cell ); + + for( auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + // check if boundary treatment is necessary + if( !boundaryHandling->isDomain( cell + *dir ) ) + { + // copy from center cell + velocityValues[ *dir ] = velocityInCenterCell; + } else { + velocityValues[ *dir ] = velocityField->get( cell + *dir ); + } + } + + // apply curl formula + // See: Ramadugu et al - Lattice differential operators for computational physics (2013) + // with T = c_s**2 + const real_t inv_c_s_sqr = real_t(3); + Vector3<real_t> curl( real_t(0) ); + for( auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + Vector3<real_t> latticeVel( real_c(dir.cx()), real_c(dir.cy()), real_c(dir.cz()) ); + curl += LatticeModel_T::w[ dir.toIdx() ] * ( latticeVel % velocityValues[ *dir ] ); + } + curl *= inv_c_s_sqr; + + return curl; + } + + const BlockDataID velocityCurlFieldID_; + const ConstBlockDataID velocityFieldID_; + const ConstBlockDataID boundaryHandlingID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/VelocityFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/VelocityFieldEvaluator.h new file mode 100644 index 000000000..70d62853b --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/VelocityFieldEvaluator.h @@ -0,0 +1,81 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file VelocityFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Evaluator of the velocity field, given a PDF field. + * + * Calculates the velocity in each domain cell, given a PDF field. + * Since in LBM, the macroscopic velocity depends on the local fluid forcing, one has to pay attention to the currently + * set forces in the corresponding force field before evaluating the velocity. + * + */ +template <typename LatticeModel_T, typename BoundaryHandling_T> +class VelocityFieldEvaluator +{ +public: + typedef lbm::PdfField< LatticeModel_T > PdfField_T; + typedef GhostLayerField< Vector3<real_t>, 1 > VelocityField_T; + + VelocityFieldEvaluator( const BlockDataID & velocityFieldID, + const ConstBlockDataID & pdfFieldID, const ConstBlockDataID & boundaryHandlingID ) + : velocityFieldID_( velocityFieldID ), pdfFieldID_( pdfFieldID ), boundaryHandlingID_( boundaryHandlingID ) + { } + + void operator()(IBlock * const block) + { + const PdfField_T* pdfField = block->getData< PdfField_T >( pdfFieldID_ ); + VelocityField_T* velocityField = block->getData< VelocityField_T >( velocityFieldID_ ); + const BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ ); + + WALBERLA_FOR_ALL_CELLS_XYZ( pdfField, + if( boundaryHandling->isDomain(x,y,z) ) + { + WALBERLA_ASSERT( !math::isnan(pdfField->getVelocity(x,y,z)[0]) && + !math::isnan(pdfField->getVelocity(x,y,z)[1]) && + !math::isnan(pdfField->getVelocity(x,y,z)[2]), "NaN found when evaluating velocity in cell " << Cell(x,y,z) ); + velocityField->get(x,y,z) = pdfField->getVelocity(x,y,z); + } + ); + } + +private: + const BlockDataID velocityFieldID_; + const ConstBlockDataID pdfFieldID_; + const ConstBlockDataID boundaryHandlingID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/VelocityGradientFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/VelocityGradientFieldEvaluator.h new file mode 100644 index 000000000..9e0397a5a --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/VelocityGradientFieldEvaluator.h @@ -0,0 +1,144 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file VelocityGradientFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" +#include "core/math/Matrix3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +#include "stencil/Directions.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + + +/*!\brief Evaluator of the velocity gradient field, given a velocity field. + * + */ +template <typename LatticeModel_T, typename BoundaryHandling_T> +class VelocityGradientFieldEvaluator +{ +public: + typedef GhostLayerField< Matrix3< real_t >, 1> TensorField_T; + typedef GhostLayerField< Vector3<real_t>, 1 > VectorField_T; + typedef typename LatticeModel_T::Stencil Stencil_T; + + VelocityGradientFieldEvaluator( const BlockDataID & velocityGradientFieldID, + const ConstBlockDataID & velocityFieldID, + const ConstBlockDataID & boundaryHandlingID ) + : velocityGradientFieldID_( velocityGradientFieldID ), + velocityFieldID_( velocityFieldID ), + boundaryHandlingID_( boundaryHandlingID ) + { } + + void operator()(IBlock * const block) + { + const VectorField_T* velocityField = block->getData< VectorField_T >( velocityFieldID_ ); + TensorField_T* velocityGradientField = block->getData< TensorField_T >( velocityGradientFieldID_ ); + const BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ ); + + Matrix3<real_t> velocityGradient( real_t(0) ); + + WALBERLA_FOR_ALL_CELLS_XYZ( velocityGradientField, + if( boundaryHandling->isDomain(x,y,z) ) + { + getVelocityGradient( Cell(x,y,z), velocityField, boundaryHandling, velocityGradient ); + velocityGradientField->get(x,y,z) = velocityGradient; + } + ); + } + +private: + + // calculates grad(u) + // grad(u) = + // | du1/dx1 du2/dx1 du3/dx1 | | 0 1 2 | | 0,0 0,1 0,2 | + // | du1/dx2 du2/dx2 du3/dx2 | = | 3 4 5 | = | 1,0 1,1 1,2 | + // | du1/dx3 du2/dx3 du3/dx3 | | 6 7 8 | | 2,0 2,1 2,2 | + void getVelocityGradient( const Cell & cell, const VectorField_T * velocityField, const BoundaryHandling_T * boundaryHandling, Matrix3<real_t> & velocityGradient ) + { + + std::vector< Vector3<real_t> > velocityValues( Stencil_T::Size, Vector3<real_t>(real_t(0)) ); + + Vector3<real_t> velocityInCenterCell = velocityField->get( cell ); + + for( auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + // check if boundary treatment is necessary + if( !boundaryHandling->isDomain( cell + *dir ) ) + { + // copy from center cell + velocityValues[ *dir ] = velocityInCenterCell; + } else { + velocityValues[ *dir ] = velocityField->get( cell + *dir ); + } + } + + // obtain the matrix grad(u) with the help of the gradient formula from + // See: Ramadugu et al - Lattice differential operators for computational physics (2013) + // with T = c_s**2 + const real_t inv_c_s_sqr = real_t(3); + velocityGradient = real_t(0); + for( auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + real_t cx = real_c(dir.cx()); + real_t cy = real_c(dir.cy()); + real_t cz = real_c(dir.cz()); + + // grad(ux) + real_t ux = velocityValues[ *dir ][0]; + velocityGradient[ 0 ] += LatticeModel_T::w[ dir.toIdx() ] * cx * ux; + velocityGradient[ 3 ] += LatticeModel_T::w[ dir.toIdx() ] * cy * ux; + velocityGradient[ 6 ] += LatticeModel_T::w[ dir.toIdx() ] * cz * ux; + + // grad(uy) + real_t uy = velocityValues[ *dir ][1]; + velocityGradient[ 1 ] += LatticeModel_T::w[ dir.toIdx() ] * cx * uy; + velocityGradient[ 4 ] += LatticeModel_T::w[ dir.toIdx() ] * cy * uy; + velocityGradient[ 7 ] += LatticeModel_T::w[ dir.toIdx() ] * cz * uy; + + // grad(uz) + real_t uz = velocityValues[ *dir ][2]; + velocityGradient[ 2 ] += LatticeModel_T::w[ dir.toIdx() ] * cx * uz; + velocityGradient[ 5 ] += LatticeModel_T::w[ dir.toIdx() ] * cy * uz; + velocityGradient[ 8 ] += LatticeModel_T::w[ dir.toIdx() ] * cz * uz; + + } + velocityGradient *= inv_c_s_sqr; + + } + + const BlockDataID velocityGradientFieldID_; + const ConstBlockDataID velocityFieldID_; + const ConstBlockDataID boundaryHandlingID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/VelocityTotalTimeDerivativeFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/evaluators/VelocityTotalTimeDerivativeFieldEvaluator.h new file mode 100644 index 000000000..62e70f973 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/VelocityTotalTimeDerivativeFieldEvaluator.h @@ -0,0 +1,103 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file VelocityTotalTimeDerivativeFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" +#include "core/math/Matrix3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + + +/*!\brief Evaluator of the total derivative of the fluid velocity. + * + * This evaluates Du/Dt = du/dt + u * grad(u). + * The gradient of the velocity, grad(u), has to be provided by a field, e.g. evaluated with VelocityGradientFieldEvaluator.h. + * The time derivative, du/dt, is approximated by a simple backward difference: du/dt = (u_new - u_old ) / deltaT. + * This requires two velocity field. + * + */ +class VelocityTotalTimeDerivativeFieldEvaluator +{ +public: + typedef GhostLayerField< Vector3<real_t>, 1 > VelocityField_T; + typedef GhostLayerField< Matrix3< real_t >, 1> TensorField_T; + + VelocityTotalTimeDerivativeFieldEvaluator( const BlockDataID & totalTimeDerivativeVelocityFieldID, + const ConstBlockDataID & currentVelocityFieldID, + const ConstBlockDataID & formerVelocityFieldID, + const ConstBlockDataID & velocityGradientFieldID, + const real_t & deltaT = real_t(1) ) + : totalTimeDerivativeVelocityFieldID_( totalTimeDerivativeVelocityFieldID ), currentVelocityFieldID_( currentVelocityFieldID ), + formerVelocityFieldID_( formerVelocityFieldID ), velocityGradientFieldID_( velocityGradientFieldID ), deltaTinv_( real_t(1) / deltaT ) + { } + + void operator()(IBlock * const block) + { + VelocityField_T* totalTimeDerivativeVelocityField = block->getData< VelocityField_T >( totalTimeDerivativeVelocityFieldID_ ); + const VelocityField_T* currentVelocityField = block->getData< VelocityField_T >( currentVelocityFieldID_ ); + const VelocityField_T* formerVelocityField = block->getData< VelocityField_T >( formerVelocityFieldID_ ); + const TensorField_T* velocityGradientField = block->getData< TensorField_T >( velocityGradientFieldID_ ); + + // simple backward difference approximation of Du/Dt + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ( totalTimeDerivativeVelocityField, + Vector3<real_t> vel = currentVelocityField->get(x,y,z); + + // grad(u) = + // | du1/dx1 du2/dx1 du3/dx1 | | 0 1 2 | | 0,0 0,1 0,2 | + // | du1/dx2 du2/dx2 du3/dx2 | = | 3 4 5 | = | 1,0 1,1 1,2 | + // | du1/dx3 du2/dx3 du3/dx3 | | 6 7 8 | | 2,0 2,1 2,2 | + Matrix3<real_t> gradVel = velocityGradientField->get(x,y,z); + + // evaluate u * grad(u) = u_i (d u_j / d x_i) + Vector3<real_t> velGradVel( gradVel(0,0) * vel[0] + gradVel(1,0) * vel[1] + gradVel(2,0) * vel[2], + gradVel(0,1) * vel[0] + gradVel(1,1) * vel[1] + gradVel(2,1) * vel[2], + gradVel(0,2) * vel[0] + gradVel(1,2) * vel[1] + gradVel(2,2) * vel[2] ); + + totalTimeDerivativeVelocityField->get(x,y,z) = ( vel - formerVelocityField->get(x,y,z) ) * deltaTinv_ + velGradVel; + ); + } + + void resetDeltaT( const real_t & deltaT ) + { + deltaTinv_ = real_t(1) / deltaT; + } + +private: + const BlockDataID totalTimeDerivativeVelocityFieldID_; + const ConstBlockDataID currentVelocityFieldID_; + const ConstBlockDataID formerVelocityFieldID_; + const ConstBlockDataID velocityGradientFieldID_; + real_t deltaTinv_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/evaluators/all.h b/src/pe_coupling/discrete_particle_methods/evaluators/all.h new file mode 100644 index 000000000..308ab5754 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/evaluators/all.h @@ -0,0 +1,39 @@ +//====================================================================================================================== +// +// 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 all.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "waLBerlaDefinitions.h" + +#include "AddedMassForceEvaluator.h" +#include "BodyVelocityTimeDerivativeEvaluator.h" +#include "EffectiveViscosityFieldEvaluator.h" +#include "InteractionForceEvaluator.h" +#include "LiftForceEvaluator.h" +#include "LubricationForceEvaluator.h" +#include "PressureFieldEvaluator.h" +#include "PressureGradientFieldEvaluator.h" +#include "SolidVolumeFractionFieldEvaluator.h" +#include "StressTensorGradientFieldEvaluator.h" +#include "VelocityCurlFieldEvaluator.h" +#include "VelocityFieldEvaluator.h" +#include "VelocityGradientFieldEvaluator.h" +#include "VelocityTotalTimeDerivativeFieldEvaluator.h" diff --git a/src/pe_coupling/discrete_particle_methods/gns_lbm/GNSSweep.h b/src/pe_coupling/discrete_particle_methods/gns_lbm/GNSSweep.h new file mode 100644 index 000000000..72649668d --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/gns_lbm/GNSSweep.h @@ -0,0 +1,381 @@ +//====================================================================================================================== +// +// 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 GNSSweep.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "field/GhostLayerField.h" + +#include "lbm/sweeps/StreamPull.h" +#include "lbm/sweeps/SweepBase.h" +#include "lbm/lattice_model/all.h" + +#include "pe/Types.h" + +#include "stencil/Directions.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + + +/*!\brief Sweep for the LBM formulation of the generalized Navier Stokes equations + * + * Method is taken from: + * Z. Guo, T. S. Zhao - "Lattice Boltzmann model for incompressible flows through porous media", Phys. Rev. E 66 (2002)036304. doi:10.1103/PhysRevE.66.036304. + * Note: when an external forcing is present, use the GNSExternalForceToForceFieldAdder class to apply the correct external force + * (i.e. fluid-volume-fraction * external-force ) + * Note: the calculated velocity is the volume averaged one! + * + * A LBM-forcing model that supports spatially (and temporally) varying forces has to be used, e.g. GuoField. + * + */ +template< typename LatticeModel_T, typename Filter_T, typename DensityVelocityIn_T, typename DensityVelocityOut_T > +class GNSSweep + : public lbm::SweepBase< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T > +{ +public: + + typedef typename lbm::SweepBase< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T >::PdfField_T PdfField_T; + typedef typename LatticeModel_T::Stencil Stencil_T; + typedef GhostLayerField< real_t, 1> ScalarField_T; + + static_assert( LatticeModel_T::ForceModel::constant == false, "Only works with non-constant force models!" ); + static_assert( LatticeModel_T::compressible == false, "Only works with incompressible models!" ); + + GNSSweep( const BlockDataID & pdfFieldID, + const BlockDataID & solidVolumeFractionFieldID, + const Filter_T & _filter = walberla::field::DefaultEvaluationFilter(), + const DensityVelocityIn_T & _densityVelocityIn = lbm::DefaultDensityEquilibriumVelocityCalculation(), + const DensityVelocityOut_T & _densityVelocityOut = lbm::DefaultDensityVelocityCallback() ) : + lbm::SweepBase< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T >( pdfFieldID, _filter, _densityVelocityIn, _densityVelocityOut ), + solidVolumeFractionFieldID_( solidVolumeFractionFieldID ) {} + + GNSSweep( const BlockDataID & src, const BlockDataID & dst, + const BlockDataID & solidVolumeFractionFieldID, + const Filter_T & _filter = walberla::field::DefaultEvaluationFilter(), + const DensityVelocityIn_T & _densityVelocityIn = lbm::DefaultDensityEquilibriumVelocityCalculation(), + const DensityVelocityOut_T & _densityVelocityOut = lbm::DefaultDensityVelocityCallback() ) : + lbm::SweepBase< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T >( src, dst, _filter, _densityVelocityIn, _densityVelocityOut ), + solidVolumeFractionFieldID_( solidVolumeFractionFieldID ) {} + + void operator()( IBlock * const block, const uint_t numberOfGhostLayersToInclude = uint_t(0) ) + { + streamCollide( block, numberOfGhostLayersToInclude ); + } + + void streamCollide( IBlock * const block, const uint_t numberOfGhostLayersToInclude = uint_t(0) ); + + void stream ( IBlock * const block, const uint_t numberOfGhostLayersToInclude = uint_t(0) ); + void collide( IBlock * const block, const uint_t numberOfGhostLayersToInclude = uint_t(0) ); + + inline ScalarField_T * getSolidVolumeFractionField( IBlock * const block ) const + { + WALBERLA_ASSERT_NOT_NULLPTR( block ); + ScalarField_T * solidVolumeFractionField = block->getData<ScalarField_T>( solidVolumeFractionFieldID_ ); + WALBERLA_ASSERT_NOT_NULLPTR( solidVolumeFractionField ); + return solidVolumeFractionField; + } + + inline void getFields( IBlock * const block, PdfField_T * & src, PdfField_T * & dst, ScalarField_T * & solidVolumeFractionField ) + { + WALBERLA_ASSERT_NOT_NULLPTR( block ); + + src = this->getSrcField( block ); + dst = this->getDstField( block, src ); + solidVolumeFractionField = getSolidVolumeFractionField( block ); + + WALBERLA_ASSERT_NOT_NULLPTR( src ); + WALBERLA_ASSERT_NOT_NULLPTR( dst ); + WALBERLA_ASSERT_NOT_NULLPTR( solidVolumeFractionField ); + + WALBERLA_ASSERT_EQUAL( src->xyzSize(), dst->xyzSize() ); + WALBERLA_ASSERT_EQUAL( src->xyzSize(), solidVolumeFractionField->xyzSize() ); + } + +private: + const BlockDataID solidVolumeFractionFieldID_; +}; + + +template< typename LatticeModel_T, typename Filter_T, typename DensityVelocityIn_T, typename DensityVelocityOut_T > +void GNSSweep< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T +>::streamCollide( IBlock * const block, const uint_t numberOfGhostLayersToInclude ) +{ + PdfField_T * src( NULL ); + PdfField_T * dst( NULL ); + ScalarField_T * solidVolumeFractionField( NULL ); + + getFields( block, src, dst, solidVolumeFractionField ); + + WALBERLA_ASSERT_GREATER( src->nrOfGhostLayers(), numberOfGhostLayersToInclude ); + WALBERLA_ASSERT_GREATER_EQUAL( dst->nrOfGhostLayers(), numberOfGhostLayersToInclude ); + WALBERLA_ASSERT_GREATER_EQUAL( solidVolumeFractionField->nrOfGhostLayers(), numberOfGhostLayersToInclude ); + + const auto & lm = src->latticeModel(); + dst->resetLatticeModel( lm ); /* required so that member functions for getting density and equilibrium velocity can be called for dst! */ + + this->filter( *block ); + this->densityVelocityIn( *block ); + this->densityVelocityOut( *block ); + + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ( src, numberOfGhostLayersToInclude, + + if( this->filter(x,y,z) ) + { + const real_t fluidVolumeFraction = real_t(1) - solidVolumeFractionField->get(x,y,z); //i.e. porosity + const real_t invFluidVolumeFraction = real_t(1)/fluidVolumeFraction; + + // stream pull + for( auto d = Stencil_T::begin(); d != Stencil_T::end(); ++d ) + dst->get( x, y, z, d.toIdx() ) = src->get( x-d.cx(), y-d.cy(), z-d.cz(), d.toIdx() ); + + Vector3<real_t> velocity; + real_t rho = this->densityVelocityIn( velocity, dst, x, y, z ); + + this->densityVelocityOut( x, y, z, lm, velocity, rho ); + + velocity /= rho; + + const real_t omega = lm.collisionModel().omega( x, y, z, velocity, rho ); + + const Vector3<real_t> extForce = lm.forceModel().force(x,y,z); + + // collide + for( auto d = Stencil_T::begin(); d != Stencil_T::end(); ++d ) + { + const real_t wq = LatticeModel_T::w[ d.toIdx() ]; + const Vector3<real_t> c( real_c(d.cx()), real_c(d.cy()), real_c(d.cz()) ); + const real_t forceTerm = real_t(3.0) * wq * ( real_t(1) - real_t(0.5) * omega ) * + ( ( c - invFluidVolumeFraction * velocity + ( real_t(3) * invFluidVolumeFraction * ( c * velocity ) * c ) ) * extForce ); // modified Guo forcing, multiplication by rho is wrong because of units + + const real_t vel = c * velocity; + real_t feq = wq * rho * ( real_t(1) - real_t(1.5) * invFluidVolumeFraction * velocity.sqrLength() + + real_t(4.5) * invFluidVolumeFraction * vel * vel + real_t(3.0) * vel ); // modified feq + feq -= wq; // center PDFs around 0 + + dst->get( x, y, z, d.toIdx() ) = ( real_t(1.0) - omega ) * dst->get( x, y, z, d.toIdx() ) + + omega * feq + + forceTerm; + } + } + + ) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ + + src->swapDataPointers( dst ); + +} + +template< typename LatticeModel_T, typename Filter_T, typename DensityVelocityIn_T, typename DensityVelocityOut_T > +void GNSSweep< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T +>::stream( IBlock * const block, const uint_t numberOfGhostLayersToInclude ) +{ + PdfField_T * src( NULL ); + PdfField_T * dst( NULL ); + lbm::SweepBase<LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T>::getFields( block, src, dst ); + lbm::StreamPull< LatticeModel_T >::execute( src, dst, block, this->filter_, numberOfGhostLayersToInclude ); +} + +template< typename LatticeModel_T, typename Filter_T, typename DensityVelocityIn_T, typename DensityVelocityOut_T > +void GNSSweep< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T +>::collide( IBlock * const block, const uint_t numberOfGhostLayersToInclude ) +{ + PdfField_T * src = this->getSrcField( block ); + ScalarField_T * solidVolumeFractionField = getSolidVolumeFractionField( block ); + + WALBERLA_ASSERT_GREATER( src->nrOfGhostLayers(), numberOfGhostLayersToInclude ); + WALBERLA_ASSERT_GREATER_EQUAL( solidVolumeFractionField->nrOfGhostLayers(), numberOfGhostLayersToInclude ); + + const auto & lm = src->latticeModel(); + + this->filter( *block ); + this->densityVelocityIn( *block ); + this->densityVelocityOut( *block ); + + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ( src, numberOfGhostLayersToInclude, + if( this->filter(x,y,z) ) + { + const real_t fluidVolumeFraction = real_t(1) - solidVolumeFractionField->get(x,y,z); //i.e. porosity + const real_t invFluidVolumeFraction = real_t(1)/fluidVolumeFraction; + + Vector3<real_t> velocity; + real_t rho = this->densityVelocityIn( velocity, src, x, y, z ); + + this->densityVelocityOut( x, y, z, lm, velocity, rho ); + + velocity /= rho; + + const real_t omega = lm.collisionModel().omega( x, y, z, velocity, rho ); + + const Vector3<real_t> extForce = lm.forceModel().force(x,y,z); + + // collide + for( auto d = Stencil_T::begin(); d != Stencil_T::end(); ++d ) + { + const real_t wq = LatticeModel_T::w[ d.toIdx() ]; + const Vector3<real_t> c( real_c(d.cx()), real_c(d.cy()), real_c(d.cz()) ); + const real_t forceTerm = real_t(3.0) * wq * ( real_t(1) - real_t(0.5) * omega ) * + ( ( c - invFluidVolumeFraction * velocity + ( real_t(3) * invFluidVolumeFraction * ( c * velocity ) * c ) ) * extForce ); // modified Guo forcing + + const real_t vel = c * velocity; + real_t feq = wq * rho * ( real_t(1) - real_t(1.5) * invFluidVolumeFraction * velocity.sqrLength() + + real_t(4.5) * invFluidVolumeFraction * vel * vel + real_t(3.0) * vel ); // modified feq + feq -= wq; // center PDFs around 0 + + src->get( x, y, z, d.toIdx() ) = ( real_t(1.0) - omega ) * src->get( x, y, z, d.toIdx() ) + + omega * feq + + forceTerm; + } + } + ) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ + +} + +///////////////////////////// +// makeGNSSweep FUNCTIONS // +//////////////////////////// + +template< typename LatticeModel_T, typename Filter_T, typename DensityVelocityIn_T, typename DensityVelocityOut_T > +shared_ptr< GNSSweep< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T > > +makeGNSSweep( const BlockDataID & pdfFieldID, const BlockDataID & solidVolumeFractionFieldID, const Filter_T & filter, + const DensityVelocityIn_T & densityVelocityIn, const DensityVelocityOut_T & densityVelocityOut ) +{ + typedef GNSSweep< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T > Sweep_T; + return shared_ptr< Sweep_T >( new Sweep_T( pdfFieldID, solidVolumeFractionFieldID, filter, densityVelocityIn, densityVelocityOut ) ); +} + +template< typename LatticeModel_T, typename Filter_T, typename DensityVelocityIn_T, typename DensityVelocityOut_T > +shared_ptr< GNSSweep< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T > > +makeGNSSweep( const BlockDataID & srcID, const BlockDataID & dstID, const BlockDataID & solidVolumeFractionFieldID, const Filter_T & filter, + const DensityVelocityIn_T & densityVelocityIn, const DensityVelocityOut_T & densityVelocityOut ) +{ + typedef GNSSweep< LatticeModel_T, Filter_T, DensityVelocityIn_T, DensityVelocityOut_T > Sweep_T; + return shared_ptr< Sweep_T >( new Sweep_T( srcID, dstID, solidVolumeFractionFieldID, filter, densityVelocityIn, densityVelocityOut ) ); +} + +// only block data IDs of PDF data + +template< typename LatticeModel_T > +shared_ptr< GNSSweep< LatticeModel_T, walberla::field::DefaultEvaluationFilter, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DefaultDensityVelocityCallback > > +makeGNSSweep( const BlockDataID & pdfFieldID, const BlockDataID & solidVolumeFractionFieldID ) +{ + return makeGNSSweep< LatticeModel_T, walberla::field::DefaultEvaluationFilter, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DefaultDensityVelocityCallback > + ( pdfFieldID, solidVolumeFractionFieldID,walberla::field::DefaultEvaluationFilter(), + lbm::DefaultDensityEquilibriumVelocityCalculation(), lbm::DefaultDensityVelocityCallback() ); +} + +template< typename LatticeModel_T > +shared_ptr< GNSSweep< LatticeModel_T, walberla::field::DefaultEvaluationFilter, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DefaultDensityVelocityCallback > > +makeGNSSweep( const BlockDataID & srcID, const BlockDataID & dstID, const BlockDataID & solidVolumeFractionFieldID ) +{ + return makeGNSSweep< LatticeModel_T, walberla::field::DefaultEvaluationFilter, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DefaultDensityVelocityCallback > + ( srcID, dstID, solidVolumeFractionFieldID, walberla::field::DefaultEvaluationFilter(), + lbm::DefaultDensityEquilibriumVelocityCalculation(), lbm::DefaultDensityVelocityCallback() ); +} + +// block data IDs of PDF data + flag field as filter + +template< typename LatticeModel_T, typename FlagField_T > +shared_ptr< GNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DefaultDensityVelocityCallback > > +makeGNSSweep( const BlockDataID & pdfFieldID, const BlockDataID & solidVolumeFractionFieldID, + const ConstBlockDataID & flagFieldID, const Set< FlagUID > & cellsToEvaluate ) +{ + return makeGNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DefaultDensityVelocityCallback > + ( pdfFieldID, solidVolumeFractionFieldID, walberla::field::FlagFieldEvaluationFilter<FlagField_T>( flagFieldID, cellsToEvaluate ), + lbm::DefaultDensityEquilibriumVelocityCalculation(), lbm::DefaultDensityVelocityCallback() ); +} + +template< typename LatticeModel_T, typename FlagField_T > +shared_ptr< GNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DefaultDensityVelocityCallback > > +makeGNSSweep( const BlockDataID & srcID, const BlockDataID & dstID, const BlockDataID & solidVolumeFractionFieldID, + const ConstBlockDataID & flagFieldID, const Set< FlagUID > & cellsToEvaluate ) +{ + return makeGNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DefaultDensityVelocityCallback > + ( srcID, dstID, solidVolumeFractionFieldID, walberla::field::FlagFieldEvaluationFilter<FlagField_T>( flagFieldID, cellsToEvaluate ), + lbm::DefaultDensityEquilibriumVelocityCalculation(), lbm::DefaultDensityVelocityCallback() ); +} + +// block data IDs of PDF data + flag field as filter + block data ID of velocity field (out) + +template< typename LatticeModel_T, typename FlagField_T, typename VelocityField_T > +shared_ptr< GNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, + lbm::DefaultDensityEquilibriumVelocityCalculation, lbm::VelocityCallback<VelocityField_T> > > +makeGNSSweep( const BlockDataID & pdfFieldID, const BlockDataID & solidVolumeFractionFieldID, + const ConstBlockDataID & flagFieldID, const Set< FlagUID > & cellsToEvaluate, const BlockDataID & velocityFieldID ) +{ + return makeGNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::VelocityCallback<VelocityField_T> > + ( pdfFieldID, solidVolumeFractionFieldID, walberla::field::FlagFieldEvaluationFilter<FlagField_T>( flagFieldID, cellsToEvaluate ), + lbm::DefaultDensityEquilibriumVelocityCalculation(), lbm::VelocityCallback<VelocityField_T>( velocityFieldID ) ); +} + +template< typename LatticeModel_T, typename FlagField_T, typename VelocityField_T > +shared_ptr< GNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, + lbm::DefaultDensityEquilibriumVelocityCalculation, lbm::VelocityCallback<VelocityField_T> > > +makeGNSSweep( const BlockDataID & srcID, const BlockDataID & dstID, const BlockDataID & solidVolumeFractionFieldID, + const ConstBlockDataID & flagFieldID, const Set< FlagUID > & cellsToEvaluate, const BlockDataID & velocityFieldID ) +{ + return makeGNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::VelocityCallback<VelocityField_T> > + ( srcID, dstID, solidVolumeFractionFieldID, walberla::field::FlagFieldEvaluationFilter<FlagField_T>( flagFieldID, cellsToEvaluate ), + lbm::DefaultDensityEquilibriumVelocityCalculation(), lbm::VelocityCallback<VelocityField_T>( velocityFieldID ) ); +} + +// block data IDs of PDF data + flag field as filter + block data IDs of velocity and density field (out) + +template< typename LatticeModel_T, typename FlagField_T, typename VelocityField_T, typename DensityField_T > +shared_ptr< GNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, + lbm::DefaultDensityEquilibriumVelocityCalculation, lbm::DensityVelocityCallback<VelocityField_T,DensityField_T> > > +makeGNSSweep( const BlockDataID & pdfFieldID, const BlockDataID & solidVolumeFractionFieldID, + const ConstBlockDataID & flagFieldID, const Set< FlagUID > & cellsToEvaluate, + const BlockDataID & velocityFieldID, const BlockDataID & densityFieldID ) +{ + return makeGNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DensityVelocityCallback<VelocityField_T,DensityField_T> > + ( pdfFieldID, solidVolumeFractionFieldID, walberla::field::FlagFieldEvaluationFilter<FlagField_T>( flagFieldID, cellsToEvaluate ), + lbm::DefaultDensityEquilibriumVelocityCalculation(), + lbm::DensityVelocityCallback<VelocityField_T,DensityField_T>( velocityFieldID, densityFieldID ) ); +} + +template< typename LatticeModel_T, typename FlagField_T, typename VelocityField_T, typename DensityField_T > +shared_ptr< GNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, + lbm::DefaultDensityEquilibriumVelocityCalculation, lbm::DensityVelocityCallback<VelocityField_T,DensityField_T> > > +makeGNSSweep( const BlockDataID & srcID, const BlockDataID & dstID, const BlockDataID & solidVolumeFractionFieldID, + const ConstBlockDataID & flagFieldID, const Set< FlagUID > & cellsToEvaluate, + const BlockDataID & velocityFieldID, const BlockDataID & densityFieldID ) +{ + return makeGNSSweep< LatticeModel_T, walberla::field::FlagFieldEvaluationFilter<FlagField_T>, lbm::DefaultDensityEquilibriumVelocityCalculation, + lbm::DensityVelocityCallback<VelocityField_T,DensityField_T> > + ( srcID, dstID, solidVolumeFractionFieldID, walberla::field::FlagFieldEvaluationFilter<FlagField_T>( flagFieldID, cellsToEvaluate ), + lbm::DefaultDensityEquilibriumVelocityCalculation(), + lbm::DensityVelocityCallback<VelocityField_T,DensityField_T>( velocityFieldID, densityFieldID ) ); +} + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/gns_lbm/all.h b/src/pe_coupling/discrete_particle_methods/gns_lbm/all.h new file mode 100644 index 000000000..9452674f1 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/gns_lbm/all.h @@ -0,0 +1,30 @@ +//====================================================================================================================== +// +// 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 all.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + + +#include "waLBerlaDefinitions.h" + +#include "utility/all.h" + +#include "GNSSweep.h" + diff --git a/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSExternalForceToForceFieldAdder.h b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSExternalForceToForceFieldAdder.h new file mode 100644 index 000000000..73fc6f547 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSExternalForceToForceFieldAdder.h @@ -0,0 +1,77 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file GNSExternalForceToForceFieldAdder.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Applies a given constant external force to a force field. + * + * Utility function when using external forces with methods that couple over the force field. + * GNS speciality: the external force is additionally multiplied by the fluid volume fraction before it is added. + */ +class GNSExternalForceToForceFieldAdder +{ +public: + typedef GhostLayerField< Vector3<real_t>, 1 > ForceField_T; + typedef GhostLayerField< real_t, 1 > ScalarField_T; + + GNSExternalForceToForceFieldAdder( const BlockDataID & forceFieldID, const ConstBlockDataID & solidVolumeFractionFieldID, const Vector3<real_t> & externalForce ) + : forceFieldID_( forceFieldID ), solidVolumeFractionFieldID_( solidVolumeFractionFieldID ), externalForce_( externalForce ) + { + } + + void operator()( IBlock * const block ) + { + ForceField_T* forceField = block->getData< ForceField_T >( forceFieldID_ ); + const ScalarField_T* solidVolumeFractionField = block->getData< ScalarField_T >( solidVolumeFractionFieldID_ ); + + //TODO include dx force scaling + WALBERLA_FOR_ALL_CELLS_XYZ( forceField, + forceField->get(x,y,z) += ( real_t(1) - solidVolumeFractionField->get(x,y,z) ) * externalForce_; + ); + } + + void reset( const Vector3<real_t> & newExternalForce ) + { + externalForce_ = newExternalForce; + } + +private: + const BlockDataID forceFieldID_; + const ConstBlockDataID solidVolumeFractionFieldID_; + Vector3<real_t> externalForce_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe2_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSPressureFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSPressureFieldEvaluator.h new file mode 100644 index 000000000..c284db5fd --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSPressureFieldEvaluator.h @@ -0,0 +1,81 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file GNSPressureFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Evaluates the pressure field based on the given (GNS) LBM field.. + * + */ +template <typename LatticeModel_T, typename BoundaryHandling_T> +class GNSPressureFieldEvaluator +{ +public: + typedef lbm::PdfField< LatticeModel_T > PdfField_T; + typedef GhostLayerField< real_t, 1 > ScalarField_T; + + GNSPressureFieldEvaluator( const BlockDataID & pressureFieldID, const ConstBlockDataID & pdfFieldID, + const ConstBlockDataID & solidVolumeFractionFieldID, const ConstBlockDataID & boundaryHandlingID ) + : pressureFieldID_( pressureFieldID ), pdfFieldID_( pdfFieldID ), solidVolumeFractionFieldID_( solidVolumeFractionFieldID ), + boundaryHandlingID_( boundaryHandlingID ) + { } + + void operator()(IBlock * const block) + { + const PdfField_T* pdfField = block->getData< PdfField_T >( pdfFieldID_ ); + ScalarField_T* pressureField = block->getData< ScalarField_T >( pressureFieldID_ ); + //const ScalarField_T* solidVolumeFractionField = block->getData< ScalarField_T >( solidVolumeFractionFieldID_ ); + const BoundaryHandling_T * boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ ); + + const real_t c_s_sqr = real_t(1)/real_t(3); + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ( pdfField, + if( boundaryHandling->isDomain(x,y,z) ) + { + real_t fluidDensity = pdfField->getDensity(x,y,z); + //real_t fluidVolumeFraction = real_t(1) - solidVolumeFractionField->get(x,y,z); + + // according to Guo (2002): p = rho * eps / 3 + // However, this would result in pressure gradients in the presence of solid volume fraction gradients. + // As a result, no quiescent steady state solution would exist for a resting fluid above and inside a porous media. + // Therefore, a "generalized" pressure is used, which is simply P = rho / 3 (i.e. the standard LBM pressure calculation). + pressureField->get(x,y,z) = c_s_sqr * fluidDensity; // / fluidVolumeFraction; + } + ); + } + +private: + const BlockDataID pressureFieldID_; + const ConstBlockDataID pdfFieldID_; + const ConstBlockDataID solidVolumeFractionFieldID_; + const ConstBlockDataID boundaryHandlingID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSSmagorinskyLESField.h b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSSmagorinskyLESField.h new file mode 100644 index 000000000..4ae1a67df --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSSmagorinskyLESField.h @@ -0,0 +1,189 @@ +//====================================================================================================================== +// +// 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 GNSSmagorinskyLESField.h +//! \ingroup lbm +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "domain_decomposition/BlockDataID.h" +#include "domain_decomposition/IBlock.h" +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/iterators/IteratorMacros.h" +#include "field/EvaluationFilter.h" +#include "field/GhostLayerField.h" + +#include "lbm/field/PdfField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Adjusts locally the fluid viscosity based on a LES-Smagorinsky turbulence model when using GNS-LBM + * + * The original LES model is discussed in: + * - S. Hou, J. Sterling, S. Chen, G. Doolen, A lattice Boltzmann subgrid model for high reynolds number flows, in: A. T. + * Lawniczak, R. Kapral (Eds.), Pattern formation and lattice gas automata, Vol. 6 of Fields Institute Communications, + * American Mathematical Society, Providence, RI, 1996, p. 151–166. + * - H. Yu, S. S. Girimaji, L.-S. Luo, DNS and LES of decaying isotropic turbulence with and without frame rotation using + * lattice Boltzmann method, Journal of Computational Physics 209 (2) (2005) 599–616. doi:10.1016/j.jcp.2005.03.022. + * + * The second reference suggests to use a Smagorinsky constant of 0.1. + * + * An implementation of if for standard LBM is found in "lbm/lattice_model/SmagorinskyLES.h" + * + * This version is adapted to use the special GNS-LBM equilibrium distribution functions to calculate the + * non-equilibrium PDFs. These are given in Z. Guo, T. S. Zhao - "Lattice Boltzmann model for incompressible flows + * through porous media", Phys. Rev. E 66 (2002)036304. doi:10.1103/PhysRevE.66.036304. + * + * Combining LES with GNS-LBM was proposed in + * - C. Rettinger, U. Ruede - "A Coupled Lattice Boltzmann Method and Discrete Element Method for Discrete Particle + * Simulations of Particulate Flows". arXiv preprint arXiv:1711.00336 (2017) + * + * Uses the value from the omegaField as tau0 and adds a calculated tauTurbulent to it. + * Can be used together with the collision model "SRTField" + */ +template< typename LatticeModel_T, typename Filter_T = field::DefaultEvaluationFilter > +class GNSSmagorinskyLESField +{ +public: + + typedef lbm::PdfField< LatticeModel_T > PdfField_T; + typedef GhostLayerField< real_t, 1 > ScalarField_T; + typedef typename LatticeModel_T::Stencil Stencil_T; + + static_assert( LatticeModel_T::CollisionModel::constant == false, "Only works with non-constant relaxation time fields!" ); + static_assert( LatticeModel_T::compressible == false, "Only works with incompressible models!" ); + + GNSSmagorinskyLESField( const shared_ptr< StructuredBlockStorage > & blocks, + const BlockDataID & omegaFieldID, const ConstBlockDataID & pdfFieldID, + const ConstBlockDataID & solidVolumeFractionFieldID, + const real_t & smagorinskyConstant, + const Set<SUID> & requiredSelectors = Set<SUID>::emptySet(), + const Set<SUID> & incompatibleSelectors = Set<SUID>::emptySet() ) + + : blocks_( blocks ), filter_( Filter_T() ), omegaFieldID_( omegaFieldID ), pdfFieldID_( pdfFieldID ), + solidVolumeFractionFieldID_( solidVolumeFractionFieldID ), smagorinskyConstant_ ( smagorinskyConstant ), + requiredSelectors_(requiredSelectors), incompatibleSelectors_( incompatibleSelectors ) + {} + + GNSSmagorinskyLESField( const shared_ptr< StructuredBlockStorage > & blocks, const Filter_T & filter, + const BlockDataID & omegaFieldID, const ConstBlockDataID & pdfFieldID, + const ConstBlockDataID & solidVolumeFractionFieldID, + const real_t & smagorinskyConstant, + const Set<SUID> & requiredSelectors = Set<SUID>::emptySet(), + const Set<SUID> & incompatibleSelectors = Set<SUID>::emptySet() ) + + : blocks_( blocks ), filter_( filter ), omegaFieldID_( omegaFieldID ), pdfFieldID_( pdfFieldID ), + solidVolumeFractionFieldID_( solidVolumeFractionFieldID ), smagorinskyConstant_ ( smagorinskyConstant ), + requiredSelectors_(requiredSelectors), incompatibleSelectors_( incompatibleSelectors ) + {} + + void operator()( IBlock * const block ); + +private: + + shared_ptr< StructuredBlockStorage > blocks_; + + Filter_T filter_; + + BlockDataID omegaFieldID_; + ConstBlockDataID pdfFieldID_; + ConstBlockDataID solidVolumeFractionFieldID_; + + real_t smagorinskyConstant_; + + Set<SUID> requiredSelectors_; + Set<SUID> incompatibleSelectors_; + +}; // class GNSSmagorinskyLESField + + + +template< typename LatticeModel_T, typename Filter_T > +void GNSSmagorinskyLESField< LatticeModel_T, Filter_T >::operator()( IBlock * const block ) +{ + ScalarField_T * omegaField = block->getData< ScalarField_T >( omegaFieldID_ ); + const PdfField_T * pdfField = block->getData< PdfField_T >( pdfFieldID_ ); + const ScalarField_T * svfField = block->getData< ScalarField_T >( solidVolumeFractionFieldID_ ); + + WALBERLA_ASSERT_NOT_NULLPTR( pdfField ); + WALBERLA_ASSERT_NOT_NULLPTR( omegaField ); + + + WALBERLA_ASSERT_EQUAL( pdfField->xyzSizeWithGhostLayer(), omegaField->xyzSizeWithGhostLayer() ); + + const real_t factor = real_t(18) * std::sqrt( real_t(2) ) * smagorinskyConstant_ * smagorinskyConstant_; + + filter_( *block ); + + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ( omegaField, + + if( filter_(x,y,z) ) + { + const real_t tau0 = real_t(1) / omegaField->get(x,y,z); + + Vector3< real_t > momentumDensity; + const real_t rho = pdfField->getDensityAndEquilibriumMomentumDensity( momentumDensity, x, y, z ); + + Vector3< real_t > velocity = momentumDensity / rho; + + const real_t porosity = real_t(1) - svfField->get(x,y,z); + const real_t invPorosity = real_t(1) / porosity; + + // use special GNS equilibrium + std::vector< real_t > equilibrium( Stencil_T::Size ); + for( auto d = Stencil_T::begin(); d != Stencil_T::end(); ++d ) + { + const real_t wq = LatticeModel_T::w[ d.toIdx() ]; + const real_t vel = real_c(d.cx()) * velocity[0] + real_c(d.cy()) * velocity[1] + real_c(d.cz()) * velocity[2]; + real_t feq = wq * rho * ( real_t(1) - real_t(1.5) * invPorosity * velocity.sqrLength() + + real_t(4.5) * invPorosity * vel * vel + real_t(3.0) * vel ); // modified feq + feq -= wq; // walberla: center around 0 in incompressible case, attention if always needed! + equilibrium[d.toIdx()] = feq; + } + + std::vector< real_t > nonEquilibrium( Stencil_T::Size ); + for( uint_t i = 0; i != Stencil_T::Size; ++i ) + nonEquilibrium[i] = pdfField->get(x,y,z,i) - equilibrium[i]; + + real_t filteredMeanMomentum = real_t(0); + for( uint_t alpha = 0; alpha < 3; ++alpha ) { + for( uint_t beta = 0; beta < 3; ++beta ) + { + real_t qij = real_t(0); + for( auto d = Stencil_T::begin(); d != Stencil_T::end(); ++d ) + qij += nonEquilibrium[ d.toIdx() ] * real_c(stencil::c[alpha][*d]) * real_c(stencil::c[beta][*d]); + filteredMeanMomentum += qij * qij; + } + } + + const real_t tauTurbulent = LatticeModel_T::compressible ? ( real_t(0.5) * ( std::sqrt( tau0 * tau0 + (factor / rho) * std::sqrt( real_t(2) * filteredMeanMomentum ) ) - tau0 ) ) : + ( real_t(0.5) * ( std::sqrt( tau0 * tau0 + factor * std::sqrt( real_t(2) * filteredMeanMomentum ) ) - tau0 ) ); + + omegaField->get(x,y,z) = real_t(1) / ( tau0 + tauTurbulent ); + } + ) +} + + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSVelocityFieldEvaluator.h b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSVelocityFieldEvaluator.h new file mode 100644 index 000000000..157c812b7 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/GNSVelocityFieldEvaluator.h @@ -0,0 +1,88 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file GNSVelocityFieldEvaluator.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Evaluator for the fluid-phase velocity, given a (GNS) PDf field. + * + * Evaluator for the fluid-phase velocity when using GNS-LBM (in contrast to the volume-averaged velocity that is + * obtained by calculating the first order moment of the PDFs). + * + * See: Z. Guo, T. S. Zhao - "Lattice Boltzmann model for incompressible flows through porous media", + * Phys. Rev. E 66 (2002)036304. doi:10.1103/PhysRevE.66.036304. + * + * Since in LBM, the macroscopic velocity depends on the local fluid forcing, one has to pay attention to the currently + * set forces in the corresponding force field before evaluating the velocity. + */ +template <typename LatticeModel_T, typename BoundaryHandling_T> +class GNSVelocityFieldEvaluator +{ +public: + typedef lbm::PdfField< LatticeModel_T > PdfField_T; + typedef GhostLayerField< Vector3<real_t>, 1 > VelocityField_T; + typedef GhostLayerField< real_t, 1 > ScalarField_T; + + GNSVelocityFieldEvaluator( const BlockDataID & velocityFieldID, const ConstBlockDataID & pdfFieldID, const ConstBlockDataID & solidVolumeFractionFieldID, + const ConstBlockDataID & boundaryHandlingID ) + : velocityFieldID_( velocityFieldID ), pdfFieldID_( pdfFieldID ), solidVolumeFractionFieldID_( solidVolumeFractionFieldID ), + boundaryHandlingID_( boundaryHandlingID ) + { } + + void operator()(IBlock * const block) + { + const PdfField_T* pdfField = block->getData< PdfField_T >( pdfFieldID_ ); + VelocityField_T* velocityField = block->getData< VelocityField_T >( velocityFieldID_ ); + const ScalarField_T* svfField = block->getData< ScalarField_T >( solidVolumeFractionFieldID_ ); + const BoundaryHandling_T* boundaryHandling = block->getData< BoundaryHandling_T >( boundaryHandlingID_ ); + + WALBERLA_FOR_ALL_CELLS_XYZ( pdfField, + if( boundaryHandling->isDomain(x,y,z) ) + { + real_t fluidVolumeFraction = real_t(1) - svfField->get(x,y,z); + Vector3<real_t> volumeAverageVelocity = pdfField->getVelocity(x,y,z); + velocityField->get(x,y,z) = volumeAverageVelocity / fluidVolumeFraction; + } + ); + } + +private: + const BlockDataID velocityFieldID_; + const ConstBlockDataID pdfFieldID_; + const ConstBlockDataID solidVolumeFractionFieldID_; + const ConstBlockDataID boundaryHandlingID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/all.h b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/all.h new file mode 100644 index 000000000..25f944954 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/gns_lbm/utility/all.h @@ -0,0 +1,29 @@ +//====================================================================================================================== +// +// 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 all.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "waLBerlaDefinitions.h" + +#include "GNSExternalForceToForceFieldAdder.h" +#include "GNSPressureFieldEvaluator.h" +#include "GNSSmagorinskyLESField.h" +#include "GNSVelocityFieldEvaluator.h" diff --git a/src/pe_coupling/discrete_particle_methods/utility/AveragedInteractionForceFieldToForceFieldAdder.h b/src/pe_coupling/discrete_particle_methods/utility/AveragedInteractionForceFieldToForceFieldAdder.h new file mode 100644 index 000000000..bffee1573 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/utility/AveragedInteractionForceFieldToForceFieldAdder.h @@ -0,0 +1,100 @@ +//====================================================================================================================== +// +// 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 AveragedInteractionForceFieldToForceFieldAdder.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Adds an accumulated force field value to another force field, applying continuous averaging. + * + * The force field contains all external force, etc acting on the fluid, except the fluid-solid interaction forces + * from the DPS coupling. + * The interaction force field only contains the interaction forces, simply added up over several subcycling timesteps, + * with the maximum number given by maximumAveragingSteps. + * This class will average the interaction force over the currently carried out sub cycles (counted via an internal counter) + * and add this averaged quantity to the force field, to get rid of possible oscillations in the interaction force + * and have a valid force field in each sub cycling iteration. + * + * Can also be used to simply add the current interaction force field to the force field if maximumAveragingSteps = 1. + */ +class AveragedInteractionForceFieldToForceFieldAdder +{ +public: + typedef GhostLayerField< Vector3<real_t>, 1 > ForceField_T; + + AveragedInteractionForceFieldToForceFieldAdder( const shared_ptr<StructuredBlockStorage> & blockStorage, const BlockDataID & forceFieldID, + const ConstBlockDataID & interactionForceFieldID, uint_t maximumAveragingSteps ) + : blockStorage_( blockStorage), forceFieldID_( forceFieldID ), interactionForceFieldID_( interactionForceFieldID ), + maximumAveragingSteps_( maximumAveragingSteps ), averagingCounter_( uint_t(0) ) + { + } + + void operator()() + { + ++averagingCounter_; + + const real_t averagingFactor = real_t(1)/real_c(averagingCounter_); + + + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + ForceField_T* forceField = blockIt->getData< ForceField_T >( forceFieldID_ ); + const ForceField_T* interactionForceField = blockIt->getData< ForceField_T >( interactionForceFieldID_ ); + + WALBERLA_FOR_ALL_CELLS_XYZ( forceField, + forceField->get(x,y,z) += averagingFactor * interactionForceField->get(x,y,z); + ); + } + + if( averagingCounter_ >= maximumAveragingSteps_ ) + { + resetAveragingCounter(); + } + + } + + void resetAveragingCounter( ) + { + averagingCounter_ = uint_t(0); + } + +private: + shared_ptr<StructuredBlockStorage> blockStorage_; + const BlockDataID forceFieldID_; + const ConstBlockDataID interactionForceFieldID_; + uint_t maximumAveragingSteps_; + uint_t averagingCounter_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/utility/BodyVelocityInitializer.h b/src/pe_coupling/discrete_particle_methods/utility/BodyVelocityInitializer.h new file mode 100644 index 000000000..5e9a475eb --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/utility/BodyVelocityInitializer.h @@ -0,0 +1,110 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file BodyVelocityInitializer.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/debug/Debug.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/interpolators/all.h" +#include "field/distributors/all.h" +#include "field/GhostLayerField.h" + +#include "pe/rigidbody/BodyIterators.h" +#include "pe/rigidbody/RigidBody.h" +#include "pe/Types.h" +#include "pe/Materials.h" + +#include "pe_coupling/geometry/SphereEquivalentDiameter.h" + +#include <boost/function.hpp> + +#include <cmath> + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Initializes the bodies with the velocity of the surrounding fluid. + * + * The class uses an interpolator to obtain the approximate value of the fluid velocity at the bodies' position, + * and then sets the bodies' velocity accordingly. + * + * For more infos on interpolators, see field interpolators in src/field/interpolators. + */ + +template< typename FlagField_T, template<typename,typename> class FieldInterpolator_T > +class BodyVelocityInitializer +{ + +public: + + typedef GhostLayerField< Vector3<real_t>, 1> Vec3Field_T; + typedef FieldInterpolator_T<Vec3Field_T, FlagField_T> Vec3FieldInterpolator_T; + + BodyVelocityInitializer( const shared_ptr<StructuredBlockStorage> & blockStorage, + const BlockDataID & bodyStorageID, const BlockDataID & flagFieldID, const Set< FlagUID > & domainFlags, + const BlockDataID & velocityFieldID ) + : blockStorage_( blockStorage ), bodyStorageID_( bodyStorageID ) + { + velocityFieldInterpolatorID_ = field::addFieldInterpolator< Vec3FieldInterpolator_T, FlagField_T >( blockStorage, velocityFieldID, flagFieldID, domainFlags ); + } + + void operator()() + { + for( auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt ) + { + + Vec3FieldInterpolator_T * velocityInterpolator = blockIt->getData< Vec3FieldInterpolator_T >( velocityFieldInterpolatorID_ ); + + for( auto bodyIt = pe::LocalBodyIterator::begin(*blockIt, bodyStorageID_); bodyIt != pe::LocalBodyIterator::end(); ++bodyIt ) + { + //TODO check if body should be treated by DPM + + Vector3<real_t> forceOnFluid( real_t(0) ); + + Vector3<real_t> bodyPosition = bodyIt->getPosition(); + + // interpolate fluid velocity to body position + Vector3<real_t> fluidVelocity( real_t(0) ); + velocityInterpolator->get( bodyPosition, &fluidVelocity ); + + WALBERLA_ASSERT( !math::isnan(fluidVelocity[0]) && !math::isnan(fluidVelocity[1]) && !math::isnan(fluidVelocity[2]), + "NaN found in interpolated fluid velocity at position " << bodyPosition ); + + bodyIt->setLinearVel( fluidVelocity ); + } + } + } + +private: + + shared_ptr<StructuredBlockStorage> blockStorage_; + BlockDataID bodyStorageID_; + BlockDataID velocityFieldInterpolatorID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/utility/CombinedReductionFieldCommunication.h b/src/pe_coupling/discrete_particle_methods/utility/CombinedReductionFieldCommunication.h new file mode 100644 index 000000000..0d5345775 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/utility/CombinedReductionFieldCommunication.h @@ -0,0 +1,75 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file CombinedReductionFieldCommunication.h +//! \ingroup pe_coupling +//! \author Tobias Schruff <schruff@iww.rwth-aachen.de> +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "blockforest/communication/UniformBufferedScheme.h" +#include "blockforest/StructuredBlockForest.h" + +#include "field/communication/PackInfo.h" +#include "field/communication/UniformPullReductionPackInfo.h" + +#include "stencil/D3Q27.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +/*!\brief Special communication routine to += the values in the ghost layers and update the ghost layers. + * + * Reduces the field with += to obtain the values from the block neighbors' ghost layers and add them onto the corresponding values inside the domain. + * This is followed by a regular ghost layer communication to synchronize the ghost layer values. + * + * This functionality is typically used to synchronize the solid volume fraction field and the interaction force fields, + * after they have been filled by a distributor. + + * For more infos on distributors, see src/field/distributors. + * + */ +template< typename GhostLayerField_T > +class CombinedReductionFieldCommunication +{ +public: + + CombinedReductionFieldCommunication( const shared_ptr<StructuredBlockForest> & bf, const BlockDataID & glFieldID ) + : pullReductionScheme_( bf ), commScheme_( bf ) + { + pullReductionScheme_.addPackInfo( make_shared< field::communication::UniformPullReductionPackInfo<std::plus, GhostLayerField_T> >( glFieldID ) ); + commScheme_.addPackInfo( make_shared< field::communication::PackInfo<GhostLayerField_T> >( glFieldID ) ); + + } + + void operator()() + { + pullReductionScheme_(); + commScheme_(); + } + +private: + blockforest::communication::UniformBufferedScheme<stencil::D3Q27> pullReductionScheme_; + blockforest::communication::UniformBufferedScheme<stencil::D3Q27> commScheme_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/utility/ExternalForceToForceFieldAdder.h b/src/pe_coupling/discrete_particle_methods/utility/ExternalForceToForceFieldAdder.h new file mode 100644 index 000000000..f5e27bc31 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/utility/ExternalForceToForceFieldAdder.h @@ -0,0 +1,72 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file ExternalForceToForceFieldAdder.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Applies a given constant external force to a force field. + * + * Utility function when using external forces with methods that couple over the force field + * (all discrete particle methods, e.g.) since the force field is typically reset in each iteration. + */ +class ExternalForceToForceFieldAdder +{ +public: + typedef GhostLayerField< Vector3<real_t>, 1 > ForceField_T; + + ExternalForceToForceFieldAdder( const BlockDataID & forceFieldID, const Vector3<real_t> & externalForce ) + : forceFieldID_( forceFieldID ), externalForce_( externalForce ) + { + } + + void operator()( IBlock * const block ) + { + ForceField_T* forceField = block->getData< ForceField_T >( forceFieldID_ ); + + WALBERLA_FOR_ALL_CELLS_XYZ( forceField, + //TODO include level dependent dx force scaling + forceField->get(x,y,z) += externalForce_; + ); + } + + void reset( const Vector3<real_t> & newExternalForce ) + { + externalForce_ = newExternalForce; + } + +private: + const BlockDataID forceFieldID_; + Vector3<real_t> externalForce_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/utility/FieldDataSwapper.h b/src/pe_coupling/discrete_particle_methods/utility/FieldDataSwapper.h new file mode 100644 index 000000000..de5bce4d0 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/utility/FieldDataSwapper.h @@ -0,0 +1,63 @@ +//====================================================================================================================== +// +// 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 FieldDataSwapper.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Swaps the data of two identical fields. + * + * This functionality is e.g. used to store a former version of the velocity field to calculate the time derivative + * of the velocity numerically. + */ +template< typename Field_T > +class FieldDataSwapper +{ +public: + + FieldDataSwapper( const BlockDataID & srcFieldID, const BlockDataID & dstFieldID ) + : srcFieldID_( srcFieldID ), dstFieldID_( dstFieldID ) + { } + + void operator()(IBlock * const block) + { + Field_T* srcField = block->getData< Field_T >( srcFieldID_ ); + Field_T* dstField = block->getData< Field_T >( dstFieldID_ ); + srcField->swapDataPointers(dstField); + } + +private: + const BlockDataID srcFieldID_; + const BlockDataID dstFieldID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/utility/ForceFieldResetter.h b/src/pe_coupling/discrete_particle_methods/utility/ForceFieldResetter.h new file mode 100644 index 000000000..d58e30d51 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/utility/ForceFieldResetter.h @@ -0,0 +1,61 @@ +//====================================================================================================================== +// +// 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 ForceFieldResetter.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "field/GhostLayerField.h" + +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +using math::Vector3; + +/*!\brief Resets the values currently stored in the force field to (0,0,0). + */ +class ForceFieldResetter +{ +public: + typedef GhostLayerField< Vector3<real_t>, 1 > ForceField_T; + + ForceFieldResetter( const BlockDataID & forceFieldID ) + : forceFieldID_( forceFieldID ) + { + } + + void operator()( IBlock * const block ) + { + ForceField_T* forceField = block->getData<ForceField_T>(forceFieldID_); + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(forceField, + forceField->get(x,y,z).reset(); + ) + } + +private: + const BlockDataID forceFieldID_; +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/utility/SubgridTimeStep.h b/src/pe_coupling/discrete_particle_methods/utility/SubgridTimeStep.h new file mode 100644 index 000000000..130a4829f --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/utility/SubgridTimeStep.h @@ -0,0 +1,164 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file SubgridTimeStep.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/debug/Debug.h" +#include "core/timing/TimingTree.h" + +#include "domain_decomposition/BlockStorage.h" + +#include "field/GhostLayerField.h" + +#include "pe/cr/ICR.h" +#include "pe/rigidbody/BodyIterators.h" +#include "pe/synchronization/SyncForces.h" + +#include <boost/function.hpp> + +#include <map> + +/*!\brief Carries out the the PE time steps, including sub iteration functionality. + * + * This class is similar to src/pe_coupling/utility/TimeStep. + * + * It executes 'intermediateSteps' PE steps within one timestep of size 'timeStepSize'. + * + * These PE sub iterations require, that the current external (e.g. hydrodynamic, gravitational,..) forces and torques + * acting on each particle remains unchanged. Thus, a map is set up internally that stores and re-sets these forces + * and torques in each PE sub iteration + * . + * Additionally, a function 'forceEvaluationFunc' can be given that allows to evaluate different forces before the PE + * step is carried out. An example are particle-particle lubrication forces that have to be updated in each sub iteration. + * + */ +namespace walberla { +namespace pe_coupling { +namespace discrete_particle_methods { + +class SubgridTimeStep +{ +public: + + typedef field::GhostLayerField< Vector3<real_t>, 1 > ForceField_T; + + explicit SubgridTimeStep( const shared_ptr<StructuredBlockStorage> & blockStorage, + const BlockDataID & bodyStorageID, + pe::cr::ICR & collisionResponse, + const boost::function<void (void)> & synchronizeFunc, + const boost::function<void ()> & forceEvaluationFunc, + const real_t timeStepSize = real_t(1), const uint_t intermediateSteps = uint_t(1) ) + : timeStepSize_( timeStepSize ) + , intermediateSteps_( ( intermediateSteps == 0 ) ? uint_t(1) : intermediateSteps ) + , blockStorage_( blockStorage ) + , bodyStorageID_(bodyStorageID) + , collisionResponse_(collisionResponse) + , synchronizeFunc_(synchronizeFunc) + , forceEvaluationFunc_(forceEvaluationFunc) + {} + + void operator()() + { + if( intermediateSteps_ == 1 ) + { + forceEvaluationFunc_(); + + collisionResponse_.timestep( timeStepSize_ ); + synchronizeFunc_(); + } + else + { + // sum up all forces from shadow copies on local body (owner) + pe::reduceForces(blockStorage_->getBlockStorage(), bodyStorageID_); + // send total forces to shadow owners + pe::distributeForces(blockStorage_->getBlockStorage(), bodyStorageID_); + + // store force/torques. Those should only contain external forces (gravity, buoyancy,..) + // generate map from all known bodies (process local) to total forces + std::map< walberla::id_t, std::vector< real_t > > forceMap; + for (auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt) + { + for( auto bodyIt = pe::BodyIterator::begin(*blockIt, bodyStorageID_); bodyIt != pe::BodyIterator::end(); ++bodyIt ) + { + auto & f = forceMap[ bodyIt->getSystemID() ]; + + const auto & force = bodyIt->getForce(); + f.push_back( force[0] ); + f.push_back( force[1] ); + f.push_back( force[2] ); + + const auto & torque = bodyIt->getTorque(); + f.push_back( torque[0] ); + f.push_back( torque[1] ); + f.push_back( torque[2] ); + + if ( bodyIt->isRemote() ) + { + bodyIt->resetForceAndTorque(); + } + } + } + + // perform pe sub time steps + const real_t subTimeStepSize = timeStepSize_ / real_c( intermediateSteps_ ); + for( uint_t i = 0; i != intermediateSteps_; ++i ) + { + // evaluate forces (e.g. lubrication forces) + forceEvaluationFunc_(); + + // in the first set forces on local bodies are already set by force synchronization + if( i != 0 ) { + for (auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt) + { + for( auto body = pe::LocalBodyIterator::begin(*blockIt, bodyStorageID_); body != pe::LocalBodyIterator::end(); ++body ) + { + const auto & f = forceMap[ body->getSystemID() ]; + body->addForce ( f[0], f[1], f[2] ); + body->addTorque( f[3], f[4], f[5] ); + } + } + } + + collisionResponse_.timestep( subTimeStepSize ); + synchronizeFunc_(); + } + } + } + +protected: + + const real_t timeStepSize_; + + const uint_t intermediateSteps_; + + shared_ptr<StructuredBlockStorage> blockStorage_; + const BlockDataID & bodyStorageID_; + + pe::cr::ICR & collisionResponse_; + boost::function<void (void)> synchronizeFunc_; + boost::function<void ()> forceEvaluationFunc_; + +}; + + +} // namespace discrete_particle_methods +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/discrete_particle_methods/utility/all.h b/src/pe_coupling/discrete_particle_methods/utility/all.h new file mode 100644 index 000000000..6c4711714 --- /dev/null +++ b/src/pe_coupling/discrete_particle_methods/utility/all.h @@ -0,0 +1,32 @@ +//====================================================================================================================== +// +// 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 all.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "waLBerlaDefinitions.h" + +#include "AveragedInteractionForceFieldToForceFieldAdder.h" +#include "BodyVelocityInitializer.h" +#include "CombinedReductionFieldCommunication.h" +#include "ExternalForceToForceFieldAdder.h" +#include "ForceFieldResetter.h" +#include "SubgridTimeStep.h" +#include "FieldDataSwapper.h" diff --git a/src/pe_coupling/geometry/SphereEquivalentDiameter.h b/src/pe_coupling/geometry/SphereEquivalentDiameter.h new file mode 100644 index 000000000..3e1bfbc7b --- /dev/null +++ b/src/pe_coupling/geometry/SphereEquivalentDiameter.h @@ -0,0 +1,47 @@ +//====================================================================================================================== +// +// 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 SphereEquivalentDiameter.h +//! \ingroup pe_coupling +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#pragma once + + +#include "pe/rigidbody/RigidBody.h" +#include "pe/rigidbody/Sphere.h" + +namespace walberla { +namespace pe_coupling { + + +// calculates sphere-equivalent diameter (diameter of a sphere with same volume as given body) +real_t getSphereEquivalentDiameter( pe::RigidBody & body ) +{ + if( body.getTypeID() == pe::Sphere::getStaticTypeID() ) + { + pe::Sphere & sphere = static_cast<pe::Sphere &>( body ); + real_t radius = sphere.getRadius(); + return real_t(2) * radius; + } else { + const real_t preFac = real_t(6) / math::M_PI; + return std::cbrt( body.getVolume() * preFac ); + } +} + +} // namespace pe_coupling +} // namespace walberla diff --git a/src/pe_coupling/geometry/all.h b/src/pe_coupling/geometry/all.h index 20c149c33..559cd2f3e 100644 --- a/src/pe_coupling/geometry/all.h +++ b/src/pe_coupling/geometry/all.h @@ -25,3 +25,4 @@ #include "PeBodyOverlapFunctions.h" #include "PeIntersectionRatio.h" #include "PeOverlapFraction.h" +#include "SphereEquivalentDiameter.h" diff --git a/tests/pe_coupling/CMakeLists.txt b/tests/pe_coupling/CMakeLists.txt index 8e9fc4f57..25caff061 100644 --- a/tests/pe_coupling/CMakeLists.txt +++ b/tests/pe_coupling/CMakeLists.txt @@ -131,3 +131,13 @@ waLBerla_execute_test( NAME TorqueSphereMEMMRParallelTest COMMAND $<TARGET_FILE waLBerla_compile_test( FILES momentum_exchange_method/SquirmerTest.cpp DEPENDS blockforest pe timeloop ) waLBerla_execute_test( NAME SquirmerShortTest COMMAND $<TARGET_FILE:SquirmerTest> --shortrun PROCESSES 1 ) waLBerla_execute_test( NAME SquirmerTest COMMAND $<TARGET_FILE:SquirmerTest> PROCESSES 1 CONFIGURATIONS Release RelWithDbgInfo ) + +################################################################################################### +# Discrete particle methods tests +################################################################################################### + +waLBerla_compile_test( FILES discrete_particle_methods/SphereWallCollisionBehaviorDPM.cpp DEPENDS blockforest pe timeloop ) +waLBerla_execute_test( NAME SphereWallCollisionBehaviorDPMFuncTest COMMAND $<TARGET_FILE:SphereWallCollisionBehaviorDPM> --funcTest PROCESSES 1 ) + +waLBerla_compile_test( FILES discrete_particle_methods/HinderedSettlingDynamicsDPM.cpp DEPENDS blockforest pe timeloop ) +waLBerla_execute_test( NAME HinderedSettlingDynamicsDPMFuncTest COMMAND $<TARGET_FILE:HinderedSettlingDynamicsDPM> --funcTest PROCESSES 4 LABELS longrun CONFIGURATIONS RelWithDbgInfo ) diff --git a/tests/pe_coupling/discrete_particle_methods/HinderedSettlingDynamicsDPM.cpp b/tests/pe_coupling/discrete_particle_methods/HinderedSettlingDynamicsDPM.cpp new file mode 100644 index 000000000..8786074dd --- /dev/null +++ b/tests/pe_coupling/discrete_particle_methods/HinderedSettlingDynamicsDPM.cpp @@ -0,0 +1,1771 @@ +//====================================================================================================================== +// +// 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 HinderedSettlingDynamicsDPM.cpp +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#include "core/all.h" +#include "boundary/all.h" +#include "lbm/all.h" +#include "pe_coupling/all.h" + +#include "blockforest/Initialization.h" +#include "blockforest/communication/all.h" + +#include "core/grid_generator/SCIterator.h" +#include "core/logging/all.h" + +#include "field/AddToStorage.h" +#include "field/communication/PackInfo.h" +#include "field/distributors/all.h" +#include "field/interpolators/all.h" + +#include "pe/basic.h" +#include "pe/vtk/SphereVtkOutput.h" +#include "pe/Types.h" + +#include "stencil/D3Q27.h" + +#include "timeloop/SweepTimeloop.h" + +#include "vtk/VTKOutput.h" + +#include <vector> +#include <iomanip> +#include <iostream> +#include <random> + +namespace hindered_settling_dynamics_dpm +{ + +/////////// +// USING // +/////////// + +using namespace walberla; +using walberla::uint_t; + + +/////////////// +// CONSTANTS // +/////////////// + +const uint_t FieldGhostLayers( 1 ); + +////////////// +// TYPEDEFS // +////////////// + +// PDF field, flag field & body field +typedef GhostLayerField< Matrix3<real_t>, 1 > TensorField_T; +typedef GhostLayerField< Vector3<real_t>, 1 > Vec3Field_T; +typedef GhostLayerField< real_t, 1 > ScalarField_T; +typedef lbm::force_model::GuoField< Vec3Field_T > ForceModel_T; + +typedef lbm::D3Q19< lbm::collision_model::SRTField<ScalarField_T>, false, ForceModel_T > 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; + +// boundary handling +typedef lbm::NoSlip< LatticeModel_T, flag_t > NoSlip_T; + +typedef boost::tuples::tuple< NoSlip_T > BoundaryConditions_T; +typedef BoundaryHandling<FlagField_T, Stencil_T, BoundaryConditions_T> BoundaryHandling_T; + +typedef boost::tuple<pe::Plane, pe::Sphere> BodyTypeTuple ; + +/////////// +// FLAGS // +/////////// + +const FlagUID Fluid_Flag ( "fluid" ); +const FlagUID NoSlip_Flag ( "no slip flag" ); + +// coupling methods +enum DPMethod { GNS }; + +// interpolation (for PDFs, velocity and/or solid volume fraction) +enum Interpolation { INearestNeighbor, IKernel, ITrilinear }; + +// force distribution +enum Distribution { DNearestNeighbor, DKernel }; + +//drag correlation +enum DragCorrelation { ErgunWenYu, Tang, Stokes, Felice, Tenneti, NoDrag }; + +//lift correlation +enum LiftCorrelation { NoLift, Saffman }; + +//added mass correlation +enum AddedMassCorrelation { NoAM, Finn }; + +//effective viscosity +enum EffectiveViscosity { None, Rescaled, Eilers }; + +///////////////////////////////////// +// BOUNDARY HANDLING CUSTOMIZATION // +///////////////////////////////////// +class MyBoundaryHandling +{ +public: + + MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID ) : + flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ) {} + + BoundaryHandling_T * operator()( IBlock* const block, const StructuredBlockStorage* const/* storage */ ) const; + +private: + + const BlockDataID flagFieldID_; + const BlockDataID pdfFieldID_; + +}; // class MyBoundaryHandling + + +BoundaryHandling_T * MyBoundaryHandling::operator()( IBlock * const block, const StructuredBlockStorage * const /*storage*/ ) const +{ + WALBERLA_ASSERT_NOT_NULLPTR( block ); + + FlagField_T * flagField = block->getData< FlagField_T >( flagFieldID_ ); + PdfField_T * pdfField = block->getData< PdfField_T > ( pdfFieldID_ ); + + const auto fluid = flagField->flagExists( Fluid_Flag ) ? flagField->getFlag( Fluid_Flag ) : flagField->registerFlag( Fluid_Flag ); + + BoundaryHandling_T * handling = new BoundaryHandling_T( "Boundary handling", flagField, fluid, + boost::tuples::make_tuple( NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ) ) ); + + handling->fillWithDomain( FieldGhostLayers ); + return handling; +} +//******************************************************************************************************************* + + +// VTK Output +shared_ptr<vtk::VTKOutput> createFluidFieldVTKWriter( shared_ptr< StructuredBlockForest > & blocks, + const BlockDataID & pdfFieldID, const BlockDataID & flagFieldID, uint_t writeFrequency, + const std::string & vtkBaseFolder ) +{ + auto pdfFieldVTKWriter = vtk::createVTKOutput_BlockData( blocks, "fluid_field", writeFrequency, uint_t(1), false, vtkBaseFolder ); + + blockforest::communication::UniformBufferedScheme<stencil::D3Q27> pdfGhostLayerSync( blocks ); + pdfGhostLayerSync.addPackInfo( make_shared< field::communication::PackInfo<PdfField_T> >( pdfFieldID ) ); + pdfFieldVTKWriter->addBeforeFunction( pdfGhostLayerSync ); + + field::FlagFieldCellFilter< FlagField_T > fluidFilter( flagFieldID ); + fluidFilter.addFlag( Fluid_Flag ); + pdfFieldVTKWriter->addCellInclusionFilter( fluidFilter ); + + auto velocityWriter = make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "Velocity (Lattice)" ); + auto densityWriter = make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "Density (Lattice)" ); + pdfFieldVTKWriter->addCellDataWriter( velocityWriter ); + pdfFieldVTKWriter->addCellDataWriter( densityWriter ); + + return pdfFieldVTKWriter; +} + +DPMethod to_DPMethod( const std::string& s ) +{ + if( s == "GNS" ) return DPMethod::GNS; + throw std::runtime_error("invalid conversion from " + s + " to DPMethod"); +} + +Interpolation to_interpolation( const std::string& s ) +{ + if( s == "nearest" ) return Interpolation::INearestNeighbor; + if( s == "kernel" ) return Interpolation::IKernel; + if( s == "trilinear" ) return Interpolation::ITrilinear; + throw std::runtime_error("invalid conversion from " + s + " to Interpolation"); +} + +Distribution to_distribution( const std::string& s ) +{ + if( s == "nearest" ) return Distribution::DNearestNeighbor; + if( s == "kernel" ) return Distribution::DKernel; + throw std::runtime_error("invalid conversion from " + s + " to Distribution"); +} + +DragCorrelation to_dragCorrelation( const std::string& s ) +{ + if( s == "ergun" ) return DragCorrelation::ErgunWenYu; + if( s == "tang" ) return DragCorrelation::Tang; + if( s == "stokes" ) return DragCorrelation::Stokes; + if( s == "felice" ) return DragCorrelation::Felice; + if( s == "tenneti" ) return DragCorrelation::Tenneti; + if( s == "none" ) return DragCorrelation::NoDrag; + throw std::runtime_error("invalid conversion from " + s + " to DragCorrelation"); +} + +LiftCorrelation to_liftCorrelation( const std::string& s ) +{ + if( s == "none" ) return LiftCorrelation::NoLift; + if( s == "saffman" ) return LiftCorrelation::Saffman; + throw std::runtime_error("invalid conversion from " + s + " to LiftCorrelation"); +} + +AddedMassCorrelation to_addedMassCorrelation( const std::string& s ) +{ + if( s == "none" ) return AddedMassCorrelation::NoAM; + if( s == "finn" ) return AddedMassCorrelation::Finn; + throw std::runtime_error("invalid conversion from " + s + " to AddedMassCorrelation"); +} + +EffectiveViscosity to_effvisc( const std::string& s ) +{ + if( s == "none" ) return EffectiveViscosity::None; + if( s == "rescaled" ) return EffectiveViscosity::Rescaled; + if( s == "eilers" ) return EffectiveViscosity::Eilers; + throw std::runtime_error("invalid conversion from " + s + " to effective viscosity"); +} + +std::string dpm_to_string( const DPMethod& dpm ) +{ + if( dpm == DPMethod::GNS ) return "GNS"; + throw std::runtime_error("invalid conversion from DPMethod to string"); +} + +uint_t createSpheresRandomly( StructuredBlockForest & forest, pe::BodyStorage & globalBodyStorage, + const BlockDataID & bodyStorageID, const AABB & generationDomain, + real_t diameter, real_t solidVolumeFraction, pe::MaterialID & material, real_t initialZVelocity ) +{ + real_t domainVolume = generationDomain.volume(); + real_t totalSphereVolume = domainVolume * solidVolumeFraction; + real_t sphereVolume = diameter * diameter * diameter * math::M_PI / real_t(6); + uint_t numberOfSpheres = uint_c( totalSphereVolume / sphereVolume ); + + real_t xParticle = real_t(0); + real_t yParticle = real_t(0); + real_t zParticle = real_t(0); + + for( uint_t nSphere = 0; nSphere < numberOfSpheres; ++nSphere ) + { + + WALBERLA_ROOT_SECTION() + { + xParticle = math::realRandom<real_t>(generationDomain.xMin(), generationDomain.xMax()); + yParticle = math::realRandom<real_t>(generationDomain.yMin(), generationDomain.yMax()); + zParticle = math::realRandom<real_t>(generationDomain.zMin(), generationDomain.zMax()); + } + + WALBERLA_MPI_SECTION() + { + mpi::broadcastObject( xParticle ); + mpi::broadcastObject( yParticle ); + mpi::broadcastObject( zParticle ); + } + + + pe::SphereID sp = pe::createSphere( globalBodyStorage, forest.getBlockStorage(), bodyStorageID, 0, Vector3<real_t>( xParticle, yParticle, zParticle ), diameter * real_t(0.5), material ); + if( sp != NULL ) + { + sp->setLinearVel(Vector3<real_t>(real_t(0),real_t(0),initialZVelocity)); + } + } + + return numberOfSpheres; +} + +uint_t createSphereLattice( StructuredBlockForest & forest, pe::BodyStorage & globalBodyStorage, + const BlockDataID & bodyStorageID, const AABB & generationDomain, + real_t diameter, real_t solidVolumeFraction, pe::MaterialID & material, real_t initialZVelocity ) +{ + real_t sphereVolume = math::M_PI * diameter * diameter * diameter / real_t(6); + real_t numSpheresDesired = solidVolumeFraction * generationDomain.volume() / sphereVolume; + uint_t spheresPerDirection = uint_c(std::cbrt(numSpheresDesired) ); + + real_t spacing = generationDomain.xSize() / real_c(spheresPerDirection); + + WALBERLA_ASSERT( spacing >= diameter ); + + Vector3<real_t> generationOrigin( generationDomain.xMin() + spacing * real_t(0.5), generationDomain.yMin() + spacing * real_t(0.5), generationDomain.zMin() + spacing * real_t(0.5)); + + uint_t numSpheres( 0 ); + + for( auto it = grid_generator::SCIterator(generationDomain, generationOrigin, spacing); it != grid_generator::SCIterator(); ++it ) + { + pe::SphereID sp = pe::createSphere( globalBodyStorage, forest.getBlockStorage(), bodyStorageID, 0, *it, diameter * real_t(0.5), material ); + + if( sp != NULL ) + { + sp->setLinearVel(Vector3<real_t>(real_t(0),real_t(0),initialZVelocity)); + ++numSpheres; + } + } + + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( numSpheres, mpi::SUM ); + } + + WALBERLA_LOG_INFO_ON_ROOT("Created spheres in lattice arrangement with a center-to-center spacing of " << spacing << " ( " << real_t(100)*spacing/diameter << "% of diameter )" ); + + return numSpheres; +} + +void resetSphereVelocities( const shared_ptr<StructuredBlockForest> & blocks, const BlockDataID & bodyStorageID, Vector3<real_t> vel ) +{ + 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->setAngularVel(real_t(0),real_t(0),real_t(0)); + bodyIt->setLinearVel(vel); + } + } +} + +class QuantityEvaluator +{ +public: + + QuantityEvaluator( SweepTimeloop* timeloop, StructuredBlockStorage & blocks, + const BlockDataID & bodyStorageID, const BlockDataID & forceFieldID, const BlockDataID & pdfFieldID, + const std::string & fileName, bool fileIO, const uint_t & numSpheres, const real_t & velExpected, + const boost::function<Vector3<real_t> ()> & evaluateMeanFluidVelocity ) : + timeloop_( timeloop ), blocks_( blocks ), bodyStorageID_( bodyStorageID ), forceFieldID_( forceFieldID ), + pdfFieldID_( pdfFieldID ), fileName_( fileName ), fileIO_( fileIO ), + numSpheres_( numSpheres ), velExpected_( velExpected ), evaluateMeanFluidVelocity_( evaluateMeanFluidVelocity ) + { + if( fileIO ) { + std::ofstream file; + file.open(fileName_.c_str()); + file + << "#t velExpected fX_particle fY_particle fZ_particle fX_fluid fY_fluid fZ_fluid velX_particle velY_particle velZ_particle velX_fluid velY_fluid velZ_fluid velX_rel velY_rel velZ_rel\n"; + file.close(); + } + } + + void operator()() { + // get mean particle velocity + Vector3<real_t> particleVelocity = getMeanParticleVelocity(); + // get force on particles + Vector3<real_t> particleForce = getParticleForce(); + // get force on fluid + Vector3<real_t> fluidForce = getFluidForce(); + //get mean fluid velocity + Vector3<real_t> fluidVelocity = evaluateMeanFluidVelocity_(); + + WALBERLA_LOG_INFO_ON_ROOT( + "mean particle force = " << particleForce[2] << ", mean fluid velocity = " << fluidVelocity[2] + << ", mean fluid force = " << fluidForce[2]); + + if( fileIO_ ) + { + WALBERLA_ROOT_SECTION() + { + writeToFile(particleVelocity, particleForce, fluidForce, fluidVelocity); + } + } + } + + real_t getSettlingVelocity() + { + Vector3<real_t> particleVelocity = getMeanParticleVelocity(); + Vector3<real_t> fluidVelocity = evaluateMeanFluidVelocity_(); + return particleVelocity[2] - fluidVelocity[2]; + } + + Vector3<real_t> getMeanFluidVelocity() + { + return evaluateMeanFluidVelocity_(); + } + + Vector3<real_t> getMeanParticleVelocity() + { + Vector3<real_t> velocity(0.); + uint_t counter = 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 ) + { + velocity += bodyIt->getLinearVel(); + ++counter; + } + } + + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( velocity[0], mpi::SUM ); + mpi::allReduceInplace( velocity[1], mpi::SUM ); + mpi::allReduceInplace( velocity[2], mpi::SUM ); + mpi::allReduceInplace( counter, mpi::SUM ); + } + + WALBERLA_CHECK_EQUAL( counter, numSpheres_, "Total number of spheres in the domain has changed!" ); + + return velocity / real_c(counter); + } + + Vector3<real_t> getMeanForceOnParticles() + { + Vector3<real_t> force = getParticleForce(); + return force; + } + +private: + + Vector3<real_t> getParticleForce() + { + Vector3<real_t> force(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 ) + { + force += bodyIt->getForce(); + } + } + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( force[0], mpi::SUM ); + mpi::allReduceInplace( force[1], mpi::SUM ); + mpi::allReduceInplace( force[2], mpi::SUM ); + } + return force; + } + Vector3<real_t> getFluidForce() + { + + Vector3<real_t> force(0.); + for( auto blockIt = blocks_.begin(); blockIt != blocks_.end(); ++blockIt ) + { + Vec3Field_T* forceField = blockIt->getData< Vec3Field_T >( forceFieldID_ ); + WALBERLA_FOR_ALL_CELLS_XYZ( forceField, + force += forceField->get(x,y,z); + ); + } + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( force[0], mpi::SUM ); + mpi::allReduceInplace( force[1], mpi::SUM ); + mpi::allReduceInplace( force[2], mpi::SUM ); + } + return force; + } + + void writeToFile( const Vector3<real_t> & meanParticleVel, const Vector3<real_t> & particleForce, const Vector3<real_t> & fluidForce, + const Vector3<real_t> & meanFluidVel ) + { + std::ofstream file; + file.open( fileName_.c_str(), std::ofstream::app ); + file.precision(8); + + file << timeloop_->getCurrentTimeStep() << "\t " << velExpected_ << "\t " + << particleForce[0] << "\t " << particleForce[1] << "\t " << particleForce[2] << "\t " + << fluidForce[0] << "\t " << fluidForce[1] << "\t " << fluidForce[2] << "\t " + << meanParticleVel[0] << "\t " << meanParticleVel[1] << "\t " << meanParticleVel[2] << "\t " + << meanFluidVel[0] << "\t " << meanFluidVel[1] << "\t " << meanFluidVel[2] << "\t" + << meanParticleVel[0] - meanFluidVel[0] << "\t " << meanParticleVel[1] - meanFluidVel[1] << "\t " << meanParticleVel[2] - meanFluidVel[2] << "\n"; + file.close(); + } + + SweepTimeloop* timeloop_; + StructuredBlockStorage & blocks_; + const BlockDataID bodyStorageID_; + const BlockDataID forceFieldID_; + const BlockDataID pdfFieldID_; + std::string fileName_; + bool fileIO_; + const uint_t numSpheres_; + const real_t velExpected_; + + boost::function<Vector3<real_t> ()> evaluateMeanFluidVelocity_; + + +}; + +Vector3<real_t> getGNSMeanFluidVelocity( const shared_ptr<StructuredBlockStorage> & blocks, BlockDataID pdfFieldID, BlockDataID svfFieldID, real_t domainVolume ) +{ + Vector3<real_t> velocity( real_t(0) ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) + { + PdfField_T* pdfField = blockIt->getData< PdfField_T >( pdfFieldID ); + ScalarField_T* svfField = blockIt->getData< ScalarField_T >( svfFieldID ); + WALBERLA_FOR_ALL_CELLS_XYZ( pdfField, + real_t svf = svfField->get(x,y,z); + velocity += pdfField->getVelocity(x,y,z) / (real_t(1) - svf ); + ); + } + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( velocity[0], mpi::SUM ); + mpi::allReduceInplace( velocity[1], mpi::SUM ); + mpi::allReduceInplace( velocity[2], mpi::SUM ); + } + return velocity / domainVolume; +} + +void logSingleResultToFile( const std::string & fileName, const real_t & solidVolumeFraction, const real_t & settlingVel, + const real_t & expectedVel, const real_t & velUnhindered, const real_t & ReynoldsNumber, const real_t & tau ) +{ + WALBERLA_ROOT_SECTION() + { + std::ofstream file; + file.open( fileName.c_str(), std::ofstream::app ); + file.precision(8); + + file << solidVolumeFraction << " \t" << settlingVel << " \t" << expectedVel << " \t" + << settlingVel/velUnhindered << " \t" << expectedVel/velUnhindered << " \t" << ReynoldsNumber << " \t" << tau << "\n"; + } +} + + +class CollisionPropertiesEvaluator +{ +public: + CollisionPropertiesEvaluator( pe::cr::ICR & collisionResponse ) : collisionResponse_( collisionResponse ), maximumPenetration_(real_t(0)) + {} + + void operator()() + { + real_t maxPen = collisionResponse_.getMaximumPenetration(); + maximumPenetration_ = std::max( maximumPenetration_, maxPen ); + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( maximumPenetration_, mpi::MAX ); + } + } + real_t getMaximumPenetrationInSimulation() + { + return maximumPenetration_; + } + void resetMaximumPenetration() + { + maximumPenetration_ = real_t(0); + } +private: + pe::cr::ICR & collisionResponse_; + real_t maximumPenetration_; +}; + +class DummySweep +{ +public: + DummySweep( ) + {} + + void operator()(IBlock * const /*block*/) + {} +}; + +void emptyFunction(){} + +//******************************************************************************************************************* +/*!\brief Testcase that evaluates the hindered settling velocity of several spheres in a periodic box filled with fluid + * + * Sphere of radius D are randomly generated inside the domain to obtain a specific global solid volume fraction + * and then start to settle under gravity. + * The domain size is [32 x 32 x 32] * D and fully periodic. + * _______________ + * | o o o o | + * | o o | | + * | o o | | gravity (z-direction) + * | o o o| v + * |o o o o | + * | o o o | + * |_______o_____o_| + * + * An external force is applied onto the fluid in opposite gravitational direction to counteract the settling motion and + * avoid infinitely large settling velocity (since no walls are present). + * + * + * Many parts of the discrete particle method can be modified via command line arguments: + * - discrete particle method + * - interpolation method + * - distribution method + * - number of interaction subcycles + * - number of pe steps per interaction subcycle + * - correlations for drag, lift, added mass + * - effective viscosity + * - turbulence model + * - lubrication force and its cut off distance + * + * An extensive description of the different components and this test case is given in Rettinger, Ruede (2017). + * + * + * References: + * Experimental: + * - J. Richardson, W. Zaki - "The sedimentation of a suspension of uniform spheres under conditions of viscous flow", + * Chemical Engineering Science 3 (2) (1954) 65–73. doi:10.1016/0009-2509(54)85015-9. + * - T. Baldock, M. Tomkins, P. Nielsen, M. Hughes - "Settling velocity of sediments at high concentrations", + * Coastal Engineering 51 (1) (2004) 91–100. doi:10.1016/j.coastaleng.2003.12.004. + * + * Discrete particle simulations: + * - C. Rettinger, U. Ruede - "A Coupled Lattice Boltzmann Method and Discrete Element Method for Discrete Particle + * Simulations of Particulate Flows". arXiv preprint arXiv:1711.00336 (2017) + * - J. R. Finn, M. Li, S. V. Apte - "Particle based modelling and simulation of natural sand dynamics in the wave bottom + * boundary layer", Journal of Fluid Mechanics 796 (2016) 340–385. doi:10.1017/jfm.2016.246. + * + */ +//******************************************************************************************************************* +int main( int argc, char **argv ) +{ + debug::enterTestMode(); + + mpi::Environment env( argc, argv ); + + + ///////////////////////////////////////////// + // // + // Command Line Argument Customization // + // // + ///////////////////////////////////////////// + + bool funcTest = false; + bool vtkIO = false; + bool fileIO = false; + uint_t vtkWriteFrequency = 0; + std::string baseFolder = "vtk_out_HinderedSettlingDPM"; + + real_t densityRatio = real_t(2500) / real_t(1000); + real_t gravity = real_t(0.002); // has to be small enough to keep settling velocities small + real_t diameter = real_t(0.5); + uint_t interactionSubCycles = uint_t(1); // number of subcycles that involve evaluation of the interaction force -> around 3 for stability with added mass + uint_t peSubSteps = uint_t(1); // number of pe only calls in each subcycle + real_t solidVolumeFraction = real_t(0.05); + + DPMethod dpm = DPMethod::GNS; + Interpolation interpol = Interpolation::IKernel; + Distribution dist = Distribution::DKernel; + DragCorrelation dragCorr = DragCorrelation::Tenneti; + LiftCorrelation liftCorr = LiftCorrelation ::NoLift; + AddedMassCorrelation addedMassCorr = AddedMassCorrelation::NoAM; + EffectiveViscosity effVisc = EffectiveViscosity::None; + + bool useTurbulenceModel = false; + const real_t smagorinskyConstant = real_t(0.1); //for turbulence model + + real_t lubricationCutOffDistance = real_t(0); //0 switches it off + bool useLubricationCorrection = false; // false: use full lubrication force, true: use only correction part + + bool createSpheresInLattice = false; + bool initialSimulationToAdjustFluidForcing = false; + + real_t initialSphereVelocityZ( real_t(0) ); + + real_t relativeVelDiffLimit( real_t(1e-5) ); //set negative to avoid convergence before 30 dimensionless timesteps + + for( int i = 1; i < argc; ++i ) + { + if( std::strcmp( argv[i], "--funcTest" ) == 0 ) funcTest = true; + else if( std::strcmp( argv[i], "--vtkIOFreq" ) == 0 ) vtkWriteFrequency = uint_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--fileIO" ) == 0 ) fileIO = true; + else if( std::strcmp( argv[i], "--baseFolder" ) == 0 ) baseFolder = argv[++i]; + else if( std::strcmp( argv[i], "--gravity" ) == 0 ) gravity = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--diameter" ) == 0 ) diameter = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--solidVolumeFraction" ) == 0 ) solidVolumeFraction = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--interactionSubCycles" ) == 0 ) interactionSubCycles = uint_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--peSubSteps" ) == 0 ) peSubSteps = uint_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--DPMethod" ) == 0 ) dpm = to_DPMethod( argv[++i] ); + else if( std::strcmp( argv[i], "--interpolation" ) == 0 ) interpol = to_interpolation( argv[++i] ); + else if( std::strcmp( argv[i], "--distribution" ) == 0 ) dist = to_distribution( argv[++i] ); + else if( std::strcmp( argv[i], "--dragCorrelation" ) == 0 ) dragCorr = to_dragCorrelation( argv[++i] ); + else if( std::strcmp( argv[i], "--liftCorrelation" ) == 0 ) liftCorr = to_liftCorrelation( argv[++i] ); + else if( std::strcmp( argv[i], "--addedMassCorrelation" ) == 0 ) addedMassCorr = to_addedMassCorrelation( argv[++i] ); + else if( std::strcmp( argv[i], "--effectiveViscosity" ) == 0 ) effVisc = to_effvisc( argv[++i] ); + else if( std::strcmp( argv[i], "--useTurbulenceModel" ) == 0 ) useTurbulenceModel = true; + else if( std::strcmp( argv[i], "--useLubricationCorrection" ) == 0 ) useLubricationCorrection = true; + else if( std::strcmp( argv[i], "--lubricationCutOff" ) == 0 ) lubricationCutOffDistance = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--createSpheresInLattice" ) == 0 ) createSpheresInLattice = true; + else if( std::strcmp( argv[i], "--initialSimulation" ) == 0 ) initialSimulationToAdjustFluidForcing = true; + else if( std::strcmp( argv[i], "--convergenceLimit" ) == 0 ) relativeVelDiffLimit = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--initialSphereVelocityZ" ) == 0 ) initialSphereVelocityZ = real_c( std::atof( argv[++i] ) ); + else WALBERLA_ABORT("Found invalid command line argument \"" << argv[i] << "\" - aborting..."); + } + + if( vtkWriteFrequency > 0 ) vtkIO = true; + + WALBERLA_CHECK( diameter <= real_t(1), "Diameter is not allowed to be > 1!" ); + WALBERLA_CHECK( solidVolumeFraction <= real_t(0.65), "Solid volume fraction is not allowed to be > 0.65!" ); + WALBERLA_CHECK( interactionSubCycles > uint_t(0), "Number of interaction sub cycles has to be at least 1!"); + WALBERLA_CHECK( peSubSteps > uint_t(0), "Number of pe sub steps has to be at least 1!"); + WALBERLA_CHECK( lubricationCutOffDistance >= real_t(0), "Lubrication cut off distance has to be non-negative!"); + + if( funcTest ) + { + walberla::logging::Logging::instance()->setLogLevel(logging::Logging::LogLevel::WARNING); + } + if( fileIO ) + { + // create base directory if it does not yet exist + boost::filesystem::path tpath( baseFolder ); + if( !boost::filesystem::exists( tpath ) ) + boost::filesystem::create_directory( tpath ); + } + + /////////////////////////////// + // // + // SIMULATION PROPERTIES // + // // + /////////////////////////////// + + const uint_t xlength = uint_c( real_t(32) * diameter ) ; + const uint_t ylength = xlength; + const uint_t zlength = xlength; + + if( solidVolumeFraction < real_t(1e-10) ) + { + // create only a single sphere + solidVolumeFraction = math::M_PI * diameter * diameter * diameter / ( real_t(6) * real_c( xlength * ylength * zlength)); + } + + const real_t diameter_SI = real_t(0.00035); // m, Finn et al, Tab 5 + const real_t gravity_SI = real_t(9.81); // m/s^2 + + const real_t dx_SI = diameter_SI / diameter; + const real_t dx = real_t(1); + const real_t viscosity_SI = real_t(1e-3); // kg/(ms) + const real_t densityFluid_SI = real_t(1e3); // kg/m^3 + + const real_t dt_SI = std::sqrt(gravity * dx_SI / gravity_SI); + const real_t viscosity = ( viscosity_SI/densityFluid_SI ) * dt_SI / ( dx_SI * dx_SI ); + const real_t tau = real_t(1) / lbm::collision_model::omegaFromViscosity( viscosity ); + + real_t gravitationalForce = - gravity * ( densityRatio - real_t(1) ) * diameter * diameter * diameter * math::PI / real_t(6); + + // unhindered settling velocity of a single sphere in infinite fluid, would come from experiments or DNS, NOT Stokes settling velocity, from Finn et al, Tab 5 + const real_t velUnhindered_SI = real_t(-0.048); // m/s + + const real_t velStokes = -( densityRatio - real_t(1) ) * diameter * diameter * gravity / ( real_t(18) * viscosity ); + const real_t velUnhindered = velUnhindered_SI * dt_SI / dx_SI; + + const real_t dt_DEM = real_t(1) / real_c(interactionSubCycles * peSubSteps); + + const real_t dt = real_t(1); + const real_t dtInteractionSubCycle = dt / real_c(interactionSubCycles); + const real_t dtBodyVelocityTimeDerivativeEvaluation = dtInteractionSubCycle; + + const uint_t tStokes = uint_c(densityRatio * diameter * diameter / ( real_t(18) * viscosity )); + + const uint_t timesteps = (funcTest) ? uint_t(10) : uint_t(30) * tStokes; // total number of time steps for the whole simulation + + const Vector3<real_t> initialFluidVelocity( real_t(0) ); + + const std::string fileNameLoggingInit = baseFolder+"/evalHinderedSettlingDynamicsSubgrid_eps"+std::to_string(uint_c(real_t(100) * solidVolumeFraction))+"_d"+std::to_string(uint_c(real_t(100) * diameter))+"_init.txt"; + const std::string fileNameLogging = baseFolder+"/evalHinderedSettlingDynamicsSubgrid_eps"+std::to_string(uint_c(real_t(100) * solidVolumeFraction))+"_d"+std::to_string(uint_c(real_t(100) * diameter))+".txt"; + + if( !funcTest ) { + WALBERLA_LOG_INFO_ON_ROOT("Lx x Ly x Lz = " << xlength << " x " << ylength << " x " << zlength); + WALBERLA_LOG_INFO_ON_ROOT("tau = " << tau); + WALBERLA_LOG_INFO_ON_ROOT("viscosity = " << viscosity); + WALBERLA_LOG_INFO_ON_ROOT("gravity = " << gravity); + WALBERLA_LOG_INFO_ON_ROOT("total external (gravity & buoyancy) force on single sphere = " << gravitationalForce); + WALBERLA_LOG_INFO_ON_ROOT("diameter = " << diameter); + WALBERLA_LOG_INFO_ON_ROOT("input solid volume fraction = " << solidVolumeFraction); + WALBERLA_LOG_INFO_ON_ROOT("discrete particle method = " << dpm_to_string(dpm)); + WALBERLA_LOG_INFO_ON_ROOT("interpolator = " << interpol); + WALBERLA_LOG_INFO_ON_ROOT("distribution = " << dist); + WALBERLA_LOG_INFO_ON_ROOT("dragCorrelation = " << dragCorr); + WALBERLA_LOG_INFO_ON_ROOT("addedMassCorrelation = " << addedMassCorr); + WALBERLA_LOG_INFO_ON_ROOT("liftCorrelation = " << liftCorr); + WALBERLA_LOG_INFO_ON_ROOT("effective viscosity = " << effVisc); + WALBERLA_LOG_INFO_ON_ROOT("turbulence model = " << (useTurbulenceModel ? "yes" : "no")); + WALBERLA_LOG_INFO_ON_ROOT("interaction sub cycles = " << interactionSubCycles); + WALBERLA_LOG_INFO_ON_ROOT("pe sub steps = " << peSubSteps); + WALBERLA_LOG_INFO_ON_ROOT("lubrication cut off distance = " << lubricationCutOffDistance); + WALBERLA_LOG_INFO_ON_ROOT("use lubrication correction term instead of full formula = " << (useLubricationCorrection ? "yes" : "no")); + WALBERLA_LOG_INFO_ON_ROOT("Ga = " << std::sqrt((densityRatio - real_t(1)) * gravity * diameter * diameter * diameter) / viscosity); + WALBERLA_LOG_INFO_ON_ROOT("dx_SI = " << dx_SI << ", dt_SI = " << dt_SI); + WALBERLA_LOG_INFO_ON_ROOT("dt_DEM = " << dt_DEM); + WALBERLA_LOG_INFO_ON_ROOT("t_ref = " << tStokes << " simulation steps"); + WALBERLA_LOG_INFO_ON_ROOT("logging is written to " + fileNameLogging ); + } + + /////////////////////////// + // BLOCK STRUCTURE SETUP // + /////////////////////////// + + const uint_t XBlocks = uint_t(4); + const uint_t YBlocks = uint_t(4); + const uint_t ZBlocks = uint_t(4); + + const uint_t XCells = xlength / XBlocks; + const uint_t YCells = ylength / YBlocks; + const uint_t ZCells = zlength / ZBlocks; + + if( XBlocks * XCells != xlength || + YBlocks * YCells != ylength || + ZBlocks * ZCells != zlength ) WALBERLA_ABORT("Domain decomposition failed!"); + + auto blocks = blockforest::createUniformBlockGrid( XBlocks, YBlocks, ZBlocks, XCells, YCells, ZCells, + dx, 0, false, false, + true, true, true, + false ); + + + //////// + // PE // + //////// + + 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"); + + pe::cr::ICR* cr; + pe::cr::DEM cr_dem(globalBodyStorage, blocks->getBlockStoragePointer(), bodyStorageID, ccdID, fcdID ); + cr = &cr_dem; + + const real_t restitutionCoeff = real_t(0.88); + const real_t frictionCoeff = real_t(0.25); + + real_t sphereVolume = diameter * diameter * diameter * math::M_PI / real_t(6); + const real_t particleMass = densityRatio * sphereVolume; + const real_t Mij = particleMass * particleMass / ( real_t(2) * particleMass ); + const real_t lnDryResCoeff = std::log(restitutionCoeff); + const real_t collisionTime = real_t(0.5); + const real_t stiffnessCoeff = math::M_PI * math::M_PI * Mij / ( collisionTime * collisionTime * ( real_t(1) - lnDryResCoeff * lnDryResCoeff / ( math::M_PI * math::M_PI + lnDryResCoeff* lnDryResCoeff ) ) ); + const real_t dampingCoeff = - real_t(2) * std::sqrt( Mij * stiffnessCoeff ) * + ( std::log(restitutionCoeff) / std::sqrt( math::M_PI * math::M_PI + (std::log(restitutionCoeff) * std::log(restitutionCoeff) ) ) ); + const real_t contactDuration = real_t(2) * math::M_PI * Mij / ( std::sqrt( real_t(4) * Mij * stiffnessCoeff - dampingCoeff * dampingCoeff )); //formula from Uhlman + + if( !funcTest ) { + WALBERLA_LOG_INFO_ON_ROOT("Created sediment material with:\n" + << " - coefficient of restitution = " << restitutionCoeff << "\n" + << " - coefficient of friction = " << frictionCoeff << "\n" + << " - stiffness coefficient kn = " << stiffnessCoeff << "\n" + << " - damping coefficient cdn = " << dampingCoeff << "\n" + << " - contact time Tc = " << contactDuration); + } + auto peMaterial = pe::createMaterial( "sedimentMat", densityRatio, restitutionCoeff, frictionCoeff, frictionCoeff, real_t(0), real_t(200), stiffnessCoeff, dampingCoeff, dampingCoeff ); + + ///////////////// + // PE COUPLING // + ///////////////// + + // connect to pe + const real_t overlap = real_t( 1.5 ) * dx; + auto syncCall = boost::bind( pe::syncNextNeighbors<BodyTypeTuple>, boost::ref(blocks->getBlockForest()), bodyStorageID, static_cast<WcTimingTree*>(NULL), overlap, false ); + shared_ptr<CollisionPropertiesEvaluator> collisionPropertiesEvaluator = walberla::make_shared<CollisionPropertiesEvaluator>( *cr ); + + // create the spheres + uint_t numSpheres = 0; + + if ( createSpheresInLattice ) + { + numSpheres = createSphereLattice( *blocks, *globalBodyStorage, bodyStorageID, AABB( real_t(0), real_t(0), real_t(0), real_c(xlength), real_c(ylength), real_c(zlength) ), + diameter, solidVolumeFraction, peMaterial, real_t(0) ); + syncCall(); + + } else { + numSpheres = createSpheresRandomly( *blocks, *globalBodyStorage, bodyStorageID, AABB( real_t(0), real_t(0), real_t(0), real_c(xlength), real_c(ylength), real_c(zlength) ), + diameter, solidVolumeFraction, peMaterial, real_t(0) ); + syncCall(); + + const uint_t initialPeSteps = uint_t(50000); + const real_t dt_DEM_init = collisionTime / real_t(10); + const real_t overlapLimit = real_t(0.05) * diameter; + + WALBERLA_LOG_INFO_ON_ROOT("Sphere creation done --- resolving overlaps with goal all < " << overlapLimit / diameter * real_t(100) << "%"); + + for( uint_t pet = uint_t(1); pet <= initialPeSteps; ++pet ) + { + cr->timestep( dt_DEM_init ); + syncCall(); + (*collisionPropertiesEvaluator)(); + real_t maxPen = collisionPropertiesEvaluator->getMaximumPenetrationInSimulation(); + if( maxPen < overlapLimit ) + { + WALBERLA_LOG_INFO_ON_ROOT("Carried out " << pet << " DEM-only time steps to resolve initial overlaps"); + break; + }else{ + if( pet % uint_t(200) == uint_t(0) ) + { + WALBERLA_LOG_INFO_ON_ROOT(pet << " - current max overlap = " << maxPen / diameter * real_t(100) << "%"); + } + } + collisionPropertiesEvaluator->resetMaximumPenetration(); + } + } + + Vector3<real_t> initialSphereVelocity(real_t(0), real_t(0), initialSphereVelocityZ); + WALBERLA_LOG_INFO_ON_ROOT("Resetting sphere velocity to " << initialSphereVelocity ); + resetSphereVelocities( blocks, bodyStorageID, initialSphereVelocity ); + + + const real_t domainVolume = real_c( xlength * ylength * zlength ); + real_t actualSolidVolumeFraction = real_c( numSpheres ) * diameter * diameter * diameter * math::M_PI / ( real_t(6) * domainVolume ); + real_t ReynoldsNumber = std::fabs(velUnhindered) * diameter / viscosity; + + // apply external forcing on fluid to approximately balance the force from the settling particles to avoid too large fluid or particle velocities + const real_t extForceZ = actualSolidVolumeFraction * gravity * (densityRatio - real_t(1)); + Vector3<real_t> extForce = Vector3<real_t>(real_t(0), real_t(0), extForceZ ); + + // apply estimate by Richardson & Zaki (1954) for unbounded flow (DomainLength->infty) + real_t n = 0; + if( ReynoldsNumber < real_t(0.2) ) + { + n = real_t(4.65); + } else if ( ReynoldsNumber < real_t(1) ) + { + n = real_t(4.35) * std::pow( ReynoldsNumber, real_t(-0.03) ); + } else if ( ReynoldsNumber < real_t(500) ) + { + n = real_t(4.45) * std::pow( ReynoldsNumber, real_t(-0.1) ); + } else + { + n = real_t(2.39); + } + + real_t expectedVelocity = velUnhindered * std::pow( ( real_t(1) - actualSolidVolumeFraction ), n ); + + WALBERLA_LOG_INFO_ON_ROOT("solid volume fraction = " << actualSolidVolumeFraction ); + WALBERLA_LOG_INFO_ON_ROOT("number of spheres = " << numSpheres ); + WALBERLA_LOG_INFO_ON_ROOT("velocity_Stokes = " << velStokes ); + WALBERLA_LOG_INFO_ON_ROOT("velocity_Unhindered = " << velUnhindered ); + WALBERLA_LOG_INFO_ON_ROOT("Re = " << ReynoldsNumber ); + WALBERLA_LOG_INFO_ON_ROOT("expected settling velocity = " << expectedVelocity <<" = " << expectedVelocity * dx_SI / dt_SI << " m/s" ); + WALBERLA_LOG_INFO_ON_ROOT("external forcing on fluid = " << extForce ); + WALBERLA_LOG_INFO_ON_ROOT("total external forcing applied on all fluid cells = " << extForce[2] * (real_t(1) - actualSolidVolumeFraction) * real_c( xlength * ylength * zlength ) ); + WALBERLA_LOG_INFO_ON_ROOT("total external (gravity & buoyancy) force on all spheres = " << gravitationalForce * real_c(numSpheres) ); + + ////////////////////// + // // + // BLOCK DATA // + // // + ////////////////////// + + // create force field + BlockDataID forceFieldID = field::addToStorage< Vec3Field_T >( blocks, "force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + + BlockDataID dragForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "drag force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + BlockDataID amForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "am force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + BlockDataID liftForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "lift force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + + // create omega field + BlockDataID omegaFieldID = field::addToStorage< ScalarField_T >( blocks, "omega field", real_t(0), field::zyxf, FieldGhostLayers ); + + // create the lattice model + LatticeModel_T latticeModel = LatticeModel_T( omegaFieldID, ForceModel_T( forceFieldID ) ); + + // add PDF field + BlockDataID pdfFieldID = lbm::addPdfFieldToStorage( blocks, "pdf field (zyxf)", latticeModel, initialFluidVelocity, real_t(1), FieldGhostLayers, field::zyxf ); + + // add flag field + BlockDataID flagFieldID = field::addFlagFieldToStorage< FlagField_T >( blocks, "flag field" ); + + // add boundary handling & initialize outer domain boundaries + BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >( MyBoundaryHandling( flagFieldID, pdfFieldID ), "boundary handling" ); + + // field to store fluid velolcity + BlockDataID velocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "velocity field", initialFluidVelocity, field::zyxf, FieldGhostLayers ); + BlockDataID oldVelocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "old velocity field", initialFluidVelocity, field::zyxf, FieldGhostLayers ); + BlockDataID swappedOldVelocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "swapped old velocity field", initialFluidVelocity, field::zyxf, FieldGhostLayers ); + + // create pressure field + BlockDataID pressureFieldID = field::addToStorage< ScalarField_T >( blocks, "pressure field", real_t(0), field::zyxf, FieldGhostLayers ); + + // create solid volume fraction field + BlockDataID svfFieldID = field::addToStorage< ScalarField_T >( blocks, "svf field", real_t(0), field::zyxf, FieldGhostLayers ); + + // field to store pressure gradient + BlockDataID pressureGradientFieldID = field::addToStorage< Vec3Field_T >( blocks, "pressure gradient field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store curl of fluid velocity + BlockDataID velocityCurlFieldID = field::addToStorage< Vec3Field_T >( blocks, "velocity curl field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store velocity gradient + BlockDataID velocityGradientFieldID = field::addToStorage< TensorField_T >( blocks, "velocity gradient field", Matrix3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store gradient of stress tensor + BlockDataID stressTensorGradientFieldID = field::addToStorage< Vec3Field_T >( blocks, "stress tensor gradient field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store time derivative of fluid velocity + BlockDataID timeDerivativeVelocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "time derivative velocity field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // communication schemes + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> forceComm( blocks, forceFieldID ); + + blockforest::communication::UniformBufferedScheme<stencil::D3Q27> pdfScheme( blocks ); + pdfScheme.addPackInfo( make_shared< field::communication::PackInfo<PdfField_T> >( pdfFieldID ) ); + + // setup of the communication for synchronizing the velocity field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( velocityFieldID ) ); + + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > oldVelocityCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + oldVelocityCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( oldVelocityFieldID ) ); + + // setup of the communication for synchronizing the solid volume fraction field between neighboring blocks which takes into account the svf values inside the ghost layers + shared_ptr<pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >svfCommunicationScheme = make_shared<pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >( blocks, svfFieldID ); + + // setup of the communication for synchronizing the pressure field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > pressureCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + pressureCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<ScalarField_T> >( pressureFieldID ) ); + + // setup of the communication for synchronizing the pressure gradient field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > pressureGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + pressureGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( pressureGradientFieldID ) ); + + // setup of the communication for synchronizing the velocity curl field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityCurlCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityCurlCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( velocityCurlFieldID ) ); + + // communication for synchronizing the velocity gradient values + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<TensorField_T> >( velocityGradientFieldID ) ); + + // communication for synchronizing the stress tensor gradient values + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > stressTensorGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + stressTensorGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( stressTensorGradientFieldID ) ); + + // communication for synchronizing the interaction force field + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> dragForceComm( blocks, dragForceFieldID ); + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> amForceComm( blocks, amForceFieldID ); + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> liftForceComm( blocks, liftForceFieldID ); + + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> dragForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, dragForceFieldID, uint_t(1) ); + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> amForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, amForceFieldID, uint_t(1) ); + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> liftForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, liftForceFieldID, uint_t(1) ); + + ///////////////////////////////// + // // + // CORRELATION FUNCTIONS // + // // + ///////////////////////////////// + + // drag correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t>&, const Vector3<real_t> &, real_t, real_t, real_t, real_t)> dragCorrelationFunction; + if( dragCorr == DragCorrelation::ErgunWenYu ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceErgunWenYu; + } + else if( dragCorr == DragCorrelation::Tang ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceTang; + } + else if( dragCorr == DragCorrelation::Stokes ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceStokes; + } + else if( dragCorr == DragCorrelation::Felice ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceFelice; + } + else if( dragCorr == DragCorrelation::Tenneti ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceTenneti; + } + else if( dragCorr == DragCorrelation::NoDrag ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::noDragForce; + } + else + { + WALBERLA_ABORT("Drag correlation not yet implemented!"); + } + + // lift correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t, real_t )> liftCorrelationFunction; + if( liftCorr == LiftCorrelation::NoLift ) + { + liftCorrelationFunction = pe_coupling::discrete_particle_methods::noLiftForce; + } + else if( liftCorr == LiftCorrelation::Saffman ) + { + liftCorrelationFunction = pe_coupling::discrete_particle_methods::liftForceSaffman; + } + else + { + WALBERLA_ABORT("Lift correlation not yet implemented!"); + } + + // added mass correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t )> addedMassCorrelationFunction; + if( addedMassCorr == AddedMassCorrelation::NoAM ) + { + addedMassCorrelationFunction = pe_coupling::discrete_particle_methods::noAddedMassForce; + } + else if( addedMassCorr == AddedMassCorrelation::Finn ) + { + addedMassCorrelationFunction = pe_coupling::discrete_particle_methods::addedMassForceFinn; + } + else + { + WALBERLA_ABORT("Added mass correlation not yet implemented!"); + } + + // set up effective viscosity calculation + boost::function<real_t ( real_t, real_t)> effectiveViscosityFunction; + if( effVisc == EffectiveViscosity::None ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateUnchangedEffectiveViscosity; + } + else if( effVisc == EffectiveViscosity::Rescaled ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateRescaledEffectiveViscosity; + } + else if( effVisc == EffectiveViscosity::Eilers ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateEilersEffectiveViscosity; + } + else + { + WALBERLA_ABORT("Effective viscosity not yet implemented!"); + } + shared_ptr<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator> effectiveViscosityEvaluator = + walberla::make_shared<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator>(omegaFieldID, svfFieldID, viscosity, effectiveViscosityFunction ); + + + //////////////////////////////// + // // + // EVALUATION FUNCTIONS // + // // + //////////////////////////////// + + // evaluator for bodies' velocity time derivative + shared_ptr<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator> bodyVelocityTimeDerivativeEvaluator = make_shared<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator>( blocks, bodyStorageID, dtBodyVelocityTimeDerivativeEvaluation ); + (*bodyVelocityTimeDerivativeEvaluator)(); + + // function used to evaluate the interaction force between fluid and particles + boost::function<void(void)> dragAndPressureForceEvaluationFunction; + if( dpm == DPMethod::GNS ) { + if (interpol == Interpolation::INearestNeighbor) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } else if (interpol == Interpolation::IKernel) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } else if (interpol == Interpolation::ITrilinear) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + } + else + { + WALBERLA_ABORT("Discrete particle method not yet implemented!"); + } + + + // function to evaluate the lift force contribution + boost::function<void(void)> liftForceEvaluationFunction; + if( interpol == Interpolation::INearestNeighbor ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::IKernel ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::ITrilinear ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + + // function to evaluate the added mass contribution + boost::function<void(void)> addedMassEvaluationFunction; + if( interpol == Interpolation::INearestNeighbor ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::IKernel ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::ITrilinear ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + + // function to evaluate lubrication forces + boost::function<void(void)> lubricationEvaluationFunction; + if( lubricationCutOffDistance > real_t(0) ) + { + if( useLubricationCorrection ) + { + typedef pe_coupling::LubricationCorrection LE_T; + shared_ptr<LE_T> lubEval = make_shared<LE_T>( blocks, globalBodyStorage, bodyStorageID, viscosity, lubricationCutOffDistance ); + lubricationEvaluationFunction = boost::bind(&LE_T::operator(), lubEval); + } + else + { + typedef pe_coupling::discrete_particle_methods::LubricationForceEvaluator LE_T; + shared_ptr<LE_T> lubEval = make_shared<LE_T>( blocks, globalBodyStorage, bodyStorageID, viscosity, lubricationCutOffDistance ); + lubricationEvaluationFunction = boost::bind(&LE_T::operator(), lubEval); + } + } else { + lubricationEvaluationFunction = emptyFunction; + } + + + // function to evaluate the mean fluid velocity + boost::function<Vector3<real_t> ()> evaluateMeanFluidVelocity = boost::bind(getGNSMeanFluidVelocity, blocks, pdfFieldID, svfFieldID, domainVolume); + + + ////////////////////////////// + // // + // INITIAL SIMULATION // + // // + ////////////////////////////// + + // initial simulation: keep particles fix, evaluate acting force on particles + // if this force is approximately converged, see if it matches the gravitational force + // if not, change the external force accordingly + + if( solidVolumeFraction > real_t(0.01) && initialSimulationToAdjustFluidForcing ) + { + WALBERLA_LOG_INFO_ON_ROOT("===================================================================================" ); + WALBERLA_LOG_INFO_ON_ROOT("Starting initial simulation to equilibrate fluid forcing and interaction force"); + + // create the timeloop + SweepTimeloop timeloopInit( blocks->getBlockStorage(), timesteps ); + + + // evaluation function + shared_ptr<QuantityEvaluator> quantityEvaluator = walberla::make_shared< QuantityEvaluator >( &timeloopInit, *blocks, bodyStorageID, forceFieldID, pdfFieldID, fileNameLoggingInit, fileIO, numSpheres, expectedVelocity, evaluateMeanFluidVelocity ); + + shared_ptr<pe_coupling::discrete_particle_methods::GNSExternalForceToForceFieldAdder> gnsExternalForceOnForceFieldAdder = + walberla::make_shared<pe_coupling::discrete_particle_methods::GNSExternalForceToForceFieldAdder>( forceFieldID, svfFieldID, extForce ); + + + //initial setup + { + // init solid volume fraction field + if( dist == Distribution::DNearestNeighbor ) + { + pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::NearestNeighborDistributor> svfEvaluator( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) svfEvaluator( &(*blockIt) ); + } else { + pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::KernelDistributor> svfEvaluator( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) svfEvaluator( &(*blockIt) ); + } + + (*svfCommunicationScheme)(); + + pe_coupling::discrete_particle_methods::ForceFieldResetter forceFieldResetter( forceFieldID ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) forceFieldResetter( &(*blockIt) ); + + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) (*gnsExternalForceOnForceFieldAdder)( &(*blockIt) ); + + } + + (*quantityEvaluator)(); + + ///////// begin of GNS timeloop /////////////////////// + + timeloopInit.addFuncBeforeTimeStep( pe_coupling::ForceTorqueOnBodiesResetter( blocks, bodyStorageID), "Resetting force on bodies" ); + + + timeloopInit.add() << Sweep( pe_coupling::discrete_particle_methods::GNSPressureFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( pressureFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Pressure Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureCommunicationScheme), "Pressure Field Communication" ); + + timeloopInit.add() << Sweep( pe_coupling::discrete_particle_methods::PressureGradientFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( pressureGradientFieldID, pressureFieldID, boundaryHandlingID ), "Pressure Gradient Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureGradientCommunicationScheme), "Pressure Gradient Field Communication" ); + + // subcycling loop begin + for( uint_t subcycle = 1; subcycle <= interactionSubCycles; ++subcycle ) + { + timeloopInit.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ); + timeloopInit.add() << Sweep( makeSharedSweep(gnsExternalForceOnForceFieldAdder), "Force Field Add" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloopInit.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCommunicationScheme), "Velocity Field Communication" ); + + // evaluate Fdrag + timeloopInit.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( dragForceFieldID ), "Drag Force Field Reset" ) + << AfterFunction( dragAndPressureForceEvaluationFunction, "Fluid-Particle Interaction Force Evaluation" ) + << AfterFunction( dragForceComm, "Drag Force Field Communication" ); + + if( subcycle != interactionSubCycles ) + { + timeloopInit.add() << Sweep( DummySweep(), "DummySweep") + << AfterFunction( pe_coupling::ForceTorqueOnBodiesResetter( blocks, bodyStorageID), "Resetting force on bodies" ); + } + } + + timeloopInit.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ); + timeloopInit.add() << Sweep( makeSharedSweep(gnsExternalForceOnForceFieldAdder), "Force Field Add" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloopInit.add() << Sweep( makeSharedSweep<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator>( effectiveViscosityEvaluator ), "Effective Viscosity Evaluation"); + if( useTurbulenceModel ) timeloopInit.add() << Sweep( pe_coupling::discrete_particle_methods::GNSSmagorinskyLESField<LatticeModel_T>(blocks, omegaFieldID, pdfFieldID, svfFieldID, smagorinskyConstant), "Turbulence Model" ); + + // execute GNS-LBM sweep, boundary handling, PDF communication + auto sweep = pe_coupling::discrete_particle_methods::makeGNSSweep< LatticeModel_T, FlagField_T >( pdfFieldID, svfFieldID, flagFieldID, Fluid_Flag ); + + timeloopInit.add() << Sweep( lbm::makeCollideSweep( sweep ), "GNS-LBM sweep (collide)" ); + + timeloopInit.add() << BeforeFunction( pdfScheme, "LBM Communication" ) + << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" ); + + timeloopInit.add() << Sweep( lbm::makeStreamSweep( sweep ), "GNS-LBM sweep (stream)" ); + + + // evaluate the forces / velocities + timeloopInit.addFuncAfterTimeStep( SharedFunctor<QuantityEvaluator>( quantityEvaluator ), "Quantity Evaluator" ); + + // configure vtk output + if( vtkIO ) + { + auto fluidVTKWriter = vtk::createVTKOutput_BlockData( blocks, "fluid_field_init", vtkWriteFrequency, uint_t(1), false, baseFolder ); + blockforest::communication::UniformBufferedScheme<stencil::D3Q27> pdfGhostLayerSync( blocks ); + pdfGhostLayerSync.addPackInfo( make_shared< field::communication::PackInfo<PdfField_T> >( pdfFieldID ) ); + fluidVTKWriter->addBeforeFunction( pdfGhostLayerSync ); + auto velocityWriter = make_shared< lbm::VelocityVTKWriter< LatticeModel_T, float > >( pdfFieldID, "Velocity (Lattice)" ); + auto densityWriter = make_shared< lbm::DensityVTKWriter < LatticeModel_T, float > >( pdfFieldID, "Density (Lattice)" ); + fluidVTKWriter->addCellDataWriter( velocityWriter ); + fluidVTKWriter->addCellDataWriter( densityWriter ); + timeloopInit.addFuncAfterTimeStep( vtk::writeFiles( fluidVTKWriter ), "VTK (fluid field data)" ); + + timeloopInit.addFuncAfterTimeStep( field::createVTKOutput<Vec3Field_T, float>( forceFieldID, *blocks, "force_field_init", vtkWriteFrequency, 1, false, baseFolder ), "VTK (force field init)" ); + } + + // execute simulation + WcTimingPool timeloopInitTiming; + + real_t oldInteractionForce( real_t(0) ); + real_t curInteractionForce( real_t(0) ); + real_t relativeForceDiffLimit( real_t(1e-4) ); + real_t relativeForceConvergenceLimit( real_t( 1e-3 ) ); + for( uint_t t = 0; t <= timesteps; ++t ) + { + timeloopInit.singleStep( timeloopInitTiming ); + curInteractionForce = quantityEvaluator->getMeanForceOnParticles()[2]; + + if( std::isnan(curInteractionForce) ) WALBERLA_ABORT( "Nan found in interaction force!" ); + + if( t > tStokes ) // leave enough timesteps to let the velocity field adapt to the new forcing + { + if( std::fabs((curInteractionForce - oldInteractionForce ) / curInteractionForce) < relativeForceDiffLimit || t == timesteps) + { + WALBERLA_LOG_INFO_ON_ROOT("initial simulation ended with relative difference of interaction forces of " << relativeForceDiffLimit << " after " << t << " time steps."); + + + real_t actingExternalForceOnSpheres = real_c(numSpheres) * ( ( - gravity * densityRatio * diameter * diameter * diameter * math::PI / real_t(6) ) + + ( gravity * real_t(1) * diameter * diameter * diameter * math::PI / real_t(6) ) + + ( extForce[2] * real_t(1) * diameter * diameter * diameter * math::PI / real_t(6) ) ); + WALBERLA_LOG_INFO_ON_ROOT("f_interaction_z = " << curInteractionForce << ", f_ext_z = " << actingExternalForceOnSpheres ); + if( std::fabs( ( std::fabs( curInteractionForce ) - std::fabs( actingExternalForceOnSpheres ) )/ std::fabs( curInteractionForce ) ) < relativeForceConvergenceLimit ) + { + WALBERLA_LOG_INFO_ON_ROOT("initial simulation converged"); + break; + } + else + { + t = 0u; + //timeloopInit.setCurrentTimeStepToZero(); + extForce[2] = extForce[2] * ( std::fabs( actingExternalForceOnSpheres ) / std::fabs( curInteractionForce ) ); + gnsExternalForceOnForceFieldAdder->reset(extForce); + WALBERLA_LOG_INFO_ON_ROOT("restarting initial simulation with new external force = " << extForce[2]); + curInteractionForce = real_t(0); + } + } + oldInteractionForce = curInteractionForce; + } + + } + + } + + // reset remaining force on all bodies + pe_coupling::ForceTorqueOnBodiesResetter forceResetter( blocks, bodyStorageID); + forceResetter(); + + WALBERLA_LOG_INFO_ON_ROOT("===================================================================================" ); + WALBERLA_LOG_INFO_ON_ROOT("Starting simulation with:" ); + WALBERLA_LOG_INFO_ON_ROOT("external forcing on fluid = " << extForce ); + WALBERLA_LOG_INFO_ON_ROOT("total external forces on all particles = " << real_c(numSpheres) * ( - gravity * ( densityRatio - real_t(1) ) + extForce[2] ) * diameter * diameter * diameter * math::PI / real_t(6) ); + WALBERLA_LOG_INFO_ON_ROOT("simulating " << timesteps << " time steps" ); + + + ///////////////////////////// + // // + // ACTUAL SIMULATION // + // // + ///////////////////////////// + + // create the timeloop + SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps ); + + // evaluation function + shared_ptr<QuantityEvaluator> quantityEvaluator = walberla::make_shared< QuantityEvaluator >( &timeloop, *blocks, bodyStorageID, forceFieldID, pdfFieldID, fileNameLogging, fileIO, numSpheres, expectedVelocity, evaluateMeanFluidVelocity ); + { + (*collisionPropertiesEvaluator)(); + real_t maxPen = collisionPropertiesEvaluator->getMaximumPenetrationInSimulation(); + WALBERLA_LOG_INFO_ON_ROOT("maximum penetration before the simulation (maxPen) = " << maxPen << ", maxPen / D = " << real_t(100) * maxPen / diameter << "%"); + } + collisionPropertiesEvaluator->resetMaximumPenetration(); + + //initial setup if no initial simulation has been carried out earlier + if( !initialSimulationToAdjustFluidForcing ) + { + // init solid volume fraction field + if( dist == Distribution::DNearestNeighbor ) + { + pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::NearestNeighborDistributor> svfEvaluator( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) svfEvaluator( &(*blockIt) ); + } else { + pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::KernelDistributor> svfEvaluator( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) svfEvaluator( &(*blockIt) ); + } + + (*svfCommunicationScheme)(); + + pe_coupling::discrete_particle_methods::ForceFieldResetter forceFieldResetter( forceFieldID ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) forceFieldResetter( &(*blockIt) ); + + pe_coupling::discrete_particle_methods::GNSExternalForceToForceFieldAdder gnsExternalForceAdder( forceFieldID, svfFieldID, extForce ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) gnsExternalForceAdder( &(*blockIt) ); + } + + + ///////// begin of GNS timeloop /////////////////////// + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::FieldDataSwapper<Vec3Field_T>( velocityFieldID, swappedOldVelocityFieldID ), "Velocity Field Swap" ); + } + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( oldVelocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Old Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(oldVelocityCommunicationScheme), "Old Velocity Field Communication" ); + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityGradientFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( velocityGradientFieldID, oldVelocityFieldID, boundaryHandlingID ), "Velocity Gradient Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityGradientCommunicationScheme), "Velocity Gradient Field Communication" ); + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityTotalTimeDerivativeFieldEvaluator( timeDerivativeVelocityFieldID, oldVelocityFieldID, swappedOldVelocityFieldID, velocityGradientFieldID, dt ), "Velocity Time Derivative Field Evaluation" ); + } + + // subcycling loop begin + for( uint_t subcycle = 1; subcycle <= interactionSubCycles; ++subcycle ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ); + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSExternalForceToForceFieldAdder( forceFieldID, svfFieldID, extForce ), "Force Field Add" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCommunicationScheme), "Velocity Field Communication" ); + + if( liftCorr != LiftCorrelation::NoLift ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityCurlFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( velocityCurlFieldID, velocityFieldID, boundaryHandlingID ), "Velocity Curl Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCurlCommunicationScheme), "Velocity Curl Field Communication" ); + } + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSPressureFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( pressureFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Pressure Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureCommunicationScheme), "Pressure Field Communication" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::PressureGradientFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( pressureGradientFieldID, pressureFieldID, boundaryHandlingID ), "Pressure Gradient Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureGradientCommunicationScheme), "Pressure Gradient Field Communication" ); + + if( liftCorr != LiftCorrelation::NoLift ) + { + // evaluate Flift + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( liftForceFieldID ), "Lift Force Field Reset" ) + << AfterFunction( liftForceEvaluationFunction, "Lift Force Evaluation") + << AfterFunction( liftForceComm, "Lift Force Field Communication" ); + } + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + // evaluate Fam + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( amForceFieldID ), "AM Force Field Reset" ) + << AfterFunction( addedMassEvaluationFunction, "Added Mass Force Evaluation") + << AfterFunction( amForceComm, "Force Field Communication" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator>( bodyVelocityTimeDerivativeEvaluator ), "Body Velocity Time Derivative Evaluation" ); + } + + if( liftCorr != LiftCorrelation::NoLift || addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ); + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSExternalForceToForceFieldAdder( forceFieldID, svfFieldID, extForce ), "Force Field Add" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCommunicationScheme), "Velocity Field Communication" ); + } + + // evaluate Fdrag + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( dragForceFieldID ), "Drag Force Field Reset" ) + << AfterFunction( dragAndPressureForceEvaluationFunction, "Fluid-Particle Interaction Force Evaluation" ) + << AfterFunction( dragForceComm, "Drag Force Field Communication" ); + + // ext forces on bodies + timeloop.add() << Sweep( DummySweep(), "Dummy Sweep ") + << AfterFunction( pe_coupling::ForceOnBodiesAdder( blocks, bodyStorageID, Vector3<real_t>(0,0,- gravity * densityRatio * diameter * diameter * diameter * math::PI / real_t(6) ) ), "Gravitational Force Add" ) + << AfterFunction( pe_coupling::ForceOnBodiesAdder( blocks, bodyStorageID, Vector3<real_t>(0,0,gravity * real_t(1) * diameter * diameter * diameter * math::PI / real_t(6) ) ), "Buoyancy Force (due to gravity) Add" ) + << AfterFunction( pe_coupling::ForceOnBodiesAdder( blocks, bodyStorageID, Vector3<real_t>(0,0,extForce[2] * real_t(1) * diameter * diameter * diameter * math::PI / real_t(6) ) ), "Buoyancy Force (due to external fluid force) Add" ) + << AfterFunction( pe_coupling::discrete_particle_methods::SubgridTimeStep( blocks, bodyStorageID, *cr, syncCall, lubricationEvaluationFunction, dtInteractionSubCycle, peSubSteps ), "Pe Time Step" ); + + timeloop.add() << Sweep( DummySweep(), "Dummy Sweep ") + << AfterFunction( SharedFunctor<CollisionPropertiesEvaluator>( collisionPropertiesEvaluator ), "Collision properties evaluator"); + + + if( dist == Distribution::DNearestNeighbor ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::NearestNeighborDistributor> ( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ), "Solid Volume Fraction Field Evaluation" ) + << AfterFunction( SharedFunctor< pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >(svfCommunicationScheme), "Solid Volume Fraction Field Communication" ); + } + else + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::KernelDistributor> ( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ), "Solid Volume Fraction Field Evaluation" ) + << AfterFunction( SharedFunctor< pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >(svfCommunicationScheme), "Solid Volume Fraction Field Communication" ); + } + } + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ); + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSExternalForceToForceFieldAdder( forceFieldID, svfFieldID, extForce ), "Force Field Add" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( makeSharedSweep<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator>( effectiveViscosityEvaluator ), "Effective Viscosity Evaluation"); + if( useTurbulenceModel ) timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSSmagorinskyLESField<LatticeModel_T>(blocks, omegaFieldID, pdfFieldID, svfFieldID, smagorinskyConstant), "Turbulence Model" ); + + // execute GNS-LBM sweep, boundary handling, PDF communication + auto sweep = pe_coupling::discrete_particle_methods::makeGNSSweep< LatticeModel_T, FlagField_T >( pdfFieldID, svfFieldID, flagFieldID, Fluid_Flag ); + + timeloop.add() << Sweep( lbm::makeCollideSweep( sweep ), "GNS-LBM sweep (collide)" ); + + timeloop.add() << BeforeFunction( pdfScheme, "LBM Communication" ) + << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" ); + + timeloop.add() << Sweep( lbm::makeStreamSweep( sweep ), "GNS-LBM sweep (stream)" ); + + + + timeloop.addFuncAfterTimeStep( SharedFunctor<QuantityEvaluator>( quantityEvaluator ), "Quantity Evaluator" ); + + timeloop.addFuncAfterTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" ); + + // configure vtk output + if( vtkIO ) + { + shared_ptr<vtk::VTKOutput> pdfFieldVTKWriter = createFluidFieldVTKWriter( blocks, pdfFieldID, flagFieldID, vtkWriteFrequency, baseFolder ); + timeloop.addFuncAfterTimeStep( vtk::writeFiles( pdfFieldVTKWriter ), "VTK (fluid field data)" ); + + auto bodyVtkOutput = make_shared<pe::SphereVtkOutput>( bodyStorageID, blocks->getBlockStorage() ); + auto bodyVTK = vtk::createVTKOutput_PointData( bodyVtkOutput, "bodies", vtkWriteFrequency, baseFolder ); + timeloop.addFuncAfterTimeStep( vtk::writeFiles( bodyVTK ), "VTK (sphere data)" ); + + timeloop.addFuncAfterTimeStep( field::createVTKOutput<Vec3Field_T, float>( forceFieldID, *blocks, "force_field", vtkWriteFrequency, uint_t(0), false, baseFolder ), "VTK (force field)" ); + timeloop.addFuncAfterTimeStep( field::createVTKOutput<ScalarField_T, float>( svfFieldID, *blocks, "svf_field", vtkWriteFrequency, uint_t(0), false, baseFolder ), "VTK (svf field)" ); + timeloop.addFuncAfterTimeStep( field::createVTKOutput<ScalarField_T, float>( omegaFieldID, *blocks, "omega_field", vtkWriteFrequency, uint_t(0), false, baseFolder ), "VTK (omega field)" ); + + } + + // execute simulation + WcTimingPool timeloopTiming; + + real_t oldSettlingVel( real_t(0) ); + real_t curSettlingVel( real_t(0) ); + for( uint_t t = 0; t < timesteps; ++t ) + { + timeloop.singleStep( timeloopTiming ); + curSettlingVel = quantityEvaluator->getSettlingVelocity(); + if( t > tStokes ) + { + if( std::fabs((curSettlingVel - oldSettlingVel ) / curSettlingVel) < relativeVelDiffLimit ) + { + WALBERLA_LOG_INFO_ON_ROOT("simulation terminated with relative difference of settling velocities of " << relativeVelDiffLimit << " after " << t << " time steps."); + break; + } + oldSettlingVel = curSettlingVel; + } + } + + //log to file + if( fileIO ) logSingleResultToFile( baseFolder+"/logSettlingVel.txt", actualSolidVolumeFraction, curSettlingVel, expectedVelocity, velUnhindered, ReynoldsNumber, tau ); + + timeloopTiming.logResultOnRoot(); + + real_t meanParticleVel = quantityEvaluator->getMeanParticleVelocity()[2]; + real_t meanFluidVel = quantityEvaluator->getMeanFluidVelocity()[2]; + + WALBERLA_LOG_INFO_ON_ROOT("Result for solid volume fraction = " << actualSolidVolumeFraction << " with " << numSpheres << " spheres."); + WALBERLA_LOG_INFO_ON_ROOT("diameter = " << diameter << ", tau = " << tau); + WALBERLA_LOG_INFO_ON_ROOT(" - simulated settling velocity = " << curSettlingVel << ", us/uT = " << curSettlingVel / velUnhindered); + WALBERLA_LOG_INFO_ON_ROOT(" - expected settling velocity = " << expectedVelocity << ", ue/uT = " << expectedVelocity / velUnhindered); + WALBERLA_LOG_INFO_ON_ROOT("detailed overview:"); + WALBERLA_LOG_INFO_ON_ROOT(" - mean particle velocity = " << meanParticleVel << " = " << meanParticleVel * dx_SI/dt_SI << " m/s ( " << std::fabs(meanParticleVel/curSettlingVel)*real_t(100) << "% of settling vel)"); + WALBERLA_LOG_INFO_ON_ROOT(" - mean fluid velocity = " << meanFluidVel << " = " << meanFluidVel * dx_SI/dt_SI << " m/s ( " << std::fabs(meanFluidVel/curSettlingVel)*real_t(100) << "% of settling vel)"); + WALBERLA_LOG_INFO_ON_ROOT(" - mean relative velocity = " << curSettlingVel << " = " << curSettlingVel * dx_SI/dt_SI << " m/s"); + WALBERLA_LOG_INFO_ON_ROOT(" - expected velocity (R&Z) = " << expectedVelocity << " = " << expectedVelocity * dx_SI/dt_SI << " m/s"); + + real_t maxPen = collisionPropertiesEvaluator->getMaximumPenetrationInSimulation(); + WALBERLA_LOG_INFO_ON_ROOT(" - maximum penetration (maxPen) = " << maxPen << ", maxPen / D = " << real_t(100) * maxPen / diameter << "%"); + + + if ( fileIO ) { + WALBERLA_ROOT_SECTION() { + std::string fileName = baseFolder + "/logSettlingBehavior.txt"; + std::ofstream file; + file.open(fileName.c_str(), std::ofstream::app); + file.precision(8); + + file << actualSolidVolumeFraction << " \t" << numSpheres << " \t" + << diameter << " \t" << tau << " \t" << extForceZ << " \t" << relativeVelDiffLimit << " \t" + << interpol << " \t" << dist << " \t" << addedMassCorr << " \t" << liftCorr << " \t" << effVisc << " \t" + << ((useTurbulenceModel) ? 1 : 0) << " \t" + << interactionSubCycles << " \t" << peSubSteps << " \t" << lubricationCutOffDistance << " \t" + << curSettlingVel << " \t" << expectedVelocity << " \t" << velUnhindered << " \t" << maxPen << " \n"; + } + } + + return 0; +} + +} //namespace hindered_settling_dynamics_dpm + +int main( int argc, char **argv ){ + hindered_settling_dynamics_dpm::main(argc, argv); +} + diff --git a/tests/pe_coupling/discrete_particle_methods/SphereWallCollisionBehaviorDPM.cpp b/tests/pe_coupling/discrete_particle_methods/SphereWallCollisionBehaviorDPM.cpp new file mode 100644 index 000000000..8b4616f72 --- /dev/null +++ b/tests/pe_coupling/discrete_particle_methods/SphereWallCollisionBehaviorDPM.cpp @@ -0,0 +1,1401 @@ +//====================================================================================================================== +// +// 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 SphereWallCollisionBehaviorDPM.cpp +//! \author Christoph Rettinger <christoph.rettinger@fau.de> +// +//====================================================================================================================== + +#include "core/all.h" +#include "boundary/all.h" +#include "lbm/all.h" +#include "pe_coupling/all.h" + +#include "blockforest/Initialization.h" +#include "blockforest/communication/all.h" + +#include "core/grid_generator/SCIterator.h" + +#include "field/AddToStorage.h" +#include "field/communication/PackInfo.h" +#include "field/distributors/all.h" +#include "field/interpolators/all.h" + +#include "pe/basic.h" +#include "pe/vtk/SphereVtkOutput.h" +#include "pe/Types.h" + +#include "stencil/D3Q27.h" + +#include "timeloop/SweepTimeloop.h" + +#include "vtk/VTKOutput.h" + +#include <vector> +#include <iomanip> +#include <iostream> +#include <random> + +namespace sphere_wall_collision_behavior_dpm +{ + +/////////// +// USING // +/////////// + +using namespace walberla; +using walberla::uint_t; + + +/////////////// +// CONSTANTS // +/////////////// + +const uint_t FieldGhostLayers( 1 ); + +////////////// +// TYPEDEFS // +////////////// + +// PDF field, flag field & body field +typedef GhostLayerField< Matrix3<real_t>, 1 > TensorField_T; +typedef GhostLayerField< Vector3<real_t>, 1 > Vec3Field_T; +typedef GhostLayerField< real_t, 1 > ScalarField_T; +typedef lbm::force_model::GuoField< Vec3Field_T > ForceModel_T; + +typedef lbm::D3Q19< lbm::collision_model::SRTField<ScalarField_T>, false, ForceModel_T > 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; + +// boundary handling +typedef lbm::NoSlip< LatticeModel_T, flag_t > NoSlip_T; + +typedef boost::tuples::tuple< NoSlip_T > BoundaryConditions_T; +typedef BoundaryHandling<FlagField_T, Stencil_T, BoundaryConditions_T> BoundaryHandling_T; + +typedef boost::tuple<pe::Plane, pe::Sphere> BodyTypeTuple ; + +/////////// +// FLAGS // +/////////// + +const FlagUID Fluid_Flag ( "fluid" ); +const FlagUID NoSlip_Flag ( "no slip flag" ); + + +// coupling methods +enum DPMethod { GNS }; + +// interpolation (for PDFs, velocity and/or solid volume fraction) +enum Interpolation { INearestNeighbor, IKernel, ITrilinear }; + +// force distribution +enum Distribution { DNearestNeighbor, DKernel }; + +//drag correlation +enum DragCorrelation { ErgunWenYu, Tang, Stokes, Felice, Tenneti, NoDrag }; + +//lift correlation +enum LiftCorrelation { NoLift, Saffman }; + +//added mass correlation +enum AddedMassCorrelation { NoAM, Finn }; + +//effective viscosity +enum EffectiveViscosity { None, Rescaled, Eilers }; + + +///////////////////////////////////// +// BOUNDARY HANDLING CUSTOMIZATION // +///////////////////////////////////// +class MyBoundaryHandling +{ +public: + + MyBoundaryHandling( const BlockDataID & flagFieldID, const BlockDataID & pdfFieldID ) : + flagFieldID_( flagFieldID ), pdfFieldID_( pdfFieldID ) {} + + BoundaryHandling_T * operator()( IBlock* const block, const StructuredBlockStorage* const storage ) const; + +private: + + const BlockDataID flagFieldID_; + const BlockDataID pdfFieldID_; + +}; // class MyBoundaryHandling + + +BoundaryHandling_T * MyBoundaryHandling::operator()( IBlock * const block, const StructuredBlockStorage * const storage ) const +{ + WALBERLA_ASSERT_NOT_NULLPTR( block ); + + FlagField_T * flagField = block->getData< FlagField_T >( flagFieldID_ ); + PdfField_T * pdfField = block->getData< PdfField_T > ( pdfFieldID_ ); + + const auto fluid = flagField->flagExists( Fluid_Flag ) ? flagField->getFlag( Fluid_Flag ) : flagField->registerFlag( Fluid_Flag ); + + BoundaryHandling_T * handling = new BoundaryHandling_T( "Boundary handling", flagField, fluid, + boost::tuples::make_tuple( NoSlip_T( "NoSlip", NoSlip_Flag, pdfField ) ) ); + + const auto noslip = flagField->getFlag( NoSlip_Flag ); + + CellInterval domainBB = storage->getDomainCellBB(); + + domainBB.xMin() -= cell_idx_c( FieldGhostLayers ); // -1 + domainBB.xMax() += cell_idx_c( FieldGhostLayers ); // cellsX + + // LEFT + CellInterval left( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMin(), domainBB.yMax(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( left, *block ); + handling->forceBoundary( noslip, left ); + + // RIGHT + CellInterval right( domainBB.xMax(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( right, *block ); + handling->forceBoundary( noslip, right ); + + domainBB.yMin() -= cell_idx_c( FieldGhostLayers ); // 0 + domainBB.yMax() += cell_idx_c( FieldGhostLayers ); // cellsY+1 + + // FRONT + CellInterval front( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMin(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( front, *block ); + handling->forceBoundary( noslip, front ); + + // BACK + CellInterval back( domainBB.xMin(), domainBB.yMax(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( back, *block ); + handling->forceBoundary( noslip, back ); + + domainBB.zMin() -= cell_idx_c( FieldGhostLayers ); // 0 + domainBB.zMax() += cell_idx_c( FieldGhostLayers ); // cellsZ+1 + + // BOTTOM + CellInterval bottom( domainBB.xMin(), domainBB.yMin(), domainBB.zMin(), domainBB.xMax(), domainBB.yMax(), domainBB.zMin() ); + storage->transformGlobalToBlockLocalCellInterval( bottom, *block ); + handling->forceBoundary( noslip, bottom ); + + // TOP + CellInterval top( domainBB.xMin(), domainBB.yMin(), domainBB.zMax(), domainBB.xMax(), domainBB.yMax(), domainBB.zMax() ); + storage->transformGlobalToBlockLocalCellInterval( top, *block ); + handling->forceBoundary( noslip, top ); + + handling->fillWithDomain( FieldGhostLayers ); + + return handling; +} +//******************************************************************************************************************* + + +DPMethod to_DPMethod( const std::string& s ) +{ + if( s == "GNS" ) return DPMethod::GNS; + throw std::runtime_error("invalid conversion from " + s + " to DPMethod"); +} + +Interpolation to_interpolation( const std::string& s ) +{ + if( s == "nearest" ) return Interpolation::INearestNeighbor; + if( s == "kernel" ) return Interpolation::IKernel; + if( s == "trilinear" ) return Interpolation::ITrilinear; + throw std::runtime_error("invalid conversion from " + s + " to Interpolation"); +} + +Distribution to_distribution( const std::string& s ) +{ + if( s == "nearest" ) return Distribution::DNearestNeighbor; + if( s == "kernel" ) return Distribution::DKernel; + throw std::runtime_error("invalid conversion from " + s + " to Distribution"); +} + +DragCorrelation to_dragCorrelation( const std::string& s ) +{ + if( s == "ergun" ) return DragCorrelation::ErgunWenYu; + if( s == "tang" ) return DragCorrelation::Tang; + if( s == "stokes" ) return DragCorrelation::Stokes; + if( s == "felice" ) return DragCorrelation::Felice; + if( s == "tenneti" ) return DragCorrelation::Tenneti; + if( s == "none" ) return DragCorrelation::NoDrag; + throw std::runtime_error("invalid conversion from " + s + " to DragCorrelation"); +} + +LiftCorrelation to_liftCorrelation( const std::string& s ) +{ + if( s == "none" ) return LiftCorrelation::NoLift; + if( s == "saffman" ) return LiftCorrelation::Saffman; + throw std::runtime_error("invalid conversion from " + s + " to LiftCorrelation"); +} + +AddedMassCorrelation to_addedMassCorrelation( const std::string& s ) +{ + if( s == "none" ) return AddedMassCorrelation::NoAM; + if( s == "finn" ) return AddedMassCorrelation::Finn; + throw std::runtime_error("invalid conversion from " + s + " to AddedMassCorrelation"); +} + +EffectiveViscosity to_effvisc( const std::string& s ) +{ + if( s == "none" ) return EffectiveViscosity::None; + if( s == "rescaled" ) return EffectiveViscosity::Rescaled; + if( s == "eilers" ) return EffectiveViscosity::Eilers; + throw std::runtime_error("invalid conversion from " + s + " to effective viscosity"); +} + +std::string dpm_to_string( const DPMethod& dpm ) +{ + if( dpm == DPMethod::GNS ) return "GNS"; + throw std::runtime_error("invalid conversion from DPMethod to string"); +} + +class QuantityEvaluator +{ +public: + + QuantityEvaluator( SweepTimeloop* timeloop, StructuredBlockStorage & blocks, + const BlockDataID & bodyStorageID, real_t diameter, real_t densityRatio, real_t Galileo, + const std::string & baseFolder, bool writeLogging ) : + timeloop_( timeloop ), blocks_( blocks ), bodyStorageID_( bodyStorageID ), diameter_( diameter ), + terminalVelocity_(0), evaluateTerminalVelocity_( true ), + writeLogging_( writeLogging ) + { + if( writeLogging_ ) + { + fileName_ = baseFolder+"/evalCollisionBehaviorDPM_"+std::to_string(uint_c(real_t(10) * densityRatio))+"_"+std::to_string(uint_c(real_t(10) * Galileo))+"_"+std::to_string(uint_c(real_t(10) * diameter))+".txt"; + std::ofstream file; + file.open( fileName_.c_str() ); + file << "#t z velz x y velx vely fx fy fz\n"; + file.close(); + } + } + + void operator()() + { + // get particle position + Vector3<real_t> particlePosition = getParticlePosition(); + // get particle velocity + Vector3<real_t> particleVelocity = getMeanParticleVelocity(); + // get force on particles + Vector3<real_t> particleForce = getParticleForce(); + + if( evaluateTerminalVelocity_ ) terminalVelocity_ = std::min(particleVelocity[2], terminalVelocity_); + + WALBERLA_ROOT_SECTION() + { + positionsOverTime_.push_back(particlePosition[2]); + velocitiesOverTime_.push_back(particleVelocity[2]); + if( writeLogging_ ) writeToFile( particlePosition, particleVelocity, particleForce ); + } + } + + real_t getVelocity() + { + return getMeanParticleVelocity()[2]; + } + + real_t getTerminalVelocity() + { + return terminalVelocity_; + } + + real_t getPosition() + { + return getParticlePosition()[2]; + } + + void stopTerminalVelocityEvaluation() + { + evaluateTerminalVelocity_ = false; + } + + void writeScaledOutputToFile(uint_t tImpact, real_t terminalVelocity) + { + std::ofstream file; + file.open( fileName_.c_str() ); + file.precision(8); + file << "# t position velocity\n"; + for(uint_t t = 0; t < positionsOverTime_.size(); ++t) + { + file << (real_c(t) - real_c(tImpact)) * terminalVelocity / diameter_ << " " << ( positionsOverTime_[t] - real_t(0.5) * diameter_ ) / diameter_ << " " << velocitiesOverTime_[t] / terminalVelocity << "\n"; + + } + file.close(); + + } + +private: + + Vector3<real_t> getParticleForce() + { + Vector3<real_t> force(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 ) + { + force += bodyIt->getForce(); + } + } + + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( force[0], mpi::SUM ); + mpi::allReduceInplace( force[1], mpi::SUM ); + mpi::allReduceInplace( force[2], mpi::SUM ); + + } + return force; + } + + Vector3<real_t> getMeanParticleVelocity() + { + Vector3<real_t> velocity(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 ) + { + velocity += bodyIt->getLinearVel(); + } + } + + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( velocity[0], mpi::SUM ); + mpi::allReduceInplace( velocity[1], mpi::SUM ); + mpi::allReduceInplace( velocity[2], mpi::SUM ); + + } + + return velocity; + } + + Vector3<real_t> getParticlePosition() + { + Vector3<real_t> position(0.); + uint_t counter( 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 ) + { + position = bodyIt->getPosition(); + ++counter; + } + } + + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( position[0], mpi::SUM ); + mpi::allReduceInplace( position[1], mpi::SUM ); + mpi::allReduceInplace( position[2], mpi::SUM ); + mpi::allReduceInplace( counter, mpi::SUM ); + } + + WALBERLA_CHECK_EQUAL(counter, uint_t(1), "No more sphere in domain!"); + + return position; + } + + void writeToFile( const Vector3<real_t> & position, const Vector3<real_t> & vel, const Vector3<real_t> & force ) + { + std::ofstream file; + file.open( fileName_.c_str(), std::ofstream::app ); + file.precision(8); + + file << timeloop_->getCurrentTimeStep() << " \t" << position[2] << " \t" << vel[2] << " \t " + << position[0] << " \t " << position[1] << " \t " + << vel[0] << " \t " << vel[1] << " \t " + << force[0] << " \t " << force[1] << " \t " << force[2] << "\n"; + file.close(); + } + + SweepTimeloop* timeloop_; + StructuredBlockStorage & blocks_; + const BlockDataID bodyStorageID_; + real_t diameter_; + real_t terminalVelocity_; + std::string fileName_; + std::vector<real_t> positionsOverTime_; + std::vector<real_t> velocitiesOverTime_; + bool evaluateTerminalVelocity_; + bool writeLogging_; + +}; + + +class CollisionPropertiesEvaluator +{ +public: + CollisionPropertiesEvaluator( pe::cr::ICR & collisionResponse ) : collisionResponse_( collisionResponse ), maximumPenetration_(real_t(0)) + {} + + void operator()() + { + real_t maxPen = collisionResponse_.getMaximumPenetration(); + maximumPenetration_ = std::max( maximumPenetration_, maxPen ); + WALBERLA_MPI_SECTION() + { + mpi::allReduceInplace( maximumPenetration_, mpi::MAX ); + } + } + real_t getMaximumPenetrationInSimulation() + { + return maximumPenetration_; + } +private: + pe::cr::ICR & collisionResponse_; + real_t maximumPenetration_; +}; + +void emptyFunction(){} + +//******************************************************************************************************************* +/*!\brief Testcase that evaluates the collision behavior of a settling sphere and a resting wall in a viscous fluid + * + * A glass sphere of radius D is settling under gravity towards the bottom plane. + * The domain size is [32 x 32 x 512] * D, to ensure that the terminal settling velocity is reached before it hits the wall. + * _______________ + * | | + * | o | | + * | | | | gravity (z-direction) + * | | | v + * | | | + * | | | + * |_______|_______| + * + * The sphere properties are: dry coeff of restitution = 0.97, coeff of friction = 0.1 + * Depending on the Stokes number ( St = ( rho_s / rho_f ) * Re / 9 ), the actual coefficient of resitution changes + * due to the presence of the viscous fluid. + * The Reynolds number is evaluated as Re = u_T * D / visc, with the terminal settling velocity u_T. + * Thus, this quantity is not a-priori available. + * The main input parameter is the Galileo number, which, together with the density ratio, determines the fluid viscosity. + * + * Many parts of the discrete particle method can be modified via command line arguments: + * - discrete particle method + * - interpolation method + * - distribution method + * - number of interaction subcycles + * - number of pe steps per interaction subcycle + * - correlations for drag, lift, added mass + * - effective viscosity + * - turbulence model + * - lubrication force and its cut off distance + * + * An extensive description of the different components and this test case is given in Rettinger, Ruede (2017). + * + * The actual coeff of restitution can be compared to experimental findings from Joseph et al and Gondret et al. + * + * + * References: + * Experimental: + * - G. G. Joseph, R. Zenit, M. L. Hunt, A. M. Rosenwinkel - "Particle–wall collisions in a viscous fluid", Journal of Fluid + * Mechanics 433 (2001) 329–346. doi:10.1017/S0022112001003470. + * - P. Gondret, M. Lance, L. Petit - "Bouncing motion of spherical particles in fluids", Physics of Fluids + * 14 (2) (2002) 643–652. doi:10.1063/1.1427920. + * + * Fully resolved simulations: + * - A. Kidanemariam, M. Uhlmann - "Direct numerical simulation of pattern formation in subaqueous sediment", Journal of + * Fluid Mechanics 750. doi:10.1017/jfm.2014.284. + * + * Discrete particle simulations: + * - C. Rettinger, U. Ruede - "A Coupled Lattice Boltzmann Method and Discrete Element Method for Discrete Particle + * Simulations of Particulate Flows". arXiv preprint arXiv:1711.00336 (2017) + * - R. Sun, H. Xiao - "SediFoam: A general–purpose, open–source CFD-–DEM solver for particle–laden flow with emphasis + * on sediment transport", Computers & Geosciences 89 (2016) 207–219. doi:10.1016/j.cageo.2016.01.011. + * - J. R. Finn, M. Li, S. V. Apte - "Particle based modelling and simulation of natural sand dynamics in the wave bottom + * boundary layer", Journal of Fluid Mechanics 796 (2016) 340–385. doi:10.1017/jfm.2016.246. + * + */ +//******************************************************************************************************************* +int main( int argc, char **argv ) +{ + debug::enterTestMode(); + + mpi::Environment env( argc, argv ); + + ///////////////////////////////////////////// + // // + // Command Line Argument Customization // + // // + ///////////////////////////////////////////// + + bool funcTest = false; + bool fileIO = false; + std::string baseFolder = "vtk_out_SphereWallDPM"; + + real_t gravity = real_t(1e-4); + real_t densityRatio = real_t(2.0); + real_t diameter = real_t(0.5); + real_t GalileoNumber = real_t(30.9); + uint_t interactionSubCycles = uint_t(1); // number of subcycles that involve evaluation of the interaction force + uint_t peSubSteps = uint_t(1); // number of pe only calls in each subcycle + real_t collisionTime = real_t(1); + + DPMethod dpm = DPMethod::GNS; + Interpolation interpol = Interpolation::IKernel; + Distribution dist = Distribution::DKernel; + DragCorrelation dragCorr = DragCorrelation::Tenneti; + LiftCorrelation liftCorr = LiftCorrelation ::NoLift; + AddedMassCorrelation addedMassCorr = AddedMassCorrelation::NoAM; + EffectiveViscosity effVisc = EffectiveViscosity::None; + bool useTurbulenceModel = false; + real_t lubricationCutOffDistance = real_t(0); //0 switches it off, should be <= diameter for sphere-wall collision, and <= diameter/2 for sphere-sphere collision + bool useLubricationCorrection = false; // false: use full lubrication force, true: use only correction part + const real_t smagorinskyConstant = real_t(0.1); //for turbulence model + + uint_t dimlessTimesteps = uint_t(500); + + for( int i = 1; i < argc; ++i ) + { + if( std::strcmp( argv[i], "--funcTest" ) == 0 ) funcTest = true; + else if( std::strcmp( argv[i], "--fileIO" ) == 0 ) fileIO = true; + else if( std::strcmp( argv[i], "--baseFolder" ) == 0 ) baseFolder = argv[++i]; + else if( std::strcmp( argv[i], "--gravity" ) == 0 ) gravity = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--Galileo" ) == 0 ) GalileoNumber = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--densityRatio" ) == 0 ) densityRatio = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--diameter" ) == 0 ) diameter = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--collisionTime" ) == 0 ) collisionTime = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--interactionSubCycles" ) == 0 ) interactionSubCycles = uint_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--peSubSteps" ) == 0 ) peSubSteps = uint_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--DPMethod" ) == 0 ) dpm = to_DPMethod( argv[++i] ); + else if( std::strcmp( argv[i], "--interpolation" ) == 0 ) interpol = to_interpolation( argv[++i] ); + else if( std::strcmp( argv[i], "--distribution" ) == 0 ) dist = to_distribution( argv[++i] ); + else if( std::strcmp( argv[i], "--dragCorrelation" ) == 0 ) dragCorr = to_dragCorrelation( argv[++i] ); + else if( std::strcmp( argv[i], "--liftCorrelation" ) == 0 ) liftCorr = to_liftCorrelation( argv[++i] ); + else if( std::strcmp( argv[i], "--addedMassCorrelation" ) == 0 ) addedMassCorr = to_addedMassCorrelation( argv[++i] ); + else if( std::strcmp( argv[i], "--effectiveViscosity" ) == 0 ) effVisc = to_effvisc( argv[++i] ); + else if( std::strcmp( argv[i], "--useTurbulenceModel" ) == 0 ) useTurbulenceModel = true; + else if( std::strcmp( argv[i], "--useLubricationCorrection" ) == 0 ) useLubricationCorrection = true; + else if( std::strcmp( argv[i], "--lubricationCutOff" ) == 0 ) lubricationCutOffDistance = real_c( std::atof( argv[++i] ) ); + else if( std::strcmp( argv[i], "--timesteps" ) == 0 ) dimlessTimesteps = uint_c( std::atof( argv[++i] ) ); + else WALBERLA_ABORT("Found invalid command line argument: \"" << argv[i] << "\" - aborting..."); + } + + WALBERLA_CHECK( diameter <= real_t(1), "Diameter is not allowed to be > 1!" ); + WALBERLA_CHECK( interactionSubCycles > uint_t(0), "Number of interaction sub cycles has to be at least 1!"); + WALBERLA_CHECK( peSubSteps > uint_t(0), "Number of pe sub steps has to be at least 1!"); + WALBERLA_CHECK( lubricationCutOffDistance >= real_t(0), "Lubrication cut off distance has to be non-negative!"); + + if( funcTest ) + { + walberla::logging::Logging::instance()->setLogLevel(logging::Logging::LogLevel::WARNING); + } + if( fileIO ) + { + // create base directory if it does not yet exist + boost::filesystem::path tpath( baseFolder ); + if( !boost::filesystem::exists( tpath ) ) + boost::filesystem::create_directory( tpath ); + } + + /////////////////////////////// + // // + // SIMULATION PROPERTIES // + // // + /////////////////////////////// + + // roughly resembles the experimental setup from Gondret et al (2002) + const uint_t xlength = uint_t( real_t(32) * diameter); + const uint_t ylength = uint_t( real_t(32) * diameter); + const uint_t zlength = uint_t(real_t(512) * diameter); + const real_t dx = real_t(1); + + const real_t ug = std::sqrt(( densityRatio - real_t(1)) * gravity * diameter ); + const real_t viscosity = ug * diameter / GalileoNumber; + const real_t tau = real_t(1) / lbm::collision_model::omegaFromViscosity(viscosity); + + const real_t sphereVolume = math::M_PI * diameter * diameter * diameter / real_t(6); + Vector3<real_t> gravitationalForce ( real_t(0), real_t(0), ( densityRatio - real_t(1) ) * sphereVolume * gravity ); + + if( !funcTest ) + { + WALBERLA_LOG_INFO_ON_ROOT("Lx x Ly x Lz = " << xlength << " x " << ylength << " x " << zlength); + WALBERLA_LOG_INFO_ON_ROOT("density ratio = " << densityRatio ); + WALBERLA_LOG_INFO_ON_ROOT("Ga = " << GalileoNumber ); + WALBERLA_LOG_INFO_ON_ROOT("diameter = " << diameter ); + WALBERLA_LOG_INFO_ON_ROOT("tau = " << tau ); + WALBERLA_LOG_INFO_ON_ROOT("viscosity = " << viscosity ); + WALBERLA_LOG_INFO_ON_ROOT("u_g = " << ug ); + WALBERLA_LOG_INFO_ON_ROOT("gravity = " << gravity ); + WALBERLA_LOG_INFO_ON_ROOT("total external (gravity & buoyancy) force on sphere = " << gravitationalForce ); + WALBERLA_LOG_INFO_ON_ROOT("-------------------------------------------------------------------------------" ); + WALBERLA_LOG_INFO_ON_ROOT("discrete particle method = " << dpm_to_string(dpm) ); + WALBERLA_LOG_INFO_ON_ROOT("interpolator = " << interpol ); + WALBERLA_LOG_INFO_ON_ROOT("distribution = " << dist ); + WALBERLA_LOG_INFO_ON_ROOT("dragCorrelation = " << dragCorr ); + WALBERLA_LOG_INFO_ON_ROOT("addedMassCorrelation = " << addedMassCorr ); + WALBERLA_LOG_INFO_ON_ROOT("liftCorrelation = " << liftCorr ); + WALBERLA_LOG_INFO_ON_ROOT("effective viscosity = " << effVisc ); + WALBERLA_LOG_INFO_ON_ROOT("turbulence model = " << ( useTurbulenceModel ? "yes" : "no" ) ); + WALBERLA_LOG_INFO_ON_ROOT("interaction sub cycles = " << interactionSubCycles ); + WALBERLA_LOG_INFO_ON_ROOT("pe sub steps = " << peSubSteps ); + WALBERLA_LOG_INFO_ON_ROOT("lubrication cut off distance = " << lubricationCutOffDistance ); + WALBERLA_LOG_INFO_ON_ROOT("use lubrication correction term instead of full formula = " << ( useLubricationCorrection ? "yes" : "no" ) ); + WALBERLA_LOG_INFO_ON_ROOT("dt_DEM = " << real_t(1) / real_c(interactionSubCycles * peSubSteps) ); + } + + + const real_t dt = real_t(1); + const real_t dtInteractionSubCycle = dt / real_c(interactionSubCycles); + const real_t dtBodyVelocityTimeDerivativeEvaluation = dtInteractionSubCycle; + + + const real_t tStokes = densityRatio * diameter * diameter / ( real_t(18) * viscosity ); + const uint_t timesteps = (funcTest) ? uint_t(3) : dimlessTimesteps * uint_c(tStokes); // total number of time steps for the whole simulation + + + /////////////////////////// + // BLOCK STRUCTURE SETUP // + /////////////////////////// + + const uint_t XBlocks = uint_t(1); + const uint_t YBlocks = uint_t(1); + const uint_t ZBlocks = uint_t(32); + + const uint_t XCells = xlength / XBlocks; + const uint_t YCells = ylength / YBlocks; + const uint_t ZCells = zlength / ZBlocks; + + if( XBlocks * XCells != xlength || + YBlocks * YCells != ylength || + ZBlocks * ZCells != zlength ) WALBERLA_ABORT("Domain decomposition failed!"); + + auto blocks = blockforest::createUniformBlockGrid( XBlocks, YBlocks, ZBlocks, XCells, YCells, ZCells, + dx, 0, false, false, + false, false, false, + false ); + + + //////// + // PE // + //////// + + 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"); + + pe::cr::ICR* cr; + pe::cr::DEM cr_dem(globalBodyStorage, blocks->getBlockStoragePointer(), bodyStorageID, ccdID, fcdID ); + cr = &cr_dem; + + ///////////////// + // PE COUPLING // + ///////////////// + + // connect to pe + const real_t overlap = real_t( 1.5 ) * dx; + auto syncCall = boost::bind( pe::syncNextNeighbors<BodyTypeTuple>, boost::ref(blocks->getBlockForest()), bodyStorageID, static_cast<WcTimingTree*>(NULL), overlap, false ); + + // create the sphere + const real_t restitutionCoeff = real_t(0.97); + const real_t frictionCoeff = real_t(0.1); + + const real_t scaledCollisionTime = collisionTime * diameter / dx; // smaller diameter -> smaller collision time to keep maximum penetration small -> more pe substeps to resolve it + + const real_t particleMass = densityRatio * sphereVolume; + const real_t Mij = particleMass; // * particleMass / ( real_t(2) * particleMass ); // Mij = M for sphere-wall collision + const real_t lnDryResCoeff = std::log(restitutionCoeff); + const real_t stiffnessCoeff = math::M_PI * math::M_PI * Mij / ( scaledCollisionTime * scaledCollisionTime * ( real_t(1) - lnDryResCoeff * lnDryResCoeff / ( math::M_PI * math::M_PI + lnDryResCoeff* lnDryResCoeff ) ) ); + const real_t normalizedStiffnessCoeff = stiffnessCoeff / ( ( densityRatio - real_t(1) ) * gravity * sphereVolume / diameter ); + + const real_t dampingCoeff = - real_t(2) * std::sqrt( Mij * stiffnessCoeff ) * + ( std::log(restitutionCoeff) / std::sqrt( math::M_PI * math::M_PI + (std::log(restitutionCoeff) * std::log(restitutionCoeff) ) ) ); + const real_t contactDuration = real_t(2) * math::M_PI * Mij / ( std::sqrt( real_t(4) * Mij * stiffnessCoeff - dampingCoeff * dampingCoeff )); //formula from Uhlman + const real_t contactDuration2 = std::sqrt(( math::M_PI * math::M_PI + std::log(restitutionCoeff) * std::log(restitutionCoeff)) / ( stiffnessCoeff / Mij)); //formula from Finn + + if( !funcTest ) + { + WALBERLA_LOG_INFO_ON_ROOT( "Created material with:\n" + << " - coefficient of restitution = " << restitutionCoeff << "\n" + << " - coefficient of friction = " << frictionCoeff << "\n" + << " - normalized stiffness coefficient kn* = " << normalizedStiffnessCoeff << "\n" + << " - stiffness coefficient kn = " << stiffnessCoeff << "\n" + << " - damping coefficient cdn = " << dampingCoeff << "\n" + << " - contact time Tc = " << contactDuration << "\n" + << " - contact time Tc2 = " << contactDuration2); + } + + auto peMaterial = pe::createMaterial( "sedimentMat", densityRatio, restitutionCoeff, frictionCoeff, frictionCoeff, real_t(0), real_t(200), stiffnessCoeff, dampingCoeff, dampingCoeff ); + real_t zPosition = real_c(zlength) - real_t(3) * diameter; + pe::createSphere( *globalBodyStorage, blocks->getBlockStorage(), bodyStorageID, 0, Vector3<real_t>(real_c(xlength) * real_t(0.5), real_c(ylength) * real_t(0.5), zPosition), diameter * real_t(0.5), peMaterial ); + pe::createPlane( *globalBodyStorage, 0, Vector3<real_t>( 0, 0, 1 ), Vector3<real_t>( 0, 0, 0 ), peMaterial ); + + + ////////////////////// + // // + // BLOCK DATA // + // // + ////////////////////// + + // create force field + BlockDataID forceFieldID = field::addToStorage< Vec3Field_T >( blocks, "force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + + BlockDataID dragForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "drag force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + BlockDataID amForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "am force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + BlockDataID liftForceFieldID = field::addToStorage< Vec3Field_T >( blocks, "lift force field", Vector3<real_t>(real_t(0)), field::zyxf, FieldGhostLayers ); + + // create omega field + BlockDataID omegaFieldID = field::addToStorage< ScalarField_T >( blocks, "omega field", real_t(0), field::zyxf, FieldGhostLayers ); + + // create the lattice model + LatticeModel_T latticeModel = LatticeModel_T( omegaFieldID, ForceModel_T( forceFieldID ) ); + + // add PDF field + BlockDataID pdfFieldID = lbm::addPdfFieldToStorage( blocks, "pdf field (zyxf)", latticeModel, Vector3<real_t>(0), real_t(1), FieldGhostLayers, field::zyxf ); + + // add flag field + BlockDataID flagFieldID = field::addFlagFieldToStorage< FlagField_T >( blocks, "flag field" ); + + // add boundary handling & initialize outer domain boundaries + BlockDataID boundaryHandlingID = blocks->addStructuredBlockData< BoundaryHandling_T >( MyBoundaryHandling( flagFieldID, pdfFieldID ), "boundary handling" ); + + // field to store fluid velolcity + BlockDataID velocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "velocity field", Vector3<real_t>(0), field::zyxf, FieldGhostLayers ); + BlockDataID swappedOldVelocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "swapped old velocity field", Vector3<real_t>(0), field::zyxf, FieldGhostLayers ); + + // create pressure field + BlockDataID pressureFieldID = field::addToStorage< ScalarField_T >( blocks, "pressure field", real_t(0), field::zyxf, FieldGhostLayers ); + + // create solid volume fraction field + BlockDataID svfFieldID = field::addToStorage< ScalarField_T >( blocks, "svf field", real_t(0), field::zyxf, FieldGhostLayers ); + + // field to store pressure gradient + BlockDataID pressureGradientFieldID = field::addToStorage< Vec3Field_T >( blocks, "pressure gradient field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store curl of fluid velocity + BlockDataID velocityCurlFieldID = field::addToStorage< Vec3Field_T >( blocks, "velocity curl field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store velocity gradient + BlockDataID velocityGradientFieldID = field::addToStorage< TensorField_T >( blocks, "velocity gradient field", Matrix3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store gradient of stress tensor + BlockDataID stressTensorGradientFieldID = field::addToStorage< Vec3Field_T >( blocks, "stress tensor gradient field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // field to store time derivative of fluid velocity + BlockDataID timeDerivativeVelocityFieldID = field::addToStorage< Vec3Field_T >( blocks, "time derivative velocity field", Vector3<real_t>(real_c(0)), field::zyxf, FieldGhostLayers ); + + // communication schemes + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> forceComm( blocks, forceFieldID ); + + blockforest::communication::UniformBufferedScheme<stencil::D3Q27> pdfScheme( blocks ); + pdfScheme.addPackInfo( make_shared< field::communication::PackInfo<PdfField_T> >( pdfFieldID ) ); + + // setup of the communication for synchronizing the velocity field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( velocityFieldID ) ); + + // setup of the communication for synchronizing the solid volume fraction field between neighboring blocks which takes into account the svf values inside the ghost layers + shared_ptr<pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >svfCommunicationScheme = make_shared<pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >( blocks, svfFieldID ); + + // setup of the communication for synchronizing the pressure field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > pressureCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + pressureCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<ScalarField_T> >( pressureFieldID ) ); + + // setup of the communication for synchronizing the pressure gradient field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > pressureGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + pressureGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( pressureGradientFieldID ) ); + + // setup of the communication for synchronizing the velocity curl field between neighboring blocks + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityCurlCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityCurlCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( velocityCurlFieldID ) ); + + // communication for synchronizing the velocity gradient values + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > velocityGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + velocityGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<TensorField_T> >( velocityGradientFieldID ) ); + + // communication for synchronizing the stress tensor gradient values + shared_ptr<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> > stressTensorGradientCommunicationScheme = make_shared<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >( blocks ); + stressTensorGradientCommunicationScheme->addPackInfo( make_shared< field::communication::PackInfo<Vec3Field_T> >( stressTensorGradientFieldID ) ); + + // communication for synchronizing the interaction force field + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> dragForceComm( blocks, dragForceFieldID ); + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> amForceComm( blocks, amForceFieldID ); + pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<Vec3Field_T> liftForceComm( blocks, liftForceFieldID ); + + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> dragForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, dragForceFieldID, uint_t(1) ); + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> amForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, amForceFieldID, uint_t(1) ); + shared_ptr<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> liftForceFieldToForceFieldAdder = + make_shared<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder>(blocks, forceFieldID, liftForceFieldID, uint_t(1) ); + + ///////////////////////////////// + // // + // CORRELATION FUNCTIONS // + // // + ///////////////////////////////// + + // drag correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t>&, const Vector3<real_t> &, real_t, real_t, real_t, real_t)> dragCorrelationFunction; + if( dragCorr == DragCorrelation::ErgunWenYu ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceErgunWenYu; + } + else if( dragCorr == DragCorrelation::Tang ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceTang; + } + else if( dragCorr == DragCorrelation::Stokes ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceStokes; + } + else if( dragCorr == DragCorrelation::Felice ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceFelice; + } + else if( dragCorr == DragCorrelation::Tenneti ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::dragForceTenneti; + } + else if( dragCorr == DragCorrelation::NoDrag ) + { + dragCorrelationFunction = pe_coupling::discrete_particle_methods::noDragForce; + } + else + { + WALBERLA_ABORT("Drag correlation not yet implemented!"); + } + + // lift correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t, real_t )> liftCorrelationFunction; + if( liftCorr == LiftCorrelation::NoLift ) + { + liftCorrelationFunction = pe_coupling::discrete_particle_methods::noLiftForce; + } + else if( liftCorr == LiftCorrelation::Saffman ) + { + liftCorrelationFunction = pe_coupling::discrete_particle_methods::liftForceSaffman; + } + else + { + WALBERLA_ABORT("Lift correlation not yet implemented!"); + } + + // added mass correlation function + boost::function<Vector3<real_t> ( const Vector3<real_t> &, const Vector3<real_t> &, real_t, real_t )> addedMassCorrelationFunction; + if( addedMassCorr == AddedMassCorrelation::NoAM ) + { + addedMassCorrelationFunction = pe_coupling::discrete_particle_methods::noAddedMassForce; + } + else if( addedMassCorr == AddedMassCorrelation::Finn ) + { + addedMassCorrelationFunction = pe_coupling::discrete_particle_methods::addedMassForceFinn; + } + else + { + WALBERLA_ABORT("Added mass correlation not yet implemented!"); + } + + // set up effective viscosity calculation + boost::function<real_t ( real_t, real_t)> effectiveViscosityFunction; + if( effVisc == EffectiveViscosity::None ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateUnchangedEffectiveViscosity; + } + else if( effVisc == EffectiveViscosity::Rescaled ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateRescaledEffectiveViscosity; + } + else if( effVisc == EffectiveViscosity::Eilers ) + { + effectiveViscosityFunction = pe_coupling::discrete_particle_methods::calculateEilersEffectiveViscosity; + } + else + { + WALBERLA_ABORT("Effective viscosity not yet implemented!"); + } + shared_ptr<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator> effectiveViscosityEvaluator = + walberla::make_shared<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator>(omegaFieldID, svfFieldID, viscosity, effectiveViscosityFunction ); + + + //////////////////////////////// + // // + // EVALUATION FUNCTIONS // + // // + //////////////////////////////// + + // evaluator for bodies' velocity time derivative + shared_ptr<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator> bodyVelocityTimeDerivativeEvaluator = make_shared<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator>( blocks, bodyStorageID, dtBodyVelocityTimeDerivativeEvaluation ); + (*bodyVelocityTimeDerivativeEvaluator)(); + + // function used to evaluate the interaction force between fluid and particles + boost::function<void(void)> dragAndPressureForceEvaluationFunction; + if( dpm == DPMethod::GNS ) { + if (interpol == Interpolation::INearestNeighbor) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } else if (interpol == Interpolation::IKernel) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } else if (interpol == Interpolation::ITrilinear) { + if (dist == Distribution::DNearestNeighbor) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } else if (dist == Distribution::DKernel) { + typedef pe_coupling::discrete_particle_methods::InteractionForceEvaluator<FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor> IFE_T; + shared_ptr<IFE_T> forceEvaluatorPtr = make_shared<IFE_T>(blocks, dragForceFieldID, bodyStorageID, + flagFieldID, Fluid_Flag, + velocityFieldID, svfFieldID, + pressureGradientFieldID, dragCorrelationFunction, + viscosity); + dragAndPressureForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + } + else + { + WALBERLA_ABORT("Discrete particle method not yet implemented!"); + } + + + // function to evaluate the lift force contribution + boost::function<void(void)> liftForceEvaluationFunction; + if( interpol == Interpolation::INearestNeighbor ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::IKernel ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::ITrilinear ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::LiftForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, liftForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + velocityFieldID, velocityCurlFieldID, liftCorrelationFunction, + viscosity ); + liftForceEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + + + // function to evaluate the added mass contribution + boost::function<void(void)> addedMassEvaluationFunction; + if( interpol == Interpolation::INearestNeighbor ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::NearestNeighborFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::IKernel ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::KernelFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + else if( interpol == Interpolation::ITrilinear ) + { + if( dist == Distribution::DNearestNeighbor ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::NearestNeighborDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + else if( dist == Distribution::DKernel ) + { + typedef pe_coupling::discrete_particle_methods::AddedMassForceEvaluator< FlagField_T, field::TrilinearFieldInterpolator, field::KernelDistributor > IFE_T; + shared_ptr< IFE_T > forceEvaluatorPtr = make_shared< IFE_T > ( blocks, amForceFieldID, bodyStorageID, flagFieldID, Fluid_Flag, + timeDerivativeVelocityFieldID, addedMassCorrelationFunction, + bodyVelocityTimeDerivativeEvaluator ); + addedMassEvaluationFunction = boost::bind(&IFE_T::operator(), forceEvaluatorPtr); + } + } + + // function to evaluate lubrication forces + boost::function<void(void)> lubricationEvaluationFunction; + if( lubricationCutOffDistance > real_t(0) ) + { + if( useLubricationCorrection ) + { + typedef pe_coupling::LubricationCorrection LE_T; + shared_ptr<LE_T> lubEval = make_shared<LE_T>( blocks, globalBodyStorage, bodyStorageID, viscosity, lubricationCutOffDistance ); + lubricationEvaluationFunction = boost::bind(&LE_T::operator(), lubEval); + } + else + { + typedef pe_coupling::discrete_particle_methods::LubricationForceEvaluator LE_T; + shared_ptr<LE_T> lubEval = make_shared<LE_T>( blocks, globalBodyStorage, bodyStorageID, viscosity, lubricationCutOffDistance ); + lubricationEvaluationFunction = boost::bind(&LE_T::operator(), lubEval); + } + } else { + lubricationEvaluationFunction = emptyFunction; + } + + + /////////////////////////// + // // + // CREATE TIMELOOP // + // // + /////////////////////////// + + // initial SVF calculation + if( dist == Distribution::DNearestNeighbor ) + { + pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::NearestNeighborDistributor> svfEvaluator( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) svfEvaluator( &(*blockIt) ); + } else { + pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::KernelDistributor> svfEvaluator( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ); + for( auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt ) svfEvaluator( &(*blockIt) ); + } + (*svfCommunicationScheme)(); + + // create the timeloop + SweepTimeloop timeloop( blocks->getBlockStorage(), timesteps ); + + // evaluation functions for simulation + shared_ptr<QuantityEvaluator> quantityEvaluator = walberla::make_shared< QuantityEvaluator >( &timeloop, *blocks, bodyStorageID, diameter, densityRatio, GalileoNumber, baseFolder, fileIO ); + shared_ptr<CollisionPropertiesEvaluator> collisionPropertiesEvaluator = walberla::make_shared<CollisionPropertiesEvaluator>( *cr ); + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::FieldDataSwapper<Vec3Field_T>( velocityFieldID, swappedOldVelocityFieldID ), "Velocity Field Swap" ); + } + + // subcycling loop begin + for( uint_t subcycle = 1; subcycle <= interactionSubCycles; ++subcycle ) + { + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Old Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCommunicationScheme), "Old Velocity Field Communication" ); + + if( liftCorr != LiftCorrelation::NoLift ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityCurlFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( velocityCurlFieldID, velocityFieldID, boundaryHandlingID ), "Velocity Curl Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCurlCommunicationScheme), "Velocity Curl Field Communication" ); + } + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityGradientFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( velocityGradientFieldID, velocityFieldID, boundaryHandlingID ), "Velocity Gradient Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityGradientCommunicationScheme), "Velocity Gradient Field Communication" ); + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::VelocityTotalTimeDerivativeFieldEvaluator( timeDerivativeVelocityFieldID, velocityFieldID, swappedOldVelocityFieldID, velocityGradientFieldID, dt ), "Velocity Time Derivative Field Evaluation" ); + } + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSPressureFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( pressureFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Pressure Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureCommunicationScheme), "Pressure Field Communication" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::PressureGradientFieldEvaluator<LatticeModel_T, BoundaryHandling_T>( pressureGradientFieldID, pressureFieldID, boundaryHandlingID ), "Pressure Gradient Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(pressureGradientCommunicationScheme), "Pressure Gradient Field Communication" ); + + + if( liftCorr != LiftCorrelation::NoLift ) + { + // evaluate Flift + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( liftForceFieldID ), "Lift Force Field Reset" ) + << AfterFunction( liftForceEvaluationFunction, "Lift Force Evaluation") + << AfterFunction( liftForceComm, "Lift Force Field Communication" ); + } + + if( addedMassCorr != AddedMassCorrelation::NoAM ) + { + + // evaluate Fam + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( amForceFieldID ), "AM Force Field Reset" ) + << AfterFunction( addedMassEvaluationFunction, "Added Mass Force Evaluation") + << AfterFunction( amForceComm, "Force Field Communication" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::BodyVelocityTimeDerivativeEvaluator>( bodyVelocityTimeDerivativeEvaluator ), "Body Velocity Time Derivative Evaluation" ); + } + + if( liftCorr != LiftCorrelation::NoLift || addedMassCorr != AddedMassCorrelation::NoAM ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSVelocityFieldEvaluator<LatticeModel_T, BoundaryHandling_T> ( velocityFieldID, pdfFieldID, svfFieldID, boundaryHandlingID ), "Velocity Field Evaluation" ) + << AfterFunction ( SharedFunctor<blockforest::communication::UniformBufferedScheme<stencil::D3Q27> >(velocityCommunicationScheme), "Velocity Field Communication" ); + } + + // evaluate Fdrag + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( dragForceFieldID ), "Drag Force Field Reset" ) + << AfterFunction( dragAndPressureForceEvaluationFunction, "Fluid-Particle Interaction Force Evaluation" ) + << AfterFunction( dragForceComm, "Drag Force Field Communication" ) + << AfterFunction( pe_coupling::ForceOnBodiesAdder( blocks, bodyStorageID, -gravitationalForce ), "Gravitational and buoyancy force" ) + << AfterFunction( pe_coupling::discrete_particle_methods::SubgridTimeStep( blocks, bodyStorageID, *cr, syncCall, lubricationEvaluationFunction, dtInteractionSubCycle, peSubSteps ), "Pe Time Step" ) + << AfterFunction( SharedFunctor<CollisionPropertiesEvaluator>( collisionPropertiesEvaluator ), "Collision properties evaluator"); + + if( dist == Distribution::DNearestNeighbor ) + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::NearestNeighborDistributor> ( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ), "Solid Volume Fraction Field Evaluation" ) + << AfterFunction( SharedFunctor< pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >(svfCommunicationScheme), "Solid Volume Fraction Field Communication" ); + } + else + { + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::SolidVolumeFractionFieldEvaluator<FlagField_T, field::KernelDistributor> ( blocks, svfFieldID, bodyStorageID, flagFieldID, Fluid_Flag ), "Solid Volume Fraction Field Evaluation" ) + << AfterFunction( SharedFunctor< pe_coupling::discrete_particle_methods::CombinedReductionFieldCommunication<ScalarField_T> >(svfCommunicationScheme), "Solid Volume Fraction Field Communication" ); + } + + } + + timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::ForceFieldResetter( forceFieldID ), "Force Field Reset" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (liftForceFieldToForceFieldAdder ), "Lift Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (amForceFieldToForceFieldAdder ), "AM Force Field To Force Field Adder" ) + << AfterFunction( SharedFunctor<pe_coupling::discrete_particle_methods::AveragedInteractionForceFieldToForceFieldAdder> (dragForceFieldToForceFieldAdder ), "Drag Force Field To Force Field Adder" ); + + timeloop.add() << Sweep( makeSharedSweep<pe_coupling::discrete_particle_methods::EffectiveViscosityFieldEvaluator>( effectiveViscosityEvaluator ), "Effective Viscosity Evaluation"); + if( useTurbulenceModel ) timeloop.add() << Sweep( pe_coupling::discrete_particle_methods::GNSSmagorinskyLESField<LatticeModel_T>(blocks, omegaFieldID, pdfFieldID, svfFieldID, smagorinskyConstant), "Turbulence Model" ); + + // execute GNS-LBM sweep, boundary handling, PDF communication + auto sweep = pe_coupling::discrete_particle_methods::makeGNSSweep< LatticeModel_T, FlagField_T >( pdfFieldID, svfFieldID, flagFieldID, Fluid_Flag ); + + timeloop.add() << Sweep( lbm::makeCollideSweep( sweep ), "GNS-LBM sweep (collide)" ); + + timeloop.add() << BeforeFunction( pdfScheme, "LBM Communication" ) + << Sweep( BoundaryHandling_T::getBlockSweep( boundaryHandlingID ), "Boundary Handling" ); + + timeloop.add() << Sweep( lbm::makeStreamSweep( sweep ), "GNS-LBM sweep (stream)" ); + + timeloop.addFuncAfterTimeStep( SharedFunctor<QuantityEvaluator>( quantityEvaluator ), "Quantity Evaluator" ); + + timeloop.addFuncAfterTimeStep( RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "Remaining Time Logger" ); + + + ////////////////////// + // // + // SIMULATION // + // // + ////////////////////// + + WcTimingPool timeloopTiming; + + real_t currentVelocity(0); + real_t oldVelocity(0); + uint_t numberOfBouncesUntilTermination = uint_t(4); + uint_t countedBounces = uint_t(0); + uint_t tImpact = uint_t(0); + real_t terminalVelocity(0); + real_t maxPositionAfterBounce = real_t(0); + + // three different evaluation times for the rebound velocity (see Kidanemariam, Uhlmann (2014) ) + real_t evalOffsetFactorS = real_t(0.05); + real_t evalOffsetFactorM = real_t(0.1); + real_t evalOffsetFactorL = real_t(0.15); + real_t reboundVelocityS( 0. ); + real_t reboundVelocityM( 0. ); + real_t reboundVelocityL( 0. ); + uint_t tEvalOffsetS = 0; + uint_t tEvalOffsetM = 0; + uint_t tEvalOffsetL = 0; + + for( size_t t = 0; t < timesteps; ++t) + { + timeloop.singleStep(timeloopTiming); + currentVelocity = quantityEvaluator->getVelocity(); + if( currentVelocity > real_t(0) && oldVelocity < real_t(0) ) + { + ++countedBounces; + if( countedBounces == 1 ) + { + tImpact = t; + terminalVelocity = std::fabs(quantityEvaluator->getTerminalVelocity()); + quantityEvaluator->stopTerminalVelocityEvaluation(); + tEvalOffsetS = uint_c(std::ceil(evalOffsetFactorS * diameter / terminalVelocity)); + tEvalOffsetM = uint_c(std::ceil(evalOffsetFactorM * diameter / terminalVelocity)); + tEvalOffsetL = uint_c(std::ceil(evalOffsetFactorL * diameter / terminalVelocity)); + } + WALBERLA_LOG_INFO_ON_ROOT("----------- " << countedBounces << ". bounce detected ----------------") + } + if( currentVelocity > oldVelocity && oldVelocity > real_t(0) && countedBounces == 1) + { + // impact was wrongly detected in an intermediate step, but should be the time when the collision is fully resolved and maximal velocity is reached + ++tImpact; + } + if( t == tImpact + tEvalOffsetS ) reboundVelocityS = currentVelocity; + if( t == tImpact + tEvalOffsetM ) reboundVelocityM = currentVelocity; + if( t == tImpact + tEvalOffsetL ) reboundVelocityL = currentVelocity; + if( countedBounces >= numberOfBouncesUntilTermination) break; + if( countedBounces >= 1 ) + { + maxPositionAfterBounce = std::max(maxPositionAfterBounce, quantityEvaluator->getPosition()); + } + if( countedBounces >= 1 && ( real_c(t-tImpact) * terminalVelocity / diameter ) > real_t(100) ) break; + + oldVelocity = currentVelocity; + } + + maxPositionAfterBounce -= diameter / real_t(2); + + timeloopTiming.logResultOnRoot(); + + if( !funcTest ) + { + WALBERLA_LOG_INFO_ON_ROOT("v_T = " << terminalVelocity); + real_t Re = terminalVelocity * diameter / viscosity; + WALBERLA_LOG_INFO_ON_ROOT("Re_T = " << Re); + real_t St = densityRatio * Re / real_t(9); + WALBERLA_LOG_INFO_ON_ROOT("density ratio = " << densityRatio); + WALBERLA_LOG_INFO_ON_ROOT("St = " << St); + + + WALBERLA_LOG_INFO_ON_ROOT("tI = " << tImpact ) + + WALBERLA_LOG_INFO_ON_ROOT(" - tRS = " << evalOffsetFactorS * diameter / terminalVelocity << " = " << tEvalOffsetS ); + real_t effectiveCoefficientOfRestitutionS = reboundVelocityS / terminalVelocity; + WALBERLA_LOG_INFO_ON_ROOT(" e_eff = " << effectiveCoefficientOfRestitutionS << ", e/e_dry = " << effectiveCoefficientOfRestitutionS / restitutionCoeff); + + WALBERLA_LOG_INFO_ON_ROOT(" - tRM = " << evalOffsetFactorM * diameter / terminalVelocity << " = " << tEvalOffsetM ); + real_t effectiveCoefficientOfRestitutionM = reboundVelocityM / terminalVelocity; + WALBERLA_LOG_INFO_ON_ROOT(" e_eff = " << effectiveCoefficientOfRestitutionM << ", e/e_dry = " << effectiveCoefficientOfRestitutionM / restitutionCoeff); + + WALBERLA_LOG_INFO_ON_ROOT(" - tRL = " << evalOffsetFactorL * diameter / terminalVelocity << " = " << tEvalOffsetL ); + real_t effectiveCoefficientOfRestitutionL = reboundVelocityL / terminalVelocity; + WALBERLA_LOG_INFO_ON_ROOT(" e_eff = " << effectiveCoefficientOfRestitutionL << ", e/e_dry = " << effectiveCoefficientOfRestitutionL / restitutionCoeff); + + WALBERLA_LOG_INFO_ON_ROOT("maximum position after first bounce (zMax) = " << maxPositionAfterBounce << ", zMax / D = " << maxPositionAfterBounce / diameter ); + real_t maxPen = collisionPropertiesEvaluator->getMaximumPenetrationInSimulation(); + WALBERLA_LOG_INFO_ON_ROOT("maximum penetration (maxPen) = " << maxPen << ", maxPen / D = " << real_t(100) * maxPen / diameter << "%"); + + if( fileIO ) quantityEvaluator->writeScaledOutputToFile(tImpact, terminalVelocity); + + //log to file + if( fileIO ) { + WALBERLA_ROOT_SECTION() { + std::string fileName = baseFolder + "/logCollisionBehavior.txt"; + std::ofstream file; + file.open(fileName.c_str(), std::ofstream::app); + file.precision(8); + + file << densityRatio << " \t" << GalileoNumber << " \t" << diameter << " \t" + << tau << " \t" << gravity << " \t" << normalizedStiffnessCoeff << " \t" << interpol << " \t" << dist << " \t" + << addedMassCorr << " \t" << liftCorr << " \t" << effVisc << " \t" << ((useTurbulenceModel) ? 1 : 0) << " \t" + << interactionSubCycles << " \t" << peSubSteps << " \t" << lubricationCutOffDistance << " \t" + << terminalVelocity << " \t" << Re << " \t" << St << " \t" + << effectiveCoefficientOfRestitutionM << " \t" << restitutionCoeff << " \t" + << maxPositionAfterBounce << " \t " << maxPen << "\n"; + } + } + + } + + return 0; +} + +} //namespace sphere_wall_collision_behavior_dpm + +int main( int argc, char **argv ){ + sphere_wall_collision_behavior_dpm::main(argc, argv); +} + -- GitLab