diff --git a/apps/benchmarks/MaterialTransport/thermal_PSM/thermal_PSM_particles/thermalPSMParticles.cpp b/apps/benchmarks/MaterialTransport/thermal_PSM/thermal_PSM_particles/thermalPSMParticles.cpp index 9cb01d6330a41d8a1df69c5503b83c496d6de6e7..cdd13348b24b5dfdee8f01cf36ab235f0c8a9d71 100644 --- a/apps/benchmarks/MaterialTransport/thermal_PSM/thermal_PSM_particles/thermalPSMParticles.cpp +++ b/apps/benchmarks/MaterialTransport/thermal_PSM/thermal_PSM_particles/thermalPSMParticles.cpp @@ -46,15 +46,36 @@ #include "lbm_mesapd_coupling/DataTypesCodegen.h" #include "lbm_mesapd_coupling/partially_saturated_cells_method/codegen/PSMSweepCollection.h" +#include "lbm_mesapd_coupling/utility/AddForceOnParticlesKernel.h" +#include "lbm_mesapd_coupling/utility/AddHydrodynamicInteractionKernel.h" +#include "lbm_mesapd_coupling/utility/AverageHydrodynamicForceTorqueKernel.h" +#include "lbm_mesapd_coupling/utility/InitializeHydrodynamicForceTorqueForAveragingKernel.h" +#include "lbm_mesapd_coupling/utility/LubricationCorrectionKernel.h" #include "lbm_mesapd_coupling/utility/ParticleSelector.h" +#include "lbm_mesapd_coupling/utility/ResetHydrodynamicForceTorqueKernel.h" +#include "mesa_pd/collision_detection/AnalyticContactDetection.h" #include "mesa_pd/data/DataTypes.h" +#include "mesa_pd/data/LinkedCells.h" #include "mesa_pd/data/ParticleAccessorWithShape.h" #include "mesa_pd/data/ParticleStorage.h" #include "mesa_pd/data/ShapeStorage.h" +#include "mesa_pd/data/shape/HalfSpace.h" #include "mesa_pd/data/shape/Sphere.h" +#include "mesa_pd/domain/BlockForestDataHandling.h" #include "mesa_pd/domain/BlockForestDomain.h" +#include "mesa_pd/kernel/AssocToBlock.h" +#include "mesa_pd/kernel/DoubleCast.h" +#include "mesa_pd/kernel/InsertParticleIntoLinkedCells.h" +#include "mesa_pd/kernel/LinearSpringDashpot.h" +#include "mesa_pd/kernel/ParticleSelector.h" +#include "mesa_pd/kernel/VelocityVerlet.h" +#include "mesa_pd/mpi/ContactFilter.h" +#include "mesa_pd/mpi/ReduceContactHistory.h" +#include "mesa_pd/mpi/ReduceProperty.h" #include "mesa_pd/mpi/SyncNextNeighbors.h" +#include "mesa_pd/mpi/notifications/ForceTorqueNotification.h" +#include "mesa_pd/mpi/notifications/HydrodynamicForceTorqueNotification.h" #include "mesa_pd/vtk/ParticleVtkOutput.h" #include "sqlite/SQLite.h" @@ -102,6 +123,100 @@ const FlagUID Inflow_Concentration_Flag("Inflow_Concentration"); // const FlagUID Dirichlet_Concentration_Flag("Dirichlet_Concentration"); const FlagUID Neumann_Concentration_Flag("Neumann_Concentration"); +void createPlane(const shared_ptr< mesa_pd::data::ParticleStorage >& ps, + const shared_ptr< mesa_pd::data::ShapeStorage >& ss, Vector3< real_t > position, + Vector3< real_t > normal) +{ + mesa_pd::data::Particle&& p0 = *ps->create(true); + p0.setPosition(position); + p0.setInteractionRadius(std::numeric_limits< real_t >::infinity()); + p0.setShapeID(ss->create< mesa_pd::data::HalfSpace >(normal)); + p0.setOwner(mpi::MPIManager::instance()->rank()); + p0.setType(0); + mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::INFINITE); + mesa_pd::data::particle_flags::set(p0.getFlagsRef(), mesa_pd::data::particle_flags::FIXED); +} + +void createPlaneSetup(const shared_ptr< mesa_pd::data::ParticleStorage >& ps, + const shared_ptr< mesa_pd::data::ShapeStorage >& ss, const math::AABB& simulationDomain, + bool periodicInX, bool periodicInY, real_t offsetAtInflow, real_t offsetAtOutflow) +{ + createPlane(ps, ss, simulationDomain.minCorner() + Vector3< real_t >(0, 0, offsetAtInflow), + Vector3< real_t >(0, 0, 1)); + createPlane(ps, ss, simulationDomain.maxCorner() + Vector3< real_t >(0, 0, offsetAtOutflow), + Vector3< real_t >(0, 0, -1)); + + if (!periodicInX) + { + createPlane(ps, ss, simulationDomain.minCorner(), Vector3< real_t >(1, 0, 0)); + createPlane(ps, ss, simulationDomain.maxCorner(), Vector3< real_t >(-1, 0, 0)); + } + + if (!periodicInY) + { + createPlane(ps, ss, simulationDomain.minCorner(), Vector3< real_t >(0, 1, 0)); + createPlane(ps, ss, simulationDomain.maxCorner(), Vector3< real_t >(0, -1, 0)); + } +} + +struct ParticleInfo +{ + real_t averageVelocity = 0_r; + real_t maximumVelocity = 0_r; + uint_t numParticles = 0; + real_t maximumHeight = 0_r; + real_t particleVolume = 0_r; + real_t heightOfMass = 0_r; + + void allReduce() + { + walberla::mpi::allReduceInplace(numParticles, walberla::mpi::SUM); + walberla::mpi::allReduceInplace(averageVelocity, walberla::mpi::SUM); + walberla::mpi::allReduceInplace(maximumVelocity, walberla::mpi::MAX); + walberla::mpi::allReduceInplace(maximumHeight, walberla::mpi::MAX); + walberla::mpi::allReduceInplace(particleVolume, walberla::mpi::SUM); + walberla::mpi::allReduceInplace(heightOfMass, walberla::mpi::SUM); + + averageVelocity /= real_c(numParticles); + heightOfMass /= particleVolume; + } +}; + +std::ostream& operator<<(std::ostream& os, ParticleInfo const& m) +{ + return os << "Particle Info: uAvg = " << m.averageVelocity << ", uMax = " << m.maximumVelocity + << ", numParticles = " << m.numParticles << ", zMax = " << m.maximumHeight << ", Vp = " << m.particleVolume + << ", zMass = " << m.heightOfMass; +} + +template< typename Accessor_T > +ParticleInfo evaluateParticleInfo(const Accessor_T& ac) +{ + static_assert(std::is_base_of_v< mesa_pd::data::IAccessor, Accessor_T >, "Provide a valid accessor"); + + ParticleInfo info; + for (uint_t i = 0; i < ac.size(); ++i) + { + if (isSet(ac.getFlags(i), mesa_pd::data::particle_flags::GHOST)) continue; + if (isSet(ac.getFlags(i), mesa_pd::data::particle_flags::GLOBAL)) continue; + + ++info.numParticles; + real_t velMagnitude = ac.getLinearVelocity(i).length(); + real_t particleVolume = ac.getShape(i)->getVolume(); + real_t height = ac.getPosition(i)[2]; + info.averageVelocity += velMagnitude; + info.maximumVelocity = std::max(info.maximumVelocity, velMagnitude); + info.maximumHeight = std::max(info.maximumHeight, height); + info.particleVolume += particleVolume; + info.heightOfMass += particleVolume * height; + } + + info.allReduce(); + + return info; +} + + ////////// // MAIN // ////////// @@ -121,57 +236,134 @@ int main(int argc, char** argv) WALBERLA_LOG_INFO_ON_ROOT("build machine: " << std::string(WALBERLA_BUILD_MACHINE)); WALBERLA_LOG_INFO_ON_ROOT(*cfgFile); - // Read config file - Config::BlockHandle numericalSetup = cfgFile->getBlock("NumericalSetup"); + // read all parameters from the config file + + Config::BlockHandle physicalSetup = cfgFile->getBlock("PhysicalSetup"); + const bool showcase = physicalSetup.getParameter< bool >("showcase"); + const real_t xSize_SI = physicalSetup.getParameter< real_t >("xSize"); + const real_t ySize_SI = physicalSetup.getParameter< real_t >("ySize"); + const real_t zSize_SI = physicalSetup.getParameter< real_t >("zSize"); + const bool periodicInX = physicalSetup.getParameter< bool >("periodicInX"); + const bool periodicInY = physicalSetup.getParameter< bool >("periodicInY"); + const real_t runtime_SI = physicalSetup.getParameter< real_t >("runtime"); + const real_t uInflow_SI = physicalSetup.getParameter< real_t >("uInflow"); + const real_t gravitationalAcceleration_SI = physicalSetup.getParameter< real_t >("gravitationalAcceleration"); + const real_t kinematicViscosityFluid_SI = physicalSetup.getParameter< real_t >("kinematicViscosityFluid"); + const real_t densityFluid_SI = physicalSetup.getParameter< real_t >("densityFluid"); + const real_t particleDiameter_SI = physicalSetup.getParameter< real_t >("particleDiameter"); + const real_t densityParticle_SI = physicalSetup.getParameter< real_t >("densityParticle"); + const real_t dynamicFrictionCoefficient = physicalSetup.getParameter< real_t >("dynamicFrictionCoefficient"); + const real_t coefficientOfRestitution = physicalSetup.getParameter< real_t >("coefficientOfRestitution"); + const real_t collisionTimeFactor = physicalSetup.getParameter< real_t >("collisionTimeFactor"); + const real_t particleGenerationSpacing_SI = physicalSetup.getParameter< real_t >("particleGenerationSpacing"); + - const uint_t numXBlocks = numericalSetup.getParameter< uint_t >("numXBlocks"); - const uint_t numYBlocks = numericalSetup.getParameter< uint_t >("numYBlocks"); - const uint_t numZBlocks = numericalSetup.getParameter< uint_t >("numZBlocks"); - const uint_t cellsPerBlockPerDirection = - numericalSetup.getParameter< uint_t >("cellsPerBlockPerDirection"); // same in all directions + Config::BlockHandle numericalSetup = cfgFile->getBlock("NumericalSetup"); + const real_t dx_SI = numericalSetup.getParameter< real_t >("dx"); + const real_t uInflow = numericalSetup.getParameter< real_t >("uInflow"); + const real_t timeStepSize = numericalSetup.getParameter< real_t >("dt"); + const uint_t numXBlocks = numericalSetup.getParameter< uint_t >("numXBlocks"); + const uint_t numYBlocks = numericalSetup.getParameter< uint_t >("numYBlocks"); + const uint_t numZBlocks = numericalSetup.getParameter< uint_t >("numZBlocks"); WALBERLA_CHECK_EQUAL(numXBlocks * numYBlocks * numZBlocks, uint_t(MPIManager::instance()->numProcesses()), "When using GPUs, the number of blocks (" << numXBlocks * numYBlocks * numZBlocks << ") has to match the number of MPI processes (" << uint_t(MPIManager::instance()->numProcesses()) << ")"); - const bool periodicInY = numericalSetup.getParameter< bool >("periodicInY"); - const bool periodicInZ = numericalSetup.getParameter< bool >("periodicInZ"); - const bool sendDirectlyFromGPU = numericalSetup.getParameter< bool >("sendDirectlyFromGPU"); - const bool useCommunicationHiding = numericalSetup.getParameter< bool >("useCommunicationHiding"); - const Vector3< uint_t > frameWidth = numericalSetup.getParameter< Vector3< uint_t > >("frameWidth"); - const real_t gravity = numericalSetup.getParameter< real_t >("gravity"); - - const real_t Thot = numericalSetup.getParameter< real_t >("Thot"); - const real_t Tcold = numericalSetup.getParameter< real_t >("Tcold"); - const real_t Pr = numericalSetup.getParameter< real_t >("PrandtlNumber"); - const real_t Ra = numericalSetup.getParameter< real_t >("RayleighNumber"); - const real_t Ma = numericalSetup.getParameter< real_t >("Mach"); - const real_t relaxationRate = numericalSetup.getParameter< real_t >("relaxationRate"); - const uint_t timeSteps = numericalSetup.getParameter< uint_t >("timeSteps"); - - const bool useParticles = numericalSetup.getParameter< bool >("useParticles"); - const real_t particleDiameter = numericalSetup.getParameter< real_t >("particleDiameter"); - const real_t particleGenerationSpacing = numericalSetup.getParameter< real_t >("particleGenerationSpacing"); - const Vector3< real_t > generationDomainFraction = - numericalSetup.getParameter< Vector3< real_t > >("generationDomainFraction"); - const Vector3< uint_t > generationPointOfReferenceOffset = - numericalSetup.getParameter< Vector3< uint_t > >("generationPointOfReferenceOffset"); - const bool useParticleOffset = numericalSetup.getParameter< bool >("useParticleOffset"); + if ((periodicInX && numXBlocks == 1) || (periodicInY && numYBlocks == 1)) + { + WALBERLA_ABORT("The number of blocks must be greater than 1 in periodic dimensions.") + } + + const bool useLubricationForces = numericalSetup.getParameter< bool >("useLubricationForces"); + const uint_t numberOfParticleSubCycles = numericalSetup.getParameter< uint_t >("numberOfParticleSubCycles"); + const bool useParticles = numericalSetup.getParameter< bool >("useParticles"); + const bool useIntegrators = numericalSetup.getParameter< bool >("useIntegrators"); const Vector3< uint_t > particleSubBlockSize = numericalSetup.getParameter< Vector3< uint_t > >("particleSubBlockSize"); + const real_t linkedCellWidthRation = numericalSetup.getParameter< real_t >("linkedCellWidthRation"); + const bool particleBarriers = numericalSetup.getParameter< bool >("particleBarriers"); + const Vector3< real_t > generationDomainFraction = + numericalSetup.getParameter< Vector3< real_t > >("generationDomainFraction"); - if ((periodicInY && numYBlocks == 1) || (periodicInZ && numZBlocks == 1)) - { - WALBERLA_LOG_WARNING_ON_ROOT("Using only 1 block in periodic dimensions can lead to unexpected behavior.") - } + + Config::BlockHandle TemperatureSetup = cfgFile->getBlock("TemperatureSetup"); + const real_t Thot = TemperatureSetup.getParameter< real_t >("Thot"); + const real_t Tcold = TemperatureSetup.getParameter< real_t >("Tcold"); + const real_t Pr = TemperatureSetup.getParameter< real_t >("PrandtlNumber"); + const real_t Ra = TemperatureSetup.getParameter< real_t >("RayleighNumber"); + const real_t Ma = TemperatureSetup.getParameter< real_t >("Mach"); + const real_t particleTemperature = TemperatureSetup.getParameter< real_t >("particleTemperature"); Config::BlockHandle outputSetup = cfgFile->getBlock("Output"); - const uint_t vtkSpacing = outputSetup.getParameter< uint_t >("vtkSpacing"); + const real_t infoSpacing_SI = outputSetup.getParameter< real_t >("infoSpacing"); + const real_t vtkSpacingParticles_SI = outputSetup.getParameter< real_t >("vtkSpacingParticles"); + const real_t vtkSpacingFluid_SI = outputSetup.getParameter< real_t >("vtkSpacingFluid"); const std::string vtkFolder = outputSetup.getParameter< std::string >("vtkFolder"); const uint_t performanceLogFrequency = outputSetup.getParameter< uint_t >("performanceLogFrequency"); + + + + // convert SI units to simulation (LBM) units and check setup + + Vector3< uint_t > domainSize(uint_c(std::ceil(xSize_SI / dx_SI)), uint_c(std::ceil(ySize_SI / dx_SI)), + uint_c(std::ceil(zSize_SI / dx_SI))); + WALBERLA_CHECK_FLOAT_EQUAL(real_t(domainSize[0]) * dx_SI, xSize_SI, "domain size in x is not divisible by given dx"); + WALBERLA_CHECK_FLOAT_EQUAL(real_t(domainSize[1]) * dx_SI, ySize_SI, "domain size in y is not divisible by given dx"); + WALBERLA_CHECK_FLOAT_EQUAL(real_t(domainSize[2]) * dx_SI, zSize_SI, "domain size in z is not divisible by given dx"); + + Vector3< uint_t > cellsPerBlockPerDirection(domainSize[0] / numXBlocks, domainSize[1] / numYBlocks, + domainSize[2] / numZBlocks); + + WALBERLA_CHECK_EQUAL(domainSize[0], cellsPerBlockPerDirection[0] * numXBlocks, + "number of cells in x of " << domainSize[0] + << " is not divisible by given number of blocks in x direction"); + WALBERLA_CHECK_EQUAL(domainSize[1], cellsPerBlockPerDirection[1] * numYBlocks, + "number of cells in y of " << domainSize[1] + << " is not divisible by given number of blocks in y direction"); + WALBERLA_CHECK_EQUAL(domainSize[2], cellsPerBlockPerDirection[2] * numZBlocks, + "number of cells in z of " << domainSize[2] + << " is not divisible by given number of blocks in z direction"); + + WALBERLA_CHECK_GREATER_EQUAL( + particleDiameter_SI / dx_SI, 5_r, + "Your numerical resolution is below 5 cells per diameter and thus too small for such simulations!"); + + const real_t densityRatio = densityParticle_SI / densityFluid_SI; + const real_t ReynoldsNumberParticle = uInflow_SI * particleDiameter_SI / kinematicViscosityFluid_SI; + const real_t GalileiNumber = std::sqrt((densityRatio - 1_r) * particleDiameter_SI * gravitationalAcceleration_SI) * + particleDiameter_SI / kinematicViscosityFluid_SI; + + // in simulation units: dt = 1, dx = 1, densityFluid = 1 + + real_t dt_SI = timeStepSize; + if(uInflow != real_c(0)){dt_SI = uInflow / uInflow_SI * dx_SI;} + const real_t particleDiameter = particleDiameter_SI / dx_SI; + const real_t particleGenerationSpacing = particleGenerationSpacing_SI / dx_SI; + const real_t viscosity = kinematicViscosityFluid_SI * dt_SI / (dx_SI * dx_SI); + const real_t omega = lbm::collision_model::omegaFromViscosity(viscosity); + const real_t gravitationalAcceleration = gravitationalAcceleration_SI * dt_SI * dt_SI / dx_SI; + const real_t particleVolume = math::pi / 6_r * particleDiameter * particleDiameter * particleDiameter; + + const real_t densityFluid = real_t(1); + const real_t densityParticle = densityRatio; + const real_t dx = real_t(1); + + const uint_t numTimeSteps = uint_c(std::ceil(runtime_SI / dt_SI)); + const uint_t infoSpacing = uint_c(std::ceil(infoSpacing_SI / dt_SI)); + const uint_t vtkSpacingParticles = uint_c(std::ceil(vtkSpacingParticles_SI / dt_SI)); + const uint_t vtkSpacingFluid = uint_c(std::ceil(vtkSpacingFluid_SI / dt_SI)); + + const Vector3< real_t > inflowVec(0_r, 0_r, uInflow); + + const real_t poissonsRatio = real_t(0.22); + const real_t kappa = real_t(2) * (real_t(1) - poissonsRatio) / (real_t(2) - poissonsRatio); + const real_t particleCollisionTime = collisionTimeFactor * particleDiameter; + + // Necessary Calculations - const real_t length_conversion = (numXBlocks * real_c(cellsPerBlockPerDirection)); + const real_t length_conversion = (numXBlocks * real_c(cellsPerBlockPerDirection[0])); Vector3< uint_t > domainSizeLB(uint_c(length_conversion), uint_c(length_conversion), uint_c(length_conversion)); const real_t uCharacteristicLB = Ma * (std::sqrt(real_c(1 / 3.0))); @@ -180,11 +372,10 @@ int main(int argc, char** argv) const real_t delta_T = Thot - Tcold; const real_t T0 = (Thot + Tcold) / (2 * delta_T); const real_t kinematicViscosityLB = (Ma / std::sqrt(3)) * std::sqrt(Pr / Ra) * length_conversion; - // std::min((Ma / std::sqrt(3)) * std::sqrt(Pr / Ra) * length_conversion, std::sqrt(3.6 / Ra)); const real_t thermalDiffusivityLB = kinematicViscosityLB / Pr; const real_t time_conversion = (length_conversion * length_conversion) / (thermalDiffusivityLB); - const real_t gravityLB = gravity * time_conversion * time_conversion / length_conversion; + const real_t gravityLB = gravitationalAcceleration_SI * time_conversion * time_conversion / length_conversion; const real_t alphaLB = (uCharacteristicLB * uCharacteristicLB) / (gravityLB * delta_T * length_conversion); const real_t rho_0 = 1.0; @@ -202,6 +393,8 @@ int main(int argc, char** argv) const real_t machnumber = uchar * std::sqrt(3); const real_t thermal_diffusivity_2 = (Ma / std::sqrt(3)) / (std::sqrt(Ra * Pr)) * length_conversion; const real_t ratio = length_conversion / thermalDiffusivityLB; + + WALBERLA_LOG_INFO_ON_ROOT("dx_SI is " << dx_SI << " " << "dt_SI is " << dt_SI); WALBERLA_LOG_INFO_ON_ROOT("length conversion is " << length_conversion << " " << "time conversion is " << time_conversion); WALBERLA_LOG_INFO_ON_ROOT("kinematic viscosity is " << kinematicViscosityLB); @@ -215,16 +408,16 @@ int main(int argc, char** argv) WALBERLA_LOG_INFO_ON_ROOT("alpha LB is " << alphaLB); WALBERLA_LOG_INFO_ON_ROOT("gravity LB is " << gravityLB); WALBERLA_LOG_INFO_ON_ROOT("Temperature difference delta_t is " << delta_T); - //WALBERLA_LOG_INFO_ON_ROOT("Number of time steps is " << timeSteps); + WALBERLA_LOG_INFO_ON_ROOT("Number of time steps is " << numTimeSteps); /////////////////////////// // BLOCK STRUCTURE SETUP // /////////////////////////// - const bool periodicInX = false; + const bool periodicInZ = false; shared_ptr< StructuredBlockForest > blocks = blockforest::createUniformBlockGrid( - numXBlocks, numYBlocks, numZBlocks, cellsPerBlockPerDirection, cellsPerBlockPerDirection, cellsPerBlockPerDirection, real_t(1), uint_t(0), + numXBlocks, numYBlocks, numZBlocks, cellsPerBlockPerDirection[0], cellsPerBlockPerDirection[1], cellsPerBlockPerDirection[2], real_t(1), uint_t(0), false, false, periodicInX, periodicInY, periodicInZ, // periodicity false); @@ -250,19 +443,36 @@ int main(int argc, char** argv) WALBERLA_CHECK_FLOAT_EQUAL(simulationDomain.yMin(), real_t(0)); WALBERLA_CHECK_FLOAT_EQUAL(simulationDomain.zMin(), real_t(0)); - auto generationDomain = math::AABB::createFromMinMaxCorner( - math::Vector3< real_t >(simulationDomain.xMax() * (real_t(1) - generationDomainFraction[0]) / real_t(2), - simulationDomain.yMax() * (real_t(1) - generationDomainFraction[1]) / real_t(2), - simulationDomain.zMax() * (real_t(1) - generationDomainFraction[2]) / real_t(2)), - math::Vector3< real_t >(simulationDomain.xMax() * (real_t(1) + generationDomainFraction[0]) / real_t(2), - simulationDomain.yMax() * (real_t(1) + generationDomainFraction[1]) / real_t(2), - simulationDomain.zMax() * (real_t(1) + generationDomainFraction[2]) / real_t(2))); - real_t particleOffset = particleGenerationSpacing / real_t(2); - /*for (auto pt : - grid_generator::SCGrid(generationDomain, generationDomain.center(), - particleGenerationSpacing))*/ - //{ - auto pt = Vector3< real_t >(simulationDomain.xMax()/2,simulationDomain.yMax()/2,simulationDomain.zMax()/2); + if(showcase) + { + auto generationDomain = math::AABB::createFromMinMaxCorner( + math::Vector3< real_t >(simulationDomain.xMax() * (real_t(1) - generationDomainFraction[0]) / real_t(2), + simulationDomain.yMax() * (real_t(1) - generationDomainFraction[1]) / real_t(2), + simulationDomain.zMax() * (real_t(1) - generationDomainFraction[2]) / real_t(2)), + math::Vector3< real_t >(simulationDomain.xMax() * (real_t(1) + generationDomainFraction[0]) / real_t(2), + simulationDomain.yMax() * (real_t(1) + generationDomainFraction[1]) / real_t(2), + simulationDomain.zMax() * (real_t(1) + generationDomainFraction[2]) / real_t(2))); + real_t particleOffset = particleGenerationSpacing / real_t(2); + uint_t numparticles = 0; + for (auto pt : grid_generator::SCGrid(generationDomain, generationDomain.center(), particleGenerationSpacing)) + { + // auto pt = Vector3< real_t >(simulationDomain.xMax()/2,simulationDomain.yMax()/2,simulationDomain.zMax()/2); + if (rpdDomain->isContainedInProcessSubdomain(uint_c(mpi::MPIManager::instance()->rank()), pt)) + { + mesa_pd::data::Particle&& p = *ps->create(); + p.setPosition(pt); + p.setInteractionRadius(particleDiameter * real_t(0.5)); + p.setOwner(mpi::MPIManager::instance()->rank()); + p.setShapeID(sphereShape); + p.setType(0); + p.setTemperature(particleTemperature); + } + numparticles += 1; + if (numparticles == 1000) { break; } + } + } + else{ + auto pt = Vector3< real_t >(simulationDomain.xMax()/2,simulationDomain.yMax()/2,simulationDomain.zMax()/2); if (rpdDomain->isContainedInProcessSubdomain(uint_c(mpi::MPIManager::instance()->rank()), pt)) { mesa_pd::data::Particle&& p = *ps->create(); @@ -271,16 +481,12 @@ int main(int argc, char** argv) p.setOwner(mpi::MPIManager::instance()->rank()); p.setShapeID(sphereShape); p.setType(0); - p.setTemperature(math::realRandom(real_t(0.2),real_t(1))); - - //p.setTemperature(0.308382); - //p.setLinearVelocity(Vector3<real_t>(0.1,0.1,0.1)); - - + p.setTemperature(particleTemperature); } - //} + } } + //////////////////////// // ADD DATA TO BLOCKS // /////////////////////// @@ -342,8 +548,41 @@ int main(int argc, char** argv) field::addFlagFieldToStorage< FlagField_T >(blocks, "concentration flag field"); // Synchronize particles between the blocks for the correct mapping of ghost particles - mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc; - syncNextNeighborFunc(*ps, *rpdDomain); + // set up RPD functionality + std::function< void(void) > syncCall = [&ps, &rpdDomain]() { + // keep overlap for lubrication + const real_t overlap = real_t(1.5); + mesa_pd::mpi::SyncNextNeighbors syncNextNeighborFunc; + syncNextNeighborFunc(*ps, *rpdDomain, overlap); + }; + + syncCall(); + + real_t timeStepSizeRPD = real_t(1) / real_t(numberOfParticleSubCycles); + mesa_pd::kernel::VelocityVerletPreForceUpdate vvIntegratorPreForce(timeStepSizeRPD); + mesa_pd::kernel::VelocityVerletPostForceUpdate vvIntegratorPostForce(timeStepSizeRPD); + mesa_pd::kernel::LinearSpringDashpot collisionResponse(2); + collisionResponse.setFrictionCoefficientDynamic(0, 1, dynamicFrictionCoefficient); + collisionResponse.setFrictionCoefficientDynamic(1, 1, dynamicFrictionCoefficient); + real_t massSphere = densityParticle * particleVolume; + real_t meffSpherePlane = massSphere; + real_t meffSphereSphere = massSphere * massSphere / (real_t(2) * massSphere); + collisionResponse.setStiffnessAndDamping(0, 1, coefficientOfRestitution, particleCollisionTime, kappa, + meffSpherePlane); + collisionResponse.setStiffnessAndDamping(1, 1, coefficientOfRestitution, particleCollisionTime, kappa, + meffSphereSphere); + mesa_pd::kernel::AssocToBlock assoc(blocks->getBlockForestPointer()); + mesa_pd::mpi::ReduceProperty reduceProperty; + mesa_pd::mpi::ReduceContactHistory reduceAndSwapContactHistory; + + // set up coupling functionality + Vector3< real_t > gravitationalForce(real_t(0), real_t(0), + -(densityParticle - densityFluid) * gravitationalAcceleration * particleVolume); + lbm_mesapd_coupling::AddForceOnParticlesKernel addGravitationalForce(gravitationalForce); + lbm_mesapd_coupling::ResetHydrodynamicForceTorqueKernel resetHydrodynamicForceTorque; + lbm_mesapd_coupling::AverageHydrodynamicForceTorqueKernel averageHydrodynamicForceTorque; + lbm_mesapd_coupling::LubricationCorrectionKernel lubricationCorrectionKernel( + viscosity, [](real_t r) { return (real_t(0.001 + real_t(0.00007) * r)) * r; }); // Assemble boundary block string std::string boundariesBlockString = " BoundariesFluid" @@ -368,8 +607,8 @@ int main(int argc, char** argv) boundariesBlockString += "\n BoundariesConcentration"; boundariesBlockString += "{" - "Border { direction W; walldistance -1; flag Density_Concentration_east; }" - "Border { direction E; walldistance -1; flag Density_Concentration_east; }"; + "Border { direction W; walldistance -1; flag Neumann_Concentration; }" + "Border { direction E; walldistance -1; flag Neumann_Concentration; }"; if (!periodicInY) { @@ -519,8 +758,8 @@ int main(int argc, char** argv) // time loop objects for communication with and without hiding - SweepTimeloop commTimeloop(blocks->getBlockStorage(), timeSteps); - SweepTimeloop timeloop(blocks->getBlockStorage(), timeSteps); + SweepTimeloop commTimeloop(blocks->getBlockStorage(), numTimeSteps); + SweepTimeloop timeloop(blocks->getBlockStorage(), numTimeSteps); timeloop.addFuncBeforeTimeStep(RemainingTimeLogger(timeloop.getNrOfTimeSteps()), "Remaining Time Logger"); @@ -546,7 +785,7 @@ int main(int argc, char** argv) #endif // vtk output - if (vtkSpacing != uint_t(0)) + if (vtkSpacingParticles != uint_t(0)) { // particles auto particleVtkOutput = make_shared< mesa_pd::vtk::ParticleVtkOutput >(ps); @@ -559,11 +798,13 @@ int main(int argc, char** argv) return pIt->getShapeID() == sphereShape && !(mesa_pd::data::particle_flags::isSet(pIt->getFlags(), mesa_pd::data::particle_flags::GHOST)); }); - auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "particles", vtkSpacing, vtkFolder); + auto particleVtkWriter = vtk::createVTKOutput_PointData(particleVtkOutput, "particles", vtkSpacingParticles, vtkFolder); timeloop.addFuncBeforeTimeStep(vtk::writeFiles(particleVtkWriter), "VTK (sphere data)"); + } + if (vtkSpacingFluid != uint_t(0)){ // Fields - auto vtkOutput_Fluid = vtk::createVTKOutput_BlockData(blocks, "vtk files fluid", vtkSpacing, 0, false, vtkFolder); + auto vtkOutput_Fluid = vtk::createVTKOutput_BlockData(blocks, "vtk files fluid", vtkSpacingFluid, 0, false, vtkFolder); vtkOutput_Fluid->addBeforeFunction(communication_fluid); @@ -586,7 +827,7 @@ int main(int argc, char** argv) }); auto vtkOutput_Concentration = - vtk::createVTKOutput_BlockData(blocks, "vtk files concentration", vtkSpacing, 0, false, vtkFolder); + vtk::createVTKOutput_BlockData(blocks, "vtk files concentration", vtkSpacingFluid, 0, false, vtkFolder); vtkOutput_Concentration->addBeforeFunction(communication_concentration); @@ -628,8 +869,7 @@ int main(int argc, char** argv) timeloop.addFuncBeforeTimeStep(vtk::writeFiles(vtkOutput_Fluid), "VTK output Fluid"); timeloop.addFuncBeforeTimeStep(vtk::writeFiles(vtkOutput_Concentration), "VTK output Concentration"); } - - if (vtkSpacing != uint_t(0)) { vtk::writeDomainDecomposition(blocks, "domain_decomposition", vtkFolder); } + if (vtkSpacingFluid != uint_t(0)) { vtk::writeDomainDecomposition(blocks, "domain_decomposition", vtkFolder); } //////////////////////////////////////////////////////////////////////////////////////////////// // add LBM communication, boundary handling and the LBM sweeps to the time loop for codegen // @@ -639,10 +879,6 @@ int main(int argc, char** argv) particleAndVolumeFractionSoA.particleForcesFieldID, particleAndVolumeFractionSoA.particleVelocitiesFieldID, pdfFieldFluidCPUGPUID, velFieldFluidCPUGPUID, T0, alphaLB, gravityLB, omega_f, rho_0); - pystencils::PSMFluidSweepSplit psmFluidSplitSweep( - particleAndVolumeFractionSoA.BsFieldID, particleAndVolumeFractionSoA.BFieldID, densityConcentrationFieldCPUGPUID,particleAndVolumeFractionSoA.particleForcesFieldID, - particleAndVolumeFractionSoA.particleVelocitiesFieldID, - pdfFieldFluidCPUGPUID,velFieldFluidCPUGPUID,T0,alphaLB,gravityLB,omega_f,rho_0,frameWidth); pystencils::PSMConcentrationSweep psmConcentrationSweep( particleAndVolumeFractionSoA.BsFieldID, particleAndVolumeFractionSoA.BFieldID, densityConcentrationFieldCPUGPUID, @@ -655,7 +891,6 @@ int main(int argc, char** argv) pystencils::LBMFluidSweep lbmFluidSweep(densityConcentrationFieldCPUGPUID,pdfFieldFluidCPUGPUID,velFieldFluidCPUGPUID,T0,alphaLB,gravityLB,omega_f,rho_0); - pystencils::LBMFluidSplitSweep lbmFluidSplitSweep(densityConcentrationFieldCPUGPUID,pdfFieldFluidCPUGPUID,velFieldFluidCPUGPUID,T0,alphaLB,gravityLB,omega_f,rho_0,frameWidth); pystencils::LBMConcentrationSweep lbmConcentrationSweep(densityConcentrationFieldCPUGPUID, pdfFieldConcentrationCPUGPUID, velFieldFluidCPUGPUID,qe,qk); @@ -663,19 +898,6 @@ int main(int argc, char** argv) - // Add LBM (fluid and concentration) communication function and boundary handling sweep - if (useCommunicationHiding) - { - timeloop.add() << Sweep(deviceSyncWrapper(noSlip_fluid_bc.getSweep()), "Boundary Handling (No slip fluid)"); - timeloop.add() << Sweep(deviceSyncWrapper(neumann_concentration_bc.getSweep()), - "Boundary Handling (Concentration Neumann)"); - timeloop.add() << Sweep(deviceSyncWrapper(density_concentration_bc_west.getSweep()), - "Boundary Handling (Concentration Density west)"); - timeloop.add() << Sweep(deviceSyncWrapper(density_concentration_bc_east.getSweep()), - "Boundary Handling (Concentration Density east)"); - } - else - { timeloop.add() << BeforeFunction(communication_fluid, "LBM fluid Communication") << Sweep(deviceSyncWrapper(noSlip_fluid_bc.getSweep()), "Boundary Handling (No slip fluid)"); timeloop.add() << BeforeFunction(communication_concentration, "LBM concentration Communication") @@ -685,33 +907,19 @@ int main(int argc, char** argv) "Boundary Handling (Concentration Density west)"); timeloop.add() << Sweep(deviceSyncWrapper(density_concentration_bc_east.getSweep()), "Boundary Handling (Concentration Density east)"); - } + if (useParticles) { - - timeloop.add() << Sweep(deviceSyncWrapper(psmSweepCollection.particleMappingSweep), "Particle mapping"); - timeloop.add() << Sweep(deviceSyncWrapper(psmSweepCollection.setParticleVelocitiesSweep), - "Set particle velocities"); - timeloop.add() << Sweep(deviceSyncWrapper(psmFluidSweep), "PSM Fluid sweep"); - //timeloop.add() << Sweep(deviceSyncWrapper(lbmConcentrationSweep), "LBM Concentration sweep"); - timeloop.add() << Sweep(deviceSyncWrapper(psmSweepCollection.setParticleTemperaturesSweep), - "Set particle temperatures"); - timeloop.add() << Sweep(deviceSyncWrapper(psmConcentrationSweep), "PSM Concentration sweep"); - - // after both the sweeps, reduce the particle forces. - timeloop.add() << Sweep(deviceSyncWrapper(psmSweepCollection.reduceParticleForcesSweep), - "Reduce particle forces"); - + addThermalPSMSweepToTimeloop(timeloop, psmSweepCollection, psmFluidSweep,psmConcentrationSweep); } else{ - /*WALBERLA_LOG_INFO_ON_ROOT("No particles used so executing normal fluid and concentration sweeps"); - timeloop.add() << Sweep(deviceSyncWrapper(lbmFluidSweep), "LBM Fluid sweep"); - timeloop.add() << Sweep(deviceSyncWrapper(lbmConcentrationSweep), "LBM Concentration sweep");*/ timeloop.add() << Sweep(deviceSyncWrapper(psmSweepCollection.particleMappingSweep), "Particle mapping"); timeloop.add() << Sweep(deviceSyncWrapper(psmSweepCollection.setParticleVelocitiesSweep), "Set particle velocities"); + timeloop.add() << Sweep(deviceSyncWrapper(psmSweepCollection.setParticleTemperaturesSweep), + "Set particle temperatures"); timeloop.add() << Sweep(deviceSyncWrapper(psmFluidSweep), "PSM Fluid sweep"); //timeloop.add() << Sweep(deviceSyncWrapper(lbmConcentrationSweep), "LBM Concentration sweep"); timeloop.add() << Sweep(deviceSyncWrapper(psmConcentrationSweep), "PSM Concentration sweep"); @@ -730,17 +938,177 @@ int main(int argc, char** argv) timeloop.addFuncAfterTimeStep(performanceLogger, "Evaluate performance logging"); } + //////////////////////// + // EXECUTE SIMULATION // + //////////////////////// + WcTimingPool timeloopTiming; - // TODO: maybe add warmup phase + const bool useOpenMP = true; + real_t linkedCellWidth = linkedCellWidthRation * particleDiameter; + mesa_pd::data::LinkedCells linkedCells(rpdDomain->getUnionOfLocalAABBs().getExtended(linkedCellWidth), + linkedCellWidth); + mesa_pd::kernel::InsertParticleIntoLinkedCells ipilc; - for (uint_t timeStep = 0; timeStep < timeSteps; ++timeStep) + // time loop + for (uint_t timeStep = 0; timeStep < numTimeSteps; ++timeStep) { - if (useCommunicationHiding) { commTimeloop.singleStep(timeloopTiming); } + // perform a single simulation step -> this contains LBM and setting of the hydrodynamic interactions + timeloop.singleStep(timeloopTiming); + + if(useParticles) + { + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle assoc"].start(); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, assoc, *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle assoc"].end(); + timeloopTiming["RPD reduceProperty HydrodynamicForceTorqueNotification"].start(); + reduceProperty.operator()< mesa_pd::HydrodynamicForceTorqueNotification >(*ps); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD reduceProperty HydrodynamicForceTorqueNotification"].end(); + + if (timeStep == 0) + { + lbm_mesapd_coupling::InitializeHydrodynamicForceTorqueForAveragingKernel + initializeHydrodynamicForceTorqueForAveragingKernel; + timeloopTiming["RPD forEachParticle initializeHydrodynamicForceTorqueForAveragingKernel"].start(); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, + initializeHydrodynamicForceTorqueForAveragingKernel, *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle initializeHydrodynamicForceTorqueForAveragingKernel"].end(); + } + timeloopTiming["RPD forEachParticle averageHydrodynamicForceTorque"].start(); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, averageHydrodynamicForceTorque, + *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle averageHydrodynamicForceTorque"].end(); + + for (auto subCycle = uint_t(0); subCycle < numberOfParticleSubCycles; ++subCycle) + { + if(useIntegrators) + { + timeloopTiming["RPD forEachParticle vvIntegratorPreForce"].start(); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPreForce, + *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle vvIntegratorPreForce"].end(); + } + timeloopTiming["RPD syncCall"].start(); + syncCall(); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD syncCall"].end(); + + timeloopTiming["RPD linkedCells.clear"].start(); + linkedCells.clear(); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD linkedCells.clear"].end(); + timeloopTiming["RPD forEachParticle ipilc"].start(); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, ipilc, *accessor, linkedCells); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle ipilc"].end(); + + if (useLubricationForces) + { + // lubrication correction + timeloopTiming["RPD forEachParticlePairHalf lubricationCorrectionKernel"].start(); + linkedCells.forEachParticlePairHalf( + useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor, + [&lubricationCorrectionKernel, &rpdDomain](const size_t idx1, const size_t idx2, auto& ac) { + mesa_pd::collision_detection::AnalyticContactDetection acd; + acd.getContactThreshold() = lubricationCorrectionKernel.getNormalCutOffDistance(); + mesa_pd::kernel::DoubleCast double_cast; + mesa_pd::mpi::ContactFilter contact_filter; + if (double_cast(idx1, idx2, ac, acd, ac)) + { + if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain)) + { + double_cast(acd.getIdx1(), acd.getIdx2(), ac, lubricationCorrectionKernel, ac, + acd.getContactNormal(), acd.getPenetrationDepth()); + } + } + }, + *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticlePairHalf lubricationCorrectionKernel"].end(); + } + + // collision response + timeloopTiming["RPD forEachParticlePairHalf collisionResponse"].start(); + linkedCells.forEachParticlePairHalf( + useOpenMP, mesa_pd::kernel::ExcludeInfiniteInfinite(), *accessor, + [&collisionResponse, &rpdDomain, timeStepSizeRPD](const size_t idx1, const size_t idx2, auto& ac) { + mesa_pd::collision_detection::AnalyticContactDetection acd; + mesa_pd::kernel::DoubleCast double_cast; + mesa_pd::mpi::ContactFilter contact_filter; + if (double_cast(idx1, idx2, ac, acd, ac)) + { + if (contact_filter(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), *rpdDomain)) + { + collisionResponse(acd.getIdx1(), acd.getIdx2(), ac, acd.getContactPoint(), + acd.getContactNormal(), acd.getPenetrationDepth(), timeStepSizeRPD); + } + } + }, + *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticlePairHalf collisionResponse"].end(); + + timeloopTiming["RPD reduceProperty reduceAndSwapContactHistory"].start(); + reduceAndSwapContactHistory(*ps); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD reduceProperty reduceAndSwapContactHistory"].end(); + + // add hydrodynamic force + lbm_mesapd_coupling::AddHydrodynamicInteractionKernel addHydrodynamicInteraction; + timeloopTiming["RPD forEachParticle addHydrodynamicInteraction + addGravitationalForce"].start(); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addHydrodynamicInteraction, + *accessor); + + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, addGravitationalForce, *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle addHydrodynamicInteraction + addGravitationalForce"].end(); + + timeloopTiming["RPD reduceProperty ForceTorqueNotification"].start(); + reduceProperty.operator()< mesa_pd::ForceTorqueNotification >(*ps); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD reduceProperty ForceTorqueNotification"].end(); + + if(useIntegrators) + { + timeloopTiming["RPD forEachParticle vvIntegratorPostForce"].start(); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectLocal(), *accessor, vvIntegratorPostForce, + *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle vvIntegratorPostForce"].end(); + } + } + + timeloopTiming["RPD syncCall"].start(); + syncCall(); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD syncCall"].end(); + + timeloopTiming["RPD forEachParticle resetHydrodynamicForceTorque"].start(); + ps->forEachParticle(useOpenMP, mesa_pd::kernel::SelectAll(), *accessor, resetHydrodynamicForceTorque, + *accessor); + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["RPD forEachParticle resetHydrodynamicForceTorque"].end(); + } + if (infoSpacing != 0 && timeStep % infoSpacing == 0 && useParticles == true) + { + timeloopTiming["Evaluate infos"].start(); + + auto particleInfo = evaluateParticleInfo(*accessor); + WALBERLA_LOG_INFO_ON_ROOT(particleInfo); + + if (particleBarriers) WALBERLA_MPI_BARRIER(); + timeloopTiming["Evaluate infos"].end(); + } } + timeloopTiming.logResultOnRoot(); - auto timeloopTimingReduced = timeloopTiming.getReduced(); return EXIT_SUCCESS; }