diff --git a/apps/showcases/FreeSurface/CMakeLists.txt b/apps/showcases/FreeSurface/CMakeLists.txt index 34bed9ecd86323ffd5f914a757ca1d4453d4fbe7..19b28ec98daca1812d3259c5fa581afce2e713ad 100644 --- a/apps/showcases/FreeSurface/CMakeLists.txt +++ b/apps/showcases/FreeSurface/CMakeLists.txt @@ -29,14 +29,17 @@ waLBerla_add_executable(NAME GravityWave DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk) if( WALBERLA_BUILD_WITH_CODEGEN ) - walberla_generate_target_from_python( NAME GravityWaveLatticeModelGeneration - FILE GravityWaveLatticeModelGeneration.py - OUT_FILES GravityWaveLatticeModel.cpp GravityWaveLatticeModel.h ) + walberla_generate_target_from_python( NAME GravityWaveGeneration + FILE GravityWave.py + OUT_FILES GravityWaveStorageSpecification.h GravityWaveStorageSpecification.${CODEGEN_FILE_SUFFIX} + GravityWaveSweepCollection.h GravityWaveSweepCollection.${CODEGEN_FILE_SUFFIX} + NoSlip.h NoSlip.${CODEGEN_FILE_SUFFIX} + GravityWaveBoundaryCollection.h + GravityWaveInfoHeader.h) waLBerla_add_executable(NAME GravityWaveCodegen FILES GravityWaveCodegen.cpp - DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk - GravityWaveLatticeModelGeneration) + DEPENDS blockforest boundary core domain_decomposition field geometry lbm_generated postprocessing timeloop vtk GravityWaveGeneration) endif() waLBerla_add_executable(NAME MovingDrop diff --git a/apps/showcases/FreeSurface/GravityWave.prm b/apps/showcases/FreeSurface/GravityWave.prm index ffc5a4c49401890cc77f02ced1d2daedb4af9b64..31d3a3d4d3364c09c77734630242c2a11cd750e1 100644 --- a/apps/showcases/FreeSurface/GravityWave.prm +++ b/apps/showcases/FreeSurface/GravityWave.prm @@ -43,6 +43,11 @@ EvaluationParameters filename gravity-wave.txt; } +Logging +{ + logLevel info; // info progress detail tracing +} + BoundaryParameters { // X diff --git a/apps/showcases/FreeSurface/GravityWave.py b/apps/showcases/FreeSurface/GravityWave.py new file mode 100644 index 0000000000000000000000000000000000000000..dc2cd9449f6d5e28af8f6834bb096f77b3f38530 --- /dev/null +++ b/apps/showcases/FreeSurface/GravityWave.py @@ -0,0 +1,70 @@ +import sympy as sp +import pystencils as ps +from lbmpy.creationfunctions import LBMConfig, LBMOptimisation, create_lb_collision_rule +from lbmpy.boundaries import NoSlip +from lbmpy.enums import ForceModel, Method, Stencil +from lbmpy.stencils import LBStencil + +from pystencils_walberla import CodeGeneration, generate_info_header, generate_sweep +from lbmpy_walberla import generate_lbm_package, lbm_boundary_generator + + +info_header = """ +const bool infoCseGlobal = {cse_global}; +const bool infoCsePdfs = {cse_pdfs}; +""" + + +with CodeGeneration() as ctx: + # general parameters + layout = 'fzyx' + data_type = "float64" if ctx.double_accuracy else "float32" + + stencil = LBStencil(Stencil.D3Q19) + omega = sp.Symbol('omega') + + assert stencil.D == 3, "This application supports only three-dimensional stencils" + pdfs, pdfs_tmp = ps.fields(f"pdfs({stencil.Q}), pdfs_tmp({stencil.Q}): {data_type}[3D]", layout='fzyx') + density_field, velocity_field = ps.fields(f"density, velocity(3) : {data_type}[3D]", layout='fzyx') + macroscopic_fields = {'density': density_field, 'velocity': velocity_field} + force_field = ps.fields(f"force(3): {data_type}[3D]", layout='fzyx') + + # method definition + lbm_config = LBMConfig(stencil=stencil, + method=Method.SRT, + relaxation_rate=omega, + compressible=True, + force=force_field, + force_model=ForceModel.GUO, + zero_centered=False, + streaming_pattern='pull') # free surface implementation only works with pull pattern + + # optimizations to be used by the code generator + lbm_opt = LBMOptimisation(cse_global=True, + field_layout=layout, symbolic_field=pdfs, symbolic_temporary_field=pdfs_tmp) + + collision_rule = create_lb_collision_rule(lbm_config=lbm_config, + lbm_optimisation=lbm_opt) + + no_slip = lbm_boundary_generator(class_name='NoSlip', flag_uid='NoSlip', + boundary_object=NoSlip()) + + generate_lbm_package(ctx, name="GravityWave", + collision_rule=collision_rule, + lbm_config=lbm_config, lbm_optimisation=lbm_opt, + nonuniform=False, boundaries=[no_slip, ], + macroscopic_fields=macroscopic_fields, + target=ps.Target.CPU) + + infoHeaderParams = { + 'cse_global': int(lbm_opt.cse_global), + 'cse_pdfs': int(lbm_opt.cse_pdfs), + } + + field_typedefs = {'VectorField_T': velocity_field, + 'ScalarField_T': density_field} + + # Info header containing correct template definitions for stencil and field + generate_info_header(ctx, 'GravityWaveInfoHeader', + field_typedefs=field_typedefs, + additional_code=info_header.format(**infoHeaderParams)) diff --git a/apps/showcases/FreeSurface/GravityWaveCodegen.cpp b/apps/showcases/FreeSurface/GravityWaveCodegen.cpp index 65d18594b5b2c9f024ef5347b871ff2b9b79501a..a248b5d6d9ff431b9d23303a72c58fa45d2ec077 100644 --- a/apps/showcases/FreeSurface/GravityWaveCodegen.cpp +++ b/apps/showcases/FreeSurface/GravityWaveCodegen.cpp @@ -23,47 +23,49 @@ #include "blockforest/Initialization.h" #include "core/Environment.h" +#include "core/logging/Initialization.h" #include "field/Gather.h" -#include "lbm/PerformanceLogger.h" -#include "lbm/field/AddToStorage.h" -#include "lbm/free_surface/LoadBalancing.h" -#include "lbm/free_surface/SurfaceMeshWriter.h" -#include "lbm/free_surface/TotalMassComputer.h" -#include "lbm/free_surface/VtkWriter.h" -#include "lbm/free_surface/bubble_model/Geometry.h" -#include "lbm/free_surface/dynamics/SurfaceDynamicsHandler.h" -#include "lbm/free_surface/surface_geometry/SurfaceGeometryHandler.h" -#include "lbm/free_surface/surface_geometry/Utility.h" -#include "lbm/lattice_model/D3Q19.h" +#include "geometry/InitBoundaryHandling.h" -#include "GravityWaveLatticeModel.h" +#include "lbm_generated/blockforest/SimpleCommunication.h" +#include "lbm_generated/evaluation/PerformanceEvaluation.h" +#include "lbm_generated/communication/UniformGeneratedPdfPackInfo.h" +#include "lbm_generated/field/AddToStorage.h" +#include "lbm_generated/free_surface/InitFunctions.h" +#include "lbm_generated/free_surface/LoadBalancing.h" +#include "lbm_generated/free_surface/SurfaceMeshWriter.h" +#include "lbm_generated/free_surface/TotalMassComputer.h" +#include "lbm_generated/free_surface/VtkWriter.h" +#include "lbm_generated/free_surface/dynamics/SurfaceDynamicsHandler.h" +#include "lbm_generated/free_surface/surface_geometry/SurfaceGeometryHandler.h" +#include "lbm_generated/free_surface/surface_geometry/Utility.h" + +#include "GravityWaveInfoHeader.h" namespace walberla { -namespace free_surface +namespace free_surface_generated { namespace GravityWaveCodegen { -using ScalarField_T = GhostLayerField< real_t, 1 >; -using VectorField_T = GhostLayerField< Vector3< real_t >, 1 >; -using VectorFieldFlattened_T = GhostLayerField< real_t, 3 >; +using StorageSpecification_T = lbm::GravityWaveStorageSpecification; +using Stencil_T = StorageSpecification_T::Stencil; +using PdfField_T = lbm_generated::PdfField< StorageSpecification_T >; +using SweepCollection_T = lbm::GravityWaveSweepCollection; -using LatticeModel_T = lbm::GravityWaveLatticeModel; -using LatticeModelStencil_T = LatticeModel_T::Stencil; -using PdfField_T = lbm::PdfField< LatticeModel_T >; -using PdfCommunication_T = blockforest::SimpleCommunication< LatticeModelStencil_T >; // the geometry computations in SurfaceGeometryHandler require meaningful values in the ghost layers in corner // directions (flag field and fill level field); this holds, even if the lattice model uses a D3Q19 stencil -using CommunicationStencil_T = - typename std::conditional< LatticeModel_T::Stencil::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type; -using Communication_T = blockforest::SimpleCommunication< CommunicationStencil_T >; +using CommunicationStencil_T = typename std::conditional< Stencil_T::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type; +using Communication_T = lbm_generated::SimpleCommunication< CommunicationStencil_T >; using flag_t = uint32_t; using FlagField_T = FlagField< flag_t >; -using FreeSurfaceBoundaryHandling_T = FreeSurfaceBoundaryHandling< LatticeModel_T, FlagField_T, ScalarField_T >; +using BoundaryCollection_T = lbm::GravityWaveBoundaryCollection< FlagField_T >; + +using blockforest::communication::UniformBufferedScheme; // write each entry in "vector" to line in a file; columns are separated by tabs template< typename T > @@ -112,6 +114,7 @@ class SymmetryXEvaluator fillFieldGathered = std::make_shared< ScalarField_T >(domainSize_[0], domainSize_[1], domainSize_[2], uint_c(0)); } + WALBERLA_ASSERT_NOT_NULLPTR(*fillFieldGathered) field::gather< ScalarField_T, ScalarField_T >(*fillFieldGathered, blockForest, fillFieldID_); WALBERLA_ROOT_SECTION() @@ -164,16 +167,15 @@ class SymmetryXEvaluator }; // class SymmetryXEvaluator // get interface position in y-direction at the specified (global) x-coordinate -template< typename FreeSurfaceBoundaryHandling_T > class SurfaceYPositionEvaluator { public: SurfaceYPositionEvaluator(const std::weak_ptr< const StructuredBlockForest >& blockForest, - const std::weak_ptr< const FreeSurfaceBoundaryHandling_T >& freeSurfaceBoundaryHandling, + const ConstBlockDataID& flagFieldID, const FlagInfo<FlagField_T>& flagInfo, const ConstBlockDataID& fillFieldID, const Vector3< uint_t >& domainSize, cell_idx_t globalXCoordinate, uint_t frequency, const std::shared_ptr< real_t >& surfaceYPosition) - : blockForest_(blockForest), freeSurfaceBoundaryHandling_(freeSurfaceBoundaryHandling), fillFieldID_(fillFieldID), + : blockForest_(blockForest), flagFieldID_(flagFieldID), flagInfo_(flagInfo), fillFieldID_(fillFieldID), domainSize_(domainSize), globalXCoordinate_(globalXCoordinate), surfaceYPosition_(surfaceYPosition), frequency_(frequency), executionCounter_(uint_c(0)) {} @@ -183,17 +185,10 @@ class SurfaceYPositionEvaluator auto blockForest = blockForest_.lock(); WALBERLA_CHECK_NOT_NULLPTR(blockForest); - auto freeSurfaceBoundaryHandling = freeSurfaceBoundaryHandling_.lock(); - WALBERLA_CHECK_NOT_NULLPTR(freeSurfaceBoundaryHandling); - ++executionCounter_; // only evaluate in given frequencies if (executionCounter_ % frequency_ != uint_c(0) && executionCounter_ != uint_c(1)) { return; } - - const BlockDataID flagFieldID = freeSurfaceBoundaryHandling->getFlagFieldID(); - const typename FreeSurfaceBoundaryHandling_T::FlagInfo_T& flagInfo = freeSurfaceBoundaryHandling->getFlagInfo(); - *surfaceYPosition_ = real_c(0); for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) @@ -209,13 +204,13 @@ class SurfaceYPositionEvaluator Cell localEvalCell = Cell(globalXCoordinate_, cell_idx_c(0), cell_idx_c(0)); blockForest->transformGlobalToBlockLocalCell(localEvalCell, *blockIt); - const FlagField_T* const flagField = blockIt->template getData< const FlagField_T >(flagFieldID); + const FlagField_T* const flagField = blockIt->template getData< const FlagField_T >(flagFieldID_); const ScalarField_T* const fillField = blockIt->template getData< const ScalarField_T >(fillFieldID_); // searching from top ensures that the interface cell with the greatest y-coordinate is found first for (cell_idx_t y = cell_idx_c((flagField)->ySize() - uint_c(1)); y >= cell_idx_t(0); --y) { - if (flagInfo.isInterface(flagField->get(localEvalCell[0], y, cell_idx_c(0)))) + if (flagInfo_.isInterface(flagField->get(localEvalCell[0], y, cell_idx_c(0)))) { const real_t fillLevel = fillField->get(localEvalCell[0], y, cell_idx_c(0)); @@ -238,7 +233,8 @@ class SurfaceYPositionEvaluator private: std::weak_ptr< const StructuredBlockForest > blockForest_; - std::weak_ptr< const FreeSurfaceBoundaryHandling_T > freeSurfaceBoundaryHandling_; + ConstBlockDataID flagFieldID_; + FlagInfo<FlagField_T> flagInfo_; ConstBlockDataID fillFieldID_; Vector3< uint_t > domainSize_; cell_idx_t globalXCoordinate_; @@ -248,6 +244,15 @@ class SurfaceYPositionEvaluator uint_t executionCounter_; }; // class SurfaceYPositionEvaluator +template< typename FlagField_T > +void flagFieldInitFunction(FlagField_T* flagField, IBlock* const, const Set< field::FlagUID >& obstacleIDs, + const Set< field::FlagUID >& outflowIDs, const Set< field::FlagUID >& inflowIDs, + const Set< field::FlagUID >& freeSlipIDs) +{ + // register flags in the flag field + FlagInfo< FlagField_T >::registerFlags(flagField, obstacleIDs, outflowIDs, inflowIDs, freeSlipIDs); +} + int main(int argc, char** argv) { Environment walberlaEnv(argc, argv); @@ -256,6 +261,7 @@ int main(int argc, char** argv) // print content of parameter file WALBERLA_LOG_INFO_ON_ROOT(*walberlaEnv.config()); + logging::configureLogging(walberlaEnv.config()); // get block forest parameters from parameter file auto blockForestParameters = walberlaEnv.config()->getOneBlock("BlockForestParameters"); @@ -358,28 +364,62 @@ int main(int argc, char** argv) WALBERLA_LOG_DEVEL_VAR_ON_ROOT(filename); // create non-uniform block forest (non-uniformity required for load balancing) - const std::shared_ptr< StructuredBlockForest > blockForest = - createNonUniformBlockForest(domainSize, cellsPerBlock, numBlocks, periodicity); + const std::shared_ptr< StructuredBlockForest > blockForest = createNonUniformBlockForest(domainSize, cellsPerBlock, numBlocks, periodicity); // add force field const BlockDataID forceDensityFieldID = - field::addToStorage< VectorFieldFlattened_T >(blockForest, "Force field", real_c(0), field::fzyx, uint_c(1)); + field::addToStorage< VectorField_T >(blockForest, "Force field", real_c(0), field::fzyx, uint_c(1)); - // create lattice model - LatticeModel_T latticeModel = LatticeModel_T(forceDensityFieldID, relaxationRate); + // create storage specification + StorageSpecification_T storageSpecification = StorageSpecification_T(); // add pdf field - const BlockDataID pdfFieldID = lbm::addPdfFieldToStorage(blockForest, "PDF field", latticeModel, field::fzyx); + const BlockDataID pdfFieldID = lbm_generated::addPdfFieldToStorage(blockForest, "PDF field", storageSpecification, uint_c(1), field::fzyx); + const BlockDataID velFieldId = field::addToStorage< VectorField_T >(blockForest, "vel", real_c(0.0), field::fzyx); + const BlockDataID densityFieldId = field::addToStorage< ScalarField_T >(blockForest, "density", real_c(1.0), field::fzyx); // add fill level field (initialized with 0, i.e., gas everywhere) - const BlockDataID fillFieldID = - field::addToStorage< ScalarField_T >(blockForest, "Fill level field", real_c(0.0), field::fzyx, uint_c(2)); - - // add boundary handling - const std::shared_ptr< FreeSurfaceBoundaryHandling_T > freeSurfaceBoundaryHandling = - std::make_shared< FreeSurfaceBoundaryHandling_T >(blockForest, pdfFieldID, fillFieldID); - const BlockDataID flagFieldID = freeSurfaceBoundaryHandling->getFlagFieldID(); - const typename FreeSurfaceBoundaryHandling_T::FlagInfo_T& flagInfo = freeSurfaceBoundaryHandling->getFlagInfo(); + const BlockDataID fillFieldID = field::addToStorage< ScalarField_T >(blockForest, "Fill level field", real_c(0.0), field::fzyx, uint_c(2)); + + // initialize obstacleIDs + Set< FlagUID > obstacleIDs; + obstacleIDs += field::FlagUID("NoSlip"); + obstacleIDs += field::FlagUID("UBB"); + obstacleIDs += field::FlagUID("UBB_Inflow"); + obstacleIDs += field::FlagUID("Pressure"); + obstacleIDs += field::FlagUID("PressureOutflow"); + obstacleIDs += field::FlagUID("Outlet"); + obstacleIDs += field::FlagUID("FreeSlip"); + + // initialize outflowIDs + Set< FlagUID > outflowIDs; + outflowIDs += field::FlagUID("PressureOutflow"); + outflowIDs += field::FlagUID("Outlet"); + + // initialize outflowIDs + Set< FlagUID > inflowIDs; + inflowIDs += field::FlagUID("UBB_Inflow"); + + // initialize freeSlipIDs + Set< FlagUID > freeSlipIDs; + freeSlipIDs += field::FlagUID("FreeSlip"); + + auto ffInitFunc = std::bind(flagFieldInitFunction< FlagField_T >, std::placeholders::_1, + std::placeholders::_2, obstacleIDs, outflowIDs, inflowIDs, freeSlipIDs); + + auto flagInfo = FlagInfo< FlagField_T >(obstacleIDs, outflowIDs, inflowIDs, freeSlipIDs); + const BlockDataID flagFieldID = field::addFlagFieldToStorage< FlagField_T >(blockForest, "Boundary Flag Field", uint_c(2), true, ffInitFunc); + + // Boundaries + const FlagUID fluidFlagUID("Fluid"); + auto boundariesConfig = walberlaEnv.config()->getBlock("Boundaries"); + if (boundariesConfig) + { + WALBERLA_LOG_INFO_ON_ROOT("Setting boundary conditions") + geometry::initBoundaryHandling< FlagField_T >(*blockForest, flagFieldID, boundariesConfig); + } + geometry::setNonBoundaryCellsToDomain< FlagField_T >(*blockForest, flagFieldID, fluidFlagUID); + BoundaryCollection_T boundaryCollection(blockForest, flagFieldID, pdfFieldID, fluidFlagUID); // samples used in the Monte-Carlo like estimation of the fill level const uint_t fillLevelInitSamples = uint_c(100); // actually there will be 101 since 0 is also included @@ -427,44 +467,33 @@ int main(int argc, char** argv) }) // WALBERLA_FOR_ALL_CELLS } - // initialize domain boundary conditions from config file - const auto boundaryParameters = walberlaEnv.config()->getOneBlock("BoundaryParameters"); - freeSurfaceBoundaryHandling->initFromConfig(boundaryParameters); - // IMPORTANT REMARK: this must be only called after every solid flag has been set; otherwise, the boundary handling // might not detect solid flags correctly - freeSurfaceBoundaryHandling->initFlagsFromFillLevel(); + // TODO: Implement as free function; might only be needed with bubble model .... + // freeSurfaceBoundaryHandling->initFlagsFromFillLevel(); // communication after initialization Communication_T communication(blockForest, flagFieldID, fillFieldID, forceDensityFieldID); communication(); - PdfCommunication_T pdfCommunication(blockForest, pdfFieldID); - pdfCommunication(); + auto packInfo = std::make_shared<lbm_generated::UniformGeneratedPdfPackInfo< PdfField_T >>(pdfFieldID); + UniformBufferedScheme< Stencil_T > pdfCommunication(blockForest); + pdfCommunication.addPackInfo(packInfo); - // add bubble model - std::shared_ptr< bubble_model::BubbleModelBase > bubbleModel = nullptr; - if (enableBubbleModel) - { - const std::shared_ptr< bubble_model::BubbleModel< LatticeModelStencil_T > > bubbleModelDerived = - std::make_shared< bubble_model::BubbleModel< LatticeModelStencil_T > >(blockForest, enableBubbleSplits); - bubbleModelDerived->initFromFillLevelField(fillFieldID); - bubbleModelDerived->setAtmosphere(Cell(domainSize[0] - uint_c(1), domainSize[1] - uint_c(1), uint_c(0)), - real_c(1)); - - bubbleModel = std::static_pointer_cast< bubble_model::BubbleModelBase >(bubbleModelDerived); - } - else { bubbleModel = std::make_shared< bubble_model::BubbleModelConstantPressure >(real_c(1)); } + const Cell innerOuterSplit = Cell(Vector3<cell_idx_t>(1, 1, 1)); + SweepCollection_T sweepCollection(blockForest, forceDensityFieldID, pdfFieldID, densityFieldId, velFieldId, relaxationRate, innerOuterSplit); // initialize hydrostatic pressure - initHydrostaticPressure< PdfField_T >(blockForest, pdfFieldID, acceleration, liquidDepth); - + initHydrostaticPressure< ScalarField_T >(blockForest, densityFieldId, acceleration, liquidDepth); // initialize force density field - initForceDensityFieldCodegen< PdfField_T, FlagField_T, VectorFieldFlattened_T, ScalarField_T >( - blockForest, forceDensityFieldID, fillFieldID, pdfFieldID, flagFieldID, flagInfo, acceleration); - + initForceDensityField< FlagField_T, VectorField_T, ScalarField_T >(blockForest, forceDensityFieldID, fillFieldID, densityFieldId, flagFieldID, flagInfo, acceleration); // set density in non-liquid or non-interface cells to 1 (after initializing with hydrostatic pressure) - setDensityInNonFluidCellsToOne< FlagField_T, PdfField_T >(blockForest, flagInfo, flagFieldID, pdfFieldID); + setDensityInNonFluidCellsToOne< FlagField_T, VectorField_T, ScalarField_T >(blockForest, flagInfo, flagFieldID, velFieldId, densityFieldId); + + for (auto& block : *blockForest) + { + sweepCollection.initialise(&block); + } // create timeloop SweepTimeloop timeloop(blockForest, timesteps); @@ -477,8 +506,8 @@ int main(int argc, char** argv) if (!realIsEqual(surfaceTension, real_c(0), real_c(1e-14))) { computeCurvature = true; } // add surface geometry handler - const SurfaceGeometryHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T > geometryHandler( - blockForest, freeSurfaceBoundaryHandling, fillFieldID, curvatureModel, computeCurvature, enableWetting, + SurfaceGeometryHandler< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo< FlagField_T > > geometryHandler( + blockForest, flagInfo, flagFieldID, fillFieldID, curvatureModel, computeCurvature, enableWetting, contactAngle); geometryHandler.addSweeps(timeloop); @@ -488,25 +517,24 @@ int main(int argc, char** argv) const ConstBlockDataID normalFieldID = geometryHandler.getConstNormalFieldID(); // add boundary handling for standard boundaries and free surface boundaries - const SurfaceDynamicsHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T, true, - VectorFieldFlattened_T > - dynamicsHandler(blockForest, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID, normalFieldID, - curvatureFieldID, freeSurfaceBoundaryHandling, bubbleModel, pdfReconstructionModel, + const SurfaceDynamicsHandler< StorageSpecification_T, SweepCollection_T, BoundaryCollection_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo< FlagField_T >> + dynamicsHandler(blockForest, sweepCollection, boundaryCollection, flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID, normalFieldID, + curvatureFieldID, pdfReconstructionModel, pdfRefillingModel, excessMassDistributionModel, relaxationRate, acceleration, surfaceTension, useSimpleMassExchange, cellConversionThreshold, cellConversionForceThreshold); dynamicsHandler.addSweeps(timeloop); // add load balancing - const LoadBalancer< FlagField_T, CommunicationStencil_T, LatticeModelStencil_T > loadBalancer( - blockForest, communication, pdfCommunication, bubbleModel, uint_c(50), uint_c(10), uint_c(5), + const LoadBalancer< FlagField_T, CommunicationStencil_T, Stencil_T > loadBalancer( + blockForest, communication, pdfCommunication, uint_c(50), uint_c(10), uint_c(5), loadBalancingFrequency, printLoadBalancingStatistics); timeloop.addFuncAfterTimeStep(loadBalancer, "Sweep: load balancing"); // add sweep for evaluating the surface position in y-direction const std::shared_ptr< real_t > surfaceYPosition = std::make_shared< real_t >(real_c(0)); - const SurfaceYPositionEvaluator< FreeSurfaceBoundaryHandling_T > positionEvaluator( - blockForest, freeSurfaceBoundaryHandling, fillFieldID, domainSize, cell_idx_c(real_c(domainWidth) * real_c(0.5)), + const SurfaceYPositionEvaluator positionEvaluator( + blockForest, flagFieldID, flagInfo, fillFieldID, domainSize, cell_idx_c(real_c(domainWidth) * real_c(0.5)), evaluationFrequency, surfaceYPosition); timeloop.addFuncAfterTimeStep(positionEvaluator, "Evaluator: surface position"); @@ -519,14 +547,13 @@ int main(int argc, char** argv) // add evaluator for total and excessive mass (mass that is currently undistributed) const std::shared_ptr< real_t > totalMass = std::make_shared< real_t >(real_c(0)); const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0)); - const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer( - blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(), + const TotalMassComputer< PdfField_T, FlagField_T, ScalarField_T > totalMassComputer( + blockForest, flagFieldID, flagInfo, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(), evaluationFrequency, totalMass, excessMass); timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass"); // add VTK output - addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T, - true, VectorFieldFlattened_T >( + addVTKOutput< FlagInfo<FlagField_T>, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >( blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID, geometryHandler.getCurvatureFieldID(), geometryHandler.getNormalFieldID(), geometryHandler.getObstNormalFieldID()); @@ -538,9 +565,9 @@ int main(int argc, char** argv) timeloop.addFuncAfterTimeStep(surfaceMeshWriter, "Writer: surface mesh"); // add logging for computational performance - const lbm::PerformanceLogger< FlagField_T > performanceLogger( - blockForest, flagFieldID, flagIDs::liquidInterfaceFlagIDs, performanceLogFrequency); - timeloop.addFuncAfterTimeStep(performanceLogger, "Evaluator: performance logging"); +// const lbm_generated::PerformanceLogger< FlagField_T > performanceLogger( +// blockForest, flagFieldID, flagIDs::liquidInterfaceFlagIDs, performanceLogFrequency); +// timeloop.addFuncAfterTimeStep(performanceLogger, "Evaluator: performance logging"); WcTimingPool timingPool; @@ -591,4 +618,4 @@ void writeVectorToFile(const std::vector< T >& vector, const std::string& filena } // namespace free_surface } // namespace walberla -int main(int argc, char** argv) { return walberla::free_surface::GravityWaveCodegen::main(argc, argv); } \ No newline at end of file +int main(int argc, char** argv) { return walberla::free_surface_generated::GravityWaveCodegen::main(argc, argv); } \ No newline at end of file diff --git a/apps/showcases/FreeSurface/GravityWaveLatticeModelGeneration.py b/apps/showcases/FreeSurface/GravityWaveLatticeModelGeneration.py deleted file mode 100644 index aa61ee7979a125e427e7c5c2d588c141fd11b81d..0000000000000000000000000000000000000000 --- a/apps/showcases/FreeSurface/GravityWaveLatticeModelGeneration.py +++ /dev/null @@ -1,37 +0,0 @@ -import sympy as sp -import pystencils as ps -from lbmpy.creationfunctions import LBMConfig, LBMOptimisation, create_lb_collision_rule -from lbmpy.enums import ForceModel, Method, Stencil -from lbmpy.stencils import LBStencil - -from pystencils_walberla import CodeGeneration -from lbmpy_walberla import generate_lattice_model - - -with CodeGeneration() as ctx: - # general parameters - layout = 'fzyx' - data_type = "float64" if ctx.double_accuracy else "float32" - - stencil = LBStencil(Stencil.D3Q19) - omega = sp.Symbol('omega') - force_field = ps.fields(f"force(3): {data_type}[3D]", layout='fzyx') - - # method definition - lbm_config = LBMConfig(stencil=stencil, - method=Method.SRT, - relaxation_rate=omega, - compressible=True, - force=force_field, - force_model=ForceModel.GUO, - zero_centered=False, - streaming_pattern='pull') # free surface implementation only works with pull pattern - - # optimizations to be used by the code generator - lbm_opt = LBMOptimisation(cse_global=True, - field_layout=layout) - - collision_rule = create_lb_collision_rule(lbm_config=lbm_config, - lbm_optimisation=lbm_opt) - - generate_lattice_model(ctx, "GravityWaveLatticeModel", collision_rule, field_layout=layout) diff --git a/src/lbm_generated/CMakeLists.txt b/src/lbm_generated/CMakeLists.txt index 2513a58f2e646025fa86107409058a7576a3f62f..431df203e05125b5f06f79c5545c12167af56fae 100644 --- a/src/lbm_generated/CMakeLists.txt +++ b/src/lbm_generated/CMakeLists.txt @@ -15,11 +15,14 @@ target_link_libraries( lbm_generated vtk ) +add_subdirectory( blockforest ) add_subdirectory( boundary ) add_subdirectory( communication ) add_subdirectory( gpu ) +add_subdirectory( macroscopics ) add_subdirectory( evaluation ) add_subdirectory( field ) +add_subdirectory( free_surface ) add_subdirectory( refinement ) add_subdirectory( storage_specification ) add_subdirectory( sweep_collection ) \ No newline at end of file diff --git a/src/lbm_generated/blockforest/CMakeLists.txt b/src/lbm_generated/blockforest/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..c9eb5be64761ed0adec532b651e6a6ea97ae78d4 --- /dev/null +++ b/src/lbm_generated/blockforest/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources( lbm_generated + PRIVATE + SimpleCommunication.h + UpdateSecondGhostLayer.h + ) diff --git a/src/lbm_generated/blockforest/SimpleCommunication.h b/src/lbm_generated/blockforest/SimpleCommunication.h new file mode 100644 index 0000000000000000000000000000000000000000..0ce61f74cadf10792da33a78c0aadf57bf1d8ffc --- /dev/null +++ b/src/lbm_generated/blockforest/SimpleCommunication.h @@ -0,0 +1,171 @@ +//====================================================================================================================== +// +// 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 SimpleCommunication.h +//! \ingroup blockforest +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "blockforest/communication/UniformBufferedScheme.h" + +#include "core/Abort.h" +#include "core/math/Vector3.h" +#include "core/mpi/BufferDataTypeExtensions.h" + +#include "field/FlagField.h" +#include "field/communication/PackInfo.h" + +namespace walberla::lbm_generated +{ +using blockforest::communication::UniformBufferedScheme; +using field::communication::PackInfo; + +template< typename Stencil_T > +class SimpleCommunication : public blockforest::communication::UniformBufferedScheme< Stencil_T > +{ + using RealScalarField_T = GhostLayerField< real_t, 1 >; + using VectorField_T = GhostLayerField< Vector3< real_t >, 1 >; + using VectorFieldFlattened_T = GhostLayerField< real_t, 3 >; + using UintScalarField_T = GhostLayerField< uint_t, 1 >; + using IDScalarField_T = walberla::GhostLayerField< walberla::id_t, 1 >; + + using FlagField16_T = FlagField< uint16_t >; + using FlagField32_T = FlagField< uint32_t >; + using FlagField64_T = FlagField< uint64_t >; + + public: + SimpleCommunication(const std::weak_ptr< StructuredBlockForest >& blockForest, BlockDataID f1) + : UniformBufferedScheme< Stencil_T >(blockForest), blockForest_(blockForest) + { + (*this) << f1; + } + SimpleCommunication(const std::weak_ptr< StructuredBlockForest >& blockForest, BlockDataID f1, BlockDataID f2) + : UniformBufferedScheme< Stencil_T >(blockForest), blockForest_(blockForest) + { + (*this) << f1 << f2; + } + SimpleCommunication(const std::weak_ptr< StructuredBlockForest >& blockForest, BlockDataID f1, BlockDataID f2, + BlockDataID f3) + : UniformBufferedScheme< Stencil_T >(blockForest), blockForest_(blockForest) + { + (*this) << f1 << f2 << f3; + } + + SimpleCommunication(const std::weak_ptr< StructuredBlockForest >& blockForest, BlockDataID f1, BlockDataID f2, + BlockDataID f3, BlockDataID f4) + : UniformBufferedScheme< Stencil_T >(blockForest), blockForest_(blockForest) + { + (*this) << f1 << f2 << f3 << f4; + } + SimpleCommunication(const std::weak_ptr< StructuredBlockForest >& blockForest, BlockDataID f1, BlockDataID f2, + BlockDataID f3, BlockDataID f4, BlockDataID f5) + : UniformBufferedScheme< Stencil_T >(blockForest), blockForest_(blockForest) + { + (*this) << f1 << f2 << f3 << f4 << f5; + } + SimpleCommunication(const std::weak_ptr< StructuredBlockForest >& blockForest, BlockDataID f1, BlockDataID f2, + BlockDataID f3, BlockDataID f4, BlockDataID f5, BlockDataID f6) + : UniformBufferedScheme< Stencil_T >(blockForest), blockForest_(blockForest) + { + (*this) << f1 << f2 << f3 << f4 << f5 << f6; + } + SimpleCommunication(const std::weak_ptr< StructuredBlockForest >& blockForest, BlockDataID f1, BlockDataID f2, + BlockDataID f3, BlockDataID f4, BlockDataID f5, BlockDataID f6, BlockDataID f7) + : UniformBufferedScheme< Stencil_T >(blockForest), blockForest_(blockForest) + { + (*this) << f1 << f2 << f3 << f4 << f5 << f6 << f7; + } + SimpleCommunication(const std::weak_ptr< StructuredBlockForest >& blockForest, BlockDataID f1, BlockDataID f2, + BlockDataID f3, BlockDataID f4, BlockDataID f5, BlockDataID f6, BlockDataID f7, BlockDataID f8) + : UniformBufferedScheme< Stencil_T >(blockForest), blockForest_(blockForest) + { + (*this) << f1 << f2 << f3 << f4 << f5 << f6 << f7 << f8; + } + + SimpleCommunication& operator<<(BlockDataID fieldId) + { + auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + if (blockForest->begin() == blockForest->end()) { return *this; } + IBlock& firstBlock = *(blockForest->begin()); + + if (firstBlock.isDataClassOrSubclassOf< RealScalarField_T >(fieldId)) + { + this->addPackInfo(make_shared< PackInfo< RealScalarField_T > >(fieldId)); + } + else + { + if (firstBlock.isDataClassOrSubclassOf< VectorField_T >(fieldId)) + { + this->addPackInfo(make_shared< PackInfo< VectorField_T > >(fieldId)); + } + else + { + if (firstBlock.isDataClassOrSubclassOf< FlagField16_T >(fieldId)) + { + this->addPackInfo(make_shared< PackInfo< FlagField16_T > >(fieldId)); + } + else + { + if (firstBlock.isDataClassOrSubclassOf< FlagField32_T >(fieldId)) + { + this->addPackInfo(make_shared< PackInfo< FlagField32_T > >(fieldId)); + } + else + { + if (firstBlock.isDataClassOrSubclassOf< FlagField64_T >(fieldId)) + { + this->addPackInfo(make_shared< PackInfo< FlagField64_T > >(fieldId)); + } + else + { + if (firstBlock.isDataClassOrSubclassOf< IDScalarField_T >(fieldId)) + { + this->addPackInfo(make_shared< PackInfo< IDScalarField_T > >(fieldId)); + } + else + { + if (firstBlock.isDataClassOrSubclassOf< UintScalarField_T >(fieldId)) + { + this->addPackInfo(make_shared< PackInfo< UintScalarField_T > >(fieldId)); + } + else + { + if (firstBlock.isDataClassOrSubclassOf< VectorFieldFlattened_T >(fieldId)) + { + this->addPackInfo(make_shared< PackInfo< VectorFieldFlattened_T > >(fieldId)); + } + else { WALBERLA_ABORT("Problem with UID"); } + } + } + } + } + } + } + } + + return *this; + } + + protected: + std::weak_ptr< StructuredBlockForest > blockForest_; +}; // class SimpleCommunication + +} // namespace walberla diff --git a/src/lbm_generated/blockforest/UpdateSecondGhostLayer.h b/src/lbm_generated/blockforest/UpdateSecondGhostLayer.h new file mode 100644 index 0000000000000000000000000000000000000000..2a9bb43b9168094f74105973ee33f8351b6c441a --- /dev/null +++ b/src/lbm_generated/blockforest/UpdateSecondGhostLayer.h @@ -0,0 +1,141 @@ +//====================================================================================================================== +// +// 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 UpdateSecondGhostLayer.h +//! \ingroup blockforest +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "blockforest/StructuredBlockForest.h" + +namespace walberla::lbm_generated +{ +/******************************************************************************************************************** + * Manual update of a field's second and further ghost layers in setups with domain size = 1 and periodicity in at + * least one direction. + * + * If a field has two ghost layers and if the inner field has only a size of one (in one or more directions), + * regular communication does not update the second (and further) ghost layers correctly. Here, the content of the + * first ghost layers is manually copied to the second (and further) ghost layers when the dimension of the field is + * one in this direction. + *******************************************************************************************************************/ +template< typename Field_T > +class UpdateSecondGhostLayer +{ + public: + UpdateSecondGhostLayer(const std::weak_ptr< StructuredBlockForest >& blockForestPtr, BlockDataID fieldID) + : blockForest_(blockForestPtr), fieldID_(fieldID) + { + const auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + Field_T* const field = blockIt->template getData< Field_T >(fieldID_); + + // this function is only necessary if the flag field has at least two ghost layers + if (field->nrOfGhostLayers() < uint_c(2)) { isNecessary_ = false; } + else + { + // running this function is only necessary when the field is of size 1 in at least one direction + isNecessary_ = (field->xSize() == uint_c(1) && blockForest->isXPeriodic()) || + (field->ySize() == uint_c(1) && blockForest->isYPeriodic()) || + (field->zSize() == uint_c(1) && blockForest->isZPeriodic()); + } + } + } + + void operator()() + { + if (!isNecessary_) { return; } + + const auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + Field_T* const field = blockIt->template getData< Field_T >(fieldID_); + + if (field->xSize() == uint_c(1) && blockForest->isXPeriodic()) + { + // iterate ghost layer at x == -1 + for (auto fieldIt = field->beginGhostLayerOnly(uint_c(1), stencil::W, true); fieldIt != field->end(); + ++fieldIt) + { + // copy data from ghost layer at x == -1 to x == -2 + fieldIt.neighbor(stencil::W) = *fieldIt; + } + + // iterate ghost layer at x == xSize()+1 + for (auto fieldIt = field->beginGhostLayerOnly(uint_c(1), stencil::E, true); fieldIt != field->end(); + ++fieldIt) + { + // copy data from ghost layer at x == xSize()+1 to x == xSize()+2 + fieldIt.neighbor(stencil::E) = *fieldIt; + } + } + + if (field->ySize() == uint_c(1) && blockForest->isYPeriodic()) + { + // iterate ghost layer at y == -1 + for (auto fieldIt = field->beginGhostLayerOnly(uint_c(1), stencil::S, true); fieldIt != field->end(); + ++fieldIt) + { + // copy data from ghost layer at y == -1 to y == -2 + fieldIt.neighbor(stencil::S) = *fieldIt; + } + + // iterate ghost layer at y == ySize()+1 + for (auto fieldIt = field->beginGhostLayerOnly(uint_c(1), stencil::N, true); fieldIt != field->end(); + ++fieldIt) + { + // copy data from ghost layer at y == ySize()+1 to y == ySize()+2 + fieldIt.neighbor(stencil::N) = *fieldIt; + } + } + + if (field->zSize() == uint_c(1) && blockForest->isZPeriodic()) + { + // iterate ghost layer at z == -1 + for (auto fieldIt = field->beginGhostLayerOnly(uint_c(1), stencil::B, true); fieldIt != field->end(); + ++fieldIt) + { + // copy data from ghost layer at z == -1 to z == -2 + fieldIt.neighbor(stencil::B) = *fieldIt; + } + + // iterate ghost layer at y == zSize()+1 + for (auto fieldIt = field->beginGhostLayerOnly(uint_c(1), stencil::T, true); fieldIt != field->end(); + ++fieldIt) + { + // copy data from ghost layer at z == zSize()+1 to z == zSize()+2 + fieldIt.neighbor(stencil::T) = *fieldIt; + } + } + } + } + + private: + std::weak_ptr< StructuredBlockForest > blockForest_; + + BlockDataID fieldID_; + + bool isNecessary_; +}; // class UpdateSecondGhostLayer + +} // namespace walberla diff --git a/src/lbm_generated/free_surface/BlockStateDetectorSweep.h b/src/lbm_generated/free_surface/BlockStateDetectorSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..e080b46844f3268cbde2bcbbd2c88974200dd943 --- /dev/null +++ b/src/lbm_generated/free_surface/BlockStateDetectorSweep.h @@ -0,0 +1,111 @@ +//====================================================================================================================== +// +// 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 BlockStateDetectorSweep.h +//! \ingroup free_surface +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Set block states according to their content, i.e., free surface flags. +// +//====================================================================================================================== + +#pragma once + +#include "core/uid/SUID.h" + +#include "FlagInfo.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Set block states according to free surface flag field: + * - blocks that contain at least one interface cell are marked as "fullFreeSurface" (computationally most expensive + * type of blocks) + * - cells without interface cell and with at least one liquid cell are marked "onlyLBM" + * - all other blocks are marked as "onlyGasAndBoundary" + **********************************************************************************************************************/ +template< typename FlagField_T > +class BlockStateDetectorSweep +{ + public: + static const SUID fullFreeSurface; + static const SUID onlyGasAndBoundary; + static const SUID onlyLBM; + + BlockStateDetectorSweep(const std::weak_ptr< StructuredBlockForest >& blockForestPtr, + FlagInfo< FlagField_T > flagInfo, ConstBlockDataID flagFieldID) + : flagFieldID_(flagFieldID), flagInfo_(flagInfo) + { + const auto blockForest = blockForestPtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + (*this)(&(*blockIt)); // initialize block states + } + } + + void operator()(IBlock* const block) + { + bool liquidFound = false; + bool interfaceFound = false; + + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + + // search the flag field for interface and liquid cells + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(flagField, uint_c(1), { + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + + // at inflow boundaries, interface cells are generated; therefore, the block must be fullFreeSurface even if it + // does not yet contain an interface cell + if (flagInfo_.isInterface(flagFieldPtr) || flagInfo_.isInflow(flagFieldPtr)) + { + interfaceFound = true; + break; // stop search, as this block belongs already to the computationally most expensive block type + } + + if (flagInfo_.isLiquid(flagFieldPtr)) { liquidFound = true; } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ + + block->clearState(); + if (interfaceFound) + { + block->addState(fullFreeSurface); + return; + } + if (liquidFound) + { + block->addState(onlyLBM); + return; + } + block->addState(onlyGasAndBoundary); + } + + protected: + ConstBlockDataID flagFieldID_; + FlagInfo< FlagField_T > flagInfo_; +}; // class BlockStateDetectorSweep + +template< typename FlagField_T > +const SUID BlockStateDetectorSweep< FlagField_T >::fullFreeSurface = SUID("fullFreeSurface"); +template< typename FlagField_T > +const SUID BlockStateDetectorSweep< FlagField_T >::onlyGasAndBoundary = SUID("onlyGasAndBoundary"); +template< typename FlagField_T > +const SUID BlockStateDetectorSweep< FlagField_T >::onlyLBM = SUID("onlyLBM"); + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/CMakeLists.txt b/src/lbm_generated/free_surface/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..50a34dce935e820251e5bfc695de97c6e91fd4f0 --- /dev/null +++ b/src/lbm_generated/free_surface/CMakeLists.txt @@ -0,0 +1,17 @@ +target_sources( lbm_generated + PRIVATE + BlockStateDetectorSweep.h + FlagDefinitions.h + FlagInfo.h + FlagInfo.impl.h + InitFunctions.h + InterfaceFromFillLevel.h + LoadBalancing.h + MaxVelocityComputer.h + SurfaceMeshWriter.h + TotalMassComputer.h + VtkWriter.h + ) + +add_subdirectory( dynamics ) +add_subdirectory( surface_geometry ) diff --git a/src/lbm_generated/free_surface/FlagDefinitions.h b/src/lbm_generated/free_surface/FlagDefinitions.h new file mode 100644 index 0000000000000000000000000000000000000000..06393e23870253d458c4c0115f45c70f6f9ca233 --- /dev/null +++ b/src/lbm_generated/free_surface/FlagDefinitions.h @@ -0,0 +1,51 @@ +//====================================================================================================================== +// +// 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 FlagInfo.h +//! \ingroup free_surface +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Define free surface flags (e.g. conversion flags). +// +//====================================================================================================================== + +#include "field/FlagUID.h" + +namespace walberla +{ +namespace free_surface_generated +{ +namespace flagIDs +{ +/*********************************************************************************************************************** + * Definition of free surface flag IDs. + **********************************************************************************************************************/ +const field::FlagUID interfaceFlagID = field::FlagUID("interface"); +const field::FlagUID liquidFlagID = field::FlagUID("liquid"); +const field::FlagUID gasFlagID = field::FlagUID("gas"); +const field::FlagUID convertedFlagID = field::FlagUID("converted"); +const field::FlagUID convertToGasFlagID = field::FlagUID("convert to gas"); +const field::FlagUID convertToLiquidFlagID = field::FlagUID("convert to liquid"); +const field::FlagUID convertedFromGasToInterfaceFlagID = field::FlagUID("convert from gas to interface"); +const field::FlagUID keepInterfaceForWettingFlagID = field::FlagUID("convert to and keep interface for wetting"); +const field::FlagUID convertToInterfaceForInflowFlagID = field::FlagUID("convert to interface for inflow"); + +const Set< field::FlagUID > liquidInterfaceFlagIDs = setUnion< field::FlagUID >(liquidFlagID, interfaceFlagID); + +const Set< field::FlagUID > liquidInterfaceGasFlagIDs = + setUnion(liquidInterfaceFlagIDs, Set< field::FlagUID >(gasFlagID)); + +} // namespace flagIDs +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/FlagInfo.h b/src/lbm_generated/free_surface/FlagInfo.h new file mode 100644 index 0000000000000000000000000000000000000000..bdad8fb03d24f6686f7abb059cdec21a792a5312 --- /dev/null +++ b/src/lbm_generated/free_surface/FlagInfo.h @@ -0,0 +1,215 @@ +//====================================================================================================================== +// +// 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 FlagInfo.h +//! \ingroup free_surface +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Manage free surface flags (e.g. conversion flags). +// +//====================================================================================================================== + +#pragma once + +#include "core/mpi/Broadcast.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/FlagField.h" + +#include "FlagDefinitions.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Manage free surface flags (e.g. conversion flags). + **********************************************************************************************************************/ +template< typename FlagField_T > +class FlagInfo +{ + public: + using flag_t = typename FlagField_T::flag_t; + + FlagInfo(); + FlagInfo(const Set< field::FlagUID >& obstacleIDs, + const Set< field::FlagUID >& outflowIDs = Set< field::FlagUID >::emptySet(), + const Set< field::FlagUID >& inflowIDs = Set< field::FlagUID >::emptySet(), + const Set< field::FlagUID >& freeSlipIDs = Set< field::FlagUID >::emptySet()); + + bool operator==(const FlagInfo& o) const; + bool operator!=(const FlagInfo& o) const; + + flag_t interfaceFlag; + flag_t liquidFlag; + flag_t gasFlag; + + flag_t convertedFlag; // interface cell that is already converted + flag_t convertToGasFlag; // interface cell with too low fill level, should be converted + flag_t convertToLiquidFlag; // interface cell with too high fill level, should be converted + flag_t convertFromGasToInterfaceFlag; // interface cell that was a gas cell and needs refilling of its pdfs + flag_t keepInterfaceForWettingFlag; // gas/liquid cell that needs to be converted to interface cell for a smooth + // continuation of the wetting surface (see dissertation of S. Donath, 2011, + // section 6.3.5.3) + flag_t convertToInterfaceForInflowFlag; // gas cell that needs to be converted to interface cell to enable inflow + + flag_t obstacleFlagMask; + flag_t outflowFlagMask; + flag_t inflowFlagMask; + flag_t freeSlipFlagMask; // free slip obstacle cell (needs to be treated separately since PDFs going from gas into + // boundary are not available and must be reconstructed) + + template< typename FieldItOrPtr_T > + inline bool isInterface(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, interfaceFlag); + } + template< typename FieldItOrPtr_T > + inline bool isLiquid(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, liquidFlag); + } + template< typename FieldItOrPtr_T > + inline bool isGas(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, gasFlag); + } + template< typename FieldItOrPtr_T > + inline bool isObstacle(const FieldItOrPtr_T& flagItOrPtr) const + { + return isPartOfMaskSet(flagItOrPtr, obstacleFlagMask); + } + template< typename FieldItOrPtr_T > + inline bool isOutflow(const FieldItOrPtr_T& flagItOrPtr) const + { + return isPartOfMaskSet(flagItOrPtr, outflowFlagMask); + } + template< typename FieldItOrPtr_T > + inline bool isInflow(const FieldItOrPtr_T& flagItOrPtr) const + { + return isPartOfMaskSet(flagItOrPtr, inflowFlagMask); + } + template< typename FieldItOrPtr_T > + inline bool isFreeSlip(const FieldItOrPtr_T& flagItOrPtr) const + { + return isPartOfMaskSet(flagItOrPtr, freeSlipFlagMask); + } + template< typename FieldItOrPtr_T > + inline bool hasConverted(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, convertedFlag); + } + template< typename FieldItOrPtr_T > + inline bool hasConvertedToGas(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, convertToGasFlag); + } + template< typename FieldItOrPtr_T > + inline bool hasConvertedToLiquid(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, convertToLiquidFlag); + } + template< typename FieldItOrPtr_T > + inline bool hasConvertedFromGasToInterface(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, convertFromGasToInterfaceFlag); + } + template< typename FieldItOrPtr_T > + inline bool isKeepInterfaceForWetting(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, keepInterfaceForWettingFlag); + } + template< typename FieldItOrPtr_T > + inline bool isConvertToInterfaceForInflow(const FieldItOrPtr_T& flagItOrPtr) const + { + return isFlagSet(flagItOrPtr, convertToInterfaceForInflowFlag); + } + + inline bool isInterface(const flag_t val) const { return isFlagSet(val, interfaceFlag); } + inline bool isLiquid(const flag_t val) const { return isFlagSet(val, liquidFlag); } + inline bool isGas(const flag_t val) const { return isFlagSet(val, gasFlag); } + inline bool isObstacle(const flag_t val) const { return isPartOfMaskSet(val, obstacleFlagMask); } + inline bool isOutflow(const flag_t val) const { return isPartOfMaskSet(val, outflowFlagMask); } + inline bool isInflow(const flag_t val) const { return isPartOfMaskSet(val, inflowFlagMask); } + inline bool isFreeSlip(const flag_t val) const { return isPartOfMaskSet(val, freeSlipFlagMask); } + inline bool isKeepInterfaceForWetting(const flag_t val) const { return isFlagSet(val, keepInterfaceForWettingFlag); } + inline bool hasConvertedToGas(const flag_t val) const { return isFlagSet(val, convertToGasFlag); } + inline bool hasConvertedToLiquid(const flag_t val) const { return isFlagSet(val, convertToLiquidFlag); } + inline bool hasConvertedFromGasToInterface(const flag_t val) const + { + return isFlagSet(val, convertFromGasToInterfaceFlag); + } + inline bool isConvertToInterfaceForInflow(const flag_t val) const + { + return isFlagSet(val, convertToInterfaceForInflowFlag); + } + + // check whether FlagInfo is identical on all blocks and all processes + bool isConsistentAcrossBlocksAndProcesses(const std::weak_ptr< StructuredBlockStorage >& blockStorage, + ConstBlockDataID flagFieldID) const; + + // register flags in flag field + static void registerFlags(FlagField_T* field, const Set< field::FlagUID >& obstacleIDs, + const Set< field::FlagUID >& outflowIDs = Set< field::FlagUID >::emptySet(), + const Set< field::FlagUID >& inflowIDs = Set< field::FlagUID >::emptySet(), + const Set< field::FlagUID >& freeSlipIDs = Set< field::FlagUID >::emptySet()); + + void registerFlags(FlagField_T* field) const; + + Set< field::FlagUID > getObstacleIDSet() const { return obstacleIDs_; } + Set< field::FlagUID > getOutflowIDs() const { return outflowIDs_; } + Set< field::FlagUID > getInflowIDs() const { return inflowIDs_; } + Set< field::FlagUID > getFreeSlipIDs() const { return freeSlipIDs_; } + + protected: + FlagInfo(const FlagField_T* field, const Set< field::FlagUID >& obstacleIDs, const Set< field::FlagUID >& outflowIDs, + const Set< field::FlagUID >& inflowIDs, const Set< field::FlagUID >& freeSlipIDs); + + // create sets of flag IDs with flags from free_surface/boundary/FreeSurfaceBoundaryHandling.impl.h + Set< field::FlagUID > obstacleIDs_; + Set< field::FlagUID > outflowIDs_; + Set< field::FlagUID > inflowIDs_; + Set< field::FlagUID > freeSlipIDs_; +}; + +template< typename FlagField_T > +mpi::SendBuffer& operator<<(mpi::SendBuffer& buf, const FlagInfo< FlagField_T >& flagInfo) +{ + buf << flagInfo.interfaceFlag << flagInfo.liquidFlag << flagInfo.gasFlag << flagInfo.convertedFlag + << flagInfo.convertToGasFlag << flagInfo.convertToLiquidFlag << flagInfo.convertFromGasToInterfaceFlag + << flagInfo.keepInterfaceForWettingFlag << flagInfo.keepInterfaceForWettingFlag + << flagInfo.convertToInterfaceForInflowFlag << flagInfo.obstacleFlagMask << flagInfo.outflowFlagMask + << flagInfo.inflowFlagMask << flagInfo.freeSlipFlagMask; + + return buf; +} + +template< typename FlagField_T > +mpi::RecvBuffer& operator>>(mpi::RecvBuffer& buf, FlagInfo< FlagField_T >& flagInfo) +{ + buf >> flagInfo.interfaceFlag >> flagInfo.liquidFlag >> flagInfo.gasFlag >> flagInfo.convertedFlag >> + flagInfo.convertToGasFlag >> flagInfo.convertToLiquidFlag >> flagInfo.convertFromGasToInterfaceFlag >> + flagInfo.keepInterfaceForWettingFlag >> flagInfo.keepInterfaceForWettingFlag >> + flagInfo.convertToInterfaceForInflowFlag >> flagInfo.obstacleFlagMask >> flagInfo.outflowFlagMask >> + flagInfo.inflowFlagMask >> flagInfo.freeSlipFlagMask; + + return buf; +} + +} // namespace free_surface +} // namespace walberla + +#include "FlagInfo.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/FlagInfo.impl.h b/src/lbm_generated/free_surface/FlagInfo.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..728fc05c54de927ff5594ee8bb9b2564a5161cd4 --- /dev/null +++ b/src/lbm_generated/free_surface/FlagInfo.impl.h @@ -0,0 +1,199 @@ +//====================================================================================================================== +// +// 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 FlagInfo.impl.h +//! \ingroup free_surface +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Define and manage free surface flags (e.g. conversion flags). +// +//====================================================================================================================== + +#include "core/DataTypes.h" +#include "core/mpi/Broadcast.h" + +#include "field/FlagField.h" + +#include "FlagInfo.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename FlagField_T > +FlagInfo< FlagField_T >::FlagInfo() + : interfaceFlag(0), liquidFlag(0), gasFlag(0), convertedFlag(0), convertToGasFlag(0), convertToLiquidFlag(0), + convertFromGasToInterfaceFlag(0), keepInterfaceForWettingFlag(0), convertToInterfaceForInflowFlag(0), + obstacleFlagMask(0), outflowFlagMask(0), inflowFlagMask(0), freeSlipFlagMask(0) +{} + +template< typename FlagField_T > +FlagInfo< FlagField_T >::FlagInfo(const FlagField_T* field, const Set< field::FlagUID >& obstacleIDs, + const Set< field::FlagUID >& outflowIDs, const Set< field::FlagUID >& inflowIDs, + const Set< field::FlagUID >& freeSlipIDs) + : interfaceFlag(field->getFlag(flagIDs::interfaceFlagID)), liquidFlag(field->getFlag(flagIDs::liquidFlagID)), + gasFlag(field->getFlag(flagIDs::gasFlagID)), convertedFlag(field->getFlag(flagIDs::convertedFlagID)), + convertToGasFlag(field->getFlag(flagIDs::convertToGasFlagID)), + convertToLiquidFlag(field->getFlag(flagIDs::convertToLiquidFlagID)), + convertFromGasToInterfaceFlag(field->getFlag(flagIDs::convertedFromGasToInterfaceFlagID)), + keepInterfaceForWettingFlag(field->getFlag(flagIDs::keepInterfaceForWettingFlagID)), + convertToInterfaceForInflowFlag(field->getFlag(flagIDs::convertToInterfaceForInflowFlagID)), + obstacleIDs_(obstacleIDs), outflowIDs_(outflowIDs), inflowIDs_(inflowIDs), freeSlipIDs_(freeSlipIDs) +{ + // create obstacleFlagMask from obstacleIDs using bitwise OR + obstacleFlagMask = 0; + for (auto obstacleID = obstacleIDs.begin(); obstacleID != obstacleIDs.end(); ++obstacleID) + { + obstacleFlagMask = flag_t(obstacleFlagMask | field->getFlag(*obstacleID)); + } + + // create outflowFlagMask from outflowIDs using bitwise OR + outflowFlagMask = 0; + for (auto outflowID = outflowIDs.begin(); outflowID != outflowIDs.end(); ++outflowID) + { + outflowFlagMask = flag_t(outflowFlagMask | field->getFlag(*outflowID)); + } + + // create inflowFlagMask from inflowIDs using bitwise OR + inflowFlagMask = 0; + for (auto inflowID = inflowIDs.begin(); inflowID != inflowIDs.end(); ++inflowID) + { + inflowFlagMask = flag_t(inflowFlagMask | field->getFlag(*inflowID)); + } + + // create freeSlipFlagMask from freeSlipIDs using bitwise OR + freeSlipFlagMask = 0; + for (auto freeSlipID = freeSlipIDs.begin(); freeSlipID != freeSlipIDs.end(); ++freeSlipID) + { + freeSlipFlagMask = flag_t(freeSlipFlagMask | field->getFlag(*freeSlipID)); + } +} + +template< typename FlagField_T > +FlagInfo< FlagField_T >::FlagInfo(const Set< field::FlagUID >& obstacleIDs, const Set< field::FlagUID >& outflowIDs, + const Set< field::FlagUID >& inflowIDs, const Set< field::FlagUID >& freeSlipIDs) + : obstacleIDs_(obstacleIDs), outflowIDs_(outflowIDs), inflowIDs_(inflowIDs), freeSlipIDs_(freeSlipIDs) +{ + // define flags + flag_t nextFreeBit = flag_t(0); + interfaceFlag = flag_t(flag_t(1) << nextFreeBit++); // outermost flag_t is necessary to avoid warning C4334 on MSVC + liquidFlag = flag_t(flag_t(1) << nextFreeBit++); + gasFlag = flag_t(flag_t(1) << nextFreeBit++); + convertedFlag = flag_t(flag_t(1) << nextFreeBit++); + convertToGasFlag = flag_t(flag_t(1) << nextFreeBit++); + convertToLiquidFlag = flag_t(flag_t(1) << nextFreeBit++); + convertFromGasToInterfaceFlag = flag_t(flag_t(1) << nextFreeBit++); + keepInterfaceForWettingFlag = flag_t(flag_t(1) << nextFreeBit++); + convertToInterfaceForInflowFlag = flag_t(flag_t(1) << nextFreeBit++); + + obstacleFlagMask = flag_t(0); + outflowFlagMask = flag_t(0); + inflowFlagMask = flag_t(0); + freeSlipFlagMask = flag_t(0); + + // define flags for obstacles, outflow, inflow and freeSlip + auto setUnion = obstacleIDs + outflowIDs + inflowIDs + freeSlipIDs; + for (auto id = setUnion.begin(); id != setUnion.end(); ++id) + { + if (obstacleIDs.contains(*id)) { obstacleFlagMask = obstacleFlagMask | flag_t((flag_t(1) << nextFreeBit)); } + + if (outflowIDs.contains(*id)) { outflowFlagMask = outflowFlagMask | flag_t((flag_t(1) << nextFreeBit)); } + + if (inflowIDs.contains(*id)) { inflowFlagMask = inflowFlagMask | flag_t((flag_t(1) << nextFreeBit)); } + + if (freeSlipIDs.contains(*id)) { freeSlipFlagMask = freeSlipFlagMask | flag_t((flag_t(1) << nextFreeBit)); } + + nextFreeBit++; + } +} + +template< typename FlagField_T > +void FlagInfo< FlagField_T >::registerFlags(FlagField_T* field, const Set< field::FlagUID >& obstacleIDs, + const Set< field::FlagUID >& outflowIDs, + const Set< field::FlagUID >& inflowIDs, + const Set< field::FlagUID >& freeSlipIDs) +{ + flag_t nextFreeBit = flag_t(0); + field->registerFlag(flagIDs::interfaceFlagID, nextFreeBit++); + field->registerFlag(flagIDs::liquidFlagID, nextFreeBit++); + field->registerFlag(flagIDs::gasFlagID, nextFreeBit++); + field->registerFlag(flagIDs::convertedFlagID, nextFreeBit++); + field->registerFlag(flagIDs::convertToGasFlagID, nextFreeBit++); + field->registerFlag(flagIDs::convertToLiquidFlagID, nextFreeBit++); + field->registerFlag(flagIDs::convertedFromGasToInterfaceFlagID, nextFreeBit++); + field->registerFlag(flagIDs::keepInterfaceForWettingFlagID, nextFreeBit++); + field->registerFlag(flagIDs::convertToInterfaceForInflowFlagID, nextFreeBit++); + + // extract flags + auto setUnion = obstacleIDs + outflowIDs + inflowIDs + freeSlipIDs; + for (auto id = setUnion.begin(); id != setUnion.end(); ++id) + { + field->registerFlag(*id, nextFreeBit++); + } +} + +template< typename FlagField_T > +void FlagInfo< FlagField_T >::registerFlags(FlagField_T* field) const +{ + registerFlags(field, obstacleIDs_, outflowIDs_, inflowIDs_, freeSlipIDs_); +} + +template< typename FlagField_T > +bool FlagInfo< FlagField_T >::isConsistentAcrossBlocksAndProcesses( + const std::weak_ptr< StructuredBlockStorage >& blockStoragePtr, ConstBlockDataID flagFieldID) const +{ + // check consistency across processes + FlagInfo rootFlagInfo = *this; + + // root broadcasts its FlagInfo to all other processes + mpi::broadcastObject(rootFlagInfo); + + // this process' FlagInfo is not identical to the one of root + if (rootFlagInfo != *this) { return false; } + + auto blockStorage = blockStoragePtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockStorage); + + // check consistency across blocks + for (auto blockIt = blockStorage->begin(); blockIt != blockStorage->end(); ++blockIt) + { + const FlagField_T* const flagField = blockIt->getData< const FlagField_T >(flagFieldID); + FlagInfo fi(flagField, obstacleIDs_, outflowIDs_, inflowIDs_, freeSlipIDs_); + if (fi != *this) { return false; } + } + + return true; +} + +template< typename FlagField_T > +bool FlagInfo< FlagField_T >::operator==(const FlagInfo& o) const +{ + return interfaceFlag == o.interfaceFlag && gasFlag == o.gasFlag && liquidFlag == o.liquidFlag && + convertToGasFlag == o.convertToGasFlag && convertToLiquidFlag == o.convertToLiquidFlag && + convertFromGasToInterfaceFlag == o.convertFromGasToInterfaceFlag && + keepInterfaceForWettingFlag == o.keepInterfaceForWettingFlag && + convertToInterfaceForInflowFlag == o.convertToInterfaceForInflowFlag && + obstacleFlagMask == o.obstacleFlagMask && outflowFlagMask == o.outflowFlagMask && + inflowFlagMask == o.inflowFlagMask && freeSlipFlagMask == o.freeSlipFlagMask; +} + +template< typename FlagField_T > +bool FlagInfo< FlagField_T >::operator!=(const FlagInfo& o) const +{ + return !(*this == o); +} + +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/InitFunctions.h b/src/lbm_generated/free_surface/InitFunctions.h new file mode 100644 index 0000000000000000000000000000000000000000..551a255b223de7275048686d6e10e8aab951cead --- /dev/null +++ b/src/lbm_generated/free_surface/InitFunctions.h @@ -0,0 +1,279 @@ +//====================================================================================================================== +// +// 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 InitFunctions.h +//! \ingroup free_surface +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Initialization functions. +// +//====================================================================================================================== + +#pragma once + +#include "blockforest/StructuredBlockForest.h" + +#include "domain_decomposition/BlockDataID.h" + +#include <functional> + +#include "FlagInfo.h" +#include "InterfaceFromFillLevel.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Initialize fill level with "value" in cells belonging to boundary and obstacles such that the bubble model does not + * detect obstacles as gas cells. + **********************************************************************************************************************/ +template< typename BoundaryHandling_T, typename Stencil_T, typename ScalarField_T > +void initFillLevelsInBoundaries(const std::weak_ptr< StructuredBlockForest >& blockForestPtr, + const ConstBlockDataID& handlingID, const BlockDataID& fillFieldID, + real_t value = real_c(1)) +{ + const auto blockForest = blockForestPtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + ScalarField_T* const fillField = blockIt->getData< ScalarField_T >(fillFieldID); + const BoundaryHandling_T* const handling = blockIt->getData< const BoundaryHandling_T >(handlingID); + + // set fill level to "value" in every cell belonging to boundary + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(fillField, { + if (handling->isBoundary(x, y, z)) { fillField->get(x, y, z) = value; } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ + } +} + +/*********************************************************************************************************************** + * Clear and initialize flags in every cell according to the fill level. + **********************************************************************************************************************/ +template< typename BoundaryHandling_T, typename Stencil_T, typename FlagField_T, typename ScalarField_T > +void initFlagsFromFillLevels(const std::weak_ptr< StructuredBlockForest >& blockForestPtr, + const FlagInfo< FlagField_T >& flagInfo, const BlockDataID& handlingID, + const ConstBlockDataID& fillFieldID) +{ + const auto blockForest = blockForestPtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + const ScalarField_T* const fillField = blockIt->getData< const ScalarField_T >(fillFieldID); + BoundaryHandling_T* const handling = blockIt->getData< BoundaryHandling_T >(handlingID); + + // clear all flags in the boundary handling + handling->removeFlag(flagInfo.gasFlag); + handling->removeFlag(flagInfo.liquidFlag); + handling->removeFlag(flagInfo.interfaceFlag); + + WALBERLA_FOR_ALL_CELLS(fillFieldIt, fillField, { + // set flags only in non-boundary and non-obstacle cells + if (!handling->isBoundary(fillFieldIt.x(), fillFieldIt.y(), fillFieldIt.z())) + { + if (floatIsEqual(*fillFieldIt, real_c(0), real_c(1e-14))) + { + // set gas flag + handling->forceFlag(flagInfo.gasFlag, fillFieldIt.x(), fillFieldIt.y(), fillFieldIt.z()); + } + else + { + if (*fillFieldIt < real_c(1.0)) + { + // set interface flag + handling->forceFlag(flagInfo.interfaceFlag, fillFieldIt.x(), fillFieldIt.y(), fillFieldIt.z()); + } + else + { + // check if the cell is an interface cell due to direct neighboring gas cells + if (isInterfaceFromFillLevel< Stencil_T >(fillFieldIt)) + { + // set interface flag + handling->forceFlag(flagInfo.interfaceFlag, fillFieldIt.x(), fillFieldIt.y(), fillFieldIt.z()); + } + else + { + // set liquid flag + handling->forceFlag(flagInfo.liquidFlag, fillFieldIt.x(), fillFieldIt.y(), fillFieldIt.z()); + } + } + } + } + }) // WALBERLA_FOR_ALL_CELLS + } +} + +/*********************************************************************************************************************** + * Initialize the hydrostatic pressure in the direction in which a force is acting in ALL cells (regardless of a cell's + * flag). The velocity remains unchanged. + * + * The force vector must have only one component, i.e., the direction of the force can only be in x-, y- or z-axis. + * The variable fluidHeight determines the height at which the density is equal to reference density (=1). + **********************************************************************************************************************/ +template< typename ScalarField_T > +void initHydrostaticPressure(const std::weak_ptr< StructuredBlockForest >& blockForestPtr, + const BlockDataID& densityFieldID, const Vector3< real_t >& force, real_t fluidHeight) +{ + // count number of non-zero components of the force vector + uint_t numForceComponents = uint_c(0); + if (!realIsEqual(force[0], real_c(0), real_c(1e-14))) { ++numForceComponents; } + if (!realIsEqual(force[1], real_c(0), real_c(1e-14))) { ++numForceComponents; } + if (!realIsEqual(force[2], real_c(0), real_c(1e-14))) { ++numForceComponents; } + + WALBERLA_CHECK_EQUAL(numForceComponents, uint_c(1), + "The current implementation of the hydrostatic pressure initialization does not support " + "forces that have none or multiple components, i. e., a force that points in a direction other " + "than the x-, y- or z-axis."); + + const auto blockForest = blockForestPtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + ScalarField_T* const densityField = blockIt->getData< ScalarField_T >(densityFieldID); + CellInterval local = densityField->xyzSizeWithGhostLayer(); // block-, i.e., process-local cell interval + + for (auto cellIt = local.begin(); cellIt != local.end(); ++cellIt) + { + // transform the block-local coordinate to global coordinates + Cell global; + blockForest->transformBlockLocalToGlobalCell(global, *blockIt, *cellIt); + + // get the current global coordinate, i.e., height of the fluid in the relevant direction + cell_idx_t coordinate = cell_idx_c(0); + real_t forceComponent = real_c(0); + if (!realIsEqual(force[0], real_c(0), real_c(1e-14))) + { + coordinate = global.x(); + forceComponent = force[0]; + } + else + { + if (!realIsEqual(force[1], real_c(0), real_c(1e-14))) + { + coordinate = global.y(); + forceComponent = force[1]; + } + else + { + if (!realIsEqual(force[2], real_c(0), real_c(1e-14))) + { + coordinate = global.z(); + forceComponent = force[2]; + } + else + { + WALBERLA_ABORT( + "The current implementation of the hydrostatic pressure initialization does not support " + "forces that have none or multiple components, i. e., a force that points in a direction other " + "than the x-, y- or z-axis.") + } + } + } + + // initialize the (hydrostatic) pressure, i.e., LBM density + // Bernoulli: p = p0 + density * gravity * height + // => LBM (density=1): rho = rho0 + gravity * height = 1 + 1/cs^2 * g * h = 1 + 3 * g * h + // shift global cell by 0.5 since density is set for cell center + densityField->get(*cellIt) = real_c(1) + real_c(3) * forceComponent * (real_c(coordinate) + real_c(0.5) - std::ceil(fluidHeight)); + } + } +} + +/*********************************************************************************************************************** + * Initialize the force density field with a given acceleration and density of each cell. + * Differs from the version above by using a flattened vector field (GhostLayerField< real_t, 3 >). This is necessary + * because Pystencils does not support VectorField_T (GhostLayerField< Vector3<real_t>, 1 >). + **********************************************************************************************************************/ +template< typename FlagField_T, typename VectorField_T, typename ScalarField_T > +void initForceDensityField(const std::weak_ptr< StructuredBlockForest >& blockForestPtr, + const BlockDataID& forceDensityFieldID, const ConstBlockDataID& fillFieldID, + const ConstBlockDataID& densityFieldID, const ConstBlockDataID& flagFieldID, + const FlagInfo< FlagField_T >& flagInfo, const Vector3< real_t >& acceleration) +{ + const auto blockForest = blockForestPtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + VectorField_T* const forceDensityField = blockIt->getData< VectorField_T >(forceDensityFieldID); + const ScalarField_T* const fillField = blockIt->getData< const ScalarField_T >(fillFieldID); + const ScalarField_T* const densityField = blockIt->getData< const ScalarField_T >(densityFieldID); + const FlagField_T* const flagField = blockIt->getData< const FlagField_T >(flagFieldID); + + WALBERLA_FOR_ALL_CELLS(forceDensityFieldIt, forceDensityField, fillFieldIt, fillField, densityFieldIt, densityField, + flagFieldIt, flagField, { + // set force density in cells to acceleration * density * fillLevel (see equation 15 + // in Koerner et al., 2005); + + if (flagInfo.isInterface(*flagFieldIt)) + { + const real_t density = densityField->get(densityFieldIt.cell()); + forceDensityFieldIt[0] = acceleration[0] * *fillFieldIt * density; + forceDensityFieldIt[1] = acceleration[1] * *fillFieldIt * density; + forceDensityFieldIt[2] = acceleration[2] * *fillFieldIt * density; + } + + else + { + if (flagInfo.isLiquid(*flagFieldIt)) + { + const real_t density = densityField->get(densityFieldIt.cell()); + forceDensityFieldIt[0] = acceleration[0] * density; + forceDensityFieldIt[1] = acceleration[1] * density; + forceDensityFieldIt[2] = acceleration[2] * density; + } + } + }) // WALBERLA_FOR_ALL_CELLS + } +} + +/*********************************************************************************************************************** + * Set density in non-liquid and non-interface cells to 1. + **********************************************************************************************************************/ +template< typename FlagField_T, typename VectorField_T, typename ScalarField_T> +void setDensityInNonFluidCellsToOne(const std::weak_ptr< StructuredBlockForest >& blockForestPtr, + const FlagInfo< FlagField_T >& flagInfo, const ConstBlockDataID& flagFieldID, + const BlockDataID& velocityFieldID, const BlockDataID& densityFieldID) +{ + const auto blockForest = blockForestPtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + VectorField_T* const velocityField = blockIt->getData< VectorField_T >(velocityFieldID); + ScalarField_T* const densityField = blockIt->getData< ScalarField_T >(densityFieldID); + + const FlagField_T* const flagField = blockIt->getData< const FlagField_T >(flagFieldID); + + WALBERLA_FOR_ALL_CELLS(velocityFieldIt, velocityField, densityFieldIt, densityField, flagFieldIt, flagField, { + if (!flagInfo.isLiquid(*flagFieldIt) && !flagInfo.isInterface(*flagFieldIt)) + { + // set density in gas cells to 1 and velocity to zero + const cell_idx_t x = densityFieldIt.x(); + const cell_idx_t y = densityFieldIt.y(); + const cell_idx_t z = densityFieldIt.z(); + densityField->get(x, y, z) = real_c(1.0); + velocityField->get(x, y, z, 0) = real_c(0.0); + velocityField->get(x, y, z, 1) = real_c(0.0); + velocityField->get(x, y, z, 2) = real_c(0.0); + } + }) // WALBERLA_FOR_ALL_CELLS + } +} +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/InterfaceFromFillLevel.h b/src/lbm_generated/free_surface/InterfaceFromFillLevel.h new file mode 100644 index 0000000000000000000000000000000000000000..cfe8ae6efc3f408575ced2d9f802cc2c6279674b --- /dev/null +++ b/src/lbm_generated/free_surface/InterfaceFromFillLevel.h @@ -0,0 +1,78 @@ +//====================================================================================================================== +// +// 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 InterfaceFromFillLevel.h +//! \ingroup free_surface +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Check whether a cell should be an interface cell according to its properties. +// +//====================================================================================================================== + +#pragma once + +#include "core/cell/Cell.h" + +#include <type_traits> + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Check whether a cell is an interface cell with respect to its fill level and its direct neighborhood (liquid cells + * can not have gas cell in their direct neighborhood; therefore, the liquid cell is forced to be an interface cell). + **********************************************************************************************************************/ +template< typename Stencil_T, typename ScalarField_T > +inline bool isInterfaceFromFillLevel(const ScalarField_T& fillField, cell_idx_t x, cell_idx_t y, cell_idx_t z) +{ + WALBERLA_ASSERT(std::is_floating_point< typename ScalarField_T::value_type >::value, + "Fill level field must contain floating point values.") + + real_t fillLevel = fillField.get(x, y, z); + + // this cell is regular gas cell + if (floatIsEqual(fillLevel, real_c(0.0), real_c(1e-14))) { return false; } + + // this cell is regular interface cell + if (fillLevel < real_c(1.0)) { return true; } + + // check this cell's direct neighborhood for gas cells (a liquid cell can not be direct neighbor to a gas cell) + for (auto d = Stencil_T::beginNoCenter(); d != Stencil_T::end(); ++d) + { + // this cell has a gas cell in its direct neighborhood; it therefore must not be a liquid cell but an interface + // cell although its fill level is 1.0 + if (fillField.get(x + d.cx(), y + d.cy(), z + d.cz()) <= real_c(0.0)) { return true; } + } + + // this cell is a regular fluid cell + return false; +} + +template< typename Stencil_T, typename ScalarFieldIt_T > +inline bool isInterfaceFromFillLevel(const ScalarFieldIt_T& fillFieldIt) +{ + return isInterfaceFromFillLevel< Stencil_T, typename ScalarFieldIt_T::FieldType >( + *(fillFieldIt.getField()), fillFieldIt.x(), fillFieldIt.y(), fillFieldIt.z()); +} + +template< typename Stencil_T, typename ScalarField > +inline bool isInterfaceFromFillLevel(const ScalarField& fillField, const Cell& cell) +{ + return isInterfaceFromFillLevel< Stencil_T >(fillField, cell.x(), cell.y(), cell.z()); +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/LoadBalancing.h b/src/lbm_generated/free_surface/LoadBalancing.h new file mode 100644 index 0000000000000000000000000000000000000000..16cc9fe3f74b9fbb7b5027696e603f02d7ceb519 --- /dev/null +++ b/src/lbm_generated/free_surface/LoadBalancing.h @@ -0,0 +1,343 @@ +//====================================================================================================================== +// +// 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 LoadBalancing.h +//! \ingroup free_surface +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Free surface-specific functionality for load balancing. +// +//====================================================================================================================== + +#include "blockforest/communication/UniformBufferedScheme.h" +#include "blockforest/BlockForest.h" +#include "blockforest/SetupBlockForest.h" +#include "blockforest/StructuredBlockForest.h" +#include "blockforest/loadbalancing/DynamicCurve.h" +#include "blockforest/loadbalancing/StaticCurve.h" + +#include "core/logging/Logging.h" +#include "core/math/AABB.h" +#include "core/math/DistributedSample.h" +#include "core/math/Vector3.h" +#include "core/mpi/MPIManager.h" + +#include "lbm_generated/blockforest/SimpleCommunication.h" +#include "lbm_generated/free_surface/BlockStateDetectorSweep.h" + +#include <algorithm> +#include <numeric> + +namespace walberla +{ +namespace free_surface_generated +{ + +template< typename FlagField_T > +class ProcessLoadEvaluator; + +/*********************************************************************************************************************** + * Create non-uniform block forest to be used for load balancing. + **********************************************************************************************************************/ +std::shared_ptr< StructuredBlockForest > createNonUniformBlockForest(const Vector3< uint_t >& domainSize, + const Vector3< uint_t >& cellsPerBlock, + const Vector3< uint_t >& numBlocks, + const Vector3< bool >& periodicity) +{ + WALBERLA_CHECK_EQUAL(domainSize[0], cellsPerBlock[0] * numBlocks[0], + "The domain size is not divisible by the specified \"cellsPerBlock\" in x-direction."); + WALBERLA_CHECK_EQUAL(domainSize[1], cellsPerBlock[1] * numBlocks[1], + "The domain size is not divisible by the specified \"cellsPerBlock\" in y-direction."); + WALBERLA_CHECK_EQUAL(domainSize[2], cellsPerBlock[2] * numBlocks[2], + "The domain size is not divisible by the specified \"cellsPerBlock\" in z-direction."); + + // create SetupBlockForest for allowing load balancing + SetupBlockForest setupBlockForest; + + AABB domainAABB(real_c(0), real_c(0), real_c(0), real_c(domainSize[0]), real_c(domainSize[1]), + real_c(domainSize[2])); + + setupBlockForest.init(domainAABB, numBlocks[0], numBlocks[1], numBlocks[2], periodicity[0], periodicity[1], + periodicity[2]); + + // compute initial process distribution + setupBlockForest.balanceLoad(blockforest::StaticLevelwiseCurveBalance(true), + uint_c(MPIManager::instance()->numProcesses())); + + WALBERLA_LOG_INFO_ON_ROOT(setupBlockForest); + + // define MPI communicator + if (!MPIManager::instance()->rankValid()) { MPIManager::instance()->useWorldComm(); } + + // create BlockForest (will be encapsulated in StructuredBlockForest) + const std::shared_ptr< BlockForest > blockForest = + std::make_shared< BlockForest >(uint_c(MPIManager::instance()->rank()), setupBlockForest, false); + + // create StructuredBlockForest + std::shared_ptr< StructuredBlockForest > structuredBlockForest = + std::make_shared< StructuredBlockForest >(blockForest, cellsPerBlock[0], cellsPerBlock[1], cellsPerBlock[2]); + structuredBlockForest->createCellBoundingBoxes(); + + return structuredBlockForest; +} + +/*********************************************************************************************************************** + * Example for a load balancing implementation. + * + * IMPORTANT REMARK: The following implementation should be considered a demonstrator based on best-practices. That is, + * it was not thoroughly benchmarked and does not guarantee a performance-optimal load balancing. + **********************************************************************************************************************/ +template< typename FlagField_T, typename CommunicationStencil_T, typename LBMStencil_T > +class LoadBalancer +{ + public: + LoadBalancer(const std::shared_ptr< StructuredBlockForest >& blockForestPtr, + const lbm_generated::SimpleCommunication< CommunicationStencil_T >& communication, + const blockforest::communication::UniformBufferedScheme< LBMStencil_T >& pdfCommunication, + uint_t blockWeightFullFreeSurface, + uint_t blockWeightOnlyLBM, uint_t blockWeightOnlyGasAndBoundary, uint_t frequency, + bool printStatistics = false) + : blockForest_(blockForestPtr), communication_(communication), pdfCommunication_(pdfCommunication), + blockWeightFullFreeSurface_(blockWeightFullFreeSurface), + blockWeightOnlyLBM_(blockWeightOnlyLBM), blockWeightOnlyGasAndBoundary_(blockWeightOnlyGasAndBoundary), + frequency_(frequency), printStatistics_(printStatistics), executionCounter_(uint_c(0)), + evaluator_(ProcessLoadEvaluator< FlagField_T >(blockForest_, blockWeightFullFreeSurface_, blockWeightOnlyLBM_, + blockWeightOnlyGasAndBoundary_, uint_c(1))) + { + BlockForest& blockForest = blockForest_->getBlockForest(); + + // refinement is not implemented in FSLBM such that this can be set to false + blockForest.recalculateBlockLevelsInRefresh(false); + + // rebalancing of blocks must be forced here, as it would normally be done when refinement levels change + blockForest.alwaysRebalanceInRefresh(true); + + // depth of levels is not changing and must therefore not be communicated + blockForest.allowRefreshChangingDepth(false); + + // refinement is not implemented in FSLBM such that this can be set to false + blockForest.allowMultipleRefreshCycles(false); + + // leave algorithm when load balancing has been performed + blockForest.checkForEarlyOutAfterLoadBalancing(true); + + // use PhantomWeight as defined below + blockForest.setRefreshPhantomBlockDataAssignmentFunction(blockWeightAssignment); + blockForest.setRefreshPhantomBlockDataPackFunction(phantomWeightsPack); + blockForest.setRefreshPhantomBlockDataUnpackFunction(phantomWeightsUnpack); + + // assign load balancing function + blockForest.setRefreshPhantomBlockMigrationPreparationFunction( + blockforest::DynamicCurveBalance< PhantomWeight >(true, true)); + } + + void operator()() + { + if (frequency_ == uint_c(0)) { return; } + + // only balance load in given frequencies + if (executionCounter_ % frequency_ == uint_c(0)) + { + BlockForest& blockForest = blockForest_->getBlockForest(); + + // balance load by updating the blockForest + const uint_t modificationStamp = blockForest.getModificationStamp(); + blockForest.refresh(); + + const uint_t newModificationStamp = blockForest.getModificationStamp(); + + if (newModificationStamp != modificationStamp) + { + // communicate all fields + communication_(); + pdfCommunication_(); + + if (printStatistics_) { evaluator_(); } + + if (blockForest.getNumberOfBlocks() == uint_c(0)) + { + WALBERLA_ABORT( + "Load balancing lead to a situation where there is a process with no blocks. This is " + "not supported yet. This can be avoided by either using smaller blocks or, equivalently, more blocks " + "per process."); + } + } + } + ++executionCounter_; + } + + private: + std::shared_ptr< StructuredBlockForest > blockForest_; + lbm_generated::SimpleCommunication< CommunicationStencil_T > communication_; + blockforest::communication::UniformBufferedScheme< LBMStencil_T > pdfCommunication_; + + uint_t blockWeightFullFreeSurface_; // empirical choice, not thoroughly benchmarked: 50 + uint_t blockWeightOnlyLBM_; // empirical choice, not thoroughly benchmarked: 10 + uint_t blockWeightOnlyGasAndBoundary_; // empirical choice, not thoroughly benchmarked: 5 + + uint_t frequency_; + bool printStatistics_; + + uint_t executionCounter_; + + ProcessLoadEvaluator< FlagField_T > evaluator_; + + class PhantomWeight // used as a 'PhantomBlockForest::PhantomBlockDataAssignmentFunction' + { + public: + using weight_t = uint_t; + PhantomWeight(const weight_t _weight) : weight_(_weight) {} + weight_t weight() const { return weight_; } + + private: + weight_t weight_; + }; // class PhantomWeight + + std::function< void(mpi::SendBuffer& buffer, const PhantomBlock& block) > phantomWeightsPack = + [](mpi::SendBuffer& buffer, const PhantomBlock& block) { buffer << block.getData< PhantomWeight >().weight(); }; + + std::function< void(mpi::RecvBuffer& buffer, const PhantomBlock&, walberla::any& data) > phantomWeightsUnpack = + [](mpi::RecvBuffer& buffer, const PhantomBlock&, walberla::any& data) { + typename PhantomWeight::weight_t w; + buffer >> w; + data = PhantomWeight(w); + }; + + std::function< void(std::vector< std::pair< const PhantomBlock*, walberla::any > >& blockData, + const PhantomBlockForest&) > + blockWeightAssignment = + [this](std::vector< std::pair< const PhantomBlock*, walberla::any > >& blockData, const PhantomBlockForest&) { + for (auto it = blockData.begin(); it != blockData.end(); ++it) + { + if (it->first->getState().contains(BlockStateDetectorSweep< FlagField_T >::fullFreeSurface)) + { + it->second = PhantomWeight(blockWeightFullFreeSurface_); + } + else + { + if (it->first->getState().contains(BlockStateDetectorSweep< FlagField_T >::onlyLBM)) + { + it->second = PhantomWeight(blockWeightOnlyLBM_); + } + else + { + if (it->first->getState().contains(BlockStateDetectorSweep< FlagField_T >::onlyGasAndBoundary)) + { + it->second = PhantomWeight(blockWeightOnlyGasAndBoundary_); + } + else { WALBERLA_ABORT("Unknown block state"); } + } + } + } + }; + +}; // class LoadBalancer + +/*********************************************************************************************************************** + * Evaluates and prints statistics about the current load distribution situation: + * - Average weight per process + * - Maximum weight per process + * - Minimum weight per process + **********************************************************************************************************************/ +template< typename FlagField_T > +class ProcessLoadEvaluator +{ + public: + ProcessLoadEvaluator(const std::weak_ptr< const StructuredBlockForest >& blockForest, + uint_t blockWeightFullFreeSurface, uint_t blockWeightOnlyLBM, + uint_t blockWeightOnlyGasAndBoundary, uint_t frequency) + : blockForest_(blockForest), blockWeightFullFreeSurface_(blockWeightFullFreeSurface), + blockWeightOnlyLBM_(blockWeightOnlyLBM), blockWeightOnlyGasAndBoundary_(blockWeightOnlyGasAndBoundary), + frequency_(frequency), executionCounter_(uint_c(0)) + {} + + void operator()() + { + if (frequency_ == uint_c(0)) { return; } + + ++executionCounter_; + + // only evaluate in given intervals + if (executionCounter_ % frequency_ != uint_c(0) && executionCounter_ != uint_c(1)) { return; } + + std::vector< real_t > weightSum = computeWeightSumPerProcess(); + + print(weightSum); + } + + std::vector< real_t > computeWeightSumPerProcess() + { + auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + std::vector< real_t > weightSum(uint_c(MPIManager::instance()->numProcesses()), real_c(0)); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + if (blockForest->blockExistsLocally(blockIt->getId())) + { + if (blockIt->getState().contains(BlockStateDetectorSweep< FlagField_T >::fullFreeSurface)) + { + weightSum[blockForest->getProcessRank(blockIt->getId())] += real_c(blockWeightFullFreeSurface_); + } + else + { + if (blockIt->getState().contains(BlockStateDetectorSweep< FlagField_T >::onlyLBM)) + { + weightSum[blockForest->getProcessRank(blockIt->getId())] += real_c(blockWeightOnlyLBM_); + } + else + { + if (blockIt->getState().contains(BlockStateDetectorSweep< FlagField_T >::onlyGasAndBoundary)) + { + weightSum[blockForest->getProcessRank(blockIt->getId())] += real_c(blockWeightOnlyGasAndBoundary_); + } + } + } + } + } + + mpi::reduceInplace< real_t >(weightSum, mpi::SUM, 0); + + return weightSum; + } + + void print(const std::vector< real_t >& weightSum) + { + WALBERLA_ROOT_SECTION() + { + const std::vector< real_t >::const_iterator max = std::max_element(weightSum.cbegin(), weightSum.end()); + const std::vector< real_t >::const_iterator min = std::min_element(weightSum.cbegin(), weightSum.end()); + const real_t sum = std::accumulate(weightSum.cbegin(), weightSum.end(), real_c(0)); + const real_t avg = sum / real_c(MPIManager::instance()->numProcesses()); + + WALBERLA_LOG_INFO("Load balancing:"); + WALBERLA_LOG_INFO("\t Average weight per process " << avg); + WALBERLA_LOG_INFO("\t Maximum weight per process " << *max); + WALBERLA_LOG_INFO("\t Minimum weight per process " << *min); + } + } + + private: + std::weak_ptr< const StructuredBlockForest > blockForest_; + uint_t blockWeightFullFreeSurface_; + uint_t blockWeightOnlyLBM_; + uint_t blockWeightOnlyGasAndBoundary_; + + uint_t frequency_; + uint_t executionCounter_; +}; // class ProcessLoadEvaluator + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/MaxVelocityComputer.h b/src/lbm_generated/free_surface/MaxVelocityComputer.h new file mode 100644 index 0000000000000000000000000000000000000000..c568f637ed47c4d79fc6c78ead51590b21d8ef71 --- /dev/null +++ b/src/lbm_generated/free_surface/MaxVelocityComputer.h @@ -0,0 +1,113 @@ +//====================================================================================================================== +// +// 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 MaxVelocityComputer.h +//! \ingroup free_surface +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Compute the maximum velocity of all liquid and interface cells (in each direction) in the system. +// +//====================================================================================================================== + +#include "blockforest/Initialization.h" + +#include "core/Environment.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename FreeSurfaceBoundaryHandling_T, typename PdfField_T, typename FlagField_T > +class MaxVelocityComputer +{ + public: + MaxVelocityComputer(const std::weak_ptr< const StructuredBlockForest >& blockForest, + const std::weak_ptr< const FreeSurfaceBoundaryHandling_T >& freeSurfaceBoundaryHandling, + const ConstBlockDataID& pdfFieldID, uint_t frequency, + const std::shared_ptr< Vector3< real_t > >& maxVelocity) + : blockForest_(blockForest), freeSurfaceBoundaryHandling_(freeSurfaceBoundaryHandling), pdfFieldID_(pdfFieldID), + maxVelocity_(maxVelocity), frequency_(frequency), executionCounter_(uint_c(0)) + {} + + void operator()() + { + if (frequency_ == uint_c(0)) { return; } + + auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + auto freeSurfaceBoundaryHandling = freeSurfaceBoundaryHandling_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(freeSurfaceBoundaryHandling); + + if (executionCounter_ == uint_c(0)) { getMaxVelocity(blockForest, freeSurfaceBoundaryHandling); } + else + { + // only evaluate in given frequencies + if (executionCounter_ % frequency_ == uint_c(0)) { getMaxVelocity(blockForest, freeSurfaceBoundaryHandling); } + } + + ++executionCounter_; + } + + void getMaxVelocity(const std::shared_ptr< const StructuredBlockForest >& blockForest, + const std::shared_ptr< const FreeSurfaceBoundaryHandling_T >& freeSurfaceBoundaryHandling) + { + const BlockDataID flagFieldID = freeSurfaceBoundaryHandling->getFlagFieldID(); + const typename FreeSurfaceBoundaryHandling_T::FlagInfo_T& flagInfo = freeSurfaceBoundaryHandling->getFlagInfo(); + + real_t maxVelocityX = real_c(0); + real_t maxVelocityY = real_c(0); + real_t maxVelocityZ = real_c(0); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + const FlagField_T* const flagField = blockIt->template getData< const FlagField_T >(flagFieldID); + const PdfField_T* const pdfField = blockIt->template getData< const PdfField_T >(pdfFieldID_); + + WALBERLA_FOR_ALL_CELLS_OMP(flagFieldIt, flagField, pdfFieldIt, pdfField, + omp parallel for schedule(static) reduction(max:maxVelocityX) + reduction(max:maxVelocityY) + reduction(max:maxVelocityZ), + { + if (flagInfo.isLiquid(flagFieldIt) || flagInfo.isInterface(flagFieldIt)) + { + const Vector3< real_t > velocity = pdfField->getVelocity(pdfFieldIt.cell()); + + if (velocity[0] > maxVelocityX) { maxVelocityX = velocity[0]; } + if (velocity[1] > maxVelocityY) { maxVelocityY = velocity[1]; } + if (velocity[2] > maxVelocityZ) { maxVelocityZ = velocity[2]; } + } + }) // WALBERLA_FOR_ALL_CELLS_OMP + } + + Vector3< real_t > maxVelocity(maxVelocityX, maxVelocityY, maxVelocityZ); + mpi::allReduceInplace< real_t >(maxVelocity, mpi::MAX); + + *maxVelocity_ = maxVelocity; + }; + + private: + std::weak_ptr< const StructuredBlockForest > blockForest_; + std::weak_ptr< const FreeSurfaceBoundaryHandling_T > freeSurfaceBoundaryHandling_; + + const ConstBlockDataID pdfFieldID_; + + std::shared_ptr< Vector3< real_t > > maxVelocity_; + + uint_t frequency_; + uint_t executionCounter_; +}; // class MaxVelocityComputer + +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/SurfaceMeshWriter.h b/src/lbm_generated/free_surface/SurfaceMeshWriter.h new file mode 100644 index 0000000000000000000000000000000000000000..f0071d49a95e875d804131196ae7935754006b8f --- /dev/null +++ b/src/lbm_generated/free_surface/SurfaceMeshWriter.h @@ -0,0 +1,176 @@ +//====================================================================================================================== +// +// This file is part of waLBerla. waLBerla is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// waLBerla is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>. +// +//! \file SurfaceMeshWriter.h +//! \ingroup free_surface +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Free surface-specific class for writing the free surface as triangle mesh. +// +//====================================================================================================================== + +#include "blockforest/StructuredBlockForest.h" + +#include "core/Filesystem.h" + +#include "field/AddToStorage.h" + +#include "geometry/mesh/TriangleMeshIO.h" + +#include "postprocessing/FieldToSurfaceMesh.h" + +#include "timeloop/SweepTimeloop.h" + +namespace walberla +{ +namespace free_surface_generated +{ +namespace abortIfNullptr +{ + +// helper function to check validity of std::weak_ptr in constructors' initializer list; WALBERLA_CHECK_NOT_NULLPTR() +// does not work there, because the macro terminates with ";" +template< typename T > +void abortIfNullptr(const std::weak_ptr< T >& weakPointer) +{ + if (weakPointer.lock() == nullptr) { WALBERLA_ABORT("Weak pointer has expired."); } +} +} // namespace abortIfNullptr + +/*********************************************************************************************************************** + * Write free surface as triangle mesh. + * + * Internally, a clone of the fill level field is stored and all cells not marked as liquidInterfaceGasFlagIDSet are set + * to "obstacleFillLevel" in the cloned field. This is done to avoid writing e.g. obstacle cells that were possibly + * assigned a fill level of 1 to not make them detect as gas cells in the bubble model. + **********************************************************************************************************************/ +template< typename ScalarField_T, typename FlagField_T > +class SurfaceMeshWriter +{ + public: + SurfaceMeshWriter(const std::weak_ptr< StructuredBlockForest >& blockForest, const ConstBlockDataID& fillFieldID, + const ConstBlockDataID& flagFieldID, const Set< FlagUID >& liquidInterfaceGasFlagIDSet, + real_t obstacleFillLevel, uint_t writeFrequency, const std::string& baseFolder) + : blockForest_(blockForest), fillFieldID_(fillFieldID), flagFieldID_(flagFieldID), + liquidInterfaceGasFlagIDSet_(liquidInterfaceGasFlagIDSet), obstacleFillLevel_(obstacleFillLevel), + writeFrequency_(writeFrequency), baseFolder_(baseFolder), executionCounter_(uint_c(0)), + fillFieldCloneID_( + (abortIfNullptr::abortIfNullptr(blockForest), + field::addCloneToStorage< ScalarField_T >(blockForest_.lock(), fillFieldID_, "Fill level field clone"))) + {} + + // config block must be named "MeshOutputParameters" + SurfaceMeshWriter(const std::weak_ptr< StructuredBlockForest >& blockForest, const ConstBlockDataID& fillFieldID, + const ConstBlockDataID& flagFieldID, const Set< FlagUID >& liquidInterfaceGasFlagIDSet, + real_t obstacleFillLevel, const std::weak_ptr< Config >& config) + : SurfaceMeshWriter( + blockForest, fillFieldID, flagFieldID, liquidInterfaceGasFlagIDSet, obstacleFillLevel, + (abortIfNullptr::abortIfNullptr(config), + config.lock()->getOneBlock("MeshOutputParameters").getParameter< uint_t >("writeFrequency")), + (abortIfNullptr::abortIfNullptr(config), + config.lock()->getOneBlock("MeshOutputParameters").getParameter< std::string >("baseFolder"))) + {} + + void operator()() + { + if (writeFrequency_ == uint_c(0)) { return; } + + if (executionCounter_ == uint_c(0)) + { + createBaseFolder(); + writeMesh(); + } + else { writeMesh(); } + + ++executionCounter_; + } + + private: + void createBaseFolder() const + { + WALBERLA_ROOT_SECTION() + { + const filesystem::path basePath(baseFolder_); + if (filesystem::exists(basePath)) { filesystem::remove_all(basePath); } + filesystem::create_directories(basePath); + } + WALBERLA_MPI_BARRIER(); + } + + void writeMesh() + { + // only write mesh in given frequency + if (executionCounter_ % writeFrequency_ != uint_c(0)) { return; } + + // rank=0 is just an arbitrary choice here + const int targetRank = 0; + + auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + // update clone of fill level field and set fill level of all non-liquid, -interface, or -gas cells to zero + updateFillFieldClone(blockForest); + + const auto surfaceMesh = postprocessing::realFieldToSurfaceMesh< ScalarField_T >( + blockForest, fillFieldCloneID_, real_c(0.5), uint_c(0), true, targetRank, MPI_COMM_WORLD); + + WALBERLA_EXCLUSIVE_WORLD_SECTION(targetRank) + { + geometry::writeMesh(baseFolder_ + "/" + "simulation_step_" + std::to_string(executionCounter_) + ".obj", + *surfaceMesh); + } + } + + // update clone of fill level field and set fill level of all non-liquid, -interface, or -gas cells to zero; + // explicitly use shared_ptr instead of weak_ptr to avoid checking the latter's validity (is done in writeMesh() + // already) + void updateFillFieldClone(const shared_ptr< StructuredBlockForest >& blockForest) + { + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + ScalarField_T* const fillFieldClone = blockIt->template getData< ScalarField_T >(fillFieldCloneID_); + const ScalarField_T* const fillField = blockIt->template getData< const ScalarField_T >(fillFieldID_); + const FlagField_T* const flagField = blockIt->template getData< const FlagField_T >(flagFieldID_); + + const auto liquidInterfaceGasFlagMask = flagField->getMask(liquidInterfaceGasFlagIDSet_); + + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(fillFieldClone, fillField->nrOfGhostLayers(), { + const typename ScalarField_T::Ptr fillFieldClonePtr(*fillFieldClone, x, y, z); + const typename ScalarField_T::ConstPtr fillFieldPtr(*fillField, x, y, z); + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + + // set fill level to zero in every non-liquid, -interface, or -gas cell + if (!isPartOfMaskSet(flagFieldPtr, liquidInterfaceGasFlagMask)) { *fillFieldClonePtr = obstacleFillLevel_; } + else + { + // copy fill level from fill level field + *fillFieldClonePtr = *fillFieldPtr; + } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ + } + } + + std::weak_ptr< StructuredBlockForest > blockForest_; + ConstBlockDataID fillFieldID_; + ConstBlockDataID flagFieldID_; + Set< FlagUID > liquidInterfaceGasFlagIDSet_; + real_t obstacleFillLevel_; + uint_t writeFrequency_; + std::string baseFolder_; + uint_t executionCounter_; + + BlockDataID fillFieldCloneID_; +}; // class SurfaceMeshWriter +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/TotalMassComputer.h b/src/lbm_generated/free_surface/TotalMassComputer.h new file mode 100644 index 0000000000000000000000000000000000000000..f91af951654bba34ee5b09c9293ea3336fa5c866 --- /dev/null +++ b/src/lbm_generated/free_surface/TotalMassComputer.h @@ -0,0 +1,149 @@ +//====================================================================================================================== +// +// 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 TotalMassComputer.h +//! \ingroup free_surface +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Compute the total mass of the system (including mass from the excessMassField). +// +//====================================================================================================================== + +#include "blockforest/Initialization.h" + +#include "core/Environment.h" +#include "core/StringUtility.h" + +#include "lbm_generated/free_surface/dynamics/SurfaceDynamicsHandler.h" +#include "lbm_generated/free_surface/surface_geometry/SurfaceGeometryHandler.h" +#include "lbm_generated/free_surface/surface_geometry/Utility.h" +#include "lbm_generated/free_surface/FlagInfo.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename PdfField_T, typename FlagField_T, typename ScalarField_T > +class TotalMassComputer +{ + public: + TotalMassComputer(const std::weak_ptr< const StructuredBlockForest >& blockForest, + const ConstBlockDataID& flagFieldID, const FlagInfo<FlagField_T>& flagInfo, + const ConstBlockDataID& pdfFieldID, const ConstBlockDataID& fillFieldID, uint_t frequency, + const std::shared_ptr< real_t >& totalMass) + : blockForest_(blockForest), flagFieldID_(flagFieldID), flagInfo_(flagInfo), pdfFieldID_(pdfFieldID), + fillFieldID_(fillFieldID), totalMass_(totalMass), frequency_(frequency), executionCounter_(uint_c(0)) + {} + + TotalMassComputer(const std::weak_ptr< const StructuredBlockForest >& blockForest, + const ConstBlockDataID& flagFieldID, const FlagInfo<FlagField_T>& flagInfo, + const ConstBlockDataID& pdfFieldID, const ConstBlockDataID& fillFieldID, + const ConstBlockDataID& excessMassFieldID, uint_t frequency, + const std::shared_ptr< real_t >& totalMass, const std::shared_ptr< real_t >& excessMass) + : blockForest_(blockForest), flagFieldID_(flagFieldID), flagInfo_(flagInfo), pdfFieldID_(pdfFieldID), + fillFieldID_(fillFieldID), excessMassFieldID_(excessMassFieldID), totalMass_(totalMass), + excessMass_(excessMass), frequency_(frequency), executionCounter_(uint_c(0)) + {} + + void operator()() + { + if (frequency_ == uint_c(0)) { return; } + + auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + // only evaluate in given frequencies + if (executionCounter_ % frequency_ == uint_c(0) || executionCounter_ == uint_c(0)) + { + computeMass(blockForest, flagFieldID_, flagInfo_); + } + + ++executionCounter_; + } + + void computeMass(const std::shared_ptr< const StructuredBlockForest >& blockForest, + const ConstBlockDataID& flagFieldID, const FlagInfo<FlagField_T>& flagInfo) + { + real_t mass = real_c(0); + real_t excessMass = real_c(0); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + const FlagField_T* const flagField = blockIt->template getData< const FlagField_T >(flagFieldID); + const PdfField_T* const pdfField = blockIt->template getData< const PdfField_T >(pdfFieldID_); + const ScalarField_T* const fillField = blockIt->template getData< const ScalarField_T >(fillFieldID_); + + // if provided, also consider mass stored in excessMassField + if (excessMassFieldID_ != ConstBlockDataID()) + { + const ScalarField_T* const excessMassField = + blockIt->template getData< const ScalarField_T >(excessMassFieldID_); + + WALBERLA_FOR_ALL_CELLS_OMP(flagFieldIt, flagField, pdfFieldIt, pdfField, fillFieldIt, fillField, + excessMassFieldIt, excessMassField, + omp parallel for schedule(static) reduction(+:mass), + { + if (flagInfo.isLiquid(flagFieldIt) || flagInfo.isInterface(flagFieldIt)) + { + //TODO Implement + const real_t density = 1.0; // pdfField->getDensity(pdfFieldIt.cell()); + mass += *fillFieldIt * density + *excessMassFieldIt; + + if (excessMass_ != nullptr) { excessMass += *excessMassFieldIt; } + } + }) // WALBERLA_FOR_ALL_CELLS_OMP + } + else + { + WALBERLA_FOR_ALL_CELLS_OMP(flagFieldIt, flagField, pdfFieldIt, pdfField, fillFieldIt, fillField, + omp parallel for schedule(static) reduction(+:mass), + { + if (flagInfo.isLiquid(flagFieldIt) || flagInfo.isInterface(flagFieldIt)) + { + //TODO Implement + const real_t density = 1.0; // pdfField->getDensity(pdfFieldIt.cell()); + mass += *fillFieldIt * density; + } + }) // WALBERLA_FOR_ALL_CELLS_OMP + } + } + + mpi::allReduceInplace< real_t >(mass, mpi::SUM); + *totalMass_ = mass; + + if (excessMass_ != nullptr) + { + mpi::allReduceInplace< real_t >(excessMass, mpi::SUM); + *excessMass_ = excessMass; + } + }; + + private: + std::weak_ptr< const StructuredBlockForest > blockForest_; + ConstBlockDataID flagFieldID_; + FlagInfo<FlagField_T> flagInfo_; + + const ConstBlockDataID pdfFieldID_; + const ConstBlockDataID fillFieldID_; + const ConstBlockDataID excessMassFieldID_ = ConstBlockDataID(); + + std::shared_ptr< real_t > totalMass_; + std::shared_ptr< real_t > excessMass_ = nullptr; // mass stored in the excessMassField + + uint_t frequency_; + uint_t executionCounter_; +}; // class TotalMassComputer + +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/VtkWriter.h b/src/lbm_generated/free_surface/VtkWriter.h new file mode 100644 index 0000000000000000000000000000000000000000..ef523a2627eee3437318228f0743dc41df4d979d --- /dev/null +++ b/src/lbm_generated/free_surface/VtkWriter.h @@ -0,0 +1,183 @@ +//====================================================================================================================== +// +// 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 VtkWriter.h +//! \ingroup free_surface +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Free surface-specific VTK writer function. +// +//====================================================================================================================== + +#include "blockforest/communication/UniformBufferedScheme.h" + +#include "field/adaptors/AdaptorCreators.h" +#include "field/communication/PackInfo.h" +#include "field/vtk/FlagFieldCellFilter.h" +#include "field/vtk/FlagFieldMapping.h" +#include "field/vtk/VTKWriter.h" + +#include "stencil/D3Q27.h" + +#include "timeloop/SweepTimeloop.h" + +#include "vtk/Initialization.h" + +#include "FlagInfo.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Add VTK output to time loop that includes all relevant free surface information. It must be configured via + * config-file. + **********************************************************************************************************************/ +template< typename FlagInfo_T, typename PdfField_T, typename FlagField_T, + typename ScalarField_T, typename VectorField_T, + typename VectorFieldFlattened_T = GhostLayerField< real_t, 3 > > +void addVTKOutput(const std::weak_ptr< StructuredBlockForest >& blockForestPtr, SweepTimeloop& timeloop, + const std::weak_ptr< Config >& configPtr, + const FlagInfo_T& flagInfo, const BlockDataID& pdfFieldID, + const BlockDataID& flagFieldID, const BlockDataID& fillFieldID, + const BlockDataID& forceDensityFieldID, const BlockDataID& curvatureFieldID, + const BlockDataID& normalFieldID, const BlockDataID& obstacleNormalFieldID) +{ + // TODO density and velocity are not added to VTK Output. + using value_type = typename FlagField_T::value_type; + const auto blockForest = blockForestPtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + const auto config = configPtr.lock(); + WALBERLA_CHECK_NOT_NULLPTR(config); + + // define VTK output (see src/vtk/Initialization.cpp, line 574 for usage) + const auto vtkConfigFunc = [&](std::vector< std::shared_ptr< vtk::BlockCellDataWriterInterface > >& writers, + std::map< std::string, vtk::VTKOutput::CellFilter >& filters, + std::map< std::string, vtk::VTKOutput::BeforeFunction >& beforeFuncs) { + using field::VTKWriter; + writers.push_back(std::make_shared< VTKWriter< PdfField_T, float > >(pdfFieldID, "pdf")); + writers.push_back(std::make_shared< VTKWriter< FlagField_T, float > >(flagFieldID, "flag")); + writers.push_back(std::make_shared< VTKWriter< ScalarField_T, float > >(fillFieldID, "fill_level")); + writers.push_back(std::make_shared< VTKWriter< ScalarField_T, float > >(curvatureFieldID, "curvature")); + writers.push_back(std::make_shared< VTKWriter< VectorField_T, float > >(normalFieldID, "normal")); + writers.push_back(std::make_shared< VTKWriter< VectorField_T, float > >(obstacleNormalFieldID, "obstacle_normal")); + + if (forceDensityFieldID != BlockDataID()) + { + writers.push_back(std::make_shared< VTKWriter< VectorFieldFlattened_T, float > >(forceDensityFieldID, "force_density")); + } + + // map flagIDs to integer values + const auto flagMapper = + std::make_shared< field::FlagFieldMapping< FlagField_T, value_type > >(flagFieldID, "mapped_flag"); + flagMapper->addMapping(flagIDs::liquidFlagID, numeric_cast< value_type >(1)); + flagMapper->addMapping(flagIDs::interfaceFlagID, numeric_cast< value_type >(2)); + flagMapper->addMapping(flagIDs::gasFlagID, numeric_cast< value_type >(3)); +// flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::noSlipFlagID, numeric_cast< value_type >(4)); +// flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::freeSlipFlagID, numeric_cast< value_type >(6)); +// flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::ubbFlagID, numeric_cast< value_type >(6)); +// flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::ubbInflowFlagID, numeric_cast< value_type >(7)); +// flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::pressureFlagID, numeric_cast< value_type >(8)); +// flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::pressureOutflowFlagID, numeric_cast< value_type >(9)); +// flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::outletFlagID, numeric_cast< value_type >(10)); + + writers.push_back(flagMapper); + + // filter for writing only liquid and interface cells to VTK + auto liquidInterfaceFilter = field::FlagFieldCellFilter< FlagField_T >(flagFieldID); + liquidInterfaceFilter.addFlag(flagIDs::liquidFlagID); + liquidInterfaceFilter.addFlag(flagIDs::interfaceFlagID); + filters["liquidInterfaceFilter"] = liquidInterfaceFilter; + + // communicate fields to update the ghost layer + auto preVTKComm = blockforest::communication::UniformBufferedScheme< stencil::D3Q27 >(blockForest); + preVTKComm.addPackInfo(std::make_shared< field::communication::PackInfo< PdfField_T > >(pdfFieldID)); + preVTKComm.addPackInfo(std::make_shared< field::communication::PackInfo< FlagField_T > >(flagFieldID)); + preVTKComm.addPackInfo(std::make_shared< field::communication::PackInfo< ScalarField_T > >(fillFieldID)); + preVTKComm.addPackInfo(std::make_shared< field::communication::PackInfo< ScalarField_T > >(curvatureFieldID)); + preVTKComm.addPackInfo(std::make_shared< field::communication::PackInfo< VectorField_T > >(normalFieldID)); + preVTKComm.addPackInfo( + std::make_shared< field::communication::PackInfo< VectorField_T > >(obstacleNormalFieldID)); + preVTKComm.addPackInfo(std::make_shared< field::communication::PackInfo< VectorFieldFlattened_T > >(forceDensityFieldID)); + + beforeFuncs["ghost_layer_synchronization"] = preVTKComm; + + // set velocity and density to zero in obstacle and gas cells (only for visualization purposes); the PDF values in + // these cells are not important and thus not set during the simulation; + // only enable this functionality if the non-liquid and non-interface cells are not excluded anyway + const auto vtkConfigBlock = config->getOneBlock("VTK"); + const auto fluidFieldConfigBlock = vtkConfigBlock.getBlock("fluid_field"); + if (fluidFieldConfigBlock) + { + auto inclusionFiltersConfigBlock = fluidFieldConfigBlock.getBlock("inclusion_filters"); + + // liquidInterfaceFilter limits VTK-output to only liquid and interface cells + if (!inclusionFiltersConfigBlock.isDefined("liquidInterfaceFilter")) + { + class ZeroSetter + { + public: + ZeroSetter(const weak_ptr< StructuredBlockForest >& blockForest, const BlockDataID& pdfFieldID, + const ConstBlockDataID& flagFieldID, + const FlagInfo_T& flagInfo) + : blockForest_(blockForest), pdfFieldID_(pdfFieldID), flagFieldID_(flagFieldID), flagInfo_(flagInfo) + {} + + void operator()() + { + auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt) + { + PdfField_T* const pdfField = blockIt->template getData< PdfField_T >(pdfFieldID_); + const FlagField_T* const flagField = blockIt->template getData< const FlagField_T >(flagFieldID_); + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(pdfField, uint_c(1), { + const typename PdfField_T::Ptr pdfFieldPtr(*pdfField, x, y, z); + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + +// if (flagInfo_.isGas(*flagFieldPtr) || flagInfo_.isObstacle(*flagFieldPtr)) +// { +// pdfField->setDensityAndVelocity(pdfFieldPtr.cell(), Vector3< real_t >(real_c(0)), +// real_c(1.0)); +// } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ + } + } + + private: + weak_ptr< StructuredBlockForest > blockForest_; + BlockDataID pdfFieldID_; + ConstBlockDataID flagFieldID_; + FlagInfo_T flagInfo_; + }; + + beforeFuncs["gas_cell_zero_setter"] = ZeroSetter(blockForest, pdfFieldID, flagFieldID, flagInfo); + } + } + }; + + // add VTK output to timeloop + std::map< std::string, vtk::SelectableOutputFunction > vtkOutputFunctions; + vtk::initializeVTKOutput(vtkOutputFunctions, vtkConfigFunc, blockForest, config); + for (auto output = vtkOutputFunctions.begin(); output != vtkOutputFunctions.end(); ++output) + { + timeloop.addFuncBeforeTimeStep(output->second.outputFunction, std::string("VTK: ") + output->first, + output->second.requiredGlobalStates, output->second.incompatibleGlobalStates); + } +} + +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/CMakeLists.txt b/src/lbm_generated/free_surface/dynamics/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f6f2fc658d31f95028dabb69c7a7fb4ddb59424f --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/CMakeLists.txt @@ -0,0 +1,17 @@ +target_sources( lbm_generated + PRIVATE + CellConversionSweep.h + ConversionFlagsResetSweep.h + ExcessMassDistributionModel.h + ExcessMassDistributionSweep.h + ExcessMassDistributionSweep.impl.h + ForceDensitySweep.h + PdfReconstructionModel.h + PdfRefillingModel.h + PdfRefillingSweep.h + PdfRefillingSweep.impl.h + StreamReconstructAdvectSweep.h + SurfaceDynamicsHandler.h + ) + +add_subdirectory( functionality ) \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/CellConversionSweep.h b/src/lbm_generated/free_surface/dynamics/CellConversionSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..2d360357ed34c687e12a356868c5307445dc8622 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/CellConversionSweep.h @@ -0,0 +1,285 @@ +//====================================================================================================================== +// +// 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 CellConversionSweep.h +//! \ingroup dynamics +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Convert cells to/from interface cells. +// +//====================================================================================================================== + +#pragma once + +#include "field/FlagField.h" + +#include "lbm_generated/field/PdfField.h" +#include "lbm_generated/free_surface/FlagInfo.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Convert cells to/from interface + * - expects that a previous sweep has set the convertToLiquidFlag and convertToGasFlag flags + * - notifies the bubble model that conversions occurred + * - all cells that have been converted, have the "converted" flag set + * - PDF field is required in order to initialize the velocity in new gas cells with information from surrounding cells + * + * A) Interface -> Liquid/Gas + * - always converts interface to liquid + * - converts interface to gas only if no newly created liquid cell is in neighborhood + * B) Liquid/Gas -> Interface (to obtain a closed interface layer) + * - "old" liquid cells in the neighborhood of newly converted cells in A) are converted to interface + * - "old" gas cells in the neighborhood of newly converted cells in A) are converted to interface + * The term "old" refers to cells that were not converted themselves in the same time step. + * C) GAS -> INTERFACE (due to inflow boundary condition) + * D) LIQUID/GAS -> INTERFACE (due to wetting; only when using local triangulation for curvature computation) + * + * For gas cells that were converted to interface in B), the flag "convertedFromGasToInterface" is set to signal another + * sweep (i.e. "PdfRefillingSweep.h") that the this cell's PDFs need to be reinitialized. + * + * For gas cells that were converted to interface in C), the cell's PDFs are reinitialized with equilibrium constructed + * with the inflow velocity. + * + * More information can be found in the dissertation of N. Thuerey, 2007, section 4.3. + * ********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T > +class CellConversionSweep +{ + public: + using flag_t = typename FlagField_T::flag_t; + using PdfField_T = lbm_generated::PdfField< StorageSpecification_T >; + using Stencil_T = typename StorageSpecification_T::Stencil; + + CellConversionSweep(BlockDataID flagFieldID, BlockDataID pdfFieldID, const FlagInfo< FlagField_T >& flagInfo) + : flagFieldID_(flagFieldID), pdfFieldID_(pdfFieldID), flagInfo_(flagInfo) + {} + + void operator()(IBlock* const block) + { + // PdfField_T* const pdfField = block->getData< PdfField_T >(pdfFieldID_); + FlagField_T* const flagField = block->getData< FlagField_T >(flagFieldID_); + + // A) INTERFACE -> LIQUID/GAS + // convert interface cells that have filled/emptied to liquid/gas (cflagInfo_. dissertation of N. Thuerey, 2007, + // section 4.3) + // the conversion is also performed in the first ghost layer, since B requires an up-to-date first ghost layer; + // explicitly avoid OpenMP, as bubble IDs are set here + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP(flagField, uint_c(1), omp critical, { + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + + // 1) convert interface cells to liquid + if (isFlagSet(flagFieldPtr, flagInfo_.convertToLiquidFlag)) + { + flagField->removeFlag(x, y, z, flagInfo_.interfaceFlag); + flagField->addFlag(x, y, z, flagInfo_.liquidFlag); + flagField->addFlag(x, y, z, flagInfo_.convertedFlag); + } + + // 2) convert interface cells to gas only if no newly converted liquid cell from 1) is in neighborhood to + // ensure a closed interface + if (isFlagSet(flagFieldPtr, flagInfo_.convertToGasFlag) && + !isFlagInNeighborhood< Stencil_T >(flagFieldPtr, flagInfo_.convertToLiquidFlag)) + { + flagField->removeFlag(x, y, z, flagInfo_.interfaceFlag); + flagField->addFlag(x, y, z, flagInfo_.gasFlag); + flagField->addFlag(x, y, z, flagInfo_.convertedFlag); + } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP + + // B) LIQUID/GAS -> INTERFACE + // convert those liquid/gas cells to interface that are in the neighborhood of the newly created liquid/gas cells + // from A; this maintains a closed interface layer; + // explicitly avoid OpenMP, as bubble IDs are set here + WALBERLA_FOR_ALL_CELLS_XYZ_OMP(flagField, omp critical, { + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + + // only consider "old" liquid cells, i.e., cells that have not been converted in this time step + if (flagInfo_.isLiquid(flagFieldPtr) && !flagInfo_.hasConverted(flagFieldPtr)) + { + const flag_t newGasFlagMask = flagInfo_.convertedFlag | flagInfo_.gasFlag; // flag newly converted gas cell + + // the state of ghost layer cells becomes relevant here + for (auto d = StorageSpecification_T::Stencil::beginNoCenter(); d != StorageSpecification_T::Stencil::end(); ++d) + if (isMaskSet(flagFieldPtr.neighbor(*d), newGasFlagMask)) // newly converted gas cell is in neighborhood + { + // convert the current cell to interface + flagField->removeFlag(x, y, z, flagInfo_.liquidFlag); + flagField->addFlag(x, y, z, flagInfo_.interfaceFlag); + flagField->addFlag(x, y, z, flagInfo_.convertedFlag); + + // current cell was already converted to interface, flags of other neighbors are not relevant + break; + } + } + // only consider "old" gas cells, i.e., cells that have not been converted in this time step + else + { + if (flagInfo_.isGas(flagFieldPtr) && !flagInfo_.hasConverted(flagFieldPtr)) + { + const flag_t newLiquidFlagMask = + flagInfo_.convertedFlag | flagInfo_.liquidFlag; // flag of newly converted liquid cell + + // the state of ghost layer cells is relevant here + for (auto d = StorageSpecification_T::Stencil::beginNoCenter(); d != StorageSpecification_T::Stencil::end(); ++d) + + // newly converted liquid cell is in neighborhood + if (isMaskSet(flagFieldPtr.neighbor(*d), newLiquidFlagMask)) + { + // convert the current cell to interface + flagField->removeFlag(x, y, z, flagInfo_.gasFlag); + flagField->addFlag(x, y, z, flagInfo_.interfaceFlag); + flagField->addFlag(x, y, z, flagInfo_.convertedFlag); + flagField->addFlag(x, y, z, flagInfo_.convertFromGasToInterfaceFlag); + + // current cell was already converted to interface, flags of other neighbors are not relevant + break; + } + } + } + }) // WALBERLA_FOR_ALL_CELLS_XYZ_OMP + + // C) GAS -> INTERFACE (due to inflow boundary condition) + // convert gas cells to interface cells near inflow boundaries; + // explicitly avoid OpenMP, such that cell conversions are performed sequentially + convertedFromGasToInterfaceDueToInflow.clear(); + WALBERLA_FOR_ALL_CELLS_XYZ_OMP(flagField, omp critical, { + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + + if (flagInfo_.isConvertToInterfaceForInflow(flagFieldPtr) && !flagInfo_.hasConverted(flagFieldPtr)) + { + // newly converted liquid cell is in neighborhood + flagField->removeFlag(flagInfo_.convertToInterfaceForInflowFlag, x, y, z); + + // convert the current cell to interface + flagField->removeFlag(x, y, z, flagInfo_.gasFlag); + flagField->addFlag(x, y, z, flagInfo_.interfaceFlag); + flagField->addFlag(x, y, z, flagInfo_.convertedFlag); + convertedFromGasToInterfaceDueToInflow.insert(flagFieldPtr.cell()); + } + }) // WALBERLA_FOR_ALL_CELLS_XYZ_OMP + + // D) LIQUID/GAS -> INTERFACE (due to wetting; only active when using local triangulation for curvature + // computation) + // convert liquid/gas to interface cells where the interface cell is required for a smooth + // continuation of the wetting surface (see dissertation of S. Donath, 2011, section 6.3.5.3); + // explicitly avoid OpenMP, as bubble IDs are set here + WALBERLA_FOR_ALL_CELLS_XYZ_OMP(flagField, omp critical, { + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + + // only consider wetting and non-interface cells + if (flagInfo_.isKeepInterfaceForWetting(flagFieldPtr) && !flagInfo_.isInterface(flagFieldPtr)) + { + // convert liquid cell to interface + if (isFlagSet(flagFieldPtr, flagInfo_.liquidFlag)) + { + flagField->removeFlag(x, y, z, flagInfo_.liquidFlag); + flagField->addFlag(x, y, z, flagInfo_.interfaceFlag); + flagField->addFlag(x, y, z, flagInfo_.convertedFlag); + flagField->removeFlag(x, y, z, flagInfo_.keepInterfaceForWettingFlag); + } + else + { + // convert gas cell to interface + if (isFlagSet(flagFieldPtr, flagInfo_.gasFlag)) + { + flagField->removeFlag(x, y, z, flagInfo_.gasFlag); + flagField->addFlag(x, y, z, flagInfo_.interfaceFlag); + flagField->addFlag(x, y, z, flagInfo_.convertedFlag); + flagField->removeFlag(x, y, z, flagInfo_.keepInterfaceForWettingFlag); + flagField->addFlag(x, y, z, flagInfo_.convertFromGasToInterfaceFlag); + } + } + } + }) // WALBERLA_FOR_ALL_CELLS_XYZ_OMP + + // initialize PDFs of interface cells that were created due to an inflow boundary; the PDFs are set to equilibrium + // with density=1 and velocity of the inflow boundary + + // TODO: should be activated again in the future + // initializeFromInflow(convertedFromGasToInterfaceDueToInflow, flagField, pdfField); + } + + protected: + /******************************************************************************************************************** + * Initializes PDFs in cells that are converted to interface due to a neighboring inflow boundary. + * + * The PDFs of these cells are set to equilibrium values using density=1 and the average velocity of neighboring + * inflow boundaries. An inflow cell is used for averaging only if the velocity actually flows towards the newly + * created interface cell. In other words, the velocity direction is compared to the converted cell's direction with + * respect to the inflow location. + * + * REMARK: The inflow boundary condition must implement function "getValue()" that returns the prescribed velocity + * (see e.g. UBB). + *******************************************************************************************************************/ +// void initializeFromInflow(const std::set< Cell >& cells, FlagField_T* flagField, PdfField_T* pdfField) +// { +// for (auto setIt = cells.begin(); setIt != cells.end(); ++setIt) +// { +// const Cell& cell = *setIt; +// +// Vector3< real_t > u(real_c(0.0)); +// uint_t numNeighbors = uint_c(0); +// +// // get UBB inflow boundary +// +// for (auto i = StorageSpecification_T::Stencil::beginNoCenter(); i != StorageSpecification_T::Stencil::end(); ++i) +// { +// using namespace stencil; +// const Cell neighborCell(cell[0] + i.cx(), cell[1] + i.cy(), cell[2] + i.cz()); +// +// const flag_t neighborFlag = flagField->get(neighborCell); +// +// // neighboring cell is inflow +// if (isPartOfMaskSet(neighborFlag, flagInfo_.inflowFlagMask)) +// { +// // get direction towards cell containing inflow boundary +// const Vector3< int > dir = Vector3< int >(-i.cx(), -i.cy(), -i.cz()); +// +// const Vector3< real_t > inflowVel = ubbInflow.getValue(cell[0] + i.cx(), cell[1] + i.cy(), cell[2] + i.cz()); +// +// // skip directions in which the corresponding velocity component is zero +// if (realIsEqual(inflowVel[0], real_c(0), real_c(1e-14)) && dir[0] != 0) { continue; } +// if (realIsEqual(inflowVel[1], real_c(0), real_c(1e-14)) && dir[1] != 0) { continue; } +// if (realIsEqual(inflowVel[2], real_c(0), real_c(1e-14)) && dir[2] != 0) { continue; } +// +// // skip directions in which the corresponding velocity component is in opposite direction +// if (inflowVel[0] > real_c(0) && dir[0] < 0) { continue; } +// if (inflowVel[1] > real_c(0) && dir[1] < 0) { continue; } +// if (inflowVel[2] > real_c(0) && dir[2] < 0) { continue; } +// +// // use inflow velocity to get average velocity +// u += inflowVel; +// numNeighbors++; +// } +// } +// if (numNeighbors > uint_c(0)) { u /= real_c(numNeighbors); } // else: velocity is zero +// +// pdfField->setDensityAndVelocity(cell, u, real_c(1)); // set density=1 +// } +// } + + BlockDataID flagFieldID_; + BlockDataID pdfFieldID_; + + FlagInfo< FlagField_T > flagInfo_; + + std::set< Cell > convertedFromGasToInterfaceDueToInflow; +}; // class CellConversionSweep + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/ConversionFlagsResetSweep.h b/src/lbm_generated/free_surface/dynamics/ConversionFlagsResetSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..f3df3bbfa0aa4213dc822d8a68fa0beac06c5a21 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/ConversionFlagsResetSweep.h @@ -0,0 +1,70 @@ +//====================================================================================================================== +// +// 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 ResetFlagSweep.h +//! \ingroup dynamics +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Reset all free surface flags that mark cell conversions. +// +//====================================================================================================================== + +#pragma once + +#include "core/logging/Logging.h" + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FlagField.h" + +#include "lbm_generated/free_surface/FlagInfo.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Reset all free surface flags that signal cell conversions. The flag "keepInterfaceForWettingFlag" is explicitly not + * reset since this flag must persist in the next time step. + **********************************************************************************************************************/ +template< typename FlagField_T > +class ConversionFlagsResetSweep +{ + public: + ConversionFlagsResetSweep(BlockDataID flagFieldID, const FlagInfo< FlagField_T >& flagInfo) + : flagFieldID_(flagFieldID), flagInfo_(flagInfo) + {} + + void operator()(IBlock* const block) + { + FlagField_T* const flagField = block->getData< FlagField_T >(flagFieldID_); + + // reset all conversion flags (except flagInfo_.keepInterfaceForWettingFlag) + const flag_t allConversionFlags = flagInfo_.convertToGasFlag | flagInfo_.convertToLiquidFlag | + flagInfo_.convertedFlag | flagInfo_.convertFromGasToInterfaceFlag | + flagInfo_.convertToInterfaceForInflowFlag; + WALBERLA_FOR_ALL_CELLS(flagFieldIt, flagField, + { removeMask(flagFieldIt, allConversionFlags); }) // WALBERLA_FOR_ALL_CELLS + } + + private: + using flag_t = typename FlagField_T::flag_t; + + BlockDataID flagFieldID_; + FlagInfo< FlagField_T > flagInfo_; +}; // class ConversionFlagsResetSweep + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionModel.h b/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionModel.h new file mode 100644 index 0000000000000000000000000000000000000000..809dbbdc0e725f2e832ee34bfdec98125013a695 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionModel.h @@ -0,0 +1,238 @@ +//====================================================================================================================== +// +// 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 ExcessMassDistributionModel.h +//! \ingroup dynamics +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Class that specifies how excessive mass is distributed. +// +//====================================================================================================================== + +#pragma once + +#include "core/StringUtility.h" +#include "core/stringToNum.h" + +#include <string> + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Class that specifies how excessive mass is distributed after cell conversions from interface to liquid or interface + * to gas. + * For example, when converting an interface cell with fill level 1.1 to liquid with fill level 1,0, an excessive mass + * corresponding to the fill level 0.1 must be distributed. + * + * Available models: + * - EvenlyAllInterface: + * Excess mass is distributed evenly among all neighboring interface cells (see dissertations of T. Pohl, S. + * Donath, S. Bogner). + * + * - EvenlyNewInterface: + * Excess mass is distributed evenly among newly converted neighboring interface cells (see Koerner et al., 2005). + * Falls back to EvenlyAllInterface if not applicable. + * + * - EvenlyOldInterface: + * Excess mass is distributed evenly among old neighboring interface cells, i.e., cells that are non-newly + * converted to interface. Falls back to EvenlyAllInterface if not applicable. + * + * - WeightedAllInterface: + * Excess mass is distributed weighted with the direction of the interface normal among all neighboring interface + * cells (see dissertation of N. Thuerey, 2007). Falls back to EvenlyAllInterface if not applicable. + * + * - WeightedNewInterface: + * Excess mass is distributed weighted with the direction of the interface normal among newly converted + * neighboring interface cells. Falls back to WeightedAllInterface if not applicable. + * + * - WeightedOldInterface: + * Excess mass is distributed weighted with the direction of the interface normal among old neighboring interface + * cells, i.e., cells that are non-newly converted to interface. Falls back to WeightedAllInterface if not + * applicable. + * + * - EvenlyAllInterfaceAndLiquid: + * Excess mass is distributed evenly among all neighboring interface and liquid cells (see p.47 in master thesis of + * M. Lehmann, 2019). The excess mass distributed to liquid cells does neither modify the cell's density nor fill + * level. Instead, it is stored in an additional excess mass field. Therefore, not only the converted interface + * cells' excess mass is distributed, but also the excess mass of liquid cells stored in this additional field. + * + * - EvenlyAllInterfaceFallbackLiquid: + * Similar to EvenlyAllInterfaceAndLiquid, however, excess mass is preferably distributed to interface cells. It is + * distributed to liquid cells only if there are no neighboring interface cells available. + * + * - EvenlyNewInterfaceFallbackLiquid: + * Similar to EvenlyAllInterfaceFallbackLiquid, however, excess mass is preferably distributed to newly + * converted interface cells. If there are no newly converted interface cells, the excess mass is distributed to + * old interface cells. The excess mass is distributed to neighboring liquid cells only if there are no neighboring + * interface cells available. + * ********************************************************************************************************************/ +class ExcessMassDistributionModel +{ + public: + enum class ExcessMassModel { + EvenlyAllInterface, + EvenlyNewInterface, + EvenlyOldInterface, + WeightedAllInterface, + WeightedNewInterface, + WeightedOldInterface, + EvenlyAllInterfaceAndLiquid, + EvenlyAllInterfaceFallbackLiquid, + EvenlyNewInterfaceFallbackLiquid + }; + + ExcessMassDistributionModel(const std::string& modelName) : modelName_(modelName), modelType_(chooseType(modelName)) + {} + + ExcessMassDistributionModel(const ExcessMassModel& modelType) + : modelName_(chooseName(modelType)), modelType_(modelType) + { + switch (modelType_) + { + case ExcessMassModel::EvenlyAllInterface: + break; + case ExcessMassModel::EvenlyNewInterface: + break; + case ExcessMassModel::EvenlyOldInterface: + break; + case ExcessMassModel::WeightedAllInterface: + break; + case ExcessMassModel::WeightedNewInterface: + break; + case ExcessMassModel::WeightedOldInterface: + break; + case ExcessMassModel::EvenlyAllInterfaceAndLiquid: + break; + case ExcessMassModel::EvenlyAllInterfaceFallbackLiquid: + break; + case ExcessMassModel::EvenlyNewInterfaceFallbackLiquid: + break; + } + } + + inline ExcessMassModel getModelType() const { return modelType_; } + inline std::string getModelName() const { return modelName_; } + inline std::string getFullModelSpecification() const { return getModelName(); } + + inline bool isEvenlyType() const + { + return modelType_ == ExcessMassModel::EvenlyAllInterface || modelType_ == ExcessMassModel::EvenlyNewInterface || + modelType_ == ExcessMassModel::EvenlyOldInterface; + } + + inline bool isWeightedType() const + { + return modelType_ == ExcessMassModel::WeightedAllInterface || + modelType_ == ExcessMassModel::WeightedNewInterface || modelType_ == ExcessMassModel::WeightedOldInterface; + } + + inline bool isEvenlyAllInterfaceFallbackLiquidType() const + { + return modelType_ == ExcessMassModel::EvenlyAllInterfaceAndLiquid || + modelType_ == ExcessMassModel::EvenlyAllInterfaceFallbackLiquid || + modelType_ == ExcessMassModel::EvenlyNewInterfaceFallbackLiquid; + ; + } + + static inline std::initializer_list< const ExcessMassModel > getTypeIterator() { return listOfAllEnums; } + + private: + ExcessMassModel chooseType(const std::string& modelName) + { + if (!string_icompare(modelName, "EvenlyAllInterface")) { return ExcessMassModel::EvenlyAllInterface; } + + if (!string_icompare(modelName, "EvenlyNewInterface")) { return ExcessMassModel::EvenlyNewInterface; } + + if (!string_icompare(modelName, "EvenlyOldInterface")) { return ExcessMassModel::EvenlyOldInterface; } + + if (!string_icompare(modelName, "WeightedAllInterface")) { return ExcessMassModel::WeightedAllInterface; } + + if (!string_icompare(modelName, "WeightedNewInterface")) { return ExcessMassModel::WeightedNewInterface; } + + if (!string_icompare(modelName, "WeightedOldInterface")) { return ExcessMassModel::WeightedOldInterface; } + + if (!string_icompare(modelName, "EvenlyAllInterfaceAndLiquid")) + { + return ExcessMassModel::EvenlyAllInterfaceAndLiquid; + } + + if (!string_icompare(modelName, "EvenlyAllInterfaceFallbackLiquid")) + { + return ExcessMassModel::EvenlyAllInterfaceFallbackLiquid; + } + + if (!string_icompare(modelName, "EvenlyNewInterfaceFallbackLiquid")) + { + return ExcessMassModel::EvenlyNewInterfaceFallbackLiquid; + } + + WALBERLA_ABORT("The specified PDF reinitialization model " << modelName << " is not available."); + } + + std::string chooseName(ExcessMassModel const& modelType) const + { + std::string modelName; + switch (modelType) + { + case ExcessMassModel::EvenlyAllInterface: + modelName = "EvenlyAllInterface"; + break; + case ExcessMassModel::EvenlyNewInterface: + modelName = "EvenlyNewInterface"; + break; + case ExcessMassModel::EvenlyOldInterface: + modelName = "EvenlyOldInterface"; + break; + case ExcessMassModel::WeightedAllInterface: + modelName = "WeightedAllInterface"; + break; + case ExcessMassModel::WeightedNewInterface: + modelName = "WeightedNewInterface"; + break; + case ExcessMassModel::WeightedOldInterface: + modelName = "WeightedOldInterface"; + break; + + case ExcessMassModel::EvenlyAllInterfaceAndLiquid: + modelName = "EvenlyAllInterfaceAndLiquid"; + break; + case ExcessMassModel::EvenlyAllInterfaceFallbackLiquid: + modelName = "EvenlyAllInterfaceFallbackLiquid"; + break; + case ExcessMassModel::EvenlyNewInterfaceFallbackLiquid: + modelName = "EvenlyNewInterfaceFallbackLiquid"; + break; + } + return modelName; + } + + std::string modelName_; + ExcessMassModel modelType_; + static constexpr std::initializer_list< const ExcessMassModel > listOfAllEnums = { + ExcessMassModel::EvenlyAllInterface, + ExcessMassModel::EvenlyNewInterface, + ExcessMassModel::EvenlyOldInterface, + ExcessMassModel::WeightedAllInterface, + ExcessMassModel::WeightedNewInterface, + ExcessMassModel::WeightedOldInterface, + ExcessMassModel::EvenlyAllInterfaceAndLiquid, + ExcessMassModel::EvenlyAllInterfaceFallbackLiquid, + ExcessMassModel::EvenlyNewInterfaceFallbackLiquid + }; + +}; // class ExcessMassDistributionModel +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionSweep.h b/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..b7fd343845d36eeeb9258abb0e106da429915390 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionSweep.h @@ -0,0 +1,213 @@ +//====================================================================================================================== +// +// 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 ExcessMassDistributionSweep.h +//! \ingroup dynamics +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Distribute excess mass, i.e., mass that is undistributed after conversions from interface to liquid or gas. +// +//====================================================================================================================== + +#pragma once + +#include "core/logging/Logging.h" + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FieldClone.h" +#include "field/FlagField.h" +#include "field/GhostLayerField.h" + +#include "lbm_generated/field/PdfField.h" +#include "lbm_generated/free_surface/FlagInfo.h" + +#include "ExcessMassDistributionModel.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Distribute excess mass, i.e., mass that is undistributed after cells have been converted from interface to + * gas/liquid. For example, when converting an interface cell with fill level 1.1 to liquid with fill level 1.0, an + * excessive mass corresponding to the fill level 0.1 must be distributed to conserve mass. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class ExcessMassDistributionSweepBase +{ + public: + ExcessMassDistributionSweepBase(const ExcessMassDistributionModel& excessMassDistributionModel, + BlockDataID fillFieldID, ConstBlockDataID flagFieldID, ConstBlockDataID pdfFieldID, + const FlagInfo< FlagField_T >& flagInfo) + : excessMassDistributionModel_(excessMassDistributionModel), fillFieldID_(fillFieldID), flagFieldID_(flagFieldID), + pdfFieldID_(pdfFieldID), flagInfo_(flagInfo) + {} + + virtual void operator()(IBlock* const block) = 0; + + virtual ~ExcessMassDistributionSweepBase() = default; + + protected: + /******************************************************************************************************************** + * Determines the number of a cell's + * - neighboring newly-converted interface cells + * - neighboring interface cells (regardless if newly converted or not) + *******************************************************************************************************************/ + void getNumberOfInterfaceNeighbors(const FlagField_T* flagField, const Cell& cell, uint_t& newInterfaceNeighbors, + uint_t& interfaceNeighbors); + + /******************************************************************************************************************** + * Determines the number of a cell's neighboring liquid and interface cells. + *******************************************************************************************************************/ + void getNumberOfLiquidAndInterfaceNeighbors(const FlagField_T* flagField, const Cell& cell, uint_t& liquidNeighbors, + uint_t& interfaceNeighbors, uint_t& newInterfaceNeighbors); + + ExcessMassDistributionModel excessMassDistributionModel_; + BlockDataID fillFieldID_; + ConstBlockDataID flagFieldID_; + ConstBlockDataID pdfFieldID_; + FlagInfo< FlagField_T > flagInfo_; +}; // class ExcessMassDistributionSweep + +/*********************************************************************************************************************** + * Distribute the excess mass evenly among either + * - all neighboring interface cells (see dissertations of T. Pohl, S. Donath, S. Bogner). + * - newly converted interface cells (see Koerner et al., 2005) + * - old, i.e., non-newly converted interface cells + * + * If either no newly converted interface cell or old interface cell is available in the neighborhood, the + * respective other approach is used as fallback. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class ExcessMassDistributionSweepInterfaceEvenly + : public ExcessMassDistributionSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T > +{ + public: + using ExcessMassDistributionSweepBase_T = + ExcessMassDistributionSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >; + + ExcessMassDistributionSweepInterfaceEvenly(const ExcessMassDistributionModel& excessMassDistributionModel, + BlockDataID fillFieldID, ConstBlockDataID flagFieldID, + ConstBlockDataID pdfFieldID, const FlagInfo< FlagField_T >& flagInfo) + : ExcessMassDistributionSweepBase_T(excessMassDistributionModel, fillFieldID, flagFieldID, pdfFieldID, flagInfo) + {} + + ~ExcessMassDistributionSweepInterfaceEvenly() override = default; + + void operator()(IBlock* const block) override; + + private: + template< typename PdfField_T > + void distributeMassEvenly(ScalarField_T* fillField, const FlagField_T* flagField, const PdfField_T* pdfField, + const Cell& cell, real_t excessFill); +}; // class ExcessMassDistributionSweepInterfaceEvenly + +/*********************************************************************************************************************** + * Distribute the excess mass weighted with the direction of the interface normal among either + * - all neighboring interface cells (see section 4.3 in dissertation of N. Thuerey, 2007) + * - newly converted interface cells + * - old, i.e., non-newly converted interface cells + * + * If either no newly converted interface cell or old interface cell is available in the neighborhood, the + * respective other approach is used as fallback. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class ExcessMassDistributionSweepInterfaceWeighted + : public ExcessMassDistributionSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T > +{ + public: + using ExcessMassDistributionSweepBase_T = + ExcessMassDistributionSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >; + + ExcessMassDistributionSweepInterfaceWeighted(const ExcessMassDistributionModel& excessMassDistributionModel, + BlockDataID fillFieldID, ConstBlockDataID flagFieldID, + ConstBlockDataID pdfFieldID, const FlagInfo< FlagField_T >& flagInfo, + ConstBlockDataID normalFieldID) + : ExcessMassDistributionSweepBase_T(excessMassDistributionModel, fillFieldID, flagFieldID, pdfFieldID, flagInfo), + normalFieldID_(normalFieldID) + {} + + ~ExcessMassDistributionSweepInterfaceWeighted() override = default; + + void operator()(IBlock* const block) override; + + private: + template< typename PdfField_T > + void distributeMassWeighted(ScalarField_T* fillField, const FlagField_T* flagField, const PdfField_T* pdfField, + const VectorField_T* normalField, const Cell& cell, bool isNewLiquid, real_t excessFill); + + /******************************************************************************************************************** + * Returns vector with weights for excess mass distribution among neighboring cells. + *******************************************************************************************************************/ + void getExcessMassWeights(const FlagField_T* flagField, const VectorField_T* normalField, const Cell& cell, + bool isNewLiquid, bool useWeightedOld, bool useWeightedAll, bool useWeightedNew, + std::vector< real_t >& weights); + + /******************************************************************************************************************** + * Computes the weights for distributing the excess mass based on the direction of the interface normal (see equation + * (4.9) in dissertation of N. Thuerey, 2007) + *******************************************************************************************************************/ + void computeWeightWithNormal(real_t n_dot_ci, bool isNewLiquid, typename StorageSpecification_T::Stencil::iterator dir, + std::vector< real_t >& weights); + + ConstBlockDataID normalFieldID_; + +}; // class ExcessMassDistributionSweepInterfaceWeighted + +/*********************************************************************************************************************** + * Distribute the excess mass evenly among + * - all neighboring liquid and interface cells (see p. 47 in master thesis of M. Lehmann, 2019) + * - all neighboring interface cells and only to liquid cells if there exists no neighboring interface cell + * - new neighboring interface cells, if not available to old interface cells and only to liquid cells if there exists + * no neighboring interface cell + * + * Neither the fill level, nor the density of liquid cells is modified. Instead, the excess mass is stored in an + * additional field. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class ExcessMassDistributionSweepInterfaceAndLiquid + : public ExcessMassDistributionSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T > +{ + public: + using ExcessMassDistributionSweepBase_T = + ExcessMassDistributionSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >; + + ExcessMassDistributionSweepInterfaceAndLiquid(const ExcessMassDistributionModel& excessMassDistributionModel, + BlockDataID fillFieldID, ConstBlockDataID flagFieldID, + ConstBlockDataID pdfFieldID, const FlagInfo< FlagField_T >& flagInfo, + BlockDataID excessMassFieldID) + : ExcessMassDistributionSweepBase_T(excessMassDistributionModel, fillFieldID, flagFieldID, pdfFieldID, flagInfo), + excessMassFieldID_(excessMassFieldID), excessMassFieldClone_(excessMassFieldID) + {} + + ~ExcessMassDistributionSweepInterfaceAndLiquid() override = default; + + void operator()(IBlock* const block) override; + + private: + template< typename PdfField_T > + void distributeMassInterfaceAndLiquid(ScalarField_T* fillField, ScalarField_T* dstExcessMassField, + const FlagField_T* flagField, const PdfField_T* pdfField, const Cell& cell, + real_t excessMass); + + BlockDataID excessMassFieldID_; + field::FieldClone< ScalarField_T, true > excessMassFieldClone_; + +}; // class ExcessMassDistributionSweepInterfaceAndLiquid + +} // namespace free_surface +} // namespace walberla + +#include "ExcessMassDistributionSweep.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionSweep.impl.h b/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionSweep.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..8a8db33b232c6ff79c5d5fde83a839455c70fcf4 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/ExcessMassDistributionSweep.impl.h @@ -0,0 +1,638 @@ +//====================================================================================================================== +// +// 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 ExcessMassDistributionSweep.impl.h +//! \ingroup dynamics +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Distribute excess mass, i.e., mass that is undistributed after conversions from interface to liquid or gas. +// +//====================================================================================================================== + +#pragma once + +#include "core/logging/Logging.h" + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FlagField.h" +#include "field/GhostLayerField.h" + +#include "lbm_generated/field/PdfField.h" +#include "lbm_generated/free_surface/FlagInfo.h" + +#include "ExcessMassDistributionSweep.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExcessMassDistributionSweepInterfaceEvenly< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T >::operator()(IBlock* const block) +{ + using Base_T = ExcessMassDistributionSweepBase_T; + + const FlagField_T* const flagField = block->getData< const FlagField_T >(Base_T::flagFieldID_); + ScalarField_T* const fillField = block->getData< ScalarField_T >(Base_T::fillFieldID_); + const lbm_generated::PdfField< StorageSpecification_T >* const pdfField = + block->getData< const lbm_generated::PdfField< StorageSpecification_T > >(Base_T::pdfFieldID_); + + // disable OpenMP to avoid mass distribution to neighboring cells before they have distributed their excess mass + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP(fillField, uint_c(1), omp critical, { + const Cell cell(x, y, z); + + if (flagField->isFlagSet(cell, Base_T::flagInfo_.convertedFlag)) + { + // identify cells that were converted to gas/liquid in this time step + const bool newGas = flagField->isMaskSet(cell, Base_T::flagInfo_.convertedFlag | Base_T::flagInfo_.gasFlag); + const bool newLiquid = + flagField->isMaskSet(cell, Base_T::flagInfo_.convertedFlag | Base_T::flagInfo_.liquidFlag); + + if (newGas || newLiquid) + { + // a cell can not be converted to both gas and liquid + WALBERLA_ASSERT(!(newGas && newLiquid)); + + // calculate excess fill level + const real_t excessFill = newGas ? fillField->get(cell) : (fillField->get(cell) - real_c(1.0)); + + distributeMassEvenly(fillField, flagField, pdfField, cell, excessFill); + + if (newGas) { fillField->get(cell) = real_c(0.0); } + else { fillField->get(cell) = real_c(1.0); } + } + } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExcessMassDistributionSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + getNumberOfLiquidAndInterfaceNeighbors(const FlagField_T* flagField, const Cell& cell, uint_t& liquidNeighbors, + uint_t& interfaceNeighbors, uint_t& newInterfaceNeighbors) +{ + newInterfaceNeighbors = uint_c(0); + interfaceNeighbors = uint_c(0); + liquidNeighbors = uint_c(0); + + for (auto d = StorageSpecification_T::Stencil::beginNoCenter(); d != StorageSpecification_T::Stencil::end(); ++d) + { + const Cell neighborCell = Cell(cell.x() + d.cx(), cell.y() + d.cy(), cell.z() + d.cz()); + auto neighborFlags = flagField->get(neighborCell); + + if (isFlagSet(neighborFlags, flagInfo_.interfaceFlag)) + { + ++interfaceNeighbors; + if (isFlagSet(neighborFlags, flagInfo_.convertedFlag)) { ++newInterfaceNeighbors; } + } + else + { + if (isFlagSet(neighborFlags, flagInfo_.liquidFlag)) { ++liquidNeighbors; } + } + } +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExcessMassDistributionSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T >::getNumberOfInterfaceNeighbors(const FlagField_T* flagField, + const Cell& cell, + uint_t& newInterfaceNeighbors, + uint_t& interfaceNeighbors) +{ + interfaceNeighbors = uint_c(0); + newInterfaceNeighbors = uint_c(0); + + for (auto d = StorageSpecification_T::Stencil::beginNoCenter(); d != StorageSpecification_T::Stencil::end(); ++d) + { + const Cell neighborCell = Cell(cell.x() + d.cx(), cell.y() + d.cy(), cell.z() + d.cz()); + auto neighborFlags = flagField->get(neighborCell); + + if (isFlagSet(neighborFlags, flagInfo_.interfaceFlag)) + { + ++interfaceNeighbors; + if (isFlagSet(neighborFlags, flagInfo_.convertedFlag)) { ++newInterfaceNeighbors; } + } + } +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +template< typename PdfField_T > +void ExcessMassDistributionSweepInterfaceEvenly< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T >::distributeMassEvenly(ScalarField_T* fillField, + const FlagField_T* flagField, + const PdfField_T* pdfField, + const Cell& cell, + real_t excessFill) +{ + using Base_T = ExcessMassDistributionSweepBase_T; + + bool useEvenlyAll = Base_T::excessMassDistributionModel_.getModelType() == + ExcessMassDistributionModel::ExcessMassModel::EvenlyAllInterface; + bool useEvenlyNew = Base_T::excessMassDistributionModel_.getModelType() == + ExcessMassDistributionModel::ExcessMassModel::EvenlyNewInterface; + bool useEvenlyOld = Base_T::excessMassDistributionModel_.getModelType() == + ExcessMassDistributionModel::ExcessMassModel::EvenlyOldInterface; + + // get number of interface neighbors + uint_t newInterfaceNeighbors = uint_c(0); + uint_t interfaceNeighbors = uint_c(0); + Base_T::getNumberOfInterfaceNeighbors(flagField, cell, newInterfaceNeighbors, interfaceNeighbors); + const uint_t oldInterfaceNeighbors = interfaceNeighbors - newInterfaceNeighbors; + + if (interfaceNeighbors == uint_c(0)) + { + WALBERLA_LOG_WARNING( + "No interface cell is in the neighborhood to distribute excess mass to. Mass is lost/gained."); + return; + } + + // get density of the current cell + // TODO Implement + const real_t density = real_c(1.0); //pdfField->getDensity(cell); + + // compute mass to be distributed to neighboring cells + real_t deltaMass = real_c(0); + if ((useEvenlyOld && oldInterfaceNeighbors > uint_c(0)) || newInterfaceNeighbors == uint_c(0)) + { + useEvenlyOld = true; + useEvenlyAll = false; + useEvenlyNew = false; + + deltaMass = excessFill / real_c(oldInterfaceNeighbors) * density; + } + else + { + if (useEvenlyNew || oldInterfaceNeighbors == uint_c(0)) + { + useEvenlyOld = false; + useEvenlyAll = false; + useEvenlyNew = true; + + deltaMass = excessFill / real_c(newInterfaceNeighbors) * density; + } + else + { + useEvenlyOld = false; + useEvenlyAll = true; + useEvenlyNew = false; + + deltaMass = excessFill / real_c(interfaceNeighbors) * density; + } + } + + // distribute the excess mass + for (auto pushDir = StorageSpecification_T::Stencil::beginNoCenter(); pushDir != StorageSpecification_T::Stencil::end(); ++pushDir) + { + const Cell neighborCell = Cell(cell.x() + pushDir.cx(), cell.y() + pushDir.cy(), cell.z() + pushDir.cz()); + + // do not push mass in the direction of the second ghost layer + // - inner domain: 0 to *Size()-1 + // - inner domain incl. first ghost layer: -1 to *Size() + if (neighborCell.x() < cell_idx_c(-1) || neighborCell.y() < cell_idx_c(-1) || neighborCell.z() < cell_idx_c(-1) || + neighborCell.x() > cell_idx_c(fillField->xSize()) || neighborCell.y() > cell_idx_c(fillField->ySize()) || + neighborCell.z() > cell_idx_c(fillField->zSize())) + { + continue; + } + + // only push mass to neighboring interface cells + if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.interfaceFlag)) + { + // get density of neighboring interface cell + //TODO Implement + const real_t neighborDensity = real_c(1.0); //pdfField->getDensity(neighborCell); + + if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.convertedFlag) && (useEvenlyAll || useEvenlyNew)) + { + // push mass to newly converted interface cell + fillField->get(neighborCell) += deltaMass / neighborDensity; + } + else + { + if (!flagField->isFlagSet(neighborCell, Base_T::flagInfo_.convertedFlag) && (useEvenlyOld || useEvenlyAll)) + { + // push mass to old, i.e., non-newly converted interface cells + fillField->getNeighbor(cell, *pushDir) += deltaMass / neighborDensity; + } + } + } + } +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExcessMassDistributionSweepInterfaceWeighted< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T >::operator()(IBlock* const block) +{ + using Base_T = ExcessMassDistributionSweepBase_T; + + const FlagField_T* const flagField = block->getData< const FlagField_T >(Base_T::flagFieldID_); + ScalarField_T* const fillField = block->getData< ScalarField_T >(Base_T::fillFieldID_); + const lbm_generated::PdfField< StorageSpecification_T >* const pdfField = + block->getData< const lbm_generated::PdfField< StorageSpecification_T > >(Base_T::pdfFieldID_); + const VectorField_T* const normalField = block->getData< const VectorField_T >(normalFieldID_); + + // disable OpenMP to avoid mass distribution to neighboring cells before they have distributed their excess mass + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP(fillField, uint_c(1), omp critical, { + const Cell cell(x, y, z); + + if (flagField->isFlagSet(cell, Base_T::flagInfo_.convertedFlag)) + { + // identify cells that were converted to gas/liquid in this time step + const bool newGas = flagField->isMaskSet(cell, Base_T::flagInfo_.convertedFlag | Base_T::flagInfo_.gasFlag); + const bool newLiquid = + flagField->isMaskSet(cell, Base_T::flagInfo_.convertedFlag | Base_T::flagInfo_.liquidFlag); + + if (newGas || newLiquid) + { + // a cell can not be converted to both gas and liquid + WALBERLA_ASSERT(!(newGas && newLiquid)); + + // calculate excess fill level + const real_t excessFill = newGas ? fillField->get(cell) : (fillField->get(cell) - real_c(1.0)); + + distributeMassWeighted(fillField, flagField, pdfField, normalField, cell, newLiquid, excessFill); + + if (newGas) { fillField->get(cell) = real_c(0.0); } + else { fillField->get(cell) = real_c(1.0); } + } + } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +template< typename PdfField_T > +void ExcessMassDistributionSweepInterfaceWeighted< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + distributeMassWeighted(ScalarField_T* fillField, const FlagField_T* flagField, const PdfField_T* pdfField, + const VectorField_T* normalField, const Cell& cell, bool isNewLiquid, real_t excessFill) +{ + using Base_T = ExcessMassDistributionSweepBase_T; + + bool useWeightedAll = Base_T::excessMassDistributionModel_.getModelType() == + ExcessMassDistributionModel::ExcessMassModel::WeightedAllInterface; + bool useWeightedNew = Base_T::excessMassDistributionModel_.getModelType() == + ExcessMassDistributionModel::ExcessMassModel::WeightedNewInterface; + bool useWeightedOld = Base_T::excessMassDistributionModel_.getModelType() == + ExcessMassDistributionModel::ExcessMassModel::WeightedOldInterface; + + // get number of interface neighbors + uint_t newInterfaceNeighbors = uint_c(0); + uint_t interfaceNeighbors = uint_c(0); + Base_T::getNumberOfInterfaceNeighbors(flagField, cell, newInterfaceNeighbors, interfaceNeighbors); + const uint_t oldInterfaceNeighbors = interfaceNeighbors - newInterfaceNeighbors; + + if (interfaceNeighbors == uint_c(0)) + { + WALBERLA_LOG_WARNING( + "No interface cell is in the neighborhood to distribute excess mass to. Mass is lost/gained."); + return; + } + + // check applicability of the chosen model + if ((useWeightedOld && oldInterfaceNeighbors > uint_c(0)) || newInterfaceNeighbors == uint_c(0)) + { + useWeightedOld = true; + useWeightedAll = false; + useWeightedNew = false; + } + else + { + if (useWeightedNew || oldInterfaceNeighbors == uint_c(0)) + { + useWeightedOld = false; + useWeightedAll = false; + useWeightedNew = true; + } + else + { + useWeightedOld = false; + useWeightedAll = true; + useWeightedNew = false; + } + } + + // get normal-direction-based weights of the excess mass + std::vector< real_t > weights(StorageSpecification_T::Stencil::Size, real_c(0)); + getExcessMassWeights(flagField, normalField, cell, isNewLiquid, useWeightedOld, useWeightedAll, useWeightedNew, + weights); + + // get the sum of all weights + real_t weightSum = real_c(0); + for (const auto& w : weights) + { + weightSum += w; + } + + // if there are either no old or no newly converted interface cells in normal direction, distribute mass to whatever + // interface cell is available in normal direction + if (realIsEqual(weightSum, real_c(0), real_c(1e-14)) && (useWeightedOld || useWeightedNew)) + { + useWeightedOld = false; + useWeightedAll = true; + useWeightedNew = false; + + // recompute mass weights since other type of interface cells are now considered also + getExcessMassWeights(flagField, normalField, cell, isNewLiquid, useWeightedOld, useWeightedAll, useWeightedNew, + weights); + + // update sum of all weights + for (const auto& w : weights) + { + weightSum += w; + } + + // if no interface cell is available in normal direction, distribute mass evenly to all neighboring interface + // cells + if (realIsEqual(weightSum, real_c(0), real_c(1e-14))) + { + WALBERLA_LOG_WARNING_ON_ROOT( + "Excess mass can not be distributed with a weighted approach since no interface cell is available in " + "normal direction. Distributing excess mass evenly among all surrounding interface cells."); + + // manually set weights to 1 to get equal weight in any direction + for (auto& w : weights) + { + w = real_c(1); + } + + // weight sum is now the number of neighboring interface cells + weightSum = real_c(interfaceNeighbors); + } + } + + WALBERLA_ASSERT_GREATER( + weightSum, real_c(0), + "Sum of all weights is zero in ExcessMassDistribution. This means that no neighboring interface cell is " + "available for distributing the excess mass to. This error should have been caught earlier."); + + // TODO Implement + const real_t excessMass = excessFill * real_c(1.0); //pdfField->getDensity(cell); + + // distribute the excess mass + for (auto pushDir = StorageSpecification_T::Stencil::beginNoCenter(); pushDir != StorageSpecification_T::Stencil::end(); ++pushDir) + { + const Cell neighborCell = Cell(cell.x() + pushDir.cx(), cell.y() + pushDir.cy(), cell.z() + pushDir.cz()); + + // do not push mass in the direction of the second ghost layer + // - inner domain: 0 to *Size()-1 + // - inner domain incl. first ghost layer: -1 to *Size() + if (neighborCell.x() < cell_idx_c(-1) || neighborCell.y() < cell_idx_c(-1) || neighborCell.z() < cell_idx_c(-1) || + neighborCell.x() > cell_idx_c(fillField->xSize()) || neighborCell.y() > cell_idx_c(fillField->ySize()) || + neighborCell.z() > cell_idx_c(fillField->zSize())) + { + continue; + } + + // only push mass to neighboring interface cells + if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.interfaceFlag)) + { + // get density of neighboring interface cell + // TODO Implement + const real_t neighborDensity = real_c(1.0); // pdfField->getDensity(neighborCell); + + if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.convertedFlag) && (useWeightedAll || useWeightedNew)) + { + // push mass to newly converted interface cell + const real_t deltaMass = excessMass * weights[pushDir.toIdx()] / weightSum; + fillField->get(neighborCell) += deltaMass / neighborDensity; + } + else + { + if (!flagField->isFlagSet(neighborCell, Base_T::flagInfo_.convertedFlag) && + (useWeightedOld || useWeightedAll)) + { + // push mass to old, i.e., non-newly converted interface cells + const real_t deltaMass = excessMass * weights[pushDir.toIdx()] / weightSum; + fillField->getNeighbor(cell, *pushDir) += deltaMass / neighborDensity; + } + } + } + } +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExcessMassDistributionSweepInterfaceWeighted< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + getExcessMassWeights(const FlagField_T* flagField, const VectorField_T* normalField, const Cell& cell, + bool isNewLiquid, bool useWeightedOld, bool useWeightedAll, bool useWeightedNew, + std::vector< real_t >& weights) +{ + using Base_T = ExcessMassDistributionSweepBase_T; + + // iterate all neighboring cells + for (auto d = StorageSpecification_T::Stencil::beginNoCenter(); d != StorageSpecification_T::Stencil::end(); ++d) + { + const Cell neighborCell = Cell(cell.x() + d.cx(), cell.y() + d.cy(), cell.z() + d.cz()); + auto neighborFlags = flagField->get(neighborCell); + + if (isFlagSet(neighborFlags, Base_T::flagInfo_.interfaceFlag)) + { + // compute dot product of normal direction and lattice direction to neighboring cell + const real_t n_dot_ci = + Vector3(normalField->get(cell, 0), normalField->get(cell, 1), normalField->get(cell, 2)) * Vector3< real_t >(real_c(d.cx()), real_c(d.cy()), real_c(d.cz())); + + if (useWeightedAll || (useWeightedOld && !isFlagSet(neighborFlags, Base_T::flagInfo_.convertedFlag))) + { + computeWeightWithNormal(n_dot_ci, isNewLiquid, d, weights); + } + else + { + if (useWeightedNew && isFlagSet(neighborFlags, Base_T::flagInfo_.convertedFlag)) + { + computeWeightWithNormal(n_dot_ci, isNewLiquid, d, weights); + } + else { weights[d.toIdx()] = real_c(0); } + } + } + else + { + // no interface cell in this direction, weight is zero + weights[d.toIdx()] = real_c(0); + } + } +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExcessMassDistributionSweepInterfaceWeighted< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + computeWeightWithNormal(real_t n_dot_ci, bool isNewLiquid, typename StorageSpecification_T::Stencil::iterator dir, + std::vector< real_t >& weights) +{ + // dissertation of N. Thuerey, 2007, equation (4.9) + if (isNewLiquid) + { + if (n_dot_ci > real_c(0)) { weights[dir.toIdx()] = n_dot_ci; } + else { weights[dir.toIdx()] = real_c(0); } + } + else // cell was converted from interface to gas + { + if (n_dot_ci < real_c(0)) { weights[dir.toIdx()] = -n_dot_ci; } + else { weights[dir.toIdx()] = real_c(0); } + } +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExcessMassDistributionSweepInterfaceAndLiquid< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T >::operator()(IBlock* const block) +{ + using Base_T = ExcessMassDistributionSweepBase_T; + + const FlagField_T* const flagField = block->getData< const FlagField_T >(Base_T::flagFieldID_); + ScalarField_T* const fillField = block->getData< ScalarField_T >(Base_T::fillFieldID_); + const lbm_generated::PdfField< StorageSpecification_T >* const pdfField = + block->getData< const lbm_generated::PdfField< StorageSpecification_T > >(Base_T::pdfFieldID_); + + ScalarField_T* const srcExcessMassField = block->getData< ScalarField_T >(excessMassFieldID_); + ScalarField_T* const dstExcessMassField = excessMassFieldClone_.get(block); + + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(dstExcessMassField, uint_c(1), { + dstExcessMassField->get(x, y, z) = real_c(0); + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ + + // disable OpenMP to avoid mass distribution to neighboring cells before they have distributed their excess mass + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP(fillField, uint_c(1), omp critical, { + const Cell cell(x, y, z); + + if (flagField->isFlagSet(cell, Base_T::flagInfo_.convertedFlag)) + { + // identify cells that were converted to gas/liquid in this time step + const bool newGas = flagField->isMaskSet(cell, Base_T::flagInfo_.convertedFlag | Base_T::flagInfo_.gasFlag); + const bool newLiquid = + flagField->isMaskSet(cell, Base_T::flagInfo_.convertedFlag | Base_T::flagInfo_.liquidFlag); + + if (newGas || newLiquid) + { + // a cell can not be converted to both gas and liquid + WALBERLA_ASSERT(!(newGas && newLiquid)); + + // calculate excess fill level + const real_t excessFill = newGas ? fillField->get(cell) : (fillField->get(cell) - real_c(1.0)); + + // store excess mass such that it can be distributed below (no += here because cell was an interface cell + // that can not have an excess mass stored in the field; any excess mass is added to the interface cell's + // fill level) + // TODO Implement + srcExcessMassField->get(cell) = excessFill * real_c(1.0); //pdfField->getDensity(cell); + + if (newGas) { fillField->get(cell) = real_c(0.0); } + else { fillField->get(cell) = real_c(1.0); } + } + } + + if (!realIsEqual(srcExcessMassField->get(cell), real_c(0), real_c(1e-14))) + { + distributeMassInterfaceAndLiquid(fillField, dstExcessMassField, flagField, pdfField, cell, + srcExcessMassField->get(cell)); + } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ + + srcExcessMassField->swapDataPointers(dstExcessMassField); +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +template< typename PdfField_T > +void ExcessMassDistributionSweepInterfaceAndLiquid< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + distributeMassInterfaceAndLiquid(ScalarField_T* fillField, ScalarField_T* dstExcessMassField, + const FlagField_T* flagField, const PdfField_T* pdfField, const Cell& cell, + real_t excessMass) +{ + using Base_T = ExcessMassDistributionSweepBase_T; + + // get number of liquid and interface neighbors + uint_t liquidNeighbors = uint_c(0); + uint_t interfaceNeighbors = uint_c(0); + uint_t newInterfaceNeighbors = uint_c(0); + Base_T::getNumberOfLiquidAndInterfaceNeighbors(flagField, cell, liquidNeighbors, interfaceNeighbors, + newInterfaceNeighbors); + const uint_t liquidAndInterfaceNeighbors = liquidNeighbors + interfaceNeighbors; + + if (liquidAndInterfaceNeighbors == uint_c(0)) + { + WALBERLA_LOG_WARNING( + "No liquid or interface cell is in the neighborhood to distribute excess mass to. Mass is lost/gained."); + return; + } + + // check if there are neighboring new interface cells + const bool preferNewInterface = Base_T::excessMassDistributionModel_.getModelType() == + ExcessMassDistributionModel::ExcessMassModel::EvenlyNewInterfaceFallbackLiquid && + newInterfaceNeighbors > uint_c(0); + + // check if there are neighboring interface cells + const bool preferInterface = (Base_T::excessMassDistributionModel_.getModelType() == + ExcessMassDistributionModel::ExcessMassModel::EvenlyAllInterfaceFallbackLiquid || + !preferNewInterface) && + interfaceNeighbors > uint_c(0) && + Base_T::excessMassDistributionModel_.getModelType() != + ExcessMassDistributionModel::ExcessMassModel::EvenlyAllInterfaceAndLiquid; + + // compute mass to be distributed to neighboring cells + real_t deltaMass; + if (preferNewInterface) { deltaMass = excessMass / real_c(newInterfaceNeighbors); } + else + { + if (preferInterface) { deltaMass = excessMass / real_c(interfaceNeighbors); } + else { deltaMass = excessMass / real_c(liquidAndInterfaceNeighbors); } + } + + // distribute the excess mass + for (auto pushDir = StorageSpecification_T::Stencil::beginNoCenter(); pushDir != StorageSpecification_T::Stencil::end(); ++pushDir) + { + const Cell neighborCell = Cell(cell.x() + pushDir.cx(), cell.y() + pushDir.cy(), cell.z() + pushDir.cz()); + + // do not push mass to cells in the ghost layer (done by the process from which the ghost layer is synchronized) + // - inner domain: 0 to *Size()-1 + // - inner domain incl. first ghost layer: -1 to *Size() + if (neighborCell.x() <= cell_idx_c(-1) || neighborCell.y() <= cell_idx_c(-1) || + neighborCell.z() <= cell_idx_c(-1) || neighborCell.x() >= cell_idx_c(fillField->xSize()) || + neighborCell.y() >= cell_idx_c(fillField->ySize()) || neighborCell.z() >= cell_idx_c(fillField->zSize())) + { + continue; + } + + // distribute excess mass to newly converted interface cell + if (flagField->isMaskSet(neighborCell, Base_T::flagInfo_.convertedFlag | Base_T::flagInfo_.interfaceFlag)) + { + // get density of neighboring interface cell + // TODO Implement + const real_t neighborDensity = real_c(1.0); //pdfField->getDensity(neighborCell); + + // add excess mass directly to fill level for newly converted neighboring interface cells + fillField->get(neighborCell) += deltaMass / neighborDensity; + } + else + { + // distribute excess mass to old interface cell + if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.interfaceFlag) && !preferNewInterface) + { + // get density of neighboring interface cell + // TODO Implement + const real_t neighborDensity = real_c(1.0); //pdfField->getDensity(neighborCell); + + // add excess mass directly to fill level for neighboring interface cells + fillField->get(neighborCell) += deltaMass / neighborDensity; + } + else + { + // distribute excess mass to liquid cell + if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.liquidFlag) && !preferInterface && + !preferNewInterface) + { + // add excess mass to excessMassField for neighboring liquid cells + dstExcessMassField->get(neighborCell) += deltaMass; + } + } + } + } +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/ForceDensitySweep.h b/src/lbm_generated/free_surface/dynamics/ForceDensitySweep.h new file mode 100644 index 0000000000000000000000000000000000000000..b30d6aa3c98dfff7dd569f5005e3a4505aa246de --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/ForceDensitySweep.h @@ -0,0 +1,107 @@ +//====================================================================================================================== +// +// 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 ForceDensitySweep.h +//! \ingroup dynamics +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Weight force in interface cells with fill level and density (equation 15 in Koerner et al., 2005). +// +//====================================================================================================================== + +#pragma once + +#include "core/logging/Logging.h" + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FlagField.h" +#include "field/GhostLayerField.h" + +#include "lbm_generated/field/PdfField.h" +#include "lbm_generated/free_surface/FlagInfo.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Update the force density field as in equation 15 in Koerner et al., 2005. with "acceleration * density * fill level". + * Differs from the version above by using a flattened vector field (GhostLayerField< real_t, 3 >). This is necessary + * because Pystencils does not support VectorField_T (GhostLayerField< Vector3<real_t>, 1 >). + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename VectorField_T, typename ScalarField_T > +class ForceDensityCodegenSweep +{ + public: + ForceDensityCodegenSweep(BlockDataID forceDensityFieldID, ConstBlockDataID pdfFieldID, ConstBlockDataID flagFieldID, + ConstBlockDataID fillFieldID, const FlagInfo< FlagField_T >& flagInfo, + const Vector3< real_t >& globalAcceleration) + : forceDensityFieldID_(forceDensityFieldID), pdfFieldID_(pdfFieldID), flagFieldID_(flagFieldID), + fillFieldID_(fillFieldID), flagInfo_(flagInfo), globalAcceleration_(globalAcceleration) + {} + + void operator()(IBlock* const block) + { + using PdfField_T = lbm_generated::PdfField< StorageSpecification_T >; + + VectorField_T* const forceDensityField = block->getData< VectorField_T >(forceDensityFieldID_); + const PdfField_T* const pdfField = block->getData< const PdfField_T >(pdfFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + const ScalarField_T* const fillField = block->getData< const ScalarField_T >(fillFieldID_); + + WALBERLA_FOR_ALL_CELLS(forceDensityFieldIt, forceDensityField, pdfFieldIt, pdfField, flagFieldIt, flagField, + fillFieldIt, fillField, {flag_t flag = *flagFieldIt; + + // set force density in cells to acceleration * density * fillLevel (see equation 15 + // in Koerner et al., 2005); + if (flagInfo_.isInterface(flag)) + { + // TODO do this more flexible + real_t density = real_c(0.0); + for (uint_t i = 0; i < 19; ++i) + density += pdfFieldIt.getF(i); + + forceDensityFieldIt[0] = globalAcceleration_[0] * *fillFieldIt * density; + forceDensityFieldIt[1] = globalAcceleration_[1] * *fillFieldIt * density; + forceDensityFieldIt[2] = globalAcceleration_[2] * *fillFieldIt * density; + } + else + { + if (flagInfo_.isLiquid(flag)) + { + real_t density = real_c(0.0); + for (uint_t i = 0; i < 19; ++i) + density += pdfFieldIt.getF(i); + forceDensityFieldIt[0] = globalAcceleration_[0] * density; + forceDensityFieldIt[1] = globalAcceleration_[1] * density; + forceDensityFieldIt[2] = globalAcceleration_[2] * density; + } + } + }) // WALBERLA_FOR_ALL_CELLS + } + + private: + using flag_t = typename FlagField_T::flag_t; + + BlockDataID forceDensityFieldID_; + ConstBlockDataID pdfFieldID_; + ConstBlockDataID flagFieldID_; + ConstBlockDataID fillFieldID_; + FlagInfo< FlagField_T > flagInfo_; + Vector3< real_t > globalAcceleration_; +}; // class ForceDensitySweep + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/PdfReconstructionModel.h b/src/lbm_generated/free_surface/dynamics/PdfReconstructionModel.h new file mode 100644 index 0000000000000000000000000000000000000000..b782c101e4422b6958a73a4ba2c56560dc6d0f59 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/PdfReconstructionModel.h @@ -0,0 +1,173 @@ +//====================================================================================================================== +// +// 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 PdfReconstructionModel.h +//! \ingroup dynamics +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Class that specifies the number of reconstructed PDFs at the free surface interface. +// +//====================================================================================================================== + +#pragma once + +#include "core/StringUtility.h" +#include "core/stringToNum.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Class that specifies the number of reconstructed PDFs at the free surface interface. PDFs need to be reconstructed + * as they might be missing, i.e., PDFs streaming from gas to interface are not available inherently. + * + * Available models: + * - NormalBasedKeepCenter: reconstruct all PDFs for which n * c_i >= 0 (approach by Koerner et al., 2005); some + * already available PDFs will be overwritten + * + * - NormalBasedReconstructCenter: reconstruct all PDFs for which n * c_i >= 0 (including the center PDF); some already + * available PDFs coming from liquid will be overwritten + * + * - OnlyMissing: reconstruct only missing PDFs (no already available PDF gets overwritten) + * + * - All: reconstruct all PDFs (any already available PDF is overwritten) + * + * - OnlyMissingMin-N-largest: Reconstruct only missing PDFs but at least N (and therefore potentially overwrite + * available PDFs); "smallest" or "largest" specifies whether PDFs with smallest or largest + * n * c_i get overwritten first. This model is motivated by the dissertation of Simon + * Bogner, 2017, section 4.2.1, where it is argued that at least 3 PDFs must be + * reconstructed, as otherwise the free surface boundary condition is claimed to be + * underdetermined. However, a mathematical proof for this statement is not given. + * + * - OnlyMissingMin-N-smallest: see comment at "OnlyMissingMin-N-largest" + * + * - OnlyMissingMin-N-normalBasedKeepCenter: see comment at "OnlyMissingMin-N-largest"; if less than N PDFs are unknown, + * reconstruct according to the model "NormalBasedKeepCenter" + * ********************************************************************************************************************/ +class PdfReconstructionModel +{ + public: + enum class ReconstructionModel { + NormalBasedReconstructCenter, + NormalBasedKeepCenter, + OnlyMissing, + All, + OnlyMissingMin, + }; + + enum class FallbackModel { + Largest, + Smallest, + NormalBasedKeepCenter, + }; + + PdfReconstructionModel(const std::string& modelName) : modelName_(modelName), modelType_(chooseType(modelName)) + { + if (modelType_ == ReconstructionModel::OnlyMissingMin) + { + const std::vector< std::string > substrings = string_split(modelName, "-"); + + modelName_ = substrings[0]; // "OnlyMissingMin" + numMinReconstruct_ = stringToNum< uint_t >(substrings[1]); // N + fallbackModelName_ = substrings[2]; // "smallest" or "largest" or "normalBasedKeepCenter" + fallbackModel_ = chooseFallbackModel(fallbackModelName_); + + if (fallbackModel_ != FallbackModel::Largest && fallbackModel_ != FallbackModel::Smallest && + fallbackModel_ != FallbackModel::NormalBasedKeepCenter) + { + WALBERLA_ABORT("The specified PDF reconstruction fallback-model " << modelName << " is not available."); + } + } + } + + inline ReconstructionModel getModelType() const { return modelType_; } + inline std::string getModelName() const { return modelName_; } + inline uint_t getNumMinReconstruct() const { return numMinReconstruct_; } + inline FallbackModel getFallbackModel() const { return fallbackModel_; } + inline std::string getFallbackModelName() const { return fallbackModelName_; } + inline std::string getFullModelSpecification() const + { + if (modelType_ == ReconstructionModel::OnlyMissingMin) + { + return modelName_ + "-" + std::to_string(numMinReconstruct_) + "-" + fallbackModelName_; + } + else { return modelName_; } + } + + private: + ReconstructionModel chooseType(const std::string& modelName) + { + if (!string_icompare(modelName, "NormalBasedReconstructCenter")) + { + return ReconstructionModel::NormalBasedReconstructCenter; + } + + else + { + if (!string_icompare(modelName, "NormalBasedKeepCenter")) + { + return ReconstructionModel::NormalBasedKeepCenter; + } + else + { + if (!string_icompare(modelName, "OnlyMissing")) { return ReconstructionModel::OnlyMissing; } + else + { + if (!string_icompare(modelName, "All")) { return ReconstructionModel::All; } + else + { + if (!string_icompare(string_split(modelName, "-")[0], "OnlyMissingMin")) + { + return ReconstructionModel::OnlyMissingMin; + } + else + { + WALBERLA_ABORT("The specified PDF reconstruction model " << modelName << " is not available."); + } + } + } + } + } + } + + FallbackModel chooseFallbackModel(const std::string& fallbackModelName) + { + if (!string_icompare(fallbackModelName, "largest")) { return FallbackModel::Largest; } + else + { + if (!string_icompare(fallbackModelName, "smallest")) { return FallbackModel::Smallest; } + else + { + if (!string_icompare(fallbackModelName, "normalBasedKeepCenter")) + { + return FallbackModel::NormalBasedKeepCenter; + } + else + { + WALBERLA_ABORT("The specified PDF reconstruction fallback-model " << fallbackModelName + << " is not available."); + } + } + } + } + + std::string modelName_; + ReconstructionModel modelType_; + uint_t numMinReconstruct_; + std::string fallbackModelName_; + FallbackModel fallbackModel_; +}; // class PdfReconstructionModel +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/PdfRefillingModel.h b/src/lbm_generated/free_surface/dynamics/PdfRefillingModel.h new file mode 100644 index 0000000000000000000000000000000000000000..0215d4b33c92977a249fca76f31fd73d26fbe27b --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/PdfRefillingModel.h @@ -0,0 +1,147 @@ +//====================================================================================================================== +// +// 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 PdfRefillingModel.h +//! \ingroup dynamics +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \author Michael Zikeli +//! \brief Defines how cells are refilled (i.e. PDFs reinitialized) after the cell was converted from gas to interface. +// +//====================================================================================================================== + +#pragma once + +#include "core/StringUtility.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Class that specifies how PDFs are reinitialized in cells that are converted from gas to interface. + * + * Available options are: + * - EquilibriumRefilling: + * initialize PDFs with equilibrium using average density and velocity from neighboring cells; default + * approach used in any known publication with free surface LBM + * + * - AverageRefilling: + * initialize PDFs with average PDFs (in the respective directions) of neighboring cells + * + * - EquilibriumAndNonEquilibriumRefilling: + * initialize PDFs with EquilibriumRefilling and add the non-equilibrium contribution of neighboring cells + * + * - ExtrapolationRefilling: + * initialize PDFs with PDFs extrapolated (in surface normal direction) from neighboring cells + * + * - GradsMomentsRefilling: + * initialize PDFs with EquilibriumRefilling and add the contribution of the non-equilibrium pressure + * tensor + * + * See src/lbm/free_surface/dynamics/PdfRefillingSweep.h for a detailed description of the models. + * + * The models and their implementation are inspired by the equivalent functionality of the lbm-particle coupling, see + * src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/Reconstructor.h. + **********************************************************************************************************************/ +class PdfRefillingModel +{ + public: + enum class RefillingModel { + EquilibriumRefilling, + AverageRefilling, + EquilibriumAndNonEquilibriumRefilling, + ExtrapolationRefilling, + GradsMomentsRefilling + }; + + PdfRefillingModel(const std::string& modelName) : modelName_(modelName), modelType_(chooseType(modelName)) {} + + PdfRefillingModel(const RefillingModel& modelType) : modelName_(chooseName(modelType)), modelType_(modelType) + { + switch (modelType_) + { + case RefillingModel::EquilibriumRefilling: + break; + case RefillingModel::AverageRefilling: + break; + case RefillingModel::EquilibriumAndNonEquilibriumRefilling: + break; + case RefillingModel::ExtrapolationRefilling: + break; + case RefillingModel::GradsMomentsRefilling: + break; + } + } + + inline RefillingModel getModelType() const { return modelType_; } + inline std::string getModelName() const { return modelName_; } + inline std::string getFullModelSpecification() const { return getModelName(); } + + static inline std::initializer_list< const RefillingModel > getTypeIterator() { return listOfAllEnums; } + + private: + RefillingModel chooseType(const std::string& modelName) + { + if (!string_icompare(modelName, "EquilibriumRefilling")) { return RefillingModel::EquilibriumRefilling; } + + if (!string_icompare(modelName, "AverageRefilling")) { return RefillingModel::AverageRefilling; } + + if (!string_icompare(modelName, "EquilibriumAndNonEquilibriumRefilling")) + { + return RefillingModel::EquilibriumAndNonEquilibriumRefilling; + } + + if (!string_icompare(modelName, "ExtrapolationRefilling")) { return RefillingModel::ExtrapolationRefilling; } + + if (!string_icompare(modelName, "GradsMomentsRefilling")) { return RefillingModel::GradsMomentsRefilling; } + + WALBERLA_ABORT("The specified PDF reinitialization model " << modelName << " is not available."); + } + + std::string chooseName(RefillingModel const& modelType) const + { + std::string modelName; + switch (modelType) + { + case RefillingModel::EquilibriumRefilling: + modelName = "EquilibriumRefilling"; + break; + case RefillingModel::AverageRefilling: + modelName = "AverageRefilling"; + break; + case RefillingModel::EquilibriumAndNonEquilibriumRefilling: + modelName = "EquilibriumAndNonEquilibriumRefilling"; + break; + case RefillingModel::ExtrapolationRefilling: + modelName = "ExtrapolationRefilling"; + break; + case RefillingModel::GradsMomentsRefilling: + modelName = "GradsMomentsRefilling"; + break; + } + return modelName; + } + + std::string modelName_; + RefillingModel modelType_; + static constexpr std::initializer_list< const RefillingModel > listOfAllEnums = { + RefillingModel::EquilibriumRefilling, RefillingModel::AverageRefilling, + RefillingModel::EquilibriumAndNonEquilibriumRefilling, RefillingModel::ExtrapolationRefilling, + RefillingModel::GradsMomentsRefilling + }; + +}; // class PdfRefillingModel +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/PdfRefillingSweep.h b/src/lbm_generated/free_surface/dynamics/PdfRefillingSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..dcbe431e144713c185d201cd11d3243c189c30bd --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/PdfRefillingSweep.h @@ -0,0 +1,447 @@ +//====================================================================================================================== +// +// 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 PdfRefillingSweep.h +//! \ingroup dynamics +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \author Michael Zikeli +//! \brief Sweeps for refilling cells (i.e. reinitializing PDFs) after the cell was converted from gas to interface. +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/StringUtility.h" +#include "core/cell/Cell.h" +#include "core/debug/CheckFunctions.h" +#include "core/logging/Logging.h" +#include "core/math/Vector3.h" + +#include "domain_decomposition/IBlock.h" + +#include "field/GhostLayerField.h" + +#include "lbm_generated/field/PdfField.h" +#include "lbm_generated/free_surface/FlagInfo.h" +#include "lbm_generated/free_surface/dynamics/PdfRefillingModel.h" +#include "lbm_generated/free_surface/surface_geometry/NormalSweep.h" + +#include "stencil/D3Q6.h" + +#include <functional> +#include <vector> + +#include "PdfRefillingModel.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Base class for all sweeps to reinitialize (refill) all PDFs in cells that were converted from gas to interface. + * This is required since gas cells do not have PDFs. The sweep expects that a previous sweep has set the + * "convertedFromGasToInterface" flag and reinitializes the PDFs in all cells with this flag according to the specified + * model. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T > +class RefillingSweepBase +{ + public: + using PdfField_T = lbm_generated::PdfField< StorageSpecification_T >; + using flag_t = typename FlagField_T::flag_t; + using Stencil_T = typename StorageSpecification_T::Stencil; + + RefillingSweepBase(const BlockDataID& pdfFieldID, const ConstBlockDataID& flagFieldID, + const FlagInfo< FlagField_T >& flagInfo, bool useDataFromGhostLayers) + : pdfFieldID_(pdfFieldID), flagFieldID_(flagFieldID), flagInfo_(flagInfo), + useDataFromGhostLayers_(useDataFromGhostLayers) + {} + + virtual void operator()(IBlock* const block) = 0; + + virtual ~RefillingSweepBase() = default; + + real_t getAverageDensityAndVelocity(const Cell& cell, const PdfField_T& pdfField, const FlagField_T& flagField, + const FlagInfo< FlagField_T >& flagInfo, Vector3< real_t >& avgVelocity) + { + std::vector< bool > validStencilIndices(Stencil_T::Size, false); + return getAverageDensityAndVelocity(cell, pdfField, flagField, flagInfo, avgVelocity, validStencilIndices); + } + + // also stores stencil indices of valid neighboring cells (liquid/ non-newly converted interface) in a vector + real_t getAverageDensityAndVelocity(const Cell& cell, const PdfField_T& pdfField, const FlagField_T& flagField, + const FlagInfo< FlagField_T >& flagInfo, Vector3< real_t >& avgVelocity, + std::vector< bool >& validStencilIndices); + + // returns the averaged PDFs of valid neighboring cells (liquid/ non-newly converted interface) + std::vector< real_t > getAveragePdfs(const Cell& cell, const PdfField_T& pdfField, const FlagField_T& flagField, + const FlagInfo< FlagField_T >& flagInfo); + + protected: + BlockDataID pdfFieldID_; + ConstBlockDataID flagFieldID_; + FlagInfo< FlagField_T > flagInfo_; + bool useDataFromGhostLayers_; +}; // class RefillingSweepBase + +/*********************************************************************************************************************** + * Base class for refilling models that need to obtain information by extrapolation from neighboring cells. + * + * The parameter "useDataFromGhostLayers" is useful, when reducedCommunication is used, i.e., when not all PDFs are + * communicated in the PDF field but only those that are actually required in a certain direction. This is currently not + * used in the free surface part of waLBerla: In SurfaceDynamicsHandler, SimpleCommunication uses the default PackInfo + * of the GhostLayerField for PDF communication. The optimized version would be using the PdfFieldPackInfo (in + * src/lbm/communication). + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class ExtrapolationRefillingSweepBase : public RefillingSweepBase< StorageSpecification_T, FlagField_T > +{ + public: + using RefillingSweepBase_T = RefillingSweepBase< StorageSpecification_T, FlagField_T >; + using PdfField_T = typename RefillingSweepBase_T::PdfField_T; + using flag_t = typename RefillingSweepBase_T::flag_t; + using Stencil_T = typename RefillingSweepBase_T::Stencil_T; + + ExtrapolationRefillingSweepBase(const BlockDataID& pdfFieldID, const ConstBlockDataID& flagFieldID, + const ConstBlockDataID& fillFieldID, const FlagInfo< FlagField_T >& flagInfo, + uint_t extrapolationOrder, bool useDataFromGhostLayers) + : RefillingSweepBase_T(pdfFieldID, flagFieldID, flagInfo, useDataFromGhostLayers), fillFieldID_(fillFieldID), + extrapolationOrder_(extrapolationOrder) + {} + + virtual ~ExtrapolationRefillingSweepBase() = default; + + virtual void operator()(IBlock* const block) = 0; + + /******************************************************************************************************************** + * Find the lattice direction in the given stencil that corresponds best to the provided direction. + * + * Mostly copied from src/lbm_mesapd_coupling/momentum_exchange_method/reconstruction/ExtrapolationDirectionFinder.h. + *******************************************************************************************************************/ + Vector3< cell_idx_t > findCorrespondingLatticeDirection(const Vector3< real_t >& direction); + + /******************************************************************************************************************** + * The extrapolation direction is chosen such that it most closely resembles the surface normal in the cell. + * + * The normal has to be recomputed here and MUST NOT be taken directly from the normal field because the fill levels + * will have changed since the last computation of the normal. As the normal is computed from the fill levels, it + * must be recomputed to have an up-to-date normal. + *******************************************************************************************************************/ + Vector3< cell_idx_t > findExtrapolationDirection(const Cell& cell, const FlagField_T& flagField, + const ScalarField_T& fillField); + + /******************************************************************************************************************** + * Determine the number of applicable (liquid or interface) cells for extrapolation in the given extrapolation + * direction. + *******************************************************************************************************************/ + uint_t getNumberOfExtrapolationCells(const Cell& cell, const FlagField_T& flagField, const PdfField_T& pdfField, + const Vector3< cell_idx_t >& extrapolationDirection); + + /******************************************************************************************************************** + * Get the non-equilibrium part of all PDFs in "cell" and store them in a std::vector<real_t>. + *******************************************************************************************************************/ + std::vector< real_t > getNonEquilibriumPdfsInCell(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField); + + /******************************************************************************************************************** + * Get all PDFs in "cell" and store them in a std::vector<real_t>. + *******************************************************************************************************************/ + std::vector< real_t > getPdfsInCell(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField); + + /******************************************************************************************************************** + * Set the PDFs in cell "x" according to the following linear combination: + * f(x,q) = f(x,q) + 3 * f^{get}(x+e,q) - 3 * f^{get}(x+2e,q) + 1 * f^{get}(x+3e,q) + * with x: cell position + * q: index of the respective PDF + * e: extrapolation direction + * f^{get}: PDF specified by getPdfFunc + * "includeThisCell" defines whether f(x,q) is included + * + * Note: this function does NOT assert out of bounds access, i.e., it may only be called for cells that have at least + * three neighboring cells in extrapolationDirection. + *******************************************************************************************************************/ + void applyQuadraticExtrapolation( + const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField, const Vector3< cell_idx_t >& extrapolationDirection, + bool includeThisCell, + const std::function< std::vector< real_t >(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField) >& + getPdfFunc); + + /******************************************************************************************************************** + * Set the PDFs in cell (x) according to the following linear combination: + * f(x,q) = f(x,q) + 2 * f^{get}(x+1e,q) - 1 * f^{get}(x+2e,q) + * with x: cell position + * q: index of the respective PDF + * e: extrapolation direction + * f^{get}: PDF specified by getPdfFunc + * "includeThisCell" defines whether f(x,q) is included + * + * Note: this function does NOT assert out of bounds access, i.e., it may only be called for cells that have at least + * two neighboring cells in extrapolationDirection. + *******************************************************************************************************************/ + void applyLinearExtrapolation( + const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField, const Vector3< cell_idx_t >& extrapolationDirection, + bool includeThisCell, + const std::function< std::vector< real_t >(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField) >& + getPdfFunc); + + /******************************************************************************************************************** + * Set the PDFs in cell (x) according to the following linear combination: + * f(x,q) = f(x,q) + 1 * f^{get}(x+1e,q) + * with x: cell position + * q: index of the respective PDF + * e: extrapolation direction + * f^{get}: PDF specified by getPdfFunc + * "includeThisCell" defines whether f(x,q) is included + * + * Note: this function does NOT assert out of bounds access, i.e., it may only be called for cells that have at least + * a neighboring cell in extrapolationDirection. + *******************************************************************************************************************/ + void applyConstantExtrapolation( + const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField, const Vector3< cell_idx_t >& extrapolationDirection, + bool includeThisCell, + const std::function< std::vector< real_t >(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField) >& + getPdfFunc); + + protected: + ConstBlockDataID fillFieldID_; + uint_t extrapolationOrder_; +}; // class ExtrapolationRefillingSweepBase + +/*********************************************************************************************************************** + * EquilibriumRefillingSweep: + * PDFs are initialized with the equilibrium based on the average density and velocity from neighboring liquid and + * (non-newly converted) interface cells. + * + * Reference: dissertation of N. Thuerey, 2007, section 4.3 + * + * f(x,q) = f^{eq}(x+e,q) + * with x: cell position + * q: index of the respective PDF + * e: direction of a valid neighbor + **********************************************************************************************************************/ + +template< typename StorageSpecification_T, typename FlagField_T > +class EquilibriumRefillingSweep : public RefillingSweepBase< StorageSpecification_T, FlagField_T > +{ + public: + using RefillingSweepBase_T = RefillingSweepBase< StorageSpecification_T, FlagField_T >; + using PdfField_T = typename RefillingSweepBase_T::PdfField_T; + using flag_t = typename RefillingSweepBase_T::flag_t; + using Stencil_T = typename RefillingSweepBase_T::Stencil_T; + + EquilibriumRefillingSweep(const BlockDataID& pdfFieldID, const ConstBlockDataID& flagFieldID, + const FlagInfo< FlagField_T >& flagInfo, bool useDataFromGhostLayers) + : RefillingSweepBase_T(pdfFieldID, flagFieldID, flagInfo, useDataFromGhostLayers) + {} + + ~EquilibriumRefillingSweep() override = default; + + void operator()(IBlock* const block) override; +}; // class EquilibriumRefillingSweep + +/*********************************************************************************************************************** + * AverageRefillingSweep: + * PDFs are initialized with the average of the PDFs in the same direction from applicable neighboring cells. + * + * f_i(x,q) = \sum_N( f_i(x+e,q) ) / N + * with x: cell position + * i: PDF index, i.e., direction + * q: index of the respective PDF + * e: direction of a valid neighbor + * N: number of applicable neighbors + * sum_N: sum over all applicable neighbors + * + * Reference: not available in literature (as of 06/2022). + **********************************************************************************************************************/ + +template< typename StorageSpecification_T, typename FlagField_T > +class AverageRefillingSweep : public RefillingSweepBase< StorageSpecification_T, FlagField_T > +{ + public: + using RefillingSweepBase_T = RefillingSweepBase< StorageSpecification_T, FlagField_T >; + using PdfField_T = typename RefillingSweepBase_T::PdfField_T; + using flag_t = typename RefillingSweepBase_T::flag_t; + using Stencil_T = typename RefillingSweepBase_T::Stencil_T; + + AverageRefillingSweep(const BlockDataID& pdfFieldID, const ConstBlockDataID& flagFieldID, + const FlagInfo< FlagField_T >& flagInfo, bool useDataFromGhostLayers) + : RefillingSweepBase_T(pdfFieldID, flagFieldID, flagInfo, useDataFromGhostLayers) + {} + + ~AverageRefillingSweep() override = default; + + void operator()(IBlock* const block) override; +}; // class AverageRefillingSweep + +/*********************************************************************************************************************** + * EquilibriumAndNonEquilibriumRefilling: + * First reconstruct the equilibrium values according to the "EquilibriumRefilling". Then extrapolate the + * non-equilibrium part of the PDFs in the direction of the surface normal and add it to the (equilibrium-) + * reinitialized PDFs. + * + * Reference: equation (51) in Peng et al., "Implementation issues and benchmarking of lattice Boltzmann method for + * moving rigid particle simulations in a viscous flow", 2015, doi: 10.1016/j.camwa.2015.08.027) + * + * f_q(x,t+dt) = f_q^{eq}(x,t) + f_q^{neq}(x+e*dt,t+dt) + * with x: cell position + * q: index of the respective PDF + * e: direction of a valid neighbor/ extrapolation direction + * t: current time step + * dt: time step width + * f^{eq}: equilibrium PDF with velocity and density averaged from neighboring cells + * f^{neq}: non-equilibrium PDF (f - f^{eq}) + * + * Note: Analogously as in the "ExtrapolationRefilling", the expression "f_q^{neq}(x+e*dt,t+dt)" can also be obtained by + * extrapolation (in literature, only zeroth order extrapolation is used/documented): + * - zeroth order: f_q^{neq}(x+e*dt,t+dt) + * - first order: 2 * f_q^{neq}(x+e*dt,t+dt) - 1 * f_q^{neq}(x+2e*dt,t+dt) + * - second order: 3 * f_q^{neq}(x+e*dt,t+dt) - 3 * f_q^{neq}(x+2e*dt,t+dt) + f_q^{neq}(x+3e*dt,t+dt) + * If not enough cells are available for the chosen extrapolation order, the algorithm falls back to the corresponding + * lower order. If even zeroth order can not be applied, only f_q^{eq}(x,t) is considered which corresponds to + * "EquilibriumRefilling". + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class EquilibriumAndNonEquilibriumRefillingSweep + : public ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T > +{ + public: + using ExtrapolationRefillingSweepBase_T = + ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >; + using RefillingSweepBase_T = typename ExtrapolationRefillingSweepBase_T::RefillingSweepBase_T; + using PdfField_T = typename ExtrapolationRefillingSweepBase_T::PdfField_T; + using flag_t = typename ExtrapolationRefillingSweepBase_T::flag_t; + using Stencil_T = typename ExtrapolationRefillingSweepBase_T::Stencil_T; + + EquilibriumAndNonEquilibriumRefillingSweep(const BlockDataID& pdfFieldID, const ConstBlockDataID& flagFieldID, + const ConstBlockDataID& fillFieldID, + const FlagInfo< FlagField_T >& flagInfo, uint_t extrapolationOrder, + bool useDataFromGhostLayers) + : ExtrapolationRefillingSweepBase_T(pdfFieldID, flagFieldID, fillFieldID, flagInfo, extrapolationOrder, + useDataFromGhostLayers) + {} + + ~EquilibriumAndNonEquilibriumRefillingSweep() override = default; + + void operator()(IBlock* const block) override; +}; // class EquilibriumAndNonEquilibriumRefillingSweep + +/*********************************************************************************************************************** + * ExtrapolationRefilling: + * Extrapolate the PDFs of one or more cells in the direction of the surface normal. + * + * Reference: equation (50) in Peng et al., "Implementation issues and benchmarking of lattice Boltzmann method for + * moving rigid particle simulations in a viscous flow", 2015, doi: 10.1016/j.camwa.2015.08.027) + * + * f_q(x,t+dt) = 3 * f_q(x+e*dt,t+dt) - 3 * f_q(x+2e*dt,t+dt) + f_q(x+3e*dt,t+dt) + * with x: cell position + * q: index of the respective PDF + * e: direction of a valid neighbor/ extrapolation direction + * t: current time step + * dt: time step width + * Note: The equation contains a second order extrapolation, however other options are also available. If not enough + * cells are available for second order extrapolation, the algorithm falls back to the next applicable + * lower order: + * - second order: 3 * f_q(x+e*dt,t+dt) - 3 * f_q(x+2e*dt,t+dt) + f_q(x+3e*dt,t+dt) + * - first order: 2 * f_q(x+e*dt,t+dt) - 1 * f_q(x+2e*dt,t+dt) + * - zeroth order: f_q(x+e*dt,t+dt) + * If even zeroth order can not be applied, only f_q^{eq}(x,t) is considered which corresponds to + * "EquilibriumRefilling". + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class ExtrapolationRefillingSweep + : public ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T > +{ + public: + using ExtrapolationRefillingSweepBase_T = + ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >; + using RefillingSweepBase_T = typename ExtrapolationRefillingSweepBase_T::RefillingSweepBase_T; + using PdfField_T = typename ExtrapolationRefillingSweepBase_T::PdfField_T; + using flag_t = typename ExtrapolationRefillingSweepBase_T::flag_t; + using Stencil_T = typename ExtrapolationRefillingSweepBase_T::Stencil_T; + + ExtrapolationRefillingSweep(const BlockDataID& pdfFieldID, const ConstBlockDataID& flagFieldID, + const ConstBlockDataID& fillFieldID, const FlagInfo< FlagField_T >& flagInfo, + uint_t extrapolationOrder, bool useDataFromGhostLayers) + : ExtrapolationRefillingSweepBase_T(pdfFieldID, flagFieldID, fillFieldID, flagInfo, extrapolationOrder, + useDataFromGhostLayers) + {} + + ~ExtrapolationRefillingSweep() override = default; + + void operator()(IBlock* const block) override; +}; // class ExtrapolationRefillingSweep + +/*********************************************************************************************************************** + * GradsMomentsRefilling: + * Reconstruct missing PDFs based on Grad's moment closure. + * + * References: - equation (11) in Chikatamarla et al., "Grad’s approximation for missing data in lattice Boltzmann + * simulations", 2006, doi: 10.1209/epl/i2005-10535-x + * - equation (10) in Dorscher et al., "Grad’s approximation for moving and stationary walls in entropic + * lattice Boltzmann simulations", 2015, doi: 10.1016/j.jcp.2015.04.017 + * + * The following equation is a rewritten and easier version of the equation in the above references: + * f_q(x,t+dt) = f_q^{eq}(x,t) + + * w_q * rho / 2 / cs^2 / omega * (du_a / dx_b + du_b / dx_a)(cs^2 * delta_{ab} - c_{q,a}c_{q,b} ) + * with x: cell position + * q: index of the respective PDF + * t: current time step + * f^{eq}: equilibrium PDF with velocity and density averaged from neighboring cells + * w_q: lattice weight + * rho: density averaged from neighboring cells + * cs: lattice speed of sound + * omega: relaxation rate + * du_a / dx_b: gradient of the velocity (in index notation) + * delta_{ab}: Kronecker delta (in index notation) + * c_q{q,a}: lattice velocity (in index notation) + * + * The velocity gradient is computed using a first order central finite difference scheme if two neighboring cells + * are available. Otherwise, a first order upwind scheme is applied. + * IMPORTANT REMARK: The current implementation only works for dx=1 (this is assumed in the gradient computation). + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T > +class GradsMomentsRefillingSweep : public RefillingSweepBase< StorageSpecification_T, FlagField_T > +{ + public: + using RefillingSweepBase_T = RefillingSweepBase< StorageSpecification_T, FlagField_T >; + using PdfField_T = typename RefillingSweepBase_T::PdfField_T; + using flag_t = typename RefillingSweepBase_T::flag_t; + using Stencil_T = typename RefillingSweepBase_T::Stencil_T; + + GradsMomentsRefillingSweep(const BlockDataID& pdfFieldID, const ConstBlockDataID& flagFieldID, + const FlagInfo< FlagField_T >& flagInfo, real_t relaxRate, bool useDataFromGhostLayers) + + : RefillingSweepBase_T(pdfFieldID, flagFieldID, flagInfo, useDataFromGhostLayers), relaxRate_(relaxRate) + {} + + ~GradsMomentsRefillingSweep() override = default; + + void operator()(IBlock* const block) override; + + // compute the gradient of the velocity in the specified direction + // - using a first order central finite difference scheme if two valid neighboring cells are available + // - using a first order upwind scheme if only one valid neighboring cell is available + // - assuming a gradient of zero if no valid neighboring cell is available + Vector3< real_t > getVelocityGradient(stencil::Direction direction, const Cell& cell, const PdfField_T* pdfField, + const Vector3< real_t >& avgVelocity, + const std::vector< bool >& validStencilIndices); + + private: + real_t relaxRate_; +}; // class GradsMomentApproximationRefilling + +} // namespace free_surface +} // namespace walberla + +#include "PdfRefillingSweep.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/PdfRefillingSweep.impl.h b/src/lbm_generated/free_surface/dynamics/PdfRefillingSweep.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..19a71c5b9dde769fd6ac176a1c0b9ee6e65c6707 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/PdfRefillingSweep.impl.h @@ -0,0 +1,727 @@ +//====================================================================================================================== +// +// 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 PdfRefillingSweep.impl.h +//! \ingroup dynamics +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \author Michael Zikeli +//! \brief Sweeps for refilling cells (i.e. reinitializing PDFs) after the cell was converted from gas to interface. +// +//====================================================================================================================== + +#include "core/DataTypes.h" +#include "core/cell/Cell.h" +#include "core/debug/CheckFunctions.h" +#include "core/logging/Logging.h" +#include "core/math/Vector3.h" +#include "core/math/Matrix3.h" + +#include "domain_decomposition/IBlock.h" + +#include "field/GhostLayerField.h" + +#include "lbm_generated/field/PdfField.h" +#include "lbm_generated/free_surface/FlagInfo.h" +#include "lbm_generated/free_surface/dynamics/PdfRefillingModel.h" +#include "lbm_generated/free_surface/surface_geometry/NormalSweep.h" +#include "lbm_generated/macroscopics/DensityAndMomentumDensity.h" +#include "lbm_generated/macroscopics/Equilibrium.h" + +#include "stencil/D3Q6.h" +#include "stencil/Directions.h" + +#include <set> +#include <vector> + +#include "PdfRefillingSweep.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename StorageSpecification_T, typename FlagField_T > +real_t RefillingSweepBase< StorageSpecification_T, FlagField_T >::getAverageDensityAndVelocity( + const Cell& cell, const PdfField_T& pdfField, const FlagField_T& flagField, const FlagInfo< FlagField_T >& flagInfo, + Vector3< real_t >& avgVelocity, std::vector< bool >& validStencilIndices) +{ + real_t rho = real_c(0.0); + Vector3< real_t > u(real_c(0.0)); + uint_t numNeighbors = uint_c(0); + + // do not use data from ghost layer if optimized communication is used (see comment in PdfRefillingSweep.h at + // ExtrapolationRefillingSweepBase) + const CellInterval localDomain = useDataFromGhostLayers_ ? pdfField.xyzSizeWithGhostLayer() : pdfField.xyzSize(); + + for (auto i = Stencil_T::beginNoCenter(); i != Stencil_T::end(); ++i) + { + const Cell neighborCell(cell[0] + i.cx(), cell[1] + i.cy(), cell[2] + i.cz()); + + const flag_t neighborFlag = flagField.get(neighborCell); + const flag_t liquidInterfaceMask = flagInfo.interfaceFlag | flagInfo.liquidFlag; + + // only use neighboring cell if + // - neighboring cell is part of the block-local domain + // - neighboring cell is liquid or interface + // - not newly converted from G->I + const bool useNeighbor = isPartOfMaskSet(neighborFlag, liquidInterfaceMask) && + !flagInfo.hasConvertedFromGasToInterface(flagField.get(neighborCell)) && + localDomain.contains(neighborCell); + + // calculate the average of valid neighbor cells to calculate an average density and velocity. + if (useNeighbor) + { + numNeighbors++; + Vector3< real_t > neighborU; + real_t neighborRho; + neighborRho = lbm_generated::getDensityAndMomentumDensity<StorageSpecification_T, PdfField_T>(neighborU, pdfField, neighborCell[0], neighborCell[1], neighborCell[2]); + neighborU /= neighborRho; + u += neighborU; + rho += neighborRho; + + validStencilIndices[i.toIdx()] = true; + } + } + + // normalize the newly calculated velocity and density + if (numNeighbors != uint_c(0)) + { + u /= real_c(numNeighbors); + rho /= real_c(numNeighbors); + } + else + { + u = Vector3< real_t >(0.0); + rho = real_c(1.0); + WALBERLA_LOG_WARNING_ON_ROOT("There are no valid neighbors for the refilling of (block-local) cell: " << cell); + } + + avgVelocity = u; + return rho; +} + +template< typename StorageSpecification_T, typename FlagField_T > +std::vector< real_t > RefillingSweepBase< StorageSpecification_T, FlagField_T >::getAveragePdfs( + const Cell& cell, const PdfField_T& pdfField, const FlagField_T& flagField, const FlagInfo< FlagField_T >& flagInfo) +{ + uint_t numNeighbors = uint_c(0); + + // do not use data from ghost layer if optimized communication is used (see comment in PdfRefillingSweep.h at + // ExtrapolationRefillingSweepBase) + const CellInterval localDomain = useDataFromGhostLayers_ ? pdfField.xyzSizeWithGhostLayer() : pdfField.xyzSize(); + + std::vector< real_t > pdfSum(Stencil_T::Size, real_c(0)); + + for (auto i = Stencil_T::beginNoCenter(); i != Stencil_T::end(); ++i) + { + const Cell neighborCell(cell[0] + i.cx(), cell[1] + i.cy(), cell[2] + i.cz()); + + const flag_t neighborFlag = flagField.get(neighborCell); + const flag_t liquidInterfaceMask = flagInfo.interfaceFlag | flagInfo.liquidFlag; + + // only use neighboring cell if + // - neighboring cell is part of the block-local domain + // - neighboring cell is liquid or interface + // - not newly converted from G->I + const bool useNeighbor = isPartOfMaskSet(neighborFlag, liquidInterfaceMask) && + !flagInfo.hasConvertedFromGasToInterface(flagField.get(neighborCell)) && + localDomain.contains(neighborCell); + + // calculate the average of valid neighbor cells to calculate an average of PDFs in each direction + if (useNeighbor) + { + ++numNeighbors; + for (auto pdfDir = Stencil_T::begin(); pdfDir != Stencil_T::end(); ++pdfDir) + { + pdfSum[pdfDir.toIdx()] += pdfField.get(neighborCell, *pdfDir); + } + } + } + + // average the PDFs of all neighboring cells + if (numNeighbors != uint_c(0)) + { + for (auto& pdf : pdfSum) + { + pdf /= real_c(numNeighbors); + } + } + else + { + // fall back to EquilibriumRefilling by setting PDFs according to equilibrium with velocity=0 and density=1 + lbm_generated::Equilibrium< StorageSpecification_T >::set(pdfSum, Vector3< real_t >(real_c(0)), real_c(1)); + + WALBERLA_LOG_WARNING_ON_ROOT("There are no valid neighbors for the refilling of (block-local) cell: " << cell); + } + + return pdfSum; +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +Vector3< cell_idx_t > ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + findCorrespondingLatticeDirection(const Vector3< real_t >& direction) +{ + if (direction == Vector3< real_t >(real_c(0))) { return direction; } + + stencil::Direction bestFittingDirection = stencil::C; // arbitrary default initialization + real_t scalarProduct = real_c(0); + + for (auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + // compute inner product <dir,c_i> + const real_t scalarProductTmp = direction[0] * stencil::cNorm[0][*dir] + direction[1] * stencil::cNorm[1][*dir] + + direction[2] * stencil::cNorm[2][*dir]; + if (scalarProductTmp > scalarProduct) + { + // largest scalar product is the best fitting lattice direction, i.e., this direction has the smallest angle to + // the given direction + scalarProduct = scalarProductTmp; + bestFittingDirection = *dir; + } + } + + return Vector3< cell_idx_t >(stencil::cx[bestFittingDirection], stencil::cy[bestFittingDirection], + stencil::cz[bestFittingDirection]); +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +Vector3< cell_idx_t > + ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T >::findExtrapolationDirection(const Cell& cell, + const FlagField_T& flagField, + const ScalarField_T& fillField) +{ + Vector3< real_t > normal = Vector3< real_t >(real_c(0)); + + // get flag mask for obstacle boundaries + const flag_t obstacleFlagMask = flagField.getMask(RefillingSweepBase_T::flagInfo_.getObstacleIDSet()); + + // get flag mask for liquid, interface, and gas cells + const flag_t liquidInterfaceGasMask = flagField.getMask(flagIDs::liquidInterfaceGasFlagIDs); + + const typename ScalarField_T::ConstPtr fillPtr(fillField, cell.x(), cell.y(), cell.z()); + const typename FlagField_T::ConstPtr flagPtr(flagField, cell.x(), cell.y(), cell.z()); + + if (!isFlagInNeighborhood< Stencil_T >(flagPtr, obstacleFlagMask)) + { + normal_computation::computeNormal< + typename std::conditional< Stencil_T::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type >( + normal, fillPtr, flagPtr, liquidInterfaceGasMask); + } + else + { + normal_computation::computeNormalNearSolidBoundary< + typename std::conditional< Stencil_T::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type >( + normal, fillPtr, flagPtr, liquidInterfaceGasMask, obstacleFlagMask); + } + + // normalize normal (in contrast to the usual definition in FSlbm_generated, it points from gas to fluid here) + normal = normal.getNormalizedOrZero(); + + // find the lattice direction that most closely resembles the normal direction + // Note: the normal must point from gas to fluid because the extrapolation direction is also defined to point from + // gas to fluid + return findCorrespondingLatticeDirection(normal); +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +uint_t ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + getNumberOfExtrapolationCells(const Cell& cell, const FlagField_T& flagField, const PdfField_T& pdfField, + const Vector3< cell_idx_t >& extrapolationDirection) +{ + // skip center cell + if (extrapolationDirection == Vector3< cell_idx_t >(cell_idx_c(0))) { return uint_c(0); } + + // do not use data from ghost layer if optimized communication is used (see comment in PdfRefillingSweep.h at + // ExtrapolationRefillingSweepBase) + const CellInterval localDomain = + (RefillingSweepBase_T::useDataFromGhostLayers_) ? pdfField.xyzSizeWithGhostLayer() : pdfField.xyzSize(); + + // for extrapolation order n, n+1 applicable cells must be available in the desired direction + const uint_t maxExtrapolationCells = extrapolationOrder_ + uint_c(1); + + for (uint_t numCells = uint_c(1); numCells <= maxExtrapolationCells; ++numCells) + { + // potential cell used for extrapolation + const Cell checkCell = cell + Cell(cell_idx_c(numCells) * extrapolationDirection); + + const flag_t neighborFlag = flagField.get(checkCell); + const flag_t liquidInterfaceMask = + RefillingSweepBase_T::flagInfo_.interfaceFlag | RefillingSweepBase_T::flagInfo_.liquidFlag; + + // only use cell if the cell is + // - inside domain + // - liquid or interface + // - not a cell that has also just been converted from gas to interface + if (!localDomain.contains(checkCell) || !isPartOfMaskSet(neighborFlag, liquidInterfaceMask) || + RefillingSweepBase_T::flagInfo_.hasConvertedFromGasToInterface(flagField.get(checkCell))) + { + return numCells - uint_c(1); + } + } + + return maxExtrapolationCells; +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +std::vector< real_t > ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + getNonEquilibriumPdfsInCell(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField) +{ + std::vector< real_t > nonEquilibriumPartOfPdfs(Stencil_T::Size); + + Vector3< real_t > velocity; + // TODO Implement + // const real_t density = pdfField.getDensityAndVelocity(velocity, cell); + const real_t density = real_c(1.0); + + // compute non-equilibrium of each PDF + for (auto dir = Stencil_T::begin(); dir != Stencil_T::end(); ++dir) + { + nonEquilibriumPartOfPdfs[dir.toIdx()] = + pdfField.get(cell, dir.toIdx()) - lbm_generated::Equilibrium< StorageSpecification_T >::get(*dir, velocity, density); + } + return nonEquilibriumPartOfPdfs; +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +std::vector< real_t > + ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >::getPdfsInCell( + const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField) +{ + std::vector< real_t > Pdfs(Stencil_T::Size); + + for (auto dir = Stencil_T::begin(); dir != Stencil_T::end(); ++dir) + { + Pdfs[dir.toIdx()] = pdfField.get(cell, dir.toIdx()); + } + return Pdfs; +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + applyQuadraticExtrapolation( + const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField, const Vector3< cell_idx_t >& extrapolationDirection, + bool includeThisCell, + const std::function< std::vector< real_t >(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField) >& + getPdfFunc) +{ + // store the PDFs of the cells participating in the extrapolation + std::vector< real_t > pdfsXf(StorageSpecification_T::Stencil::Size); // cell + 1 * extrapolationDirection + std::vector< real_t > pdfsXff(StorageSpecification_T::Stencil::Size); // cell + 2 * extrapolationDirection + std::vector< real_t > pdfsXfff(StorageSpecification_T::Stencil::Size); // cell + 3 * extrapolationDirection + + // determines if the PDFs of the current cell are also considered + const real_t centerFactor = includeThisCell ? real_c(1) : real_c(0); + + // get PDFs + pdfsXf = getPdfFunc(cell + Cell(cell_idx_c(1) * extrapolationDirection), pdfField); + pdfsXff = getPdfFunc(cell + Cell(cell_idx_c(2) * extrapolationDirection), pdfField); + pdfsXfff = getPdfFunc(cell + Cell(cell_idx_c(3) * extrapolationDirection), pdfField); + + // compute the resulting PDF values of "cell" according to second order extrapolation + for (auto dir = Stencil_T::begin(); dir != Stencil_T::end(); ++dir) + { + pdfField.get(cell, dir.toIdx()) = centerFactor * pdfField.get(cell, dir.toIdx()) + + real_c(3) * pdfsXf[dir.toIdx()] - real_c(3) * pdfsXff[dir.toIdx()] + + real_c(1) * pdfsXfff[dir.toIdx()]; + } +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + applyLinearExtrapolation( + const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField, const Vector3< cell_idx_t >& extrapolationDirection, + bool includeThisCell, + const std::function< std::vector< real_t >(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField) >& + getPdfFunc) +{ + // store the PDFs of the cells participating in the interpolation + std::vector< real_t > pdfsXf(Stencil_T::Size); // cell + 1 * extrapolationDirection + std::vector< real_t > pdfsXff(Stencil_T::Size); // cell + 2 * extrapolationDirection + + // determines if the PDFs of the current cell are also considered + const real_t centerFactor = includeThisCell ? real_c(1) : real_c(0); + + // get PDFs + pdfsXf = getPdfFunc(cell + Cell(cell_idx_c(1) * extrapolationDirection), pdfField); + pdfsXff = getPdfFunc(cell + Cell(cell_idx_c(2) * extrapolationDirection), pdfField); + + // compute the resulting PDF values of "cell" according to first order extrapolation + for (auto dir = Stencil_T::begin(); dir != Stencil_T::end(); ++dir) + { + pdfField.get(cell, dir.toIdx()) = centerFactor * pdfField.get(cell, dir.toIdx()) + + real_c(2) * pdfsXf[dir.toIdx()] - real_c(1) * pdfsXff[dir.toIdx()]; + } +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExtrapolationRefillingSweepBase< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >:: + applyConstantExtrapolation( + const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField, const Vector3< cell_idx_t >& extrapolationDirection, + bool includeThisCell, + const std::function< std::vector< real_t >(const Cell& cell, lbm_generated::PdfField< StorageSpecification_T >& pdfField) >& + getPdfFunc) +{ + // store the PDFs of the cells participating in the interpolation + std::vector< real_t > pdfsXf(Stencil_T::Size); // cell + 1 * extrapolationDirection + + // determines if the PDFs of the current cell are also considered + const real_t centerFactor = includeThisCell ? real_c(1) : real_c(0); + + // get PDFs + pdfsXf = getPdfFunc(cell + Cell(cell_idx_c(1) * extrapolationDirection), pdfField); + + // compute the resulting PDF values of "cell" according to zeroth order extrapolation + for (auto dir = Stencil_T::begin(); dir != Stencil_T::end(); ++dir) + { + pdfField.get(cell, dir.toIdx()) = + centerFactor * pdfField.get(cell, dir.toIdx()) + real_c(1) * pdfsXf[dir.toIdx()]; + } +} + +template< typename StorageSpecification_T, typename FlagField_T > +void EquilibriumRefillingSweep< StorageSpecification_T, FlagField_T >::operator()(IBlock* const block) +{ + PdfField_T* const pdfField = block->getData< PdfField_T >(RefillingSweepBase_T::pdfFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(RefillingSweepBase_T::flagFieldID_); + + WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, { + if (RefillingSweepBase_T::flagInfo_.hasConvertedFromGasToInterface(flagFieldIt)) + { + const Cell cell = pdfFieldIt.cell(); + + Vector3< real_t > avgVelocity; + real_t avgDensity; + avgDensity = RefillingSweepBase_T::getAverageDensityAndVelocity(cell, *pdfField, *flagField, + RefillingSweepBase_T::flagInfo_, avgVelocity); + + // TODO add again + // pdfField->setDensityAndVelocity(cell, avgVelocity, avgDensity); + } + }) // WALBERLA_FOR_ALL_CELLS +} + +template< typename StorageSpecification_T, typename FlagField_T > +void AverageRefillingSweep< StorageSpecification_T, FlagField_T >::operator()(IBlock* const block) +{ + PdfField_T* const pdfField = block->getData< PdfField_T >(RefillingSweepBase_T::pdfFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(RefillingSweepBase_T::flagFieldID_); + + WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, { + if (RefillingSweepBase_T::flagInfo_.hasConvertedFromGasToInterface(flagFieldIt)) + { + const Cell cell = pdfFieldIt.cell(); + + // compute average PDFs (in each direction) from all applicable neighboring cells + const std::vector< real_t > pdfAverage = + RefillingSweepBase_T::getAveragePdfs(cell, *pdfField, *flagField, RefillingSweepBase_T::flagInfo_); + + for (auto pdfDir = Stencil_T::begin(); pdfDir != Stencil_T::end(); ++pdfDir) + { + pdfField->get(cell, *pdfDir) = pdfAverage[pdfDir.toIdx()]; + } + } + }) // WALBERLA_FOR_ALL_CELLS +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void EquilibriumAndNonEquilibriumRefillingSweep< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T >::operator()(IBlock* const block) +{ + PdfField_T* const pdfField = + block->getData< PdfField_T >(ExtrapolationRefillingSweepBase_T::RefillingSweepBase_T::pdfFieldID_); + const FlagField_T* const flagField = + block->getData< const FlagField_T >(ExtrapolationRefillingSweepBase_T::RefillingSweepBase_T::flagFieldID_); + const ScalarField_T* const fillField = + block->getData< const ScalarField_T >(ExtrapolationRefillingSweepBase_T::fillFieldID_); + + // function to fetch relevant PDFs + auto getPdfFunc = std::bind(&ExtrapolationRefillingSweepBase_T::getNonEquilibriumPdfsInCell, this, + std::placeholders::_1, std::placeholders::_2); + + WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, { + if (RefillingSweepBase_T::flagInfo_.hasConvertedFromGasToInterface(flagFieldIt)) + { + const Cell cell = pdfFieldIt.cell(); + + // apply EquilibriumRefilling first + Vector3< real_t > avgVelocity; + real_t avgDensity; + avgDensity = RefillingSweepBase_T::getAverageDensityAndVelocity(cell, *pdfField, *flagField, + RefillingSweepBase_T::flagInfo_, avgVelocity); + + // TODO Implement + // pdfField->setDensityAndVelocity(cell, avgVelocity, avgDensity); + + // find valid cells for extrapolation + const Vector3< cell_idx_t > extrapolationDirection = + ExtrapolationRefillingSweepBase_T::findExtrapolationDirection(cell, *flagField, *fillField); + const uint_t numberOfCellsForExtrapolation = ExtrapolationRefillingSweepBase_T::getNumberOfExtrapolationCells( + cell, *flagField, *pdfField, extrapolationDirection); + + // add non-equilibrium part of PDF (which might be obtained by extrapolation) + if (numberOfCellsForExtrapolation >= uint_c(3)) + { + ExtrapolationRefillingSweepBase_T::applyQuadraticExtrapolation(cell, *pdfField, extrapolationDirection, + true, getPdfFunc); + } + else + { + if (numberOfCellsForExtrapolation >= uint_c(2)) + { + ExtrapolationRefillingSweepBase_T::applyLinearExtrapolation(cell, *pdfField, extrapolationDirection, + true, getPdfFunc); + } + else + { + if (numberOfCellsForExtrapolation >= uint_c(1)) + { + ExtrapolationRefillingSweepBase_T::applyConstantExtrapolation(cell, *pdfField, extrapolationDirection, + true, getPdfFunc); + } + // else: do nothing here; this corresponds to using EquilibriumRefilling (done already at the beginning) + } + } + } + }) // WALBERLA_FOR_ALL_CELLS +} + +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ExtrapolationRefillingSweep< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T >::operator()( + IBlock* const block) +{ + PdfField_T* const pdfField = block->getData< PdfField_T >(ExtrapolationRefillingSweepBase_T::pdfFieldID_); + const FlagField_T* const flagField = + block->getData< const FlagField_T >(ExtrapolationRefillingSweepBase_T::flagFieldID_); + const ScalarField_T* const fillField = + block->getData< const ScalarField_T >(ExtrapolationRefillingSweepBase_T::fillFieldID_); + + WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, { + if (RefillingSweepBase_T::flagInfo_.hasConvertedFromGasToInterface(flagFieldIt)) + { + const Cell cell = pdfFieldIt.cell(); + + // find valid cells for extrapolation + const Vector3< cell_idx_t > extrapolationDirection = + ExtrapolationRefillingSweepBase_T::findExtrapolationDirection(cell, *flagField, *fillField); + const uint_t numberOfCellsForExtrapolation = ExtrapolationRefillingSweepBase_T::getNumberOfExtrapolationCells( + cell, *flagField, *pdfField, extrapolationDirection); + + // function to fetch relevant PDFs + auto getPdfFunc = std::bind(&ExtrapolationRefillingSweepBase_T::getPdfsInCell, this, std::placeholders::_1, + std::placeholders::_2); + + if (numberOfCellsForExtrapolation >= uint_c(3)) + { + ExtrapolationRefillingSweepBase_T::applyQuadraticExtrapolation(cell, *pdfField, extrapolationDirection, + false, getPdfFunc); + } + else + { + if (numberOfCellsForExtrapolation >= uint_c(2)) + { + ExtrapolationRefillingSweepBase_T::applyLinearExtrapolation(cell, *pdfField, extrapolationDirection, + false, getPdfFunc); + } + else + { + if (numberOfCellsForExtrapolation >= uint_c(1)) + { + ExtrapolationRefillingSweepBase_T::applyConstantExtrapolation(cell, *pdfField, extrapolationDirection, + false, getPdfFunc); + } + else + { + // if not enough cells for extrapolation are available, use EquilibriumRefilling + Vector3< real_t > avgVelocity; + real_t avgDensity; + avgDensity = RefillingSweepBase_T::getAverageDensityAndVelocity( + cell, *pdfField, *flagField, RefillingSweepBase_T::flagInfo_, avgVelocity); + // TODO Implement + // pdfField->setDensityAndVelocity(cell, avgVelocity, avgDensity); + } + } + } + } + }) // WALBERLA_FOR_ALL_CELLS +} + +template< typename StorageSpecification_T, typename FlagField_T > +Vector3< real_t > GradsMomentsRefillingSweep< StorageSpecification_T, FlagField_T >::getVelocityGradient( + stencil::Direction direction, const Cell& cell, const PdfField_T* pdfField, const Vector3< real_t >& avgVelocity, + const std::vector< bool >& validStencilIndices) +{ + stencil::Direction dir; + stencil::Direction invDir; + + switch (direction) + { + case stencil::E: + case stencil::W: + dir = stencil::E; + invDir = stencil::W; + break; + case stencil::N: + case stencil::S: + dir = stencil::N; + invDir = stencil::S; + break; + case stencil::T: + case stencil::B: + dir = stencil::T; + invDir = stencil::B; + break; + default: + WALBERLA_ABORT("Velocity gradient for GradsMomentsRefilling can not be computed in a direction other than in x-, " + "y-, or z-direction."); + } + + Vector3< real_t > velocityGradient(real_c(0)); + + // apply central finite differences if both neighboring cells are available + if (validStencilIndices[Stencil_T::idx[dir]] && validStencilIndices[Stencil_T::idx[invDir]]) + { + // TODO Implement + const Vector3< real_t > neighborVelocity1 = Vector3(0,0,0); // pdfField->getVelocity(cell + dir); + const Vector3< real_t > neighborVelocity2 = Vector3(0,0,0); // pdfField->getVelocity(cell + invDir); + velocityGradient[0] = real_c(0.5) * (neighborVelocity1[0] - neighborVelocity2[0]); // assuming dx = 1 + velocityGradient[1] = real_c(0.5) * (neighborVelocity1[1] - neighborVelocity2[1]); // assuming dx = 1 + velocityGradient[2] = real_c(0.5) * (neighborVelocity1[2] - neighborVelocity2[2]); // assuming dx = 1 + } + else + { + // apply first order upwind scheme + stencil::Direction upwindDirection = (avgVelocity[0] > real_c(0)) ? invDir : dir; + + stencil::Direction sourceDirection = stencil::C; // arbitrary default initialization + + if (validStencilIndices[Stencil_T::idx[upwindDirection]]) { sourceDirection = upwindDirection; } + else + { + if (validStencilIndices[Stencil_T::idx[stencil::inverseDir[upwindDirection]]]) + { + sourceDirection = stencil::inverseDir[upwindDirection]; + } + } + + if (sourceDirection == dir) + { + auto neighborVelocity = Vector3(0,0,0); // pdfField->getVelocity(cell + sourceDirection); + velocityGradient[0] = neighborVelocity[0] - avgVelocity[0]; // assuming dx = 1 + velocityGradient[1] = neighborVelocity[1] - avgVelocity[1]; // assuming dx = 1 + velocityGradient[2] = neighborVelocity[2] - avgVelocity[2]; // assuming dx = 1 + } + else + { + if (sourceDirection == invDir) + { + auto neighborVelocity = Vector3(0,0,0); // pdfField->getVelocity(cell + sourceDirection); + velocityGradient[0] = avgVelocity[0] - neighborVelocity[0]; // assuming dx = 1 + velocityGradient[1] = avgVelocity[1] - neighborVelocity[1]; // assuming dx = 1 + velocityGradient[2] = avgVelocity[2] - neighborVelocity[2]; // assuming dx = 1 + } + // else: no stencil direction is valid, velocityGradient is zero + } + } + + return velocityGradient; +} + +template< typename StorageSpecification_T, typename FlagField_T > +void GradsMomentsRefillingSweep< StorageSpecification_T, FlagField_T >::operator()(IBlock* const block) +{ + PdfField_T* pdfField = block->getData< PdfField_T >(RefillingSweepBase_T::pdfFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(RefillingSweepBase_T::flagFieldID_); + + WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, { + if (RefillingSweepBase_T::flagInfo_.hasConvertedFromGasToInterface(flagFieldIt)) + { + const Cell cell = pdfFieldIt.cell(); + + // get average density and velocity from valid neighboring cells and store the directions of valid neighbors + std::vector< bool > validStencilIndices(Stencil_T::Size, false); + Vector3< real_t > avgVelocity; + real_t avgDensity; + avgDensity = RefillingSweepBase_T::getAverageDensityAndVelocity( + cell, *pdfField, *flagField, RefillingSweepBase_T::flagInfo_, avgVelocity, validStencilIndices); + + // get velocity gradients + // - using a first order central finite differences (if two neighboring cells are available) + // - using a first order upwind scheme (if only one neighboring cell is available) + // - assuming a zero gradient if no valid neighboring cell is available + // velocityGradient(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 | + const Vector3< real_t > gradientX = + getVelocityGradient(stencil::E, cell, pdfField, avgVelocity, validStencilIndices); + const Vector3< real_t > gradientY = + getVelocityGradient(stencil::N, cell, pdfField, avgVelocity, validStencilIndices); + Vector3< real_t > gradientZ = Vector3< real_t >(real_c(0)); + if (Stencil_T::D == 3) + { + gradientZ = getVelocityGradient(stencil::T, cell, pdfField, avgVelocity, validStencilIndices); + } + + Matrix3< real_t > velocityGradient(gradientX, gradientY, gradientZ); + + // compute non-equilibrium pressure tensor (equation (13) in Dorschner et al.); rho is not included in the + // pre-factor here, but will be considered later + Matrix3< real_t > pressureTensorNeq(real_c(0)); + + // in equation (13) in Dorschner et al., 2*beta is used in the pre-factor; note that 2*beta=omega (relaxation + // rate related to kinematic viscosity) + const real_t preFac = -real_c(1) / (real_c(3) * relaxRate_); + + for (uint_t j = uint_c(0); j != uint_c(3); ++j) + { + for (uint_t i = uint_c(0); i != uint_c(3); ++i) + { + pressureTensorNeq(i, j) += preFac * (velocityGradient(i, j) + velocityGradient(j, i)); + } + } + + // set PDFs according to equation (10) in Dorschner et al.; this is equivalent to setting the PDFs to + // equilibrium f^{eq} and adding a contribution of the non-equilibrium pressure tensor P^{neq} + for (auto q = Stencil_T::begin(); q != Stencil_T::end(); ++q) + { + const real_t velCi = real_c(stencil::cx[*q]) * avgVelocity[0] + real_c(stencil::cy[*q]) * avgVelocity[1] + real_c(stencil::cz[*q]) * avgVelocity[2]; + + real_t contributionFromPneq = real_c(0); + for (uint_t j = uint_c(0); j != uint_c(3); ++j) + { + for (uint_t i = uint_c(0); i != uint_c(3); ++i) + { + // P^{neq}_{a,b} * c_{q,a} * c_{q,b} + contributionFromPneq += + pressureTensorNeq(i, j) * real_c(stencil::c[i][*q]) * real_c(stencil::c[j][*q]); + } + } + + // - P^{neq}_{a,b} * cs^2 * delta_{a,b} + contributionFromPneq -= + (pressureTensorNeq(0, 0) + pressureTensorNeq(1, 1) + pressureTensorNeq(2, 2)) / real_c(3); + + // compute f^{eq} and add contribution from P^{neq} + const real_t pdf = StorageSpecification_T::w[q.toIdx()] * avgDensity * + (real_c(1) + real_c(3) * velCi - real_c(1.5) * avgVelocity.sqrLength() + + real_c(4.5) * velCi * velCi + real_c(4.5) * contributionFromPneq); + pdfField->get(cell, *q) = pdf; + } + } + }) // WALBERLA_FOR_ALL_CELLS +} + +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/StreamReconstructAdvectSweep.h b/src/lbm_generated/free_surface/dynamics/StreamReconstructAdvectSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..85a7292ce028189f90e2353ef960433510810ae6 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/StreamReconstructAdvectSweep.h @@ -0,0 +1,167 @@ +//====================================================================================================================== +// +// 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 StreamReconstructAdvectSweep.h +//! \ingroup free_surface +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Sweep for reconstruction of PDFs, streaming of PDFs (only in interface cells), advection of mass, update of +//! bubble volumes and marking interface cells for conversion. +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/math/Vector3.h" +#include "core/mpi/Reduce.h" +#include "core/timing/TimingPool.h" + +#include "field/FieldClone.h" +#include "field/FlagField.h" + +#include "lbm_generated/free_surface/FlagInfo.h" + +#include "PdfReconstructionModel.h" +#include "functionality/AdvectMass.h" +#include "functionality/FindInterfaceCellConversion.h" +#include "functionality/GetOredNeighborhood.h" +#include "functionality/ReconstructInterfaceCellABB.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename StorageSpecification_T, typename SweepCollection_T, typename FlagField_T, typename FlagInfo_T, + typename ScalarField_T, typename VectorField_T > +class StreamReconstructAdvectSweep +{ + public: + using flag_t = typename FlagInfo_T::flag_t; + using PdfField_T = lbm_generated::PdfField< StorageSpecification_T >; + + StreamReconstructAdvectSweep(SweepCollection_T& sweepCollection, real_t sigma, BlockDataID fillFieldID, BlockDataID flagFieldID, + BlockDataID pdfField, ConstBlockDataID normalFieldID, ConstBlockDataID curvatureFieldID, + const FlagInfo_T& flagInfo, + const PdfReconstructionModel& pdfReconstructionModel, bool useSimpleMassExchange, + real_t cellConversionThreshold, real_t cellConversionForceThreshold) + : sweepCollection_(sweepCollection), sigma_(sigma), fillFieldID_(fillFieldID), flagFieldID_(flagFieldID), + pdfFieldID_(pdfField), normalFieldID_(normalFieldID), curvatureFieldID_(curvatureFieldID), flagInfo_(flagInfo), + neighborhoodFlagFieldClone_(flagFieldID), fillFieldClone_(fillFieldID), + pdfFieldClone_(pdfField), pdfReconstructionModel_(pdfReconstructionModel), + useSimpleMassExchange_(useSimpleMassExchange), cellConversionThreshold_(cellConversionThreshold), + cellConversionForceThreshold_(cellConversionForceThreshold) + {} + + void operator()(IBlock* const block); + + protected: + SweepCollection_T & sweepCollection_; + real_t sigma_; // surface tension + + BlockDataID fillFieldID_; + BlockDataID flagFieldID_; + BlockDataID pdfFieldID_; + + ConstBlockDataID normalFieldID_; + ConstBlockDataID curvatureFieldID_; + + FlagInfo_T flagInfo_; + + // efficient clones of fields to provide temporary fields (for writing to) + field::FieldClone< FlagField_T, true > neighborhoodFlagFieldClone_; + field::FieldClone< ScalarField_T, true > fillFieldClone_; + field::FieldClone< PdfField_T, true > pdfFieldClone_; + + PdfReconstructionModel pdfReconstructionModel_; + bool useSimpleMassExchange_; + real_t cellConversionThreshold_; + real_t cellConversionForceThreshold_; +}; // class StreamReconstructAdvectSweep + +template< typename StorageSpecification_T, typename SweepCollection_T, typename FlagField_T, typename FlagInfo_T, + typename ScalarField_T, typename VectorField_T > +void StreamReconstructAdvectSweep< StorageSpecification_T, SweepCollection_T, FlagField_T, FlagInfo_T, ScalarField_T, + VectorField_T >::operator()(IBlock* const block) +{ + // fetch data + ScalarField_T* const fillSrcField = block->getData< ScalarField_T >(fillFieldID_); + PdfField_T* const pdfSrcField = block->getData< PdfField_T >(pdfFieldID_); + + const ScalarField_T* const curvatureField = block->getData< const ScalarField_T >(curvatureFieldID_); + const VectorField_T* const normalField = block->getData< const VectorField_T >(normalFieldID_); + FlagField_T* const flagField = block->getData< FlagField_T >(flagFieldID_); + + // temporary fields that act as destination fields + PdfField_T* const pdfDstField = pdfFieldClone_.get(block); + FlagField_T* const neighborhoodFlagField = neighborhoodFlagFieldClone_.get(block); + ScalarField_T* const fillDstField = fillFieldClone_.get(block); + + // combine all neighbor flags using bitwise OR and write them to the neighborhood field + // this is simply a pre-computation of often required values + // IMPORTANT REMARK: the "OredNeighborhood" is also required for each cell in the first ghost layer; this requires + // access to all first ghost layer cell's neighbors (i.e., to the second ghost layer) + WALBERLA_CHECK_GREATER_EQUAL(flagField->nrOfGhostLayers(), uint_c(2)); + getOredNeighborhood< typename StorageSpecification_T::Stencil >(flagField, neighborhoodFlagField); + + // explicitly avoid OpenMP due to bubble model update (reportFillLevelChange) + WALBERLA_FOR_ALL_CELLS_OMP( + pdfDstFieldIt, pdfDstField, pdfSrcFieldIt, pdfSrcField, fillSrcFieldIt, fillSrcField, fillDstFieldIt, + fillDstField, flagFieldIt, flagField, neighborhoodFlagFieldIt, neighborhoodFlagField, normalFieldIt, normalField, + curvatureFieldIt, curvatureField, omp critical, { + if (flagInfo_.isInterface(flagFieldIt)) + { + // get density (rhoGas) for interface PDF reconstruction + const real_t rhoGas = computeDeltaRhoLaplacePressure(sigma_, *curvatureFieldIt); + + // reconstruct missing PDFs coming from gas neighbors according to the specified model (see dissertation of + // N. Thuerey, 2007, section 4.2); reconstruction includes streaming of PDFs to interface cells (no LBM + // stream required here) + (reconstructInterfaceCellLegacy< StorageSpecification_T, FlagField_T >) (flagField, pdfSrcFieldIt, flagFieldIt, + normalFieldIt, flagInfo_, rhoGas, + pdfDstFieldIt, pdfReconstructionModel_); + } + else // treat non-interface cells + { + // manually adjust the fill level to avoid outdated fill levels being copied from fillSrcField + if (flagInfo_.isGas(flagFieldIt)) { *fillDstFieldIt = real_c(0); } + else + { + if (flagInfo_.isLiquid(flagFieldIt)) + { + const Cell cell = pdfSrcFieldIt.cell(); + const CellInterval ci(cell, cell); + sweepCollection_.streamCellInterval(pdfSrcField, pdfDstField, ci); + + *fillDstFieldIt = real_c(1); + } + else // flag is e.g. obstacle or outflow + { + *fillDstFieldIt = *fillSrcFieldIt; + } + } + } + }) // WALBERLA_FOR_ALL_CELLS_XYZ_OMP + + pdfSrcField->swapDataPointers(pdfDstField); + fillSrcField->swapDataPointers(fillDstField); + + // mark interface cell for conversion + findInterfaceCellConversions< StorageSpecification_T >(fillSrcField, flagField, neighborhoodFlagField, flagInfo_, + cellConversionThreshold_, cellConversionForceThreshold_); +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/SurfaceDynamicsHandler.h b/src/lbm_generated/free_surface/dynamics/SurfaceDynamicsHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..18e1425a8d1cb8a47aaf7e2ecf3bf088cde85d18 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/SurfaceDynamicsHandler.h @@ -0,0 +1,344 @@ +//====================================================================================================================== +// +// 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 SurfaceDynamicsHandler.h +//! \ingroup surface_dynamics +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Handles the free surface dynamics (mass advection, lbm_generated, boundary condition, cell conversion etc.). +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/AddToStorage.h" +#include "field/FlagField.h" + +#include "lbm_generated/blockforest/SimpleCommunication.h" +#include "lbm_generated/blockforest/UpdateSecondGhostLayer.h" +#include "lbm_generated/free_surface/BlockStateDetectorSweep.h" +#include "lbm_generated/free_surface/FlagInfo.h" + +#include "stencil/D3Q27.h" + +#include "timeloop/SweepTimeloop.h" + +#include "CellConversionSweep.h" +#include "ConversionFlagsResetSweep.h" +#include "ExcessMassDistributionModel.h" +#include "ExcessMassDistributionSweep.h" +#include "ForceDensitySweep.h" +#include "PdfReconstructionModel.h" +#include "PdfRefillingModel.h" +#include "PdfRefillingSweep.h" +#include "StreamReconstructAdvectSweep.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename StorageSpecification_T, typename SweepCollection_T, typename BoundaryCollection_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T, typename FlagInfo_T> +class SurfaceDynamicsHandler +{ + protected: + using Communication_T = lbm_generated::SimpleCommunication< typename StorageSpecification_T::Stencil >; + + // communication in corner directions (D2Q9/D3Q27) is required for all fields but the PDF field + using CommunicationStencil_T = + typename std::conditional< StorageSpecification_T::Stencil::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type; + using CommunicationCorner_T = lbm_generated::SimpleCommunication< CommunicationStencil_T >; + + public: + SurfaceDynamicsHandler(const std::shared_ptr< StructuredBlockForest >& blockForest, + SweepCollection_T & sweepCollection, BoundaryCollection_T & boundaryCollection, + FlagInfo_T& flagInfo, BlockDataID pdfFieldID, + BlockDataID flagFieldID, BlockDataID fillFieldID, BlockDataID forceDensityFieldID, + ConstBlockDataID normalFieldID, ConstBlockDataID curvatureFieldID, + const std::string& pdfReconstructionModel, const std::string& pdfRefillingModel, + const std::string& excessMassDistributionModel, real_t relaxationRate, + const Vector3< real_t >& globalAcceleration, real_t surfaceTension, + bool useSimpleMassExchange, real_t cellConversionThreshold, + real_t cellConversionForceThreshold) + : blockForest_(blockForest), sweepCollection_(sweepCollection), boundaryCollection_(boundaryCollection), flagInfo_(flagInfo), pdfFieldID_(pdfFieldID), flagFieldID_(flagFieldID), fillFieldID_(fillFieldID), + forceDensityFieldID_(forceDensityFieldID), normalFieldID_(normalFieldID), curvatureFieldID_(curvatureFieldID), + pdfReconstructionModel_(pdfReconstructionModel), pdfRefillingModel_({ pdfRefillingModel }), + excessMassDistributionModel_({ excessMassDistributionModel }), relaxationRate_(relaxationRate), + globalAcceleration_(globalAcceleration), surfaceTension_(surfaceTension), + useSimpleMassExchange_(useSimpleMassExchange), cellConversionThreshold_(cellConversionThreshold), + cellConversionForceThreshold_(cellConversionForceThreshold) + { + WALBERLA_CHECK(StorageSpecification_T::compressible, + "The free surface lattice Boltzmann extension works only with compressible lbm models."); + + if (excessMassDistributionModel_.isEvenlyAllInterfaceFallbackLiquidType()) + { + // add additional field for storing excess mass in liquid cells + excessMassFieldID_ = + field::addToStorage< ScalarField_T >(blockForest_, "Excess mass", real_c(0), field::fzyx, uint_c(1)); + } + + if (StorageSpecification_T::Stencil::D == uint_t(2)) + { + WALBERLA_LOG_INFO_ON_ROOT( + "IMPORTANT REMARK: You are using a D2Q9 stencil in SurfaceDynamicsHandler. In the FSlbm_generated, a D2Q9 setup is " + "not identical to a D3Q19 setup with periodicity in the third direction but only identical to the same " + "D3Q27 setup. This comes from the distribution of excess mass, where the number of available neighbors " + "differs for D2Q9 and a periodic D3Q19 setup.") + } + } + + ConstBlockDataID getConstExcessMassFieldID() const { return excessMassFieldID_; } + + /******************************************************************************************************************** + * The order of these sweeps is similar to page 40 in the dissertation of T. Pohl, 2008. + *******************************************************************************************************************/ + void addSweeps(SweepTimeloop& timeloop) const + { + using StateSweep = BlockStateDetectorSweep< FlagField_T >; + const auto blockStateUpdate = StateSweep(blockForest_, flagInfo_, flagFieldID_); + + // empty sweeps required for using selectors (e.g. StateSweep::onlyGasAndBoundary) + const auto emptySweep = [](IBlock*) {}; + + // add standard waLBerla boundary handling + timeloop.add() << Sweep(boundaryCollection_.getSweep(), "Sweep: boundary handling", + Set< SUID >::emptySet(), StateSweep::onlyGasAndBoundary) + << Sweep(emptySweep, "Empty sweep: boundary handling", StateSweep::onlyGasAndBoundary); + + if (!(floatIsEqual(globalAcceleration_[0], real_c(0), real_c(1e-14)) && + floatIsEqual(globalAcceleration_[1], real_c(0), real_c(1e-14)) && + floatIsEqual(globalAcceleration_[2], real_c(0), real_c(1e-14)))) + { + // add sweep for weighting force in interface cells with fill level and density + // different versions for codegen because pystencils does not support 'Ghostlayerfield<Vector3(), 1>' + const ForceDensityCodegenSweep< StorageSpecification_T, FlagField_T, VectorField_T, ScalarField_T > + forceDensityCodegenSweep(forceDensityFieldID_, pdfFieldID_, flagFieldID_, fillFieldID_, flagInfo_, + globalAcceleration_); + timeloop.add() << Sweep(forceDensityCodegenSweep, "Sweep: force weighting", Set< SUID >::emptySet(), + StateSweep::onlyGasAndBoundary) + << Sweep(emptySweep, "Empty sweep: force weighting", StateSweep::onlyGasAndBoundary) + << AfterFunction(CommunicationCorner_T(blockForest_, forceDensityFieldID_), + "Communication: after force weighting sweep"); + } + + // sweep for + // - reconstruction of PDFs in interface cells + // - streaming of PDFs in interface cells (and liquid cells on the same block) + // - advection of mass + // - update bubble volumes + // - marking interface cells for conversion + const StreamReconstructAdvectSweep< StorageSpecification_T, SweepCollection_T, + FlagField_T, FlagInfo<FlagField_T>, + ScalarField_T, VectorField_T > + streamReconstructAdvectSweep(sweepCollection_, surfaceTension_, fillFieldID_, + flagFieldID_, pdfFieldID_, normalFieldID_, curvatureFieldID_, flagInfo_, + pdfReconstructionModel_, useSimpleMassExchange_, + cellConversionThreshold_, cellConversionForceThreshold_); + // sweep acts only on blocks with at least one interface cell (due to StateSweep::fullFreeSurface) + timeloop.add() + << Sweep(streamReconstructAdvectSweep, "Sweep: StreamReconstructAdvect", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: StreamReconstructAdvect") + // do not communicate PDFs here: + // - stream on blocks with "StateSweep::fullFreeSurface" was performed here using post-collision PDFs + // - stream on other blocks is performed below and should also use post-collision PDFs + // => if PDFs were communicated here, the ghost layer of other blocks would have post-stream PDFs + << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_, flagFieldID_), + "Communication: after StreamReconstructAdvect sweep") + << AfterFunction(lbm_generated::UpdateSecondGhostLayer< ScalarField_T >(blockForest_, fillFieldID_), + "Second ghost layer update: after StreamReconstructAdvect sweep (fill level field)") + << AfterFunction(lbm_generated::UpdateSecondGhostLayer< FlagField_T >(blockForest_, flagFieldID_), + "Second ghost layer update: after StreamReconstructAdvect sweep (flag field)"); + + timeloop.add() << Sweep(sweepCollection_.collide(), "Sweep: collision (generated)", StateSweep::fullFreeSurface) + << Sweep(sweepCollection_.streamCollide(), "Sweep: streamCollide (generated)", StateSweep::onlyLBM) + << Sweep(emptySweep, "Empty sweep: streamCollide (generated)") + << AfterFunction(Communication_T(blockForest_, pdfFieldID_), + "Communication: after streamCollide (generated)"); + + // convert cells + // - according to the flags from StreamReconstructAdvectSweep (interface -> gas/liquid) + // - to ensure a closed layer of interface cells (gas/liquid -> interface) + // - detect and register bubble merges/splits (bubble volumes are already updated in StreamReconstructAdvectSweep) + // - convert cells and initialize PDFs near inflow boundaries + const CellConversionSweep< StorageSpecification_T, FlagField_T, ScalarField_T > + cellConvSweep(flagFieldID_, pdfFieldID_, flagInfo_); + timeloop.add() << Sweep(cellConvSweep, "Sweep: cell conversion", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: cell conversion") + << AfterFunction(Communication_T(blockForest_, pdfFieldID_), + "Communication: after cell conversion sweep (PDF field)") + // communicate the flag field also in corner directions + << AfterFunction(CommunicationCorner_T(blockForest_, flagFieldID_), + "Communication: after cell conversion sweep (flag field)") + << AfterFunction(lbm_generated::UpdateSecondGhostLayer< FlagField_T >(blockForest_, flagFieldID_), + "Second ghost layer update: after cell conversion sweep (flag field)"); + + // reinitialize PDFs, i.e., refill cells that were converted from gas to interface + // - when the flag "convertedFromGasToInterface" has been set (by CellConversionSweep) + // - according to the method specified with pdfRefillingModel_ + switch (pdfRefillingModel_.getModelType()) + { // the scope for each "case" is required since variables are defined within "case" + case PdfRefillingModel::RefillingModel::EquilibriumRefilling: { + const EquilibriumRefillingSweep< StorageSpecification_T, FlagField_T > equilibriumRefillingSweep( + pdfFieldID_, flagFieldID_, flagInfo_, true); + timeloop.add() << Sweep(equilibriumRefillingSweep, "Sweep: EquilibriumRefilling", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: EquilibriumRefilling") + << AfterFunction(Communication_T(blockForest_, pdfFieldID_), + "Communication: after EquilibriumRefilling sweep"); + break; + } + case PdfRefillingModel::RefillingModel::AverageRefilling: { + const AverageRefillingSweep< StorageSpecification_T, FlagField_T > averageRefillingSweep(pdfFieldID_, flagFieldID_, + flagInfo_, true); + timeloop.add() << Sweep(averageRefillingSweep, "Sweep: AverageRefilling", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: AverageRefilling") + << AfterFunction(Communication_T(blockForest_, pdfFieldID_), + "Communication: after AverageRefilling sweep"); + break; + } + case PdfRefillingModel::RefillingModel::EquilibriumAndNonEquilibriumRefilling: { + // default: extrapolation order: 0 + const EquilibriumAndNonEquilibriumRefillingSweep< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T > + equilibriumAndNonEquilibriumRefillingSweep(pdfFieldID_, flagFieldID_, fillFieldID_, flagInfo_, uint_c(0), + true); + timeloop.add() << Sweep(equilibriumAndNonEquilibriumRefillingSweep, + "Sweep: EquilibriumAndNonEquilibriumRefilling sweep", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: EquilibriumAndNonEquilibriumRefilling") + << AfterFunction(Communication_T(blockForest_, pdfFieldID_), + "Communication: after EquilibriumAndNonEquilibriumRefilling sweep"); + break; + } + case PdfRefillingModel::RefillingModel::ExtrapolationRefilling: { + // default: extrapolation order: 2 + const ExtrapolationRefillingSweep< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T > + extrapolationRefillingSweep(pdfFieldID_, flagFieldID_, fillFieldID_, flagInfo_, uint_c(2), true); + timeloop.add() << Sweep(extrapolationRefillingSweep, "Sweep: ExtrapolationRefilling", + StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: ExtrapolationRefilling") + << AfterFunction(Communication_T(blockForest_, pdfFieldID_), + "Communication: after ExtrapolationRefilling sweep"); + break; + } + case PdfRefillingModel::RefillingModel::GradsMomentsRefilling: { + const GradsMomentsRefillingSweep< StorageSpecification_T, FlagField_T > gradsMomentRefillingSweep( + pdfFieldID_, flagFieldID_, flagInfo_, relaxationRate_, true); + timeloop.add() << Sweep(gradsMomentRefillingSweep, "Sweep: GradsMomentRefilling", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: GradsMomentRefilling") + << AfterFunction(Communication_T(blockForest_, pdfFieldID_), + "Communication: after GradsMomentRefilling sweep"); + break; + } + default: + WALBERLA_ABORT("The specified pdf refilling model is not available."); + } + + // distribute excess mass: + // - excess mass: mass that is free after conversion from interface to gas/liquid cells + // - update the bubble model + // IMPORTANT REMARK: this sweep computes the mass via the density, i.e., the PDF field must be up-to-date and the + // PdfRefillingSweep must have been performed + if (excessMassDistributionModel_.isEvenlyType()) + { + const ExcessMassDistributionSweepInterfaceEvenly< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T > + distributeMassSweep(excessMassDistributionModel_, fillFieldID_, flagFieldID_, pdfFieldID_, flagInfo_); + timeloop.add() << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: distribute excess mass") + << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_), + "Communication: after excess mass distribution sweep") + << AfterFunction(lbm_generated::UpdateSecondGhostLayer< ScalarField_T >(blockForest_, fillFieldID_), + "Second ghost layer update: after excess mass distribution sweep (fill level field)"); + } + else + { + if (excessMassDistributionModel_.isWeightedType()) + { + const ExcessMassDistributionSweepInterfaceWeighted< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T > + distributeMassSweep(excessMassDistributionModel_, fillFieldID_, flagFieldID_, pdfFieldID_, flagInfo_, + normalFieldID_); + timeloop.add() << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: distribute excess mass") + << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_), + "Communication: after excess mass distribution sweep") + << AfterFunction(lbm_generated::UpdateSecondGhostLayer< ScalarField_T >(blockForest_, fillFieldID_), + "Second ghost layer update: after excess mass distribution sweep (fill level field)"); + } + else + { + if (excessMassDistributionModel_.isEvenlyAllInterfaceFallbackLiquidType()) + { + const ExcessMassDistributionSweepInterfaceAndLiquid< StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T > + distributeMassSweep(excessMassDistributionModel_, fillFieldID_, flagFieldID_, pdfFieldID_, flagInfo_, + excessMassFieldID_); + timeloop.add() + // perform this sweep also on "onlylbm_generated" blocks because liquid cells also exchange excess mass here + << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::fullFreeSurface) + << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::onlyLBM) + << Sweep(emptySweep, "Empty sweep: distribute excess mass") + << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_, excessMassFieldID_), + "Communication: after excess mass distribution sweep") + << AfterFunction(lbm_generated::UpdateSecondGhostLayer< ScalarField_T >(blockForest_, fillFieldID_), + "Second ghost layer update: after excess mass distribution sweep (fill level field)"); + } + } + } + + // reset all flags that signal cell conversions (except "keepInterfaceForWettingFlag") + ConversionFlagsResetSweep< FlagField_T > resetConversionFlagsSweep(flagFieldID_, flagInfo_); + timeloop.add() << Sweep(resetConversionFlagsSweep, "Sweep: conversion flag reset", StateSweep::fullFreeSurface) + << Sweep(emptySweep, "Empty sweep: conversion flag reset") + << AfterFunction(CommunicationCorner_T(blockForest_, flagFieldID_), + "Communication: after excess mass distribution sweep") + << AfterFunction(lbm_generated::UpdateSecondGhostLayer< FlagField_T >(blockForest_, flagFieldID_), + "Second ghost layer update: after excess mass distribution sweep (flag field)"); + + // update block states + timeloop.add() << Sweep(blockStateUpdate, "Sweep: block state update"); + } + + private: + std::shared_ptr< StructuredBlockForest > blockForest_; + + SweepCollection_T & sweepCollection_; + BoundaryCollection_T & boundaryCollection_; + FlagInfo_T& flagInfo_; + + BlockDataID pdfFieldID_; + BlockDataID flagFieldID_; + BlockDataID fillFieldID_; + BlockDataID forceDensityFieldID_; + + ConstBlockDataID normalFieldID_; + ConstBlockDataID curvatureFieldID_; + + PdfReconstructionModel pdfReconstructionModel_; + PdfRefillingModel pdfRefillingModel_; + ExcessMassDistributionModel excessMassDistributionModel_; + real_t relaxationRate_; + Vector3< real_t > globalAcceleration_; + real_t surfaceTension_; + bool useSimpleMassExchange_; + real_t cellConversionThreshold_; + real_t cellConversionForceThreshold_; + + BlockDataID excessMassFieldID_ = BlockDataID(); +}; // class SurfaceDynamicsHandler + +} // namespace free_surface_generated +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/dynamics/functionality/AdvectMass.h b/src/lbm_generated/free_surface/dynamics/functionality/AdvectMass.h new file mode 100644 index 0000000000000000000000000000000000000000..ad8ba33ba445256969070b28bc2263d22dd75efb --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/functionality/AdvectMass.h @@ -0,0 +1,315 @@ +//====================================================================================================================== +// +// 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 AdvectMass.h +//! \ingroup dynamics +//! \author Martin Bauer +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Calculate the mass advection for a single interface cell. +// +//====================================================================================================================== + +#pragma once + +#include "core/Abort.h" +#include "core/logging/Logging.h" +#include "core/timing/TimingPool.h" + +#include "domain_decomposition/IBlock.h" + +#include "lbm_generated/field/PdfField.h" +#include "lbm_generated/free_surface/FlagInfo.h" + +#include <type_traits> + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Calculate the mass advection for a single interface cell and returns the mass delta for this cell. + * + * With "useSimpleMassExchange==true", mass exchange is performed according to the paper from Koerner et al., 2005, + * equation (9). That is, mass is exchanged regardless of the cells' neighborhood. + * Mass exchange of interface cell with + * - obstacle cell: no mass is exchanged + * - gas cell: no mass is exchanged + * - liquid cell: mass exchange is determined by the difference between incoming and outgoing PDFs + * - interface cell: The mass exchange is determined by the difference between incoming and outgoing PDFs weighted with + * the average fill level of both interface cells. This is basically a linear estimate of the wetted + * area between the two cells. + * - free slip cell: mass is exchanged with the cell where the mirrored PDFs are coming from + * - inflow cell: mass exchange as with liquid cell + * - outflow cell: mass exchange as with interface cell (outflow cell is assumed to have the same fill level) + * + * With "useSimpleMassExchange==false", mass is exchanged according to the dissertation of N. Thuerey, + * 2007, sections 4.1 and 4.4, and table 4.1. However, here, the fill level field and density are used for the + * computations instead of storing an additional mass field. + * To ensure a single interface layer, an interface cell is + * - forced to empty if it has no fluid neighbors. + * - forced to fill if it has no gas neighbors. + * This is done by modifying the exchanged PDFs accordingly. If an interface cell is + * - forced to empty, only outgoing PDFs but no incoming PDFs are allowed. + * - forced to fill, only incoming PDFs but no outgoing PDFs are allowed. + * A more detailed description is available in the dissertation of N. Thuerey, 2007, section 4.4 and table 4.1. + * + * neighIt is an iterator pointing to a pre-computed flag field that contains the bitwise OR'ed neighborhood flags of + * the current cell. See free_surface::getOredNeighborhood for more information. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ConstScalarIt_T, typename ConstPdfIt_T, + typename ConstFlagIt_T, typename ConstNeighIt_T, typename FlagInfo_T > +real_t advectMass(const FlagField_T* flagField, const ConstScalarIt_T& fillSrc, const ConstPdfIt_T& pdfFieldIt, + const ConstFlagIt_T& flagFieldIt, const ConstNeighIt_T& neighIt, const FlagInfo_T& flagInfo, + bool useSimpleMassExchange) +{ + using flag_c_t = typename std::remove_const< typename ConstFlagIt_T::value_type >::type; + using flag_n_t = typename std::remove_const< typename ConstNeighIt_T::value_type >::type; + using flag_i_t = typename std::remove_const< typename FlagField_T::value_type >::type; + + static_assert(std::is_same< flag_i_t, flag_c_t >::value && std::is_same< flag_i_t, flag_n_t >::value, + "Flag types have to be equal."); + + WALBERLA_ASSERT(flagInfo.isInterface(flagFieldIt), "Function advectMass() must only be called for interface cell."); + + // determine the type of the current interface cell (similar to section 4.4 in the dissertation of N. Thuerey, + // 2007) neighIt is the pre-computed bitwise OR-ed neighborhood of the cell + bool localNoGasNeig = !flagInfo.isGas(*neighIt); // this cell has no gas neighbor (should be converted to liquid) + bool localNoFluidNeig = !flagInfo.isLiquid(*neighIt); // this cell has no fluid neighbor (should be converted to gas) + bool localStandardCell = !(localNoGasNeig || localNoFluidNeig); // this cell has both gas and fluid neighbors + + // evaluate flag of this cell (flagFieldIt) and not the neighborhood flags (neighIt) + bool localWettingCell = flagInfo.isKeepInterfaceForWetting(*flagFieldIt); // this cell should be kept for wetting + + if (localNoFluidNeig && localNoGasNeig && + !localWettingCell) // this cell has only interface neighbors (interface layer of 3 cells width) + { + // WALBERLA_LOG_WARNING("Interface layer of 3 cells width at cell " << fillSrc.cell()); + // set this cell to standard for enabling regular mass exchange + localNoGasNeig = false; + localNoFluidNeig = false; + localStandardCell = true; + } + + real_t deltaMass = real_c(0); + for (auto dir = StorageSpecification_T::Stencil::beginNoCenter(); dir != StorageSpecification_T::Stencil::end(); ++dir) + { + flag_c_t neighFlag = flagFieldIt.neighbor(*dir); + + bool isFreeSlip = false; // indicates whether dir points to a free slip cell + + // from the viewpoint of the current cell, direction where the PDFs are actually coming from when there is a free + // slip cell in direction dir; explicitly not a Cell object to emphasize that this denotes a direction + Vector3< cell_idx_t > freeSlipDir; + + // determine type of cell where the mirrored PDFs at free slip boundary are coming from + if (flagInfo.isFreeSlip(neighFlag)) + { + // REMARK: the following implementation is based on lbm/boundary/FreeSlip.h + + // get components of inverse direction of dir + const int ix = stencil::cx[stencil::inverseDir[*dir]]; + const int iy = stencil::cy[stencil::inverseDir[*dir]]; + const int iz = stencil::cz[stencil::inverseDir[*dir]]; + + int wnx = 0; // compute "normal" vector of free slip wall + int wny = 0; + int wnz = 0; + + // from the current cell, go into neighboring cell in direction dir and from there determine the type of the + // neighboring cell in ix, iy and iz direction + const auto flagFieldFreeSlipPtrX = typename FlagField_T::ConstPtr( + *flagField, flagFieldIt.x() + dir.cx() + ix, flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz()); + if (flagInfo.isLiquid(*flagFieldFreeSlipPtrX) || flagInfo.isInterface(*flagFieldFreeSlipPtrX)) { wnx = ix; } + + const auto flagFieldFreeSlipPtrY = typename FlagField_T::ConstPtr( + *flagField, flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy() + iy, flagFieldIt.z() + dir.cz()); + if (flagInfo.isLiquid(*flagFieldFreeSlipPtrY) || flagInfo.isInterface(*flagFieldFreeSlipPtrY)) { wny = iy; } + + const auto flagFieldFreeSlipPtrZ = typename FlagField_T::ConstPtr( + *flagField, flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz() + iz); + if (flagInfo.isLiquid(*flagFieldFreeSlipPtrZ) || flagInfo.isInterface(*flagFieldFreeSlipPtrZ)) { wnz = iz; } + + // flagFieldFreeSlipPtr denotes the cell from which the PDF is coming from + const auto flagFieldFreeSlipPtr = + typename FlagField_T::ConstPtr(*flagField, flagFieldIt.x() + dir.cx() + wnx, + flagFieldIt.y() + dir.cy() + wny, flagFieldIt.z() + dir.cz() + wnz); + + // no mass must be exchanged if: + // - PDFs are not coming from liquid or interface cells + // - PDFs are mirrored from this cell (deltaPdf=0) + if ((!flagInfo.isLiquid(*flagFieldFreeSlipPtr) && !flagInfo.isInterface(*flagFieldFreeSlipPtr)) || + flagFieldFreeSlipPtr.cell() == flagFieldIt.cell()) + { + continue; + } + else + { + // update neighFlag such that it does contain the flags of the cell that mirrored at the free slip boundary; + // PDFs from the boundary cell can be used because they were correctly updated by the boundary handling + neighFlag = *flagFieldFreeSlipPtr; + + // direction of the cell that is mirrored at the free slip boundary + freeSlipDir = Vector3< cell_idx_t >(cell_idx_c(dir.cx() + wnx), cell_idx_c(dir.cy() + wny), + cell_idx_c(dir.cz() + wnz)); + isFreeSlip = true; + } + } + + // no mass exchange with gas and obstacle cells that are neither inflow nor outflow + if (flagInfo.isGas(neighFlag) || + (flagInfo.isObstacle(neighFlag) && !flagInfo.isInflow(neighFlag) && !flagInfo.isOutflow(neighFlag))) + { + continue; + } + + // PDF pointing from neighbor to current cell + real_t neighborPdf = real_c(0); + if (!isFreeSlip) { neighborPdf = pdfFieldIt.neighbor(*dir, dir.toInvIdx()); } + else + { + // get PDF reflected at free slip boundary condition + stencil::Direction neighborPdfDir = *dir; + + if (freeSlipDir[0] != cell_idx_c(0)) { neighborPdfDir = stencil::mirrorX[neighborPdfDir]; } + if (freeSlipDir[1] != cell_idx_c(0)) { neighborPdfDir = stencil::mirrorY[neighborPdfDir]; } + if (freeSlipDir[2] != cell_idx_c(0)) { neighborPdfDir = stencil::mirrorZ[neighborPdfDir]; } + neighborPdf = pdfFieldIt.neighbor(freeSlipDir[0], freeSlipDir[1], freeSlipDir[2], + StorageSpecification_T::Stencil::idx[neighborPdfDir]); + } + + // PDF pointing to neighbor + const real_t localPdf = pdfFieldIt.getF(dir.toIdx()); + + // mass exchange with liquid cells (inflow cells are considered to be liquid) + if (flagInfo.isLiquid(neighFlag) || flagInfo.isInflow(neighFlag)) + { + // mass exchange is difference between incoming and outgoing PDFs (see equation (9) in Koerner et al., 2005) + deltaMass += neighborPdf - localPdf; + continue; + } + + // assert cells that are neither gas, obstacle nor interface + WALBERLA_ASSERT(flagInfo.isInterface(neighFlag) || flagInfo.isOutflow(neighFlag), + "In cell " << fillSrc.cell() << ", flag of neighboring cell " + << Cell(fillSrc.x() + dir.cx(), fillSrc.y() + dir.cy(), fillSrc.z() + dir.cz()) + << " is not plausible."); + + // direction of the cell from which the PDFs are coming from + const Vector3< cell_idx_t > relevantDir = + isFreeSlip ? freeSlipDir : + Vector3< cell_idx_t >(cell_idx_c(dir.cx()), cell_idx_c(dir.cy()), cell_idx_c(dir.cz())); + + // determine the type of the neighboring cell (similar to section 4.4 in the dissertation of N. Thuerey, 2007) + bool neighborNoGasNeig = !flagInfo.isGas(neighIt.neighbor(relevantDir[0], relevantDir[1], relevantDir[2])); + bool neighborNoFluidNeig = !flagInfo.isLiquid(neighIt.neighbor(relevantDir[0], relevantDir[1], relevantDir[2])); + bool neighborStandardCell = !(neighborNoGasNeig || neighborNoFluidNeig); + bool neighborWettingCell = flagInfo.isKeepInterfaceForWetting(flagFieldIt.neighbor( + relevantDir[0], relevantDir[1], + relevantDir[2])); // evaluate flag of this cell (flagFieldIt) and not the neighborhood flags (neighIt) + + if (neighborNoGasNeig && neighborNoFluidNeig && + !neighborWettingCell) // neighboring cell has only interface neighbors + { + // WALBERLA_LOG_WARNING("Interface layer of 3 cells width at cell " << fillSrc.cell()); + // set neighboring cell to standard for enabling regular mass exchange + neighborNoGasNeig = false; + neighborNoFluidNeig = false; + neighborStandardCell = true; + } + + const real_t localFill = *fillSrc; + const real_t neighborFill = fillSrc.neighbor(relevantDir[0], relevantDir[1], relevantDir[2]); + + real_t fillAvg = real_c(0); + real_t deltaPdf = real_c(0); // deltaMass = fillAvg * deltaPdf (see equation (9) in Koerner et al., 2005) + + // both cells are interface cells (standard mass exchange) + // see paper of C. Koerner et al., 2005, equation (9) + // see dissertation of N. Thuerey, 2007, table 4.1: (standard at x) <-> (standard at x_nb); + if (useSimpleMassExchange || (localStandardCell && (neighborStandardCell || neighborWettingCell)) || + (neighborStandardCell && (localStandardCell || localWettingCell)) || flagInfo.isOutflow(neighFlag)) + { + if (flagInfo.isOutflow(neighFlag)) + { + fillAvg = localFill; // use local fill level only, since outflow cells do not have a meaningful fill level + } + else { fillAvg = real_c(0.5) * (neighborFill + localFill); } + + deltaPdf = neighborPdf - localPdf; + } + else + { + // see dissertation of N. Thuerey, 2007, table 4.1: + // (standard at x) <-> (no empty neighbors at x_nb) + // (no fluid neighbors at x) <-> (standard cell at x_nb) + // (no fluid neighbors at x) <-> (no empty neighbors at x_nb) + // => push local, i.e., this cell empty (if it is not a cell needed for wetting) + if (((localStandardCell && neighborNoGasNeig) || (localNoFluidNeig && !neighborNoFluidNeig)) && + !localWettingCell) + { + fillAvg = real_c(0.5) * (neighborFill + localFill); + deltaPdf = -localPdf; + } + else + { + // see dissertation of N. Thuerey, 2007, table 4.1: + // (standard at x) <-> (no fluid neighbors at x_nb) + // (no empty neighbors at x) <-> (standard cell at x_nb) + // (no empty neighbors at x) <-> (no fluid neighbors at x_nb) + // => push neighboring cell empty (if it is not a cell needed for wetting) + if (((localStandardCell && neighborNoFluidNeig) || (localNoGasNeig && !neighborNoGasNeig)) && + !neighborWettingCell) + { + fillAvg = real_c(0.5) * (neighborFill + localFill); + deltaPdf = neighborPdf; + } + else + { + // see dissertation of N. Thuerey, 2007, table 4.1: + // (no fluid neighbors at x) <-> (no fluid neighbors at x_nb) + // (no empty neighbors at x) <-> (no empty neighbors at x_nb) + if ((localNoFluidNeig && neighborNoFluidNeig) || (localNoGasNeig && neighborNoGasNeig)) + { + fillAvg = real_c(0.5) * (neighborFill + localFill); + deltaPdf = (neighborPdf - localPdf); + } + else + { + // treat remaining cases that were not covered above and include wetting cells + if (localWettingCell || neighborWettingCell) + { + fillAvg = real_c(0.5) * (neighborFill + localFill); + deltaPdf = (neighborPdf - localPdf); + } + else + { + WALBERLA_ABORT("Unknown mass advection combination of flags (loc=" << *flagFieldIt << ", neig=" + << neighFlag << ")"); + } + } + } + } + } + // this cell's deltaMass is the sum over all stencil directions (see dissertation of N. Thuerey, 2007, equation + // (4.4)) + deltaMass += fillAvg * deltaPdf; + } + + return deltaMass; +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/functionality/CMakeLists.txt b/src/lbm_generated/free_surface/dynamics/functionality/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..244965327dd3c2eba13dfb06559e47aeadc17c29 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/functionality/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources( lbm_generated + PRIVATE + AdvectMass.h + FindInterfaceCellConversion.h + GetLaplacePressure.h + GetOredNeighborhood.h + ReconstructInterfaceCellABB.h + ) diff --git a/src/lbm_generated/free_surface/dynamics/functionality/FindInterfaceCellConversion.h b/src/lbm_generated/free_surface/dynamics/functionality/FindInterfaceCellConversion.h new file mode 100644 index 0000000000000000000000000000000000000000..d8db238c583dd46ea854add9c0e401be40d061eb --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/functionality/FindInterfaceCellConversion.h @@ -0,0 +1,257 @@ +//====================================================================================================================== +// +// 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 FindInterfaceCellConversion.h +//! \ingroup dynamics +//! \author Martin Bauer +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Find and mark interface cells for conversion to gas/liquid. +// +//====================================================================================================================== + +#pragma once + +#include "core/debug/Debug.h" +#include "core/logging/Logging.h" +#include "core/timing/TimingPool.h" + +#include "lbm_generated/free_surface/FlagInfo.h" + +#include <type_traits> + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Finds interface cell conversions to gas/liquid and sets corresponding conversion flag that marks this cell for + * conversion. + * + * This version uses the cached OR'ed flag neighborhood given in neighborFlags. See free_surface::getOredNeighborhood. + * + * This function decides by looking at the fill level which interface cells should be converted to gas or liquid cells. + * It does not change the state directly, it only sets "conversion suggestions" by setting flags called + * 'convertToGasFlag' and 'convertToLiquidFlag'. + * + * An interface is first classified as one of the following types (same classification as in free_surface::advectMass) + * - _to gas_ : if cell has no liquid cell in neighborhood, this cell should become gas + * - _to liquid_: if cell has no gas cell in neighborhood, this cell should become liquid + * - _pure interface_: if not '_to gas_' and not '_to liquid_' + * + * This classification up to now depends only on the neighborhood flags, not on the fill level. + * + * _Pure interface_ cells are marked for to-gas-conversion if their fill level is lower than + * "0 - cellConversionThreshold" and marked for to-liquid-conversion if the fill level is higher than + * "1 + cellConversionThreshold". The threshold is introduced to prevent oscillating cell conversions. + * The value of the offset is chosen heuristically (see dissertation of N. Thuerey, 2007: 1e-3, dissertation of + * T. Pohl, 2008: 1e-2). + * + * Additionally, interface cells without fluid neighbors are marked for conversion to gas if an: + * - interface cell is almost empty (fill < cellConversionForceThreshold) AND + * - interface cell has no neighboring interface cells + * OR + * - interface cell has neighboring cell with outflow boundary condition + * + * Similarly, interface cells without gas neighbors are marked for conversion to liquid if: + * - interface cell is almost full (fill > 1.0-cellConversionForceThreshold) AND + * - interface cell has no neighboring interface cells + * + * The value of cellConversionForceThreshold is chosen heuristically: 1e-1 (see dissertation of N. Thuerey, 2007, + * section 4.4). + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename ScalarField_T, typename FlagField_T, + typename ScalarIt_T, typename FlagIt_T > +void findInterfaceCellConversion(const ScalarIt_T& fillFieldIt, + FlagIt_T& flagFieldIt, const typename FlagField_T::flag_t& neighborFlags, + const FlagInfo< FlagField_T >& flagInfo, real_t cellConversionThreshold, + real_t cellConversionForceThreshold) +{ + static_assert(std::is_same< typename FlagField_T::value_type, typename FlagIt_T::value_type >::value, + "The given flagFieldIt does not seem to be an iterator of the provided FlagField_T."); + + static_assert(std::is_floating_point< typename ScalarIt_T::value_type >::value, + "The given fillFieldIt has to be a floating point value."); + + cellConversionThreshold = std::abs(cellConversionThreshold); + cellConversionForceThreshold = std::abs(cellConversionForceThreshold); + + WALBERLA_ASSERT_LESS(cellConversionThreshold, cellConversionForceThreshold); + + // in the neighborhood of inflow boundaries, convert gas cells to interface cells depending on the direction of the + // prescribed inflow velocity + // TODO implement again + /* + if (field::isFlagSet(neighborFlags, flagInfo.inflowFlagMask) && field::isFlagSet(flagFieldIt, flagInfo.gasFlag)) + { + // get UBB inflow boundary + auto ubbInflow = handling->template getBoundaryCondition< + typename FreeSurfaceBoundaryHandling< LatticeModel_T, FlagField_T, ScalarField_T >::UBB_Inflow_T >( + handling->getBoundaryUID( + FreeSurfaceBoundaryHandling< LatticeModel_T, FlagField_T, ScalarField_T >::ubbInflowFlagID)); + + for (auto d = StorageSpecification_T::Stencil::beginNoCenter(); d != StorageSpecification_T::Stencil::end(); ++d) + { + if (field::isMaskSet(flagFieldIt.neighbor(*d), flagInfo.inflowFlagMask)) + { + // get direction of cell containing inflow boundary + const Vector3< int > dir = Vector3< int >(-d.cx(), -d.cy(), -d.cz()); + + // get velocity from UBB inflow boundary + const Vector3< real_t > inflowVel = + ubbInflow.getValue(flagFieldIt.x() + d.cx(), flagFieldIt.y() + d.cy(), flagFieldIt.z() + d.cz()); + + // skip directions in which the corresponding velocity component is zero + if (realIsEqual(inflowVel[0], real_c(0), real_c(1e-14)) && dir[0] != 0) { continue; } + if (realIsEqual(inflowVel[1], real_c(0), real_c(1e-14)) && dir[1] != 0) { continue; } + if (realIsEqual(inflowVel[2], real_c(0), real_c(1e-14)) && dir[2] != 0) { continue; } + + // skip directions in which the corresponding velocity component is in opposite direction + if (inflowVel[0] > real_c(0) && dir[0] < 0) { continue; } + if (inflowVel[1] > real_c(0) && dir[1] < 0) { continue; } + if (inflowVel[2] > real_c(0) && dir[2] < 0) { continue; } + + // set conversion flag to remaining cells + field::addFlag(flagFieldIt, flagInfo.convertToInterfaceForInflowFlag); + } + } + return; + }*/ + + // only interface cells are converted directly (except for cells near inflow boundaries, see above) + if (!field::isFlagSet(flagFieldIt, flagInfo.interfaceFlag)) { return; } + + // interface cell is empty and should be converted to gas + if (*fillFieldIt < -cellConversionThreshold) + { + if (field::isFlagSet(flagFieldIt, flagInfo.keepInterfaceForWettingFlag)) + { + field::removeFlag(flagFieldIt, flagInfo.keepInterfaceForWettingFlag); + } + + field::addFlag(flagFieldIt, flagInfo.convertToGasFlag); + return; + } + + // interface cell is full and should be converted to liquid + if (*fillFieldIt > real_c(1.0) + cellConversionThreshold) + { + if (field::isFlagSet(flagFieldIt, flagInfo.keepInterfaceForWettingFlag)) + { + field::removeFlag(flagFieldIt, flagInfo.keepInterfaceForWettingFlag); + } + + field::addFlag(flagFieldIt, flagInfo.convertToLiquidFlag); + return; + } + + // interface cell has no liquid neighbor and should be converted to gas (see dissertation of N. Thuerey, 2007 + // section 4.4) + if (!field::isFlagSet(neighborFlags, flagInfo.liquidFlag) && + !field::isFlagSet(flagFieldIt, flagInfo.keepInterfaceForWettingFlag) && + !field::isFlagSet(neighborFlags, flagInfo.inflowFlagMask)) + { + // interface cell is almost empty + if (*fillFieldIt < cellConversionForceThreshold && field::isFlagSet(neighborFlags, flagInfo.interfaceFlag)) + { + // mass is not necessarily lost as it can be distributed to a neighboring interface cell + field::addFlag(flagFieldIt, flagInfo.convertToGasFlag); + return; + } + + // interface cell has no other interface neighbors; conversion might lead to loss in mass (depending on the excess + // mass distribution model) + if (!field::isFlagSet(neighborFlags, flagInfo.interfaceFlag)) + { + field::addFlag(flagFieldIt, flagInfo.convertToGasFlag); + return; + } + } + + // interface cell has no gas neighbor and should be converted to liquid (see dissertation of N. Thuerey, 2007 + // section 4.4) + if (!field::isFlagSet(neighborFlags, flagInfo.gasFlag) && + !field::isFlagSet(flagFieldIt, flagInfo.keepInterfaceForWettingFlag)) + { + // interface cell is almost full + if (*fillFieldIt > real_c(1.0) - cellConversionForceThreshold && + field::isFlagSet(neighborFlags, flagInfo.interfaceFlag)) + { + // mass is not necessarily gained as it can be taken from a neighboring interface cell + field::addFlag(flagFieldIt, flagInfo.convertToLiquidFlag); + return; + } + + // interface cell has no other interface neighbors; conversion might lead to gain in mass (depending on the excess + // mass distribution model) + if (!field::isFlagSet(neighborFlags, flagInfo.interfaceFlag)) + { + field::addFlag(flagFieldIt, flagInfo.convertToLiquidFlag); + return; + } + } +} + +/*********************************************************************************************************************** + * Triggers findInterfaceCellConversion() for each cell of the given fields and recomputes the OR'ed flag neighborhood + * info. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename ScalarField_T, typename FlagField_T > +void findInterfaceCellConversions(const ScalarField_T* fillField, + FlagField_T* flagField, const FlagInfo< FlagField_T >& flagInfo, + real_t cellConversionThreshold, real_t cellConversionForceThreshold) +{ + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP(flagField, uint_c(1), omp critical, { + const typename FlagField_T::Ptr flagFieldPtr(*flagField, x, y, z); + const typename ScalarField_T::ConstPtr fillFieldPtr(*fillField, x, y, z); + + if (field::isFlagSet(flagFieldPtr, flagInfo.interfaceFlag)) + { + const typename FlagField_T::value_type neighborFlags = + field::getOredNeighborhood< typename StorageSpecification_T::Stencil >(flagFieldPtr); + + (findInterfaceCellConversion< StorageSpecification_T, ScalarField_T, + FlagField_T >) (fillFieldPtr, flagFieldPtr, neighborFlags, flagInfo, + cellConversionThreshold, cellConversionForceThreshold); + } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ +} + +/*********************************************************************************************************************** + * Triggers findInterfaceCellConversion() for each cell of the given fields using the cached OR'ed flag neighborhood + * given in neighField. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename ScalarField_T, typename FlagField_T, + typename NeighField_T > +void findInterfaceCellConversions(const ScalarField_T* fillField, + FlagField_T* flagField, const NeighField_T* neighField, + const FlagInfo< FlagField_T >& flagInfo, real_t cellConversionThreshold, + real_t cellConversionForceThreshold) +{ + WALBERLA_ASSERT_EQUAL_2(flagField->xyzSize(), fillField->xyzSize()); + WALBERLA_ASSERT_EQUAL_2(flagField->xyzSize(), neighField->xyzSize()); + + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP(flagField, uint_c(1), omp critical, { + const typename FlagField_T::Ptr flagFieldPtr(*flagField, x, y, z); + const typename ScalarField_T::ConstPtr fillFieldPtr(*fillField, x, y, z); + + (findInterfaceCellConversion< StorageSpecification_T, ScalarField_T, + FlagField_T >) (fillFieldPtr, flagFieldPtr, neighField->get(x, y, z), + flagInfo, cellConversionThreshold, cellConversionForceThreshold); + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/functionality/GetLaplacePressure.h b/src/lbm_generated/free_surface/dynamics/functionality/GetLaplacePressure.h new file mode 100644 index 0000000000000000000000000000000000000000..6f3f1bf1c7ad96b90ac2a9b0471b51bad713ad39 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/functionality/GetLaplacePressure.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 GetLaplacePressure.h +//! \ingroup dynamics +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Compute difference in density due to Laplace pressure. +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/logging/Logging.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Compute difference in density due to Laplace pressure. + **********************************************************************************************************************/ +inline real_t computeDeltaRhoLaplacePressure(real_t sigma, real_t curvature, real_t maxDeltaRho = real_c(0.1)) +{ + static const real_t inv_cs2 = real_c(3); // 1.0 / (cs * cs) + const real_t laplacePressure = real_c(2) * sigma * curvature; + real_t deltaRho = inv_cs2 * laplacePressure; + + if (deltaRho > maxDeltaRho) + { + WALBERLA_LOG_WARNING("Too large density variation of " << deltaRho << " due to laplacePressure " + << laplacePressure << " with curvature " << curvature + << ". Will be limited to " << maxDeltaRho << ".\n"); + deltaRho = maxDeltaRho; + } + if (deltaRho < -maxDeltaRho) + { + WALBERLA_LOG_WARNING("Too large density variation of " << deltaRho << " due to laplacePressure " + << laplacePressure << " with curvature " << curvature + << ". Will be limited to " << -maxDeltaRho << ".\n"); + deltaRho = -maxDeltaRho; + } + + return deltaRho; +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/functionality/GetOredNeighborhood.h b/src/lbm_generated/free_surface/dynamics/functionality/GetOredNeighborhood.h new file mode 100644 index 0000000000000000000000000000000000000000..28008819ba81eae7c1eb997b32ae778987ed91f4 --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/functionality/GetOredNeighborhood.h @@ -0,0 +1,62 @@ +//====================================================================================================================== +// +// 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 GetOredNeighborhood.h +//! \ingroup dynamics +//! \author Martin Bauer +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Combines the flags of all neighboring cells using bitwise OR. +// +//====================================================================================================================== + +#pragma once + +#include "core/debug/Debug.h" +#include "core/timing/TimingPool.h" + +#include "field/FlagField.h" + +#include <type_traits> + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Combines the flags of all neighboring cells using bitwise OR. + * Flags are read from flagField and stored in neighborhoodFlagField. Every cell contains the bitwise OR of the + * neighboring flags in flagField. + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T > +void getOredNeighborhood(const FlagField_T* flagField, FlagField_T* neighborhoodFlagField) +{ + WALBERLA_ASSERT_GREATER_EQUAL(flagField->nrOfGhostLayers(), 2); + WALBERLA_ASSERT_EQUAL(neighborhoodFlagField->xyzSize(), flagField->xyzSize()); + WALBERLA_ASSERT_EQUAL(neighborhoodFlagField->xyzAllocSize(), flagField->xyzAllocSize()); + + // REMARK: here is the reason why the flag field MUST have two ghost layers; + // the "OredNeighborhood" of the first ghost layer is determined, such that the first ghost layer's neighbors (i.e., + // the second ghost layer) must be available + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(flagField, uint_c(1), { + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + const typename FlagField_T::Ptr neighborhoodFlagFieldPtr(*neighborhoodFlagField, x, y, z); + + *neighborhoodFlagFieldPtr = field::getOredNeighborhood< Stencil_T >(flagFieldPtr); + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOSTLAYER_XYZ +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/dynamics/functionality/ReconstructInterfaceCellABB.h b/src/lbm_generated/free_surface/dynamics/functionality/ReconstructInterfaceCellABB.h new file mode 100644 index 0000000000000000000000000000000000000000..a88f914681f880b76c2092fccd9582886bc93d1d --- /dev/null +++ b/src/lbm_generated/free_surface/dynamics/functionality/ReconstructInterfaceCellABB.h @@ -0,0 +1,439 @@ +//====================================================================================================================== +// +// 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 ReconstructInterfaceCellABB.h +//! \ingroup dynamics +//! \author Martin Bauer +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Free surface boundary condition as in Koerner et al., 2005. Similar to anti-bounce-back pressure condition. +// +//====================================================================================================================== + +#pragma once + +#include "lbm_generated/field/PdfField.h" +#include "lbm_generated/free_surface/dynamics/PdfReconstructionModel.h" + +#include "stencil/Directions.h" + +#include "GetLaplacePressure.h" + +namespace walberla +{ +namespace free_surface_generated +{ +// get index of largest entry in n_dot_ci with isInterfaceOrLiquid==true && isPdfAvailable==false +uint_t getIndexOfMaximum(const std::vector< bool >& isInterfaceOrLiquid, const std::vector< bool >& isPdfAvailable, + const std::vector< real_t >& n_dot_ci); + +// get index of smallest entry in n_dot_ci with isInterfaceOrLiquid==true && isPdfAvailable==false +uint_t getIndexOfMinimum(const std::vector< bool >& isInterfaceOrLiquid, const std::vector< bool >& isPdfAvailable, + const std::vector< real_t >& n_dot_ci); + +// reconstruct PDFs according to pressure anti bounce back boundary condition (page 31, equation 4.5 in dissertation of +// N. Thuerey, 2007) +template< typename StorageSpecification_T, typename ConstPdfIt_T > +inline real_t reconstructPressureAntiBounceBack(const stencil::Iterator< typename StorageSpecification_T::Stencil >& dir, + const ConstPdfIt_T& pdfFieldIt, const Vector3< real_t >& u, + real_t rhoGas, real_t dir_independent) +{ + const real_t vel = real_c(dir.cx()) * u[0] + real_c(dir.cy()) * u[1] + real_c(dir.cz()) * u[2]; + + // compute f^{eq}_i + f^{eq}_{\overline{i}} using rhoGas (without linear terms as they cancel out) + const real_t tmp = + real_c(2.0) * StorageSpecification_T::w[dir.toIdx()] * rhoGas * (dir_independent + real_c(4.5) * vel * vel); + + // reconstruct PDFs (page 31, equation 4.5 in dissertation of N. Thuerey, 2007) + return tmp - pdfFieldIt.getF(dir.toIdx()); +} + +/*********************************************************************************************************************** + * Free surface boundary condition as described in the publication of Koerner et al., 2005. Missing PDFs are + * reconstructed according to an anti-bounce-back pressure boundary condition at the free surface. + * + * Be aware that in Koerner et al., 2005, the PDFs are reconstructed in gas cells (neighboring to interface cells). + * These PDFs are then streamed into the interface cells in the LBM stream. Here, we directly reconstruct the PDFs in + * interface cells such that our implementation follows the notation in the dissertation of N. Thuerey, 2007, page 31. + * ********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ConstPdfIt_T, typename ConstFlagIt_T, + typename ConstVectorIt_T, typename OutputArray_T > +void reconstructInterfaceCellLegacy(const FlagField_T* flagField, const ConstPdfIt_T& pdfFieldIt, + const ConstFlagIt_T& flagFieldIt, const ConstVectorIt_T& normalFieldIt, + const FlagInfo< FlagField_T >& flagInfo, const real_t rhoGas, OutputArray_T& f, + const PdfReconstructionModel& pdfReconstructionModel) +{ + using Stencil_T = typename StorageSpecification_T::Stencil; + + // get velocity and density in interface cell + Vector3< real_t > u; + auto pdfField = dynamic_cast< const lbm_generated::PdfField< StorageSpecification_T >* >(pdfFieldIt.getField()); + WALBERLA_ASSERT_NOT_NULLPTR(pdfField); + const real_t rho = lbm_generated::getDensityAndMomentumDensity<StorageSpecification_T>(u, *pdfField, pdfFieldIt.x(), pdfFieldIt.y(), pdfFieldIt.z()); + u /= rho; + + const real_t dir_independent = + real_c(1.0) - real_c(1.5) * u.sqrLength(); // direction independent value used for PDF reconstruction + + // get type of the model that determines the PDF reconstruction + const PdfReconstructionModel::ReconstructionModel reconstructionModel = pdfReconstructionModel.getModelType(); + + // vector that stores the dot product between interface normal and lattice direction for each lattice direction + std::vector< real_t > n_dot_ci; + + // vector that stores which index from loop "for (auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); + // ++dir)" is currently available, i.e.: + // - reconstructed (or scheduled for reconstruction) + // - coming from boundary condition + // - available due to fluid or interface neighbor + std::vector< bool > isPdfAvailable; + + // vector that stores which index from loop "for (auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); + // ++dir)" points to a neighboring interface or fluid cell + std::vector< bool > isInterfaceOrLiquid; + + // count number of reconstructed links + uint_t numReconstructed = uint_c(0); + + for (auto dir = Stencil_T::begin(); dir != Stencil_T::end(); ++dir) + { + const auto neighborFlag = flagFieldIt.neighbor(*dir); + + if (flagInfo.isObstacle(neighborFlag)) + { + // free slip boundaries need special treatment because PDFs traveling from gas cells into the free slip + // boundary must be reconstructed, for instance: + // [I][G] with I: interface cell; G: gas cell; f: free slip cell + // [f][f] + // During streaming, the interface cell's PDF with direction (-1, 1) is coming from the right free slip cell. + // For a free slip boundary, this PDF is identical to the PDF with direction (-1, -1) in the gas cell. Since + // gas-side PDFs are not available, such PDFs must be reconstructed. + // Non-gas cells do not need to be treated here as they are treated correctly by the boundary handling. + + if (flagInfo.isFreeSlip(neighborFlag)) + { + // REMARK: the following implementation is based on lbm/boundary/FreeSlip.h + + // get components of inverse direction of dir + const int ix = stencil::cx[stencil::inverseDir[*dir]]; + const int iy = stencil::cy[stencil::inverseDir[*dir]]; + const int iz = stencil::cz[stencil::inverseDir[*dir]]; + + int wnx = 0; // compute "normal" vector of free slip wall + int wny = 0; + int wnz = 0; + + // from the current cell, go into neighboring cell in direction dir and from there check whether the + // neighbors in ix, iy and iz are gas cells + const auto flagFieldFreeSlipPtrX = typename FlagField_T::ConstPtr( + *flagField, flagFieldIt.x() + dir.cx() + ix, flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz()); + if (flagInfo.isGas(*flagFieldFreeSlipPtrX)) { wnx = ix; } + + const auto flagFieldFreeSlipPtrY = typename FlagField_T::ConstPtr( + *flagField, flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy() + iy, flagFieldIt.z() + dir.cz()); + if (flagInfo.isGas(*flagFieldFreeSlipPtrY)) { wny = iy; } + + const auto flagFieldFreeSlipPtrZ = typename FlagField_T::ConstPtr( + *flagField, flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz() + iz); + if (flagInfo.isGas(*flagFieldFreeSlipPtrZ)) { wnz = iz; } + + if (wnx != 0 || wny != 0 || wnz != 0) + { + // boundaryNeighbor denotes the cell from which the PDF is coming from + const auto flagFieldFreeSlipPtr = + typename FlagField_T::ConstPtr(*flagField, flagFieldIt.x() + dir.cx() + wnx, + flagFieldIt.y() + dir.cy() + wny, flagFieldIt.z() + dir.cz() + wnz); + + if (flagInfo.isGas(*flagFieldFreeSlipPtr)) + { + // reconstruct PDF + f[dir.toInvIdx()] = reconstructPressureAntiBounceBack< StorageSpecification_T, ConstPdfIt_T >( + dir, pdfFieldIt, u, rhoGas, dir_independent); + isPdfAvailable.push_back(true); + isInterfaceOrLiquid.push_back(false); + n_dot_ci.push_back( + real_c(0)); // dummy entry for having index as in vectors isPDFAvailable and isInterfaceOrLiquid + ++numReconstructed; + continue; + } + // else: do nothing here, i.e., make usual obstacle boundary treatment below + } // else: concave corner, all surrounding PDFs are known, i.e., make usual obstacle boundary treatment + // below + } + + f[dir.toInvIdx()] = pdfFieldIt.neighbor(*dir, dir.toInvIdx()); // use PDFs defined by boundary handling + isPdfAvailable.push_back(true); + isInterfaceOrLiquid.push_back(false); + n_dot_ci.push_back( + real_c(0)); // dummy entry for having same indices as in vectors isPDFAvailable and isInterfaceOrLiquid + continue; + } + else + { + if (flagInfo.isGas(neighborFlag)) + { + f[dir.toInvIdx()] = reconstructPressureAntiBounceBack< StorageSpecification_T, ConstPdfIt_T >( + dir, pdfFieldIt, u, rhoGas, dir_independent); + isPdfAvailable.push_back(true); + isInterfaceOrLiquid.push_back(false); + n_dot_ci.push_back( + real_c(0)); // dummy entry for having index as in vectors isPDFAvailable and isInterfaceOrLiquid + ++numReconstructed; + continue; + } + } + + // dot product between interface normal and lattice direction + real_t dotProduct = normalFieldIt.getF(0) * real_c(dir.cx()) + normalFieldIt.getF(1) * real_c(dir.cy()) + normalFieldIt.getF(2) * real_c(dir.cz()); + + // avoid numerical inaccuracies in the computation of the scalar product n_dot_ci + if (realIsEqual(dotProduct, real_c(0), real_c(1e-14))) { dotProduct = real_c(0); } + + // approach from Koerner; reconstruct all PDFs in direction opposite to interface normal and center PDF (not + // stated in the paper explicitly but follows from n*e_i>=0 for i=0) + if (reconstructionModel == PdfReconstructionModel::ReconstructionModel::NormalBasedReconstructCenter) + { + if (dotProduct >= real_c(0)) + { + f[dir.toInvIdx()] = reconstructPressureAntiBounceBack< StorageSpecification_T, ConstPdfIt_T >( + dir, pdfFieldIt, u, rhoGas, dir_independent); + } + else + { + // regular LBM stream with PDFs from neighbor + f[dir.toInvIdx()] = pdfFieldIt.neighbor(*dir, dir.toInvIdx()); + } + continue; + } + + if (reconstructionModel == PdfReconstructionModel::ReconstructionModel::NormalBasedKeepCenter) + { + if (*dir == stencil::C) + { + // use old center PDF + f[Stencil_T::idx[stencil::C]] = pdfFieldIt[Stencil_T::idx[stencil::C]]; + } + else + { + if (dotProduct >= real_c(0)) + { + f[dir.toInvIdx()] = reconstructPressureAntiBounceBack< StorageSpecification_T, ConstPdfIt_T >( + dir, pdfFieldIt, u, rhoGas, dir_independent); + } + else + { + // regular LBM stream with PDFs from neighbor + f[dir.toInvIdx()] = pdfFieldIt.neighbor(*dir, dir.toInvIdx()); + } + } + continue; + } + + // reconstruct all non-obstacle PDFs, including those that come from liquid and are already known + if (reconstructionModel == PdfReconstructionModel::ReconstructionModel::All) + { + f[dir.toInvIdx()] = reconstructPressureAntiBounceBack< StorageSpecification_T, ConstPdfIt_T >(dir, pdfFieldIt, u, + rhoGas, dir_independent); + continue; + } + + // reconstruct only those gas-side PDFs that are really missing + if (reconstructionModel == PdfReconstructionModel::ReconstructionModel::OnlyMissing) + { + // regular LBM stream with PDFs from neighboring interface or liquid cell + f[dir.toInvIdx()] = pdfFieldIt.neighbor(*dir, dir.toInvIdx()); + continue; + } + + // reconstruct only those gas-side PDFs that are really missing but make sure that at least a + // specified number of PDFs are reconstructed (even if available PDFs are overwritten) + if (reconstructionModel == PdfReconstructionModel::ReconstructionModel::OnlyMissingMin) + { + isPdfAvailable.push_back(false); // PDF has not yet been marked for reconstruction + isInterfaceOrLiquid.push_back(true); + n_dot_ci.push_back(dotProduct); + continue; + } + + WALBERLA_ABORT("Unknown pdfReconstructionModel.") + } + + if (reconstructionModel == PdfReconstructionModel::ReconstructionModel::OnlyMissingMin) + { + WALBERLA_ASSERT_EQUAL(Stencil_T::Size, uint_c(n_dot_ci.size())); + WALBERLA_ASSERT_EQUAL(Stencil_T::Size, uint_c(isInterfaceOrLiquid.size())); + WALBERLA_ASSERT_EQUAL(Stencil_T::Size, uint_c(isPdfAvailable.size())); + + const uint_t numMinReconstruct = pdfReconstructionModel.getNumMinReconstruct(); + + // number of remaining PDFs that need to be reconstructed according to the specified model (number can be negative + // => do not use uint_t) + int numRemainingReconstruct = int_c(numMinReconstruct) - int_c(numReconstructed); + + // count the number of neighboring cells that are interface or liquid (and not obstacle or gas) + const uint_t numLiquidNeighbors = + uint_c(std::count_if(isInterfaceOrLiquid.begin(), isInterfaceOrLiquid.end(), [](bool a) { return a; })); + + // // REMARK: this was commented because it regularly occurred in practical simulations (e.g. BreakingDam) + // if (numRemainingReconstruct > int_c(0) && numRemainingReconstruct < int_c(numLiquidNeighbors)) + // { + // // There are less neighboring liquid and interface cells than needed to reconstruct PDFs in the + // // free surface boundary condition. You have probably specified a large minimum number of PDFs to be + // // reconstructed. This happens e.g. near solid boundaries (especially corners). There, the number of + // // surrounding non-obstacle cells might be less than the number of PDFs that you want to have + // // reconstructed. + // WALBERLA_LOG_WARNING_ON_ROOT("Less PDFs reconstructed in cell " + // << pdfFieldIt.cell() + // << " than specified in the PDF reconstruction model. See comment in " + // "source code of ReconstructInterfaceCellABB.h for further information. " + // "Here, as many PDFs as possible are reconstructed now."); + // } + + // count additionally reconstructed PDFs (that come from interface or liquid) + uint_t numAdditionalReconstructed = uint_c(0); + + // define which PDFs to additionally reconstruct (overwrite known information) according to the specified model + while (numRemainingReconstruct > int_c(0) && numAdditionalReconstructed < numLiquidNeighbors) + { + if (pdfReconstructionModel.getFallbackModel() == PdfReconstructionModel::FallbackModel::Largest) + { + // get index of largest n_dot_ci with isInterfaceOrLiquid==true && isPdfAvailable==false + const uint_t maxIndex = getIndexOfMaximum(isInterfaceOrLiquid, isPdfAvailable, n_dot_ci); + isPdfAvailable[maxIndex] = true; // reconstruct this PDF later + ++numReconstructed; + ++numAdditionalReconstructed; + } + else + { + if (pdfReconstructionModel.getFallbackModel() == PdfReconstructionModel::FallbackModel::Smallest) + { + // get index of smallest n_dot_ci with isInterfaceOrLiquid==true && isPdfAvailable==false + const uint_t minIndex = getIndexOfMinimum(isInterfaceOrLiquid, isPdfAvailable, n_dot_ci); + isPdfAvailable[minIndex] = true; // reconstruct this PDF later + ++numReconstructed; + ++numAdditionalReconstructed; + } + else + { + // use approach from Koerner + if (pdfReconstructionModel.getFallbackModel() == + PdfReconstructionModel::FallbackModel::NormalBasedKeepCenter) + { + uint_t index = uint_c(0); + for (const real_t& value : n_dot_ci) + { + if (value >= real_c(0) && index > uint_c(1)) // skip center PDF with index=0 + { + isPdfAvailable[index] = true; // reconstruct this PDF later + } + + ++index; + } + break; // exit while loop + } + else { WALBERLA_ABORT("Unknown fallbackModel in pdfReconstructionModel.") } + } + } + + numRemainingReconstruct = int_c(numMinReconstruct) - int_c(numReconstructed); + } + + // reconstruct additional PDFs + uint_t index = uint_c(0); + for (auto dir = Stencil_T::begin(); dir != Stencil_T::end(); ++dir, ++index) + { + const auto neighborFlag = flagFieldIt.neighbor(*dir); + + // skip links pointing to obstacle and gas neighbors; they were treated above already + if (flagInfo.isObstacle(neighborFlag) || flagInfo.isGas(neighborFlag)) { continue; } + else + { + // reconstruct links that were marked for reconstruction + if (isPdfAvailable[index] && isInterfaceOrLiquid[index]) + { + f[dir.toInvIdx()] = reconstructPressureAntiBounceBack< StorageSpecification_T, ConstPdfIt_T >( + dir, pdfFieldIt, u, rhoGas, dir_independent); + } + else + { + if (!isPdfAvailable[index] && isInterfaceOrLiquid[index]) + { + // regular LBM stream with PDFs from neighbor + f[dir.toInvIdx()] = pdfFieldIt.neighbor(*dir, dir.toInvIdx()); + continue; + } + WALBERLA_ABORT("Error in PDF reconstruction. This point should never be reached.") + } + } + } + } +} + +uint_t getIndexOfMaximum(const std::vector< bool >& isInterfaceOrLiquid, const std::vector< bool >& isPdfAvailable, + const std::vector< real_t >& n_dot_ci) +{ + real_t maximum = -std::numeric_limits< real_t >::max(); + uint_t index = std::numeric_limits< uint_t >::max(); + + for (uint_t i = uint_c(0); i != isInterfaceOrLiquid.size(); ++i) + { + if (isInterfaceOrLiquid[i] && !isPdfAvailable[i]) + { + const real_t absValue = std::abs(n_dot_ci[i]); + if (absValue > maximum) + { + maximum = absValue; + index = i; + } + } + } + + // less Pdfs available for being reconstructed than specified by the user; these assertions should never fail, as the + // conditionals in reconstructInterfaceCellLegacy() should avoid calling this function + WALBERLA_ASSERT(maximum > -real_c(std::numeric_limits< real_t >::min())); + WALBERLA_ASSERT(index != std::numeric_limits< uint_t >::max()); + + return index; +} + +uint_t getIndexOfMinimum(const std::vector< bool >& isInterfaceOrLiquid, const std::vector< bool >& isPdfAvailable, + const std::vector< real_t >& n_dot_ci) +{ + real_t minimum = std::numeric_limits< real_t >::max(); + uint_t index = std::numeric_limits< uint_t >::max(); + + for (uint_t i = uint_c(0); i != isInterfaceOrLiquid.size(); ++i) + { + if (isInterfaceOrLiquid[i] && !isPdfAvailable[i]) + { + const real_t absValue = std::abs(n_dot_ci[i]); + if (absValue < minimum) + { + minimum = absValue; + index = i; + } + } + } + + // fewer PDFs available for being reconstructed than specified by the user; these assertions should never fail, as + // the conditionals in reconstructInterfaceCellLegacy() should avoid calling this function + WALBERLA_ASSERT(minimum < real_c(std::numeric_limits< real_t >::max())); + WALBERLA_ASSERT(index != std::numeric_limits< uint_t >::max()); + + return index; +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/CMakeLists.txt b/src/lbm_generated/free_surface/surface_geometry/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..daa509840ebebf9dd176593797702f74d770844b --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/CMakeLists.txt @@ -0,0 +1,22 @@ +target_sources( lbm_generated + PRIVATE + ContactAngle.h + CurvatureModel.h + CurvatureModel.impl.h + CurvatureSweep.h + CurvatureSweep.impl.h + DetectWettingSweep.h + ExtrapolateNormalsSweep.h + ExtrapolateNormalsSweep.impl.h + NormalSweep.h + NormalSweep.impl.h + ObstacleFillLevelSweep.h + ObstacleFillLevelSweep.impl.h + ObstacleNormalSweep.h + ObstacleNormalSweep.impl.h + SmoothingSweep.h + SmoothingSweep.impl.h + SurfaceGeometryHandler.h + Utility.cpp + Utility.h + ) diff --git a/src/lbm_generated/free_surface/surface_geometry/ContactAngle.h b/src/lbm_generated/free_surface/surface_geometry/ContactAngle.h new file mode 100644 index 0000000000000000000000000000000000000000..641f4282e06babcd0eb1b59718f7984437860f10 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/ContactAngle.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 ContactAngle.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Class to avoid re-computing sine and cosine of the contact angle. +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Constants.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Class to avoid re-computing sine and cosine of the contact angle. + **********************************************************************************************************************/ +class ContactAngle +{ + public: + ContactAngle(real_t angleInDegrees) + : angleDegrees_(angleInDegrees), angleRadians_(math::pi / real_c(180) * angleInDegrees), + sinAngle_(std::sin(angleRadians_)), cosAngle_(std::cos(angleRadians_)) + {} + + inline real_t getInDegrees() const { return angleDegrees_; } + inline real_t getInRadians() const { return angleRadians_; } + inline real_t getSin() const { return sinAngle_; } + inline real_t getCos() const { return cosAngle_; } + + private: + real_t angleDegrees_; + real_t angleRadians_; + real_t sinAngle_; + real_t cosAngle_; +}; // class ContactAngle +} // namespace free_surface_generated +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/CurvatureModel.h b/src/lbm_generated/free_surface/surface_geometry/CurvatureModel.h new file mode 100644 index 0000000000000000000000000000000000000000..08c8b7e58fdd85d269c2a3b6c709da855b166408 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/CurvatureModel.h @@ -0,0 +1,84 @@ +//====================================================================================================================== +// +// 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 CurvatureModel.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Collection of sweeps required for using a specific curvature model. +// +//====================================================================================================================== + +#pragma once + +namespace walberla +{ +namespace free_surface_generated +{ +// forward declaration +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T, typename FlagInfo_T > +class SurfaceGeometryHandler; + +namespace curvature_model +{ +/*********************************************************************************************************************** + * Collection of sweeps for computing the curvature using a finite difference-based (Parker-Youngs) approach according + * to: + * dissertation of S. Bogner, 2017 (section 4.4.2.1) + **********************************************************************************************************************/ +template< typename Stencil_T, typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T,typename VectorField_T, typename FlagInfo_T > +class FiniteDifferenceMethod +{ + private: + using SurfaceGeometryHandler_T = SurfaceGeometryHandler< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo_T >; + + public: + void addSweeps(SweepTimeloop& timeloop, const SurfaceGeometryHandler_T& geometryHandler); +}; // class FiniteDifferenceMethod + +/*********************************************************************************************************************** + * Collection of sweeps for computing the curvature using local triangulation according to: + * - dissertation of T. Pohl, 2008 (section 2.5) + * - dissertation of S. Donath, 2011 (wetting model, section 6.3.3) + **********************************************************************************************************************/ +template< typename Stencil_T, typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T, typename FlagInfo_T > +class LocalTriangulation +{ + private: + using SurfaceGeometryHandler_T = SurfaceGeometryHandler< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo_T >; + + public: + void addSweeps(SweepTimeloop& timeloop, const SurfaceGeometryHandler_T& geometryHandler); +}; // class LocalTriangulation + +/*********************************************************************************************************************** + * Collection of sweeps for computing the curvature with a simplistic finite difference method. This approach is not + * documented in literature and neither thoroughly tested or validated. + * Use it with caution and preferably for testing purposes only. + **********************************************************************************************************************/ +template< typename Stencil_T, typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T, typename FlagInfo_T > +class SimpleFiniteDifferenceMethod +{ + private: + using SurfaceGeometryHandler_T = SurfaceGeometryHandler< StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo_T >; + + public: + void addSweeps(SweepTimeloop& timeloop, const SurfaceGeometryHandler_T& geometryHandler); +}; // class SimpleFiniteDifferenceMethod + +} // namespace curvature_model +} // namespace free_surface +} // namespace walberla + +#include "CurvatureModel.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/CurvatureModel.impl.h b/src/lbm_generated/free_surface/surface_geometry/CurvatureModel.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..7a1b4f07901605cca2a7e8a99b5942c3ee11ce21 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/CurvatureModel.impl.h @@ -0,0 +1,258 @@ +//====================================================================================================================== +// +// 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 CurvatureModel.impl.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Collection of sweeps required for using a specific curvature model. +// +//====================================================================================================================== + +#include "lbm_generated/blockforest/UpdateSecondGhostLayer.h" + +#include "CurvatureModel.h" +#include "CurvatureSweep.h" +#include "DetectWettingSweep.h" +#include "ExtrapolateNormalsSweep.h" +#include "NormalSweep.h" +#include "ObstacleFillLevelSweep.h" +#include "ObstacleNormalSweep.h" +#include "SmoothingSweep.h" +#include "SurfaceGeometryHandler.h" + +namespace walberla +{ +namespace free_surface_generated +{ +namespace curvature_model +{ +// empty sweep required for using selectors (e.g. StateSweep::fullFreeSurface) +struct emptySweep +{ + void operator()(IBlock*) {} +}; + +template< typename Stencil_T, typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T, typename FlagInfo_T> +void FiniteDifferenceMethod< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo_T >::addSweeps( + SweepTimeloop& timeloop, const FiniteDifferenceMethod::SurfaceGeometryHandler_T& geometryHandler) +{ + using Communication_T = typename SurfaceGeometryHandler_T::Communication_T; + using StateSweep = typename SurfaceGeometryHandler_T::StateSweep; + + // layout for allocating the smoothed fill level field + field::Layout fillFieldLayout = field::fzyx; + + // check if an obstacle cell is in a non-periodic outermost global ghost layer; used to check if two ghost layers are + // required for the fill level field + const Vector3< bool > isObstacleInGlobalGhostLayerXYZ = geometryHandler.isObstacleInGlobalGhostLayer(); + + bool isObstacleInGlobalGhostLayer = false; + if ((!geometryHandler.blockForest_->isXPeriodic() && isObstacleInGlobalGhostLayerXYZ[0]) || + (!geometryHandler.blockForest_->isYPeriodic() && isObstacleInGlobalGhostLayerXYZ[1]) || + (!geometryHandler.blockForest_->isZPeriodic() && isObstacleInGlobalGhostLayerXYZ[2])) + { + isObstacleInGlobalGhostLayer = true; + } + + for (auto blockIt = geometryHandler.blockForest_->begin(); blockIt != geometryHandler.blockForest_->end(); ++blockIt) + { + const ScalarField_T* const fillField = blockIt->template getData< const ScalarField_T >(geometryHandler.fillFieldID_); + // check if two ghost layers are required for the fill level field + if (isObstacleInGlobalGhostLayer && fillField->nrOfGhostLayers() < uint_c(2) && geometryHandler.enableWetting_) + { + WALBERLA_ABORT( + "With wetting enabled, the curvature computation with the finite difference method requires two ghost " + "layers in the fill level field whenever solid obstacles are located in a global outermost ghost layer. " + "For more information, see the remark in the description of ObstacleFillLevelSweep.h"); + } + + // get layout of fill level field (to be used in allocating the smoothed fill level field; cloning would + // waste memory, as the fill level field might have two ghost layers, whereas the smoothed fill level field needs + // only one ghost layer) + fillFieldLayout = fillField->layout(); + } + + // IMPORTANT REMARK: ObstacleNormalSweep and ObstacleFillLevelSweep must be executed on all blocks, because the + // SmoothingSweep requires meaningful values in the ghost layers. + + // add field for smoothed fill levels + BlockDataID smoothFillFieldID = field::addToStorage< ScalarField_T >( + geometryHandler.blockForest_, "Smooth fill level field", real_c(0), fillFieldLayout, uint_c(1)); + + if (geometryHandler.enableWetting_) + { + // compute obstacle normals + ObstacleNormalSweep< Stencil_T, FlagField_T, VectorField_T > obstacleNormalSweep( + geometryHandler.obstacleNormalFieldID_, geometryHandler.flagFieldID_, flagIDs::interfaceFlagID, + flagIDs::liquidInterfaceGasFlagIDs, geometryHandler.obstacleFlagIDSet_, false, true, true); + timeloop.add() << Sweep(obstacleNormalSweep, "Sweep: obstacle normal computation") + << AfterFunction( + Communication_T(geometryHandler.blockForest_, geometryHandler.obstacleNormalFieldID_), + "Communication: after obstacle normal sweep"); + + // reflect fill level into obstacle cells such that they can be used for smoothing the fill level field and for + // computing the interface normal; MUST be performed BEFORE SmoothingSweep + ObstacleFillLevelSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T > obstacleFillLevelSweep( + smoothFillFieldID, geometryHandler.fillFieldID_, geometryHandler.flagFieldID_, + geometryHandler.obstacleNormalFieldID_, flagIDs::liquidInterfaceGasFlagIDs, + geometryHandler.obstacleFlagIDSet_); + timeloop.add() << Sweep(obstacleFillLevelSweep, "Sweep: obstacle fill level computation") + << AfterFunction(Communication_T(geometryHandler.blockForest_, smoothFillFieldID), + "Communication: after obstacle fill level sweep"); + } + + // smooth fill level field for decreasing error in finite difference normal and curvature computation (see + // dissertation of S. Bogner, 2017 (section 4.4.2.1)) + SmoothingSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T > smoothingSweep( + smoothFillFieldID, geometryHandler.fillFieldID_, geometryHandler.flagFieldID_, flagIDs::liquidInterfaceGasFlagIDs, + geometryHandler.obstacleFlagIDSet_, geometryHandler.enableWetting_); + // IMPORTANT REMARK: SmoothingSweep must be executed on all blocks, because the algorithm works on all liquid, + // interface and gas cells. This is necessary since the normals are not only computed in interface cells, but also in + // the neighborhood of interface cells. Therefore, meaningful values for the fill levels of the second neighbors of + // interface cells are also required in NormalSweep. + timeloop.add() << Sweep(smoothingSweep, "Sweep: fill level smoothing") + << AfterFunction(Communication_T(geometryHandler.blockForest_, smoothFillFieldID), + "Communication: after smoothing sweep"); + + // compute interface normals (using smoothed fill level field) + NormalSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T > normalSweep( + geometryHandler.normalFieldID_, smoothFillFieldID, geometryHandler.flagFieldID_, flagIDs::interfaceFlagID, + flagIDs::liquidInterfaceGasFlagIDs, geometryHandler.obstacleFlagIDSet_, true, geometryHandler.enableWetting_, + true, geometryHandler.enableWetting_); + timeloop.add() << Sweep(normalSweep, "Sweep: normal computation", StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: normal") + << AfterFunction(Communication_T(geometryHandler.blockForest_, geometryHandler.normalFieldID_), + "Communication: after normal sweep"); + + if (geometryHandler.computeCurvature_) + { + // compute interface curvature using finite differences according to Brackbill et al. + CurvatureSweepFiniteDifferences< Stencil_T, FlagField_T, ScalarField_T, VectorField_T > curvSweep( + geometryHandler.curvatureFieldID_, geometryHandler.normalFieldID_, geometryHandler.obstacleNormalFieldID_, + geometryHandler.flagFieldID_, flagIDs::interfaceFlagID, flagIDs::liquidInterfaceGasFlagIDs, + geometryHandler.obstacleFlagIDSet_, geometryHandler.enableWetting_, geometryHandler.contactAngle_); + timeloop.add() << Sweep(curvSweep, "Sweep: curvature computation (finite difference method)", + StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: curvature") + << AfterFunction(Communication_T(geometryHandler.blockForest_, geometryHandler.curvatureFieldID_), + "Communication: after curvature sweep"); + } +} + +template< typename Stencil_T, typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T, typename FlagInfo_T> +void LocalTriangulation< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo_T >::addSweeps( + SweepTimeloop& timeloop, const LocalTriangulation::SurfaceGeometryHandler_T& geometryHandler) +{ + using Communication_T = typename SurfaceGeometryHandler_T::Communication_T; + using StateSweep = typename SurfaceGeometryHandler_T::StateSweep; + + // compute interface normals + NormalSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T > normalSweep( + geometryHandler.normalFieldID_, geometryHandler.fillFieldID_, geometryHandler.flagFieldID_, + flagIDs::interfaceFlagID, flagIDs::liquidInterfaceGasFlagIDs, geometryHandler.obstacleFlagIDSet_, false, false, + true, false); + timeloop.add() << Sweep(normalSweep, "Sweep: normal computation", StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: normal") + << AfterFunction(Communication_T(geometryHandler.blockForest_, geometryHandler.normalFieldID_), + "Communication: after normal sweep"); + + // compute obstacle normals + ObstacleNormalSweep< Stencil_T, FlagField_T, VectorField_T > obstacleNormalSweep( + geometryHandler.obstacleNormalFieldID_, geometryHandler.flagFieldID_, flagIDs::interfaceFlagID, + flagIDs::liquidInterfaceGasFlagIDs, geometryHandler.obstacleFlagIDSet_, true, false, false); + timeloop.add() << Sweep(obstacleNormalSweep, "Sweep: obstacle normal computation", StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: obstacle normal") + << AfterFunction( + Communication_T(geometryHandler.blockForest_, geometryHandler.obstacleNormalFieldID_), + "Communication: after obstacle normal sweep"); + + if (geometryHandler.computeCurvature_) + { + // compute interface curvature using local triangulation according to dissertation of T. Pohl, 2008 + CurvatureSweepLocalTriangulation< Stencil_T, FlagField_T, ScalarField_T, VectorField_T > curvSweep( + geometryHandler.blockForest_, geometryHandler.curvatureFieldID_, geometryHandler.normalFieldID_, + geometryHandler.fillFieldID_, geometryHandler.flagFieldID_, geometryHandler.obstacleNormalFieldID_, + flagIDs::interfaceFlagID, geometryHandler.obstacleFlagIDSet_, geometryHandler.enableWetting_, + geometryHandler.contactAngle_); + timeloop.add() << Sweep(curvSweep, "Sweep: curvature computation (local triangulation)", + StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: curvature") + << AfterFunction(Communication_T(geometryHandler.blockForest_, geometryHandler.curvatureFieldID_), + "Communication: after curvature sweep"); + } + + // sweep for detecting cells that need to be converted to interface cells for continuing the wetting + // surface correctly + // IMPORTANT REMARK: this MUST NOT be performed when using finite differences for curvature computation and can + // otherwise lead to instabilities and errors + if (geometryHandler.enableWetting_) + { + DetectWettingSweep<Stencil_T, FlagField_T, ScalarField_T, VectorField_T > + detWetSweep(geometryHandler.flagFieldID_, geometryHandler.getFlagInfo(), + geometryHandler.normalFieldID_, geometryHandler.fillFieldID_); + timeloop.add() << Sweep(detWetSweep, "Sweep: wetting detection", StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: wetting detection") + << AfterFunction(Communication_T(geometryHandler.blockForest_, geometryHandler.flagFieldID_), + "Communication: after wetting detection sweep") + << AfterFunction(lbm_generated::UpdateSecondGhostLayer< FlagField_T >(geometryHandler.blockForest_, + geometryHandler.flagFieldID_), + "Second ghost layer update: after wetting detection sweep (flag field)"); + } +} + +template< typename Stencil_T, typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T, typename FlagInfo_T> +void SimpleFiniteDifferenceMethod< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo_T >::addSweeps( + SweepTimeloop& timeloop, const SimpleFiniteDifferenceMethod::SurfaceGeometryHandler_T& geometryHandler) +{ + using Communication_T = typename SurfaceGeometryHandler_T::Communication_T; + using StateSweep = typename SurfaceGeometryHandler_T::StateSweep; + + // compute interface normals + NormalSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T > normalSweep( + geometryHandler.normalFieldID_, geometryHandler.fillFieldID_, geometryHandler.flagFieldID_, + flagIDs::interfaceFlagID, flagIDs::liquidInterfaceGasFlagIDs, geometryHandler.obstacleFlagIDSet_, false, false, + false, false); + timeloop.add() << Sweep(normalSweep, "Sweep: normal computation", StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: normal") + << AfterFunction(Communication_T(geometryHandler.blockForest_, geometryHandler.normalFieldID_), + "Communication: after normal sweep"); + + // extrapolation of normals to interface neighboring cells (required for computing the curvature with finite + // differences) + ExtrapolateNormalsSweep< Stencil_T, FlagField_T, VectorField_T > extNormalsSweep( + geometryHandler.normalFieldID_, geometryHandler.flagFieldID_, flagIDs::interfaceFlagID); + timeloop.add() << Sweep(extNormalsSweep, "Sweep: normal extrapolation", StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: normal extrapolation") + << AfterFunction(Communication_T(geometryHandler.blockForest_, geometryHandler.normalFieldID_), + "Communication: after normal extrapolation sweep"); + + if (geometryHandler.computeCurvature_) + { + // curvature computation using finite differences + CurvatureSweepSimpleFiniteDifferences< Stencil_T, FlagField_T, ScalarField_T, VectorField_T > curvSweep( + geometryHandler.curvatureFieldID_, geometryHandler.normalFieldID_, geometryHandler.flagFieldID_, + flagIDs::interfaceFlagID, geometryHandler.obstacleFlagIDSet_, geometryHandler.enableWetting_, + geometryHandler.contactAngle_); + timeloop.add() << Sweep(curvSweep, "Sweep: curvature computation (simple finite difference method)", + StateSweep::fullFreeSurface) + << Sweep(emptySweep(), "Empty sweep: curvature") + << AfterFunction(Communication_T(geometryHandler.blockForest_, geometryHandler.curvatureFieldID_), + "Communication: after curvature sweep "); + } +} + +} // namespace curvature_model +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/CurvatureSweep.h b/src/lbm_generated/free_surface/surface_geometry/CurvatureSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..8f1a1994ae18543e9e4cc9affb1cf2b0c3bd7eda --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/CurvatureSweep.h @@ -0,0 +1,217 @@ +//====================================================================================================================== +// +// 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 CurvatureSweep.h +//! \ingroup surface_geometry +//! \author Martin Bauer +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Sweeps for computing the interface curvature. +// +//====================================================================================================================== + +#pragma once + +#include "blockforest/StructuredBlockForest.h" + +#include "core/logging/Logging.h" +#include "core/math/Constants.h" + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FlagField.h" + +#include "stencil/D2Q9.h" +#include "stencil/D3Q27.h" +#include "stencil/Directions.h" + +#include <type_traits> +#include <vector> + +#include "ContactAngle.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Compute the interface curvature using a finite difference scheme (Parker-Youngs approach) as described in + * - dissertation of S. Bogner, 2017 (section 4.4.2.1) + * which is based on + * - Brackbill, Kothe and Zemach, "A continuum method ...", 1992 + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class CurvatureSweepFiniteDifferences +{ + protected: + using vector_t = Vector3<real_t>; + using flag_t = typename std::remove_const< typename FlagField_T::value_type >::type; + + public: + CurvatureSweepFiniteDifferences(const BlockDataID& curvatureFieldID, const ConstBlockDataID& normalFieldID, + const ConstBlockDataID& obstacleNormalFieldID, const ConstBlockDataID& flagFieldID, + const FlagUID& interfaceFlagID, const Set< FlagUID >& liquidInterfaceGasFlagIDSet, + const Set< FlagUID >& obstacleFlagIDSet, bool enableWetting, + const ContactAngle& contactAngle) + : curvatureFieldID_(curvatureFieldID), normalFieldID_(normalFieldID), + obstacleNormalFieldID_(obstacleNormalFieldID), flagFieldID_(flagFieldID), enableWetting_(enableWetting), + contactAngle_(contactAngle), interfaceFlagID_(interfaceFlagID), + liquidInterfaceGasFlagIDSet_(liquidInterfaceGasFlagIDSet), obstacleFlagIDSet_(obstacleFlagIDSet) + {} + + void operator()(IBlock* const block); + + /******************************************************************************************************************** + * Returns an adjusted interface normal according to the wetting model from the dissertation of S. Bogner, 2017 + * (section 4.4.2.1). + *******************************************************************************************************************/ + template< typename VectorIt_T > + Vector3< real_t > getNormalWithWetting(VectorIt_T normalFieldIt, VectorIt_T obstacleNormalFieldIt, + const stencil::Direction dir) + { + vector_t normal = Vector3<real_t>( normalFieldIt.getF(0), + normalFieldIt.getF(1), + normalFieldIt.getF(2)); + + vector_t nw = Vector3(obstacleNormalFieldIt.neighbor(dir, 0), + obstacleNormalFieldIt.neighbor(dir, 1), + obstacleNormalFieldIt.neighbor(dir, 1)); + + + // get reversed interface normal + const Vector3< real_t > n = -normal; + + // get n_t: vector tangent to the wall and normal to the contact line; obtained by subtracting the wall-normal + // component from the interface normal n + Vector3< real_t > nt = n - (nw * n) * nw; // "(nw * n) * nw" is orthogonal projection of nw on n + nt = nt.getNormalizedOrZero(); + + // compute interface normal at wall according to wetting model (equation 4.21 in dissertation of S. Bogner, + // 2017) + const Vector3< real_t > nWall = nw * contactAngle_.getCos() + nt * contactAngle_.getSin(); + + // extrapolate into obstacle cell to obtain boundary value; expression comes from 1D extrapolation formula + // IMPORTANT REMARK: corner and diagonal directions must use different formula, Bogner did not consider this in + // his implementation; here, nevertheless Bogner's approach is used + const Vector3< real_t > nWallExtrapolated = n + real_c(2) * (nWall - n); + + return -nWallExtrapolated; + } + + private: + BlockDataID curvatureFieldID_; + ConstBlockDataID normalFieldID_; + ConstBlockDataID obstacleNormalFieldID_; + ConstBlockDataID flagFieldID_; + + bool enableWetting_; + ContactAngle contactAngle_; + + FlagUID interfaceFlagID_; + Set< FlagUID > liquidInterfaceGasFlagIDSet_; + Set< FlagUID > obstacleFlagIDSet_; +}; // class CurvatureSweepFiniteDifferences + +/*********************************************************************************************************************** + * Compute the interface curvature using local triangulation as described in + * - dissertation of T. Pohl, 2008 (section 2.5) + * - dissertation of S. Donath, 2011 (wetting model, section 6.3.3) + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class CurvatureSweepLocalTriangulation +{ + protected: + using vector_t = Vector3<real_t>; + + public: + CurvatureSweepLocalTriangulation(const std::weak_ptr< const StructuredBlockForest >& blockForest, + const BlockDataID& curvatureFieldID, const ConstBlockDataID& normalFieldID, + const ConstBlockDataID& fillFieldID, const ConstBlockDataID& flagFieldID, + const ConstBlockDataID& obstacleNormalFieldID, const FlagUID& interfaceFlagID, + const Set< FlagUID >& obstacleFlagIDSet, bool enableWetting, + const ContactAngle& contactAngle) + : blockForest_(blockForest), curvatureFieldID_(curvatureFieldID), normalFieldID_(normalFieldID), + fillFieldID_(fillFieldID), flagFieldID_(flagFieldID), obstacleNormalFieldID_(obstacleNormalFieldID), + enableWetting_(enableWetting), contactAngle_(contactAngle), interfaceFlagID_(interfaceFlagID), + obstacleFlagIDSet_(obstacleFlagIDSet) + { + if constexpr (std::is_same_v< Stencil_T, stencil::D2Q9 >) + { + WALBERLA_ABORT( + "Curvature computation with local triangulation using a D2Q9 stencil has not been thoroughly tested."); + } + } + + void operator()(IBlock* const block); + + private: + std::weak_ptr< const StructuredBlockForest > blockForest_; + BlockDataID curvatureFieldID_; + ConstBlockDataID normalFieldID_; + ConstBlockDataID fillFieldID_; + ConstBlockDataID flagFieldID_; + ConstBlockDataID obstacleNormalFieldID_; + + bool enableWetting_; + ContactAngle contactAngle_; + + FlagUID interfaceFlagID_; + Set< FlagUID > obstacleFlagIDSet_; +}; // class CurvatureSweepLocalTriangulation + +/*********************************************************************************************************************** + * Compute the interface curvature with a simplistic finite difference method. This approach is not documented in + * literature and neither thoroughly tested or validated. + * Use it with caution and preferably for testing purposes only. + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class CurvatureSweepSimpleFiniteDifferences +{ + protected: + using vector_t = Vector3<real_t>; + + public: + CurvatureSweepSimpleFiniteDifferences(const BlockDataID& curvatureFieldID, const ConstBlockDataID& normalFieldID, + const ConstBlockDataID& flagFieldID, const FlagUID& interfaceFlagID, + const Set< FlagUID >& obstacleFlagIDSet, bool enableWetting, + const ContactAngle& contactAngle) + : curvatureFieldID_(curvatureFieldID), normalFieldID_(normalFieldID), flagFieldID_(flagFieldID), + enableWetting_(enableWetting), contactAngle_(contactAngle), interfaceFlagID_(interfaceFlagID), + obstacleFlagIDSet_(obstacleFlagIDSet) + { + WALBERLA_LOG_WARNING_ON_ROOT( + "You are using curvature computation based on a simplistic finite difference method. This " + "was implemented for testing purposes only and has not been thoroughly " + "validated and tested in the current state of the code. Use it with caution."); + } + + void operator()(IBlock* const block); + + private: + BlockDataID curvatureFieldID_; + ConstBlockDataID normalFieldID_; + ConstBlockDataID flagFieldID_; + + bool enableWetting_; + ContactAngle contactAngle_; + + FlagUID interfaceFlagID_; + Set< FlagUID > obstacleFlagIDSet_; +}; // class CurvatureSweepSimpleFiniteDifferences + +} // namespace free_surface +} // namespace walberla + +#include "CurvatureSweep.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/CurvatureSweep.impl.h b/src/lbm_generated/free_surface/surface_geometry/CurvatureSweep.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..53eb0c6a8893ce7034bc3c91764c7bdae791230b --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/CurvatureSweep.impl.h @@ -0,0 +1,520 @@ +//====================================================================================================================== +// +// 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 CurvatureSweep.impl.h +//! \ingroup surface_geometry +//! \author Martin Bauer +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Sweeps for computing the interface curvature. +// +//====================================================================================================================== + +#include "core/debug/CheckFunctions.h" +#include "core/logging/Logging.h" +#include "core/math/Matrix3.h" +#include "core/math/Utility.h" +#include "core/math/Vector3.h" + +#include "field/FlagField.h" + +#include "stencil/D3Q27.h" +#include "stencil/Directions.h" + +#include <algorithm> +#include <cmath> + +#include "ContactAngle.h" +#include "CurvatureSweep.h" +#include "Utility.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void CurvatureSweepFiniteDifferences< Stencil_T, FlagField_T, ScalarField_T, VectorField_T >::operator()( + IBlock* const block) +{ + // get fields + ScalarField_T* const curvatureField = block->getData< ScalarField_T >(curvatureFieldID_); + const VectorField_T* const normalField = block->getData< const VectorField_T >(normalFieldID_); + const VectorField_T* const obstacleNormalField = block->getData< const VectorField_T >(obstacleNormalFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + + // get flags + const flag_t interfaceFlag = flagField->getFlag(interfaceFlagID_); + const flag_t liquidInterfaceGasFlagMask = flagField->getMask(liquidInterfaceGasFlagIDSet_); + const flag_t obstacleFlagMask = flagField->getMask(obstacleFlagIDSet_); + + WALBERLA_FOR_ALL_CELLS( + flagFieldIt, flagField, normalFieldIt, normalField, obstacleNormalFieldIt, obstacleNormalField, curvatureFieldIt, + curvatureField, { + real_t& curv = *curvatureFieldIt; + curv = real_c(0.0); + vector_t normal = Vector3(normalFieldIt.getF(0), normalFieldIt.getF(1), normalFieldIt.getF(2)); + + if (isFlagSet(flagFieldIt, interfaceFlag)) // only treat interface cells + { + real_t weightSum = real_c(0); + + if (normal.sqrLength() < real_c(1e-14)) + { + WALBERLA_LOG_WARNING("Invalid normal detected in CurvatureSweep.") + continue; + } + + // Parker-Youngs central finite difference approximation of curvature (see dissertation + // of S. Bogner, 2017, section 4.4.2.1) + for (auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + const Vector3< real_t > dirVector = Vector3< real_t >(real_c(dir.cx()), real_c(dir.cy()), real_c(dir.cz())); + + Vector3< real_t > neighborNormal; + + if (isPartOfMaskSet(flagFieldIt.neighbor(*dir), liquidInterfaceGasFlagMask | obstacleFlagMask)) + { + // get interface normal of neighbor in direction dir with respect to wetting model + if (enableWetting_ && isPartOfMaskSet(flagFieldIt.neighbor(*dir), obstacleFlagMask)) + { + neighborNormal = getNormalWithWetting(normalFieldIt, obstacleNormalFieldIt, *dir); + neighborNormal = neighborNormal.getNormalizedOrZero(); + } + else + { + if (isPartOfMaskSet(flagFieldIt.neighbor(*dir), liquidInterfaceGasFlagMask)) + { + neighborNormal = Vector3(normalFieldIt.neighbor(*dir, 0), normalFieldIt.neighbor(*dir, 1), normalFieldIt.neighbor(*dir, 2)); + } + else + { + // skip remainder of this direction such that it is not considered in curvature computation + continue; + } + } + } + + // equation (35) in Brackbill et al. discretized with finite difference method + // according to Parker-Youngs + if constexpr (Stencil_T::D == uint_t(2)) + { + const real_t weight = + real_c(stencil::gaussianMultipliers[stencil::D3Q27::idx[stencil::map2Dto3D[2][*dir]]]); + weightSum += weight; + curv += weight * (dirVector * neighborNormal); + } + else + { + const real_t weight = real_c(stencil::gaussianMultipliers[dir.toIdx()]); + weightSum += weight; + curv += weight * (dirVector * neighborNormal); + } + } + + // divide by sum of weights in Parker-Youngs approximation; sum does not contain weights of directions in + // which there is no liquid, interface, gas, or obstacle cell (only when wetting is enabled, otherwise + // obstacle cell is also not considered); must be done like this because otherwise such non-valid + // directions would implicitly influence the finite difference scheme by assuming a normal of zero + curv /= weightSum; + } + }) // WALBERLA_FOR_ALL_CELLS +} + +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void CurvatureSweepLocalTriangulation< Stencil_T, FlagField_T, ScalarField_T, VectorField_T >::operator()( + IBlock* const block) +{ + const auto blockForest = blockForest_.lock(); + WALBERLA_CHECK_NOT_NULLPTR(blockForest); + + // struct for storing relevant information of each neighboring interface cell (POD type) + using Neighbor = struct + { + Vector3< real_t > diff; // difference (distance in coordinates) between this and neighboring interface point + Vector3< real_t > diffNorm; // normalized difference between this and neighboring interface point + Vector3< real_t > normal; // interface normal of neighboring interface point + real_t dist2; // square of the distance between this and neighboring interface point + real_t area; // sum of the area of the two triangles that this neighboring interface is part of + real_t sort; // angle that is used to sort the order neighboring points accordingly + bool valid; // validity, used to remove triangles with too narrow angles + bool wall; // used to include wetting effects near solid cells + }; + + // get fields + ScalarField_T* const curvatureField = block->getData< ScalarField_T >(curvatureFieldID_); + const VectorField_T* const normalField = block->getData< const VectorField_T >(normalFieldID_); + const ScalarField_T* const fillField = block->getData< const ScalarField_T >(fillFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + const VectorField_T* const obstacleNormalField = block->getData< const VectorField_T >(obstacleNormalFieldID_); + + // get flags + auto interfaceFlag = flagField->getFlag(interfaceFlagID_); + auto obstacleFlagMask = flagField->getMask(obstacleFlagIDSet_); + + WALBERLA_FOR_ALL_CELLS( + flagFieldIt, flagField, normalFieldIt, normalField, fillFieldIt, fillField, obstacleNormalFieldIt, + obstacleNormalField, curvatureFieldIt, curvatureField, { + real_t& curv = *curvatureFieldIt; + curv = real_c(0.0); + std::vector< Neighbor > neighbors; + Vector3< real_t > meanInterfaceNormal; + vector_t normal = Vector3(normalFieldIt.getF(0), normalFieldIt.getF(1), normalFieldIt.getF(2)); + + // compute curvature only in interface points + if (!isFlagSet(flagFieldIt, interfaceFlag)) { continue; } + + // normal of this cell also contributes to mean normal + meanInterfaceNormal = normal; + + // iterate over all neighboring cells (Eq. 2.18 in dissertation of T. Pohl, 2008) + for (auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + auto neighborFlags = flagFieldIt.neighbor(*dir); + + if (isFlagSet(neighborFlags, interfaceFlag) || // Eq. 2.19 in dissertation of T. Pohl, 2008 + (isPartOfMaskSet(neighborFlags, obstacleFlagMask) && + dir.toIdx() <= uint_c(18))) // obstacle in main direction or diagonal direction (not corner direction) + { + Vector3< real_t > neighborNormal; + const real_t neighborFillLevel = fillFieldIt.neighbor(*dir); + + // if loop was entered because of neighboring solid cell, normal of this solid cell points towards the + // currently processed interface cell + Vector3< real_t > wallNormal(real_c(-dir.cx()), real_c(-dir.cy()), real_c(-dir.cz())); + + if (isPartOfMaskSet(neighborFlags, obstacleFlagMask)) + { + neighborNormal = + Vector3< real_t >(real_c(1)); // temporarily guarantees "neighborNormal.sqrLength()>0" + wallNormal.getNormalized(); + } + else { neighborNormal = Vector3(normalFieldIt.neighbor(*dir, 0), normalFieldIt.neighbor(*dir, 1), normalFieldIt.neighbor(*dir, 2)); } + + if (neighborNormal.sqrLength() > real_c(0)) + { + Neighbor n; + + // get global coordinate (with respect to whole simulation domain) of the currently processed cell + Vector3< real_t > globalCellCoordinate = + blockForest->getBlockLocalCellCenter(*block, flagFieldIt.cell()) - Vector3< real_t >(real_c(0.5)); + + for (auto dir2 = Stencil_T::beginNoCenter(); dir2 != Stencil_T::end(); ++dir2) + { + // stay in the close neighborhood of the currently processed interface cell + if ((dir.cx() != 0 && dir2.cx() != 0) || (dir.cy() != 0 && dir2.cy() != 0) || + (dir.cz() != 0 && dir2.cz() != 0)) + { + continue; + } + + if (isPartOfMaskSet(neighborFlags, obstacleFlagMask) && enableWetting_) + { + // get flags of neighboring cell in direction dir2 + auto neighborFlagsInDir2 = flagFieldIt.neighbor(*dir2); + + // the currently processed interface cell i has a neighboring solid cell j in direction dir; + // get the flags of j's neighboring cell in direction dir2 + // i.e., from the current cell, go to neighbor in dir; from there, go to next cell in dir2 + auto neighborNeighborFlags = + flagFieldIt.neighbor(dir.cx() + dir2.cx(), dir.cy() + dir2.cy(), dir.cz() + dir2.cz()); + + // true if the currently processed interface cell i has a neighboring interface cell j (in + // dir2), which (j) has a neighboring obstacle cell in the same direction as i does (in dir) + if (isFlagSet(neighborFlagsInDir2, interfaceFlag) && + isPartOfMaskSet(neighborNeighborFlags, obstacleFlagMask)) + { + // get the normal of the currently processed interface cell's neighboring interface cell + // (in direction 2) + Vector3< real_t > neighborNormalDir2 = Vector3(normalFieldIt.neighbor(*dir2, 0), normalFieldIt.neighbor(*dir2, 1), normalFieldIt.neighbor(*dir2, 2)); + Vector3< real_t > neighborGlobalCoordDir2 = globalCellCoordinate; + neighborGlobalCoordDir2[0] += real_c(dir2.cx()); + neighborGlobalCoordDir2[1] += real_c(dir2.cy()); + neighborGlobalCoordDir2[2] += real_c(dir2.cz()); + + // get neighboring interface point, i.e., location of interface within cell + + + Vector3< real_t > neighborGlobalInterfacePoint = + getInterfacePoint(Vector3(normalFieldIt.neighbor(*dir2, 0), normalFieldIt.neighbor(*dir2, 1), normalFieldIt.neighbor(*dir2, 2)), fillFieldIt.neighbor(*dir2)); + + // transform to global coordinates, i.e., neighborInterfacePoint specifies the global + // location of the interface point in the currently processed interface cell's neighbor in + // direction dir2 + neighborGlobalInterfacePoint += neighborGlobalCoordDir2; + + // get the mean (averaged over multiple solid cells) wall normal of the neighbor in + // direction dir2 + Vector3< real_t > obstacleNormal = Vector3(obstacleNormalFieldIt.neighbor(*dir2, 0), obstacleNormalFieldIt.neighbor(*dir2, 1), obstacleNormalFieldIt.neighbor(*dir2, 2)); + obstacleNormal *= real_c(-1); + if (obstacleNormal.sqrLength() < real_c(1e-10)) { obstacleNormal = wallNormal; } + + Vector3< real_t > neighborPoint; + + bool result = computeArtificalWallPoint( + neighborGlobalInterfacePoint, neighborGlobalCoordDir2, neighborNormalDir2, wallNormal, + obstacleNormal, contactAngle_, neighborPoint, neighborNormal); + if (!result) { continue; } + n.wall = true; + n.diff = neighborPoint - neighborGlobalInterfacePoint; + n.dist2 = n.diff.sqrLength(); + } + else { continue; } + } + else + // will be entered if: + // isFlagSet(neighborFlags, interfaceFlag) && !isPartOfMaskSet(neighborFlags, obstacleFlagMask) + { + n.wall = false; + + // get neighboring interface point, i.e., location of interface within cell + n.diff = getInterfacePoint(neighborNormal, neighborFillLevel); + + // get distance between this cell (0,0,0) and neighboring interface point + (dx,dy,dz) + n.diff += Vector3< real_t >(real_c(dir.cx()), real_c(dir.cy()), real_c(dir.cz())); + + // get distance between this and neighboring interface point + n.diff -= getInterfacePoint(Vector3(normalFieldIt.getF(0), normalFieldIt.getF(1), normalFieldIt.getF(2)), *fillFieldIt); + n.dist2 = n.diff.sqrLength(); + } + + // exclude neighboring interface points that are too close or too far away from this cell's + // interface point + if (n.dist2 >= real_c(0.64) && n.dist2 <= real_c(3.24)) // Eq. 2.20, 0.64 = 0.8^2; 3.24 = 1.8^2 + { + n.normal = neighborNormal; + n.diffNorm = n.diff.getNormalized(); + n.area = real_c(0); + n.sort = real_c(0); + n.valid = true; + + neighbors.push_back(n); + } + + // if there is no obstacle, loop should be interrupted immediately + if (!isPartOfMaskSet(neighborFlags, obstacleFlagMask)) + { + // interrupt loop + break; + } + } + } + } + } + + // remove degenerated triangles, see dissertation of T. Pohl, 2008, p. 27 + for (auto nIt1 = ++neighbors.begin(); !neighbors.empty() && nIt1 != neighbors.end(); ++nIt1) + { + if (!nIt1->valid) { continue; } + + for (auto nIt2 = neighbors.begin(); nIt2 != nIt1; ++nIt2) + { + if (!nIt2->valid) { continue; } + + // triangle is degenerated if angle between surface normals is less than 30° (heuristically chosen + // in dissertation of T. Pohl, 2008, p. 27); greater sign is correct here due to cos(29) > cos(30) + if (nIt1->diffNorm * nIt2->diffNorm > + real_c(0.866)) // cos(30°) = 0.866, as in dissertation of T. Pohl, 2008, p. 27 + { + const real_t diff = nIt1->dist2 - nIt2->dist2; + + if (diff < real_c(1e-4)) { nIt1->valid = nIt1->wall ? true : false; } + if (diff > real_c(-1e-4)) { nIt2->valid = nIt2->wall ? true : false; } + } + } + } + + // remove invalid neighbors + neighbors.erase(std::remove_if(neighbors.begin(), neighbors.end(), [](const Neighbor& a) { return !a.valid; }), + neighbors.end()); + + if (neighbors.size() < 4) + { + // WALBERLA_LOG_WARNING_ON_ROOT( + // "Not enough faces in curvature reconstruction, setting curvature in this cell to zero."); + curv = real_c(0); // not documented in literature but taken from S. Donath's code + continue; // process next cell in WALBERLA_FOR_ALL_CELLS + } + + // compute mean normal + for (auto const& n : neighbors) + { + meanInterfaceNormal += n.normal; + } + meanInterfaceNormal = meanInterfaceNormal.getNormalized(); + + // compute xAxis and yAxis that define a coordinate system on a tangent plane for sorting neighbors + // T_i' = (I - N * N^t) * (p_i - p); projection of neighbors.diff[0] onto tangent plane (Figure 2.14 in + // dissertation of T. Pohl, 2008) + Vector3< real_t > xAxis = + (Matrix3< real_t >::makeIdentityMatrix() - dyadicProduct(meanInterfaceNormal, meanInterfaceNormal)) * + neighbors[0].diff; + + // T_i = T_i' / ||T_i'|| + xAxis = xAxis.getNormalized(); + + // get vector that is orthogonal to xAxis and meanInterfaceNormal + const Vector3< real_t > yAxis = cross(xAxis, meanInterfaceNormal); + + for (auto& n : neighbors) + { + // get cosine of angles between n.diff and axes of the new coordinate system + const real_t cosAngX = xAxis * n.diff; + const real_t cosAngY = yAxis * n.diff; + + // sort the neighboring interface points using atan2 (which is not just atan(wy/wx), see Wikipedia) + n.sort = std::atan2(cosAngY, cosAngX); + } + + std::sort(neighbors.begin(), neighbors.end(), + [](const Neighbor& a, const Neighbor& b) { return a.sort < b.sort; }); + + Vector3< real_t > meanTriangleNormal(real_c(0)); + for (auto nIt1 = neighbors.begin(); neighbors.size() > uint_c(1) && nIt1 != neighbors.end(); ++nIt1) + { + // index of second neighbor starts over at 0: (k + 1) mod N_P + auto nIt2 = (nIt1 != (neighbors.end() - 1)) ? (nIt1 + 1) : neighbors.begin(); + + // N_f_k (with real length, i.e., not normalized yet) + const Vector3< real_t > triangleNormal = cross(nIt1->diff, nIt2->diff); + + // |f_k| (multiplication with 0.5, since triangleNormal.length() is area of parallelogram and not + // triangle) + const real_t area = real_c(0.5) * triangleNormal.length(); + + // lambda_i' = |f_{(i-1+N_p) mod N_p}| + |f_i| + nIt1->area += area; // from the view of na, this is |f_i| + nIt2->area += area; // from the view of nb, this is |f_{(i-1+N_p) mod N_p}|, i.e., area of the face + // from the neighbor with smaller index + + // N' = sum(|f_k| * N_f_k) + meanTriangleNormal += area * triangleNormal; + } + + if (meanTriangleNormal.length() < real_c(1e-10)) + { + // WALBERLA_LOG_WARNING_ON_ROOT("Invalid meanTriangleNormal, setting curvature in this cell to zero."); + curv = real_c(0); // not documented in literature but taken from S. Donath's code + continue; // process next cell in WALBERLA_FOR_ALL_CELLS + } + + // N = N' / ||N'|| + meanTriangleNormal = meanTriangleNormal.getNormalized(); + + // I - N * N^t; matrix for projection of vector on tangent plane + const Matrix3< real_t > projMatrix = + Matrix3< real_t >::makeIdentityMatrix() - dyadicProduct(meanTriangleNormal, meanTriangleNormal); + + // M + Matrix3< real_t > mMatrix(real_c(0)); + + // sum(lambda_i') + real_t neighborAreaSum = real_c(0); + + // M = sum(lambda_i' * kappa_i * T_i * T_i^t) + for (auto& n : neighbors) + { + if (n.area > real_c(0)) + { + // kappa_i = 2 * N^t * (p_i - p) / ||p_i - p||^2 + const real_t kappa = real_c(2) * (meanTriangleNormal * n.diff) / n.dist2; + + // T_i' = (I - N * N^t) * (p_i - p) + Vector3< real_t > tVector = projMatrix * n.diff; + + // T_i = T_i' / ||T_i'|| + tVector = tVector.getNormalized(); + + // T_i * T_i^t + const Matrix3< real_t > auxMat = dyadicProduct(tVector, tVector); + + // M += T_i * T_i^t * kappa_i * lambda_i' + mMatrix += auxMat * kappa * n.area; + + // sum(lambda_i') + neighborAreaSum += n.area; + } + } + + // M = M * 1 / sum(lambda_i') + mMatrix = mMatrix * (real_c(1) / neighborAreaSum); + + // kappa = tr(M) + curv = (mMatrix(0, 0) + mMatrix(1, 1) + mMatrix(2, 2)); + }) // WALBERLA_FOR_ALL_CELLS +} + +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void CurvatureSweepSimpleFiniteDifferences< Stencil_T, FlagField_T, ScalarField_T, VectorField_T >::operator()( + IBlock* const block) +{ + // get fields + ScalarField_T* const curvatureField = block->getData< ScalarField_T >(curvatureFieldID_); + const VectorField_T* const normalField = block->getData< const VectorField_T >(normalFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + + // get flags + auto interfaceFlag = flagField->getFlag(interfaceFlagID_); + auto obstacleFlagMask = flagField->getMask(obstacleFlagIDSet_); + + WALBERLA_FOR_ALL_CELLS(flagFieldIt, flagField, normalFieldIt, normalField, curvatureFieldIt, curvatureField, { + real_t& curv = *curvatureFieldIt; + curv = real_c(0.0); + + // interface cells + if (isFlagSet(flagFieldIt, interfaceFlag)) + { + // this cell is next to a wall/obstacle + if (enableWetting_ && isFlagInNeighborhood< Stencil_T >(flagFieldIt, obstacleFlagMask)) + { + // compute wall/obstacle curvature + vector_t obstacleNormal(real_c(0), real_c(0), real_c(0)); + for (auto it = Stencil_T::beginNoCenter(); it != Stencil_T::end(); ++it) + { + // calculate obstacle normal with central finite difference approximation of the surface's gradient (see + // dissertation of S. Donath, 2011, section 6.3.5.2) + if (isPartOfMaskSet(flagFieldIt.neighbor(*it), obstacleFlagMask)) + { + obstacleNormal[0] -= real_c(it.cx()); + obstacleNormal[1] -= real_c(it.cy()); + obstacleNormal[2] -= real_c(it.cz()); + } + } + + if (obstacleNormal.sqrLength() > real_c(0)) + { + obstacleNormal = obstacleNormal.getNormalized(); + + // IMPORTANT REMARK: + // the following wetting model is not documented in literature and not tested for correctness; use it + // with caution + curv = -real_c(0.25) * (contactAngle_.getCos() - (Vector3(normalFieldIt.getF(0), normalFieldIt.getF(1), normalFieldIt.getF(2))) * obstacleNormal); + } + } + else // no obstacle cell is in next neighborhood + { + // central finite difference approximation of curvature (see dissertation of S. Bogner, 2017, + // section 4.4.2.1) + curv = normalFieldIt.neighbor(1, 0, 0, 0) - normalFieldIt.neighbor(-1, 0, 0, 0) + + normalFieldIt.neighbor(0, 1, 0, 1) - normalFieldIt.neighbor(0, -1, 0, 1) + + normalFieldIt.neighbor(0, 0, 1, 2) - normalFieldIt.neighbor(0, 0, -1, 2); + + curv *= real_c(0.25); + } + } + }) // WALBERLA_FOR_ALL_CELLS +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/DetectWettingSweep.h b/src/lbm_generated/free_surface/surface_geometry/DetectWettingSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..694f678d73f89ecb874e07b4330f1c55fa454b6f --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/DetectWettingSweep.h @@ -0,0 +1,317 @@ +//====================================================================================================================== +// +// 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 DetectWettingSweep.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Sweep for detecting cells that need to be converted to interface to obtain a smooth wetting interface. +// +//====================================================================================================================== + +#pragma once + +#include "blockforest/StructuredBlockForest.h" + +#include "core/math/Constants.h" + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FlagField.h" + +#include "stencil/D2Q4.h" +#include "stencil/D3Q19.h" + +#include <type_traits> +#include <vector> + +#include "ContactAngle.h" +#include "Utility.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Sweep for detecting interface cells that need to be created in order to obtain a smooth interface continuation in + * case of wetting. + * + * See dissertation of S. Donath, 2011 section 6.3.5.3. + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class DetectWettingSweep +{ + protected: + using FlagUIDSet = Set< FlagUID >; + + using vector_t = typename std::remove_const< typename VectorField_T::value_type >::type; + + // restrict stencil because surface continuation in corner directions is not meaningful + using WettingStencil_T = typename std::conditional< Stencil_T::D == uint_t(2), stencil::D2Q9, stencil::D3Q19 >::type; + + public: + DetectWettingSweep(const BlockDataID& flagFieldID, const FlagInfo< FlagField_T >& flagInfo, + const ConstBlockDataID& normalFieldID, const ConstBlockDataID& fillFieldID) + : flagFieldID_(flagFieldID), flagInfo_(flagInfo), normalFieldID_(normalFieldID), fillFieldID_(fillFieldID) + {} + + void operator()(IBlock* const block); + + private: + BlockDataID flagFieldID_; + FlagInfo< FlagField_T > flagInfo_; + ConstBlockDataID normalFieldID_; + ConstBlockDataID fillFieldID_; + +}; // class DetectWettingSweep + +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void DetectWettingSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T >::operator()( + IBlock* const block) +{ +// // get free surface boundary handling +// // get fields +// auto normalField = block->getData< const VectorField_T >(normalFieldID_); +// auto fillField = block->getData< const ScalarField_T >(fillFieldID_); +// FlagField_T flagField = block->getData< FlagField_T >(flagFieldID_); +// +// // get flags +// const FlagInfo< FlagField_T >& flagInfo = flagInfo_; +// using flag_t = typename FlagField_T::flag_t; +// +// const flag_t liquidInterfaceGasFlagMask = flagInfo_.liquidFlag | flagInfo_.interfaceFlag | flagInfo_.gasFlag; +// +// WALBERLA_FOR_ALL_CELLS(flagFieldIt, flagField, normalFieldIt, normalField, fillFieldIt, fillField, { +// // skip non-interface cells +// if (!isFlagSet(flagFieldIt, flagInfo.interfaceFlag)) { continue; } +// +// // skip cells that have no solid cell in their neighborhood +// if (!isFlagInNeighborhood< WettingStencil_T >(flagFieldIt, flagInfo.obstacleFlagMask)) { continue; } +// +// // restrict maximal and minimal angle such that the surface continuation does not become too flat +// if (*fillFieldIt < real_c(0.005) || *fillFieldIt > real_c(0.995)) { continue; } +// +// const Vector3< real_t > interfacePointLocation = getInterfacePoint(Vector3(normalFieldIt.getF(0), normalFieldIt.getF(1), normalFieldIt.getF(2)), *fillFieldIt); +// +// for (auto dir = WettingStencil_T::beginNoCenter(); dir != WettingStencil_T::end(); ++dir) +// { +// const Cell neighborCell = +// Cell(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz()); +// const flag_t neighborFlag = flagField->get(neighborCell); +// +// // skip neighboring cells that +// // - are not liquid, gas or interface +// // - are already marked for conversion to interface due to wetting +// // IMPORTANT REMARK: It is crucial that interface cells are NOT skipped here. Since the +// // "keepInterfaceForWettingFlag" flag is cleared in all cells after performing the conversion, interface cells +// // that were converted due to wetting must still get this flag to avoid being prematurely converted back. +// if (!isPartOfMaskSet(neighborFlag, liquidInterfaceGasFlagMask) || +// isFlagSet(neighborFlag, flagInfo.keepInterfaceForWettingFlag)) +// { +// continue; +// } +// +// // skip neighboring cells that do not have solid cells in their neighborhood +// bool hasObstacle = false; +// for (auto dir2 = WettingStencil_T::beginNoCenter(); dir2 != WettingStencil_T::end(); ++dir2) +// { +// const Cell neighborNeighborCell = +// Cell(flagFieldIt.x() + dir.cx() + dir2.cx(), flagFieldIt.y() + dir.cy() + dir2.cy(), +// flagFieldIt.z() + dir.cz() + dir2.cz()); +// const flag_t neighborNeighborFlag = flagField->get(neighborNeighborCell); +// +// if (isPartOfMaskSet(neighborNeighborFlag, flagInfo.obstacleFlagMask)) +// { +// hasObstacle = true; +// break; // exit dir2 loop +// } +// } +// if (!hasObstacle) { continue; } +// +// // check cell edges for intersection with the interface surface plane and mark the respective neighboring for +// // conversion +// // bottom south +// if ((dir.cx() == 0 && dir.cy() == -1 && dir.cz() == -1) || +// (dir.cx() == 0 && dir.cy() == 0 && dir.cz() == -1) || (dir.cx() == 0 && dir.cy() == -1 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), +// Vector3< real_t >(real_c(1), real_c(0), real_c(0)), +// Vector3(normalFieldIt.getF(0), normalFieldIt.getF(1), normalFieldIt.getF(2)), interfacePointLocation); +// if (intersection > real_c(0)) +// { +// // TODO look carefully at boundary handling and use correct method +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // bottom north +// if ((dir.cx() == 0 && dir.cy() == 1 && dir.cz() == -1) || (dir.cx() == 0 && dir.cy() == 0 && dir.cz() == -1) || +// (dir.cx() == 0 && dir.cy() == 1 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(0)), +// Vector3< real_t >(real_c(1), real_c(0), real_c(0)), +// Vector3(normalFieldIt.getF(0), normalFieldIt.getF(1), normalFieldIt.getF(2)), interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // bottom west +// if ((dir.cx() == -1 && dir.cy() == 0 && dir.cz() == -1) || +// (dir.cx() == 0 && dir.cy() == 0 && dir.cz() == -1) || (dir.cx() == -1 && dir.cy() == 0 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), +// Vector3< real_t >(real_c(0), real_c(1), real_c(0)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // bottom east +// if ((dir.cx() == 1 && dir.cy() == 0 && dir.cz() == -1) || (dir.cx() == 0 && dir.cy() == 0 && dir.cz() == -1) || +// (dir.cx() == 1 && dir.cy() == 0 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(0)), +// Vector3< real_t >(real_c(0), real_c(1), real_c(0)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // top south +// if ((dir.cx() == 0 && dir.cy() == -1 && dir.cz() == 1) || (dir.cx() == 0 && dir.cy() == 0 && dir.cz() == 1) || +// (dir.cx() == 0 && dir.cy() == -1 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(1)), +// Vector3< real_t >(real_c(1), real_c(0), real_c(0)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // top north +// if ((dir.cx() == 0 && dir.cy() == 1 && dir.cz() == 1) || (dir.cx() == 0 && dir.cy() == 0 && dir.cz() == 1) || +// (dir.cx() == 0 && dir.cy() == 1 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(1)), +// Vector3< real_t >(real_c(1), real_c(0), real_c(0)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // top west +// if ((dir.cx() == -1 && dir.cy() == 0 && dir.cz() == 1) || (dir.cx() == 0 && dir.cy() == 0 && dir.cz() == 1) || +// (dir.cx() == -1 && dir.cy() == 0 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(1)), +// Vector3< real_t >(real_c(0), real_c(1), real_c(0)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // top east +// if ((dir.cx() == 1 && dir.cy() == 0 && dir.cz() == 1) || (dir.cx() == 0 && dir.cy() == 0 && dir.cz() == 1) || +// (dir.cx() == 1 && dir.cy() == 0 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(1)), +// Vector3< real_t >(real_c(0), real_c(1), real_c(0)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // south-west +// if ((dir.cx() == -1 && dir.cy() == -1 && dir.cz() == 0) || +// (dir.cx() == 0 && dir.cy() == -1 && dir.cz() == 0) || (dir.cx() == -1 && dir.cy() == 0 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), +// Vector3< real_t >(real_c(0), real_c(0), real_c(1)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // south-east +// if ((dir.cx() == 1 && dir.cy() == -1 && dir.cz() == 0) || (dir.cx() == 0 && dir.cy() == -1 && dir.cz() == 0) || +// (dir.cx() == 1 && dir.cy() == 0 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(0)), +// Vector3< real_t >(real_c(0), real_c(0), real_c(1)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // north-west +// if ((dir.cx() == -1 && dir.cy() == 1 && dir.cz() == 0) || (dir.cx() == 0 && dir.cy() == 1 && dir.cz() == 0) || +// (dir.cx() == -1 && dir.cy() == 0 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(0)), +// Vector3< real_t >(real_c(0), real_c(0), real_c(1)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// +// // north-east +// if ((dir.cx() == 1 && dir.cy() == 1 && dir.cz() == 0) || (dir.cx() == 0 && dir.cy() == 1 && dir.cz() == 0) || +// (dir.cx() == 1 && dir.cy() == 0 && dir.cz() == 0)) +// { +// const real_t intersection = getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(1), real_c(0)), +// Vector3< real_t >(real_c(0), real_c(0), real_c(1)), +// *normalFieldIt, interfacePointLocation); +// if (intersection > real_c(0)) +// { +// flagField->addFlag(flagFieldIt.x() + dir.cx(), flagFieldIt.y() + dir.cy(), flagFieldIt.z() + dir.cz(), flagInfo.keepInterfaceForWettingFlag); +// continue; +// } +// } +// } +// }) // WALBERLA_FOR_ALL_CELLS +} + +} // namespace free_surface +} // namespace walberla \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/ExtrapolateNormalsSweep.h b/src/lbm_generated/free_surface/surface_geometry/ExtrapolateNormalsSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..417419043936c1779cae92a3edad06555c34e666 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/ExtrapolateNormalsSweep.h @@ -0,0 +1,71 @@ +//====================================================================================================================== +// +// 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 ExtrapolateNormalsSweep.h +//! \ingroup surface_geometry +//! \author Martin Bauer +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Extrapolate interface normals to neighboring cells in D3Q27 direction. +// +//====================================================================================================================== + +#pragma once + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FlagField.h" + +#include <type_traits> +#include <vector> + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Approximates the normals of non-interface cells using the normals of all neighboring interface cells in D3Q27 + * direction. + * The approximation is computed by summing the weighted normals of all neighboring interface cells. The weights are + * chosen as in the Parker-Youngs approximation. + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T, typename VectorField_T > +class ExtrapolateNormalsSweep +{ + protected: + using FlagUIDSet = Set< FlagUID >; + + using vector_t = Vector3<real_t>; + using flag_t = typename std::remove_const< typename FlagField_T::value_type >::type; + + public: + ExtrapolateNormalsSweep(const BlockDataID& normalFieldID, const ConstBlockDataID& flagFieldID, + const FlagUID& interfaceFlagID) + : normalFieldID_(normalFieldID), flagFieldID_(flagFieldID), interfaceFlagID_(interfaceFlagID) + {} + + void operator()(IBlock* const block); + + private: + BlockDataID normalFieldID_; + ConstBlockDataID flagFieldID_; + + FlagUID interfaceFlagID_; +}; // class ExtrapolateNormalsSweep + +} // namespace free_surface +} // namespace walberla + +#include "ExtrapolateNormalsSweep.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/ExtrapolateNormalsSweep.impl.h b/src/lbm_generated/free_surface/surface_geometry/ExtrapolateNormalsSweep.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..d2f553e303c1cbc5d5db227c10e880b097cb5a7a --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/ExtrapolateNormalsSweep.impl.h @@ -0,0 +1,68 @@ +//====================================================================================================================== +// +// 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 ExtrapolateNormalsSweep.impl.h +//! \ingroup surface_geometry +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Extrapolate interface normals to neighboring cells in D3Q27 direction. +// +//====================================================================================================================== + +#include "core/math/Vector3.h" + +#include "field/GhostLayerField.h" + +#include "stencil/D3Q27.h" + +#include "ExtrapolateNormalsSweep.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename Stencil_T, typename FlagField_T, typename VectorField_T > +void ExtrapolateNormalsSweep< Stencil_T, FlagField_T, VectorField_T >::operator()(IBlock* const block) +{ + VectorField_T* const normalField = block->getData< VectorField_T >(normalFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + + const auto interfaceFlag = flagField->getFlag(interfaceFlagID_); + + // compute normals in interface neighboring cells, i.e., in D3Q27 direction of interface cells + WALBERLA_FOR_ALL_CELLS(flagFieldIt, flagField, normalFieldIt, normalField, { + if (!isFlagSet(flagFieldIt, interfaceFlag) && isFlagInNeighborhood< stencil::D3Q27 >(flagFieldIt, interfaceFlag)) + { + vector_t normal = Vector3(normalFieldIt.getF(0), normalFieldIt.getF(1), normalFieldIt.getF(2)); + normal.set(real_c(0), real_c(0), real_c(0)); + + // approximate the normal of non-interface cells with the normal of neighboring interface cells (weights as + // in Parker-Youngs approximation) + for (auto i = Stencil_T::beginNoCenter(); i != Stencil_T::end(); ++i) + { + if (isFlagSet(flagFieldIt.neighbor(*i), interfaceFlag)) + { + normal += real_c(stencil::gaussianMultipliers[i.toIdx()]) * Vector3(normalFieldIt.neighbor(*i, 0), normalFieldIt.neighbor(*i, 1), normalFieldIt.neighbor(*i, 2)); + } + } + + // normalize the normal + normal = normal.getNormalizedOrZero(); + } + }) // WALBERLA_FOR_ALL_CELLS +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/NormalSweep.h b/src/lbm_generated/free_surface/surface_geometry/NormalSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..47ff64fca7bc101952223dbb875dccf5b0fc9ef9 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/NormalSweep.h @@ -0,0 +1,109 @@ +//====================================================================================================================== +// +// 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 NormalSweep.h +//! \ingroup surface_geometry +//! \author Martin Bauer <martin.bauer@fau.de> +//! \author Daniela Anderl +//! \author Stefan Donath +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Compute interface normal. +// +//====================================================================================================================== + +#pragma once + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FlagField.h" + +#include <type_traits> +#include <vector> + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Compute normals in interface cells by taking the derivative of the fill level field using the Parker-Youngs + * approximation. Near boundary cells, a modified Parker-Youngs approximation is applied with the cell being shifted by + * 0.5 away from the boundary. + * + * Details can be found in the Dissertation of S. Donath, page 21f. + * + * IMPORTANT REMARK: In this FSLBM implementation, the normal is defined to point from liquid to gas. + * + * More general: compute the gradient of a given scalar field on cells that are marked with a specific flag. + **********************************************************************************************************************/ + +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class NormalSweep +{ + protected: + using vector_t = Vector3<real_t>; + using scalar_t = typename std::remove_const< typename ScalarField_T::value_type >::type; + using flag_t = typename std::remove_const< typename FlagField_T::value_type >::type; + + public: + NormalSweep(const BlockDataID& normalFieldID, const ConstBlockDataID& fillFieldID, + const ConstBlockDataID& flagFieldID, const FlagUID& interfaceFlagID, + const Set< FlagUID >& liquidInterfaceGasFlagIDSet, const Set< FlagUID >& obstacleFlagIDSet, + bool computeInInterfaceNeighbors, bool includeObstacleNeighbors, bool modifyNearObstacles, + bool computeInGhostLayer) + : normalFieldID_(normalFieldID), fillFieldID_(fillFieldID), flagFieldID_(flagFieldID), + interfaceFlagID_(interfaceFlagID), liquidInterfaceGasFlagIDSet_(liquidInterfaceGasFlagIDSet), + obstacleFlagIDSet_(obstacleFlagIDSet), computeInInterfaceNeighbors_(computeInInterfaceNeighbors), + includeObstacleNeighbors_(includeObstacleNeighbors), modifyNearObstacles_(modifyNearObstacles), + computeInGhostLayer_(computeInGhostLayer) + {} + + void operator()(IBlock* const block); + + private: + BlockDataID normalFieldID_; + ConstBlockDataID fillFieldID_; + ConstBlockDataID flagFieldID_; + + FlagUID interfaceFlagID_; + Set< FlagUID > liquidInterfaceGasFlagIDSet_; + Set< FlagUID > obstacleFlagIDSet_; + + bool computeInInterfaceNeighbors_; + bool includeObstacleNeighbors_; + bool modifyNearObstacles_; + bool computeInGhostLayer_; +}; // class NormalSweep + +// namespace to use these functions outside NormalSweep, e.g., in ReinitializationSweep +namespace normal_computation +{ +// compute the normal using Parker-Youngs approximation (see dissertation of S. Donath, 2011, section 2.3.3.1.1) +template< typename Stencil_T, typename vector_t, typename ScalarFieldIt_T, typename FlagFieldIt_T, typename flag_t > +void computeNormal(vector_t& normal, const ScalarFieldIt_T& fillFieldIt, const FlagFieldIt_T& flagFieldIt, + const flag_t& validNeighborFlagMask); + +// near solid boundary cells, compute a Parker-Youngs approximation around a virtual (constructed) midpoint that is +// displaced by a distance of 0.5 away from the boundary (see dissertation of S. Donath, 2011, section 6.3.5.1) +template< typename Stencil_T, typename vector_t, typename ScalarFieldIt_T, typename FlagFieldIt_T, typename flag_t > +void computeNormalNearSolidBoundary(vector_t& normal, const ScalarFieldIt_T& fillFieldIt, + const FlagFieldIt_T& flagFieldIt, const flag_t& validNeighborFlagMask, + const flag_t& obstacleFlagMask); +} // namespace normal_computation + +} // namespace free_surface +} // namespace walberla + +#include "NormalSweep.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/NormalSweep.impl.h b/src/lbm_generated/free_surface/surface_geometry/NormalSweep.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..e144416fe478618775f9d7665b6c9a9c18cfaec7 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/NormalSweep.impl.h @@ -0,0 +1,458 @@ +//====================================================================================================================== +// +// 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 NormalSweep.impl.h +//! \ingroup surface_geometry +//! \author Martin Bauer +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Compute interface normal. +// +//====================================================================================================================== + +#include "core/logging/Logging.h" +#include "core/math/Vector3.h" + +#include "field/iterators/IteratorMacros.h" + +#include "stencil/D2Q9.h" +#include "stencil/D3Q19.h" +#include "stencil/D3Q27.h" + +#include <type_traits> + +#include "NormalSweep.h" +namespace walberla +{ +namespace free_surface_generated +{ + +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void NormalSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T >::operator()(IBlock* const block) +{ + // fetch fields + VectorField_T* const normalField = block->getData< VectorField_T >(normalFieldID_); + const ScalarField_T* const fillField = block->getData< const ScalarField_T >(fillFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + + // two ghost layers are required in the flag field + WALBERLA_ASSERT_EQUAL(flagField->nrOfGhostLayers(), uint_c(2)); + + // get flags + const flag_t interfaceFlag = flagField->getFlag(interfaceFlagID_); + const flag_t liquidInterfaceGasFlagMask = flagField->getMask(liquidInterfaceGasFlagIDSet_); + const flag_t obstacleFlagMask = flagField->getMask(obstacleFlagIDSet_); + + // evaluate flags in D2Q19 neighborhood for 2D, and in D3Q27 neighborhood for 3D simulations + using NeighborhoodStencil_T = + typename std::conditional< Stencil_T::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type; + + // include ghost layer because solid cells might be located in the (outermost global) ghost layer + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(normalField, uint_c(1), { + if (!computeInGhostLayer_ && (!flagField->isInInnerPart(Cell(x, y, z)))) { continue; } + + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + const typename ScalarField_T::ConstPtr fillFieldPtr(*fillField, x, y, z); + + const bool computeNormalInCell = + isFlagSet(flagFieldPtr, interfaceFlag) || + (computeInInterfaceNeighbors_ && isFlagInNeighborhood< NeighborhoodStencil_T >(flagFieldPtr, interfaceFlag)); + + vector_t normal = Vector3(normalField->get(x, y, z, 0), + normalField->get(x, y, z, 1), + normalField->get(x, y, z, 2)) ; + + if (computeNormalInCell) + { + if (includeObstacleNeighbors_) + { + // requires meaningful fill level values in obstacle cells, as set by ObstacleFillLevelSweep when using + // curvature computation via the finite difference method + normal_computation::computeNormal< Stencil_T >(normal, fillFieldPtr, flagFieldPtr, + liquidInterfaceGasFlagMask | obstacleFlagMask); + } + else + { + if (modifyNearObstacles_ && isFlagInNeighborhood< Stencil_T >(flagFieldPtr, obstacleFlagMask)) + { + // near solid boundary cells, compute a Parker-Youngs approximation around a virtual (constructed) + // midpoint that is displaced by a distance of 0.5 away from the boundary (see dissertation of S. Donath, + // 2011, section 6.3.5.1); use only for curvature computation based on local triangulation + normal_computation::computeNormalNearSolidBoundary< Stencil_T >( + normal, fillFieldPtr, flagFieldPtr, liquidInterfaceGasFlagMask, obstacleFlagMask); + } + else + { + normal_computation::computeNormal< Stencil_T >(normal, fillFieldPtr, flagFieldPtr, + liquidInterfaceGasFlagMask); + } + } + + // normalize and negate normal (to make it point from liquid to gas) + normal = real_c(-1) * normal.getNormalizedOrZero(); + } + else { normal.set(real_c(0), real_c(0), real_c(0)); } + }); // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ +} + +namespace normal_computation +{ +template< typename Stencil_T, typename vector_t, typename ScalarFieldIt_T, typename FlagFieldIt_T, typename flag_t > +void computeNormal(vector_t& normal, const ScalarFieldIt_T& fillFieldIt, const FlagFieldIt_T& flagFieldIt, + const flag_t& validNeighborFlagMask) +{ + // All computations are performed in double precision here (and truncated later). This is done to avoid an issue + // observed with the Intel 19 compiler when built in "DebugOptimized" mode with single precision. There, the result + // is dependent on the order of the "tmp_*" variables' definitions. It is assumed that an Intel-specific optimization + // leads to floating point inaccuracies. + normal = vector_t(real_c(0)); + + // avoid accessing neighbors that are out-of-range, i.e., restrict neighbor access to first ghost layer + const bool useW = flagFieldIt.x() >= cell_idx_c(0); + const bool useE = flagFieldIt.x() < cell_idx_c(flagFieldIt.getField()->xSize()); + const bool useS = flagFieldIt.y() >= cell_idx_c(0); + const bool useN = flagFieldIt.y() < cell_idx_c(flagFieldIt.getField()->ySize()); + + // loops are unrolled for improved computational performance + // IMPORTANT REMARK: the non-unrolled implementation was observed to give different results at O(1e-15); this + // accumulated and lead to inaccuracies, e.g., a drop wetting a surface became asymmetrical and started to move + // sideways + if constexpr (std::is_same_v< Stencil_T, stencil::D2Q9 >) + { + // get fill level in neighboring cells + const double tmp_S = useS && isPartOfMaskSet(flagFieldIt.neighbor(0, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, -1, 0)) : + static_cast< double >(0); + const double tmp_N = useN && isPartOfMaskSet(flagFieldIt.neighbor(0, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 1, 0)) : + static_cast< double >(0); + const double tmp_W = useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 0, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 0, 0)) : + static_cast< double >(0); + const double tmp_E = useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 0, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 0, 0)) : + static_cast< double >(0); + const double tmp_SW = useS && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, -1, 0)) : + static_cast< double >(0); + const double tmp_SE = useS && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, -1, 0)) : + static_cast< double >(0); + const double tmp_NW = useN && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 1, 0)) : + static_cast< double >(0); + const double tmp_NE = useN && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 1, 0)) : + static_cast< double >(0); + + // compute normal with Parker-Youngs approximation (PY) + const double weight_1 = real_c(4); + normal[0] = real_c(weight_1 * (tmp_E - tmp_W)); + normal[1] = real_c(weight_1 * (tmp_N - tmp_S)); + normal[2] = real_c(0); + + const double weight_2 = real_c(2); + normal[0] += real_c(weight_2 * ((tmp_NE + tmp_SE) - (tmp_NW + tmp_SW))); + normal[1] += real_c(weight_2 * ((tmp_NE + tmp_NW) - (tmp_SE + tmp_SW))); + } + else + { + const bool useB = flagFieldIt.z() >= cell_idx_c(0); + const bool useT = flagFieldIt.z() < cell_idx_c(flagFieldIt.getField()->zSize()); + + if constexpr (std::is_same_v< Stencil_T, stencil::D3Q19 >) + { + // get fill level in neighboring cells + const double tmp_S = useS && isPartOfMaskSet(flagFieldIt.neighbor(0, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, -1, 0)) : + static_cast< double >(0); + const double tmp_N = useN && isPartOfMaskSet(flagFieldIt.neighbor(0, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 1, 0)) : + static_cast< double >(0); + const double tmp_W = useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 0, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 0, 0)) : + static_cast< double >(0); + const double tmp_E = useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 0, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 0, 0)) : + static_cast< double >(0); + const double tmp_SW = useS && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, -1, 0)) : + static_cast< double >(0); + const double tmp_SE = useS && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, -1, 0)) : + static_cast< double >(0); + const double tmp_NW = useN && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 1, 0)) : + static_cast< double >(0); + const double tmp_NE = useN && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 1, 0)) : + static_cast< double >(0); + const double tmp_B = useB && isPartOfMaskSet(flagFieldIt.neighbor(0, 0, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 0, -1)) : + static_cast< double >(0); + const double tmp_T = useT && isPartOfMaskSet(flagFieldIt.neighbor(0, 0, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 0, 1)) : + static_cast< double >(0); + const double tmp_BS = useB && useS && isPartOfMaskSet(flagFieldIt.neighbor(0, -1, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, -1, -1)) : + static_cast< double >(0); + const double tmp_BN = useB && useN && isPartOfMaskSet(flagFieldIt.neighbor(0, 1, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 1, -1)) : + static_cast< double >(0); + const double tmp_BW = useB && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 0, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 0, -1)) : + static_cast< double >(0); + const double tmp_BE = useB && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 0, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 0, -1)) : + static_cast< double >(0); + const double tmp_TS = useT && useS && isPartOfMaskSet(flagFieldIt.neighbor(0, -1, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, -1, 1)) : + static_cast< double >(0); + const double tmp_TN = useT && useN && isPartOfMaskSet(flagFieldIt.neighbor(0, 1, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 1, 1)) : + static_cast< double >(0); + const double tmp_TW = useT && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 0, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 0, 1)) : + static_cast< double >(0); + const double tmp_TE = useT && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 0, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 0, 1)) : + static_cast< double >(0); + + // compute normal with Parker-Youngs approximation (PY) + const double weight_1 = real_c(4); + normal[0] = real_c(weight_1 * (tmp_E - tmp_W)); + normal[1] = real_c(weight_1 * (tmp_N - tmp_S)); + normal[2] = real_c(weight_1 * (tmp_T - tmp_B)); + + const double weight_2 = real_c(2); + normal[0] += real_c(weight_2 * ((tmp_NE + tmp_SE + tmp_TE + tmp_BE) - (tmp_NW + tmp_SW + tmp_TW + tmp_BW))); + normal[1] += real_c(weight_2 * ((tmp_NE + tmp_NW + tmp_TN + tmp_BN) - (tmp_SE + tmp_SW + tmp_TS + tmp_BS))); + normal[2] += real_c(weight_2 * ((tmp_TN + tmp_TS + tmp_TE + tmp_TW) - (tmp_BN + tmp_BS + tmp_BE + tmp_BW))); + } + else + { + if constexpr (std::is_same_v< Stencil_T, stencil::D3Q27 >) + { + // get fill level in neighboring cells + const double tmp_S = useS && isPartOfMaskSet(flagFieldIt.neighbor(0, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, -1, 0)) : + static_cast< double >(0); + const double tmp_N = useN && isPartOfMaskSet(flagFieldIt.neighbor(0, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 1, 0)) : + static_cast< double >(0); + const double tmp_W = useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 0, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 0, 0)) : + static_cast< double >(0); + const double tmp_E = useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 0, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 0, 0)) : + static_cast< double >(0); + const double tmp_SW = + useS && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, -1, 0)) : + static_cast< double >(0); + const double tmp_SE = + useS && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, -1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, -1, 0)) : + static_cast< double >(0); + const double tmp_NW = + useN && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 1, 0)) : + static_cast< double >(0); + const double tmp_NE = + useN && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 1, 0), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 1, 0)) : + static_cast< double >(0); + const double tmp_B = useB && isPartOfMaskSet(flagFieldIt.neighbor(0, 0, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 0, -1)) : + static_cast< double >(0); + const double tmp_T = useT && isPartOfMaskSet(flagFieldIt.neighbor(0, 0, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 0, 1)) : + static_cast< double >(0); + const double tmp_BS = + useB && useS && isPartOfMaskSet(flagFieldIt.neighbor(0, -1, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, -1, -1)) : + static_cast< double >(0); + const double tmp_BN = + useB && useN && isPartOfMaskSet(flagFieldIt.neighbor(0, 1, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 1, -1)) : + static_cast< double >(0); + const double tmp_BW = + useB && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 0, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 0, -1)) : + static_cast< double >(0); + const double tmp_BE = + useB && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 0, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 0, -1)) : + static_cast< double >(0); + const double tmp_TS = + useT && useS && isPartOfMaskSet(flagFieldIt.neighbor(0, -1, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, -1, 1)) : + static_cast< double >(0); + const double tmp_TN = + useT && useN && isPartOfMaskSet(flagFieldIt.neighbor(0, 1, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(0, 1, 1)) : + static_cast< double >(0); + const double tmp_TW = + useT && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 0, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 0, 1)) : + static_cast< double >(0); + const double tmp_TE = + useT && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 0, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 0, 1)) : + static_cast< double >(0); + const double tmp_BSW = + useB && useS && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, -1, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, -1, -1)) : + static_cast< double >(0); + const double tmp_BNW = + useB && useN && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 1, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 1, -1)) : + static_cast< double >(0); + const double tmp_BSE = + useB && useS && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, -1, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, -1, -1)) : + static_cast< double >(0); + const double tmp_BNE = + useB && useN && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 1, -1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 1, -1)) : + static_cast< double >(0); + const double tmp_TSW = + useT && useS && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, -1, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, -1, 1)) : + static_cast< double >(0); + const double tmp_TNW = + useT && useN && useW && isPartOfMaskSet(flagFieldIt.neighbor(-1, 1, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(-1, 1, 1)) : + static_cast< double >(0); + const double tmp_TSE = + useT && useS && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, -1, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, -1, 1)) : + static_cast< double >(0); + const double tmp_TNE = + useT && useN && useE && isPartOfMaskSet(flagFieldIt.neighbor(1, 1, 1), validNeighborFlagMask) ? + static_cast< double >(fillFieldIt.neighbor(1, 1, 1)) : + static_cast< double >(0); + + // compute normal with Parker-Youngs approximation (PY) + const double weight_1 = real_c(4); + normal[0] = real_c(weight_1 * (tmp_E - tmp_W)); + normal[1] = real_c(weight_1 * (tmp_N - tmp_S)); + normal[2] = real_c(weight_1 * (tmp_T - tmp_B)); + + const double weight_2 = real_c(2); + normal[0] += real_c(weight_2 * ((tmp_NE + tmp_SE + tmp_TE + tmp_BE) - (tmp_NW + tmp_SW + tmp_TW + tmp_BW))); + normal[1] += real_c(weight_2 * ((tmp_NE + tmp_NW + tmp_TN + tmp_BN) - (tmp_SE + tmp_SW + tmp_TS + tmp_BS))); + normal[2] += real_c(weight_2 * ((tmp_TN + tmp_TS + tmp_TE + tmp_TW) - (tmp_BN + tmp_BS + tmp_BE + tmp_BW))); + + // weight (=1) corresponding to Parker-Youngs approximation + normal[0] += real_c((tmp_TNE + tmp_TSE + tmp_BNE + tmp_BSE) - (tmp_TNW + tmp_TSW + tmp_BNW + tmp_BSW)); + normal[1] += real_c((tmp_TNE + tmp_TNW + tmp_BNE + tmp_BNW) - (tmp_TSE + tmp_TSW + tmp_BSE + tmp_BSW)); + normal[2] += real_c((tmp_TNE + tmp_TNW + tmp_TSE + tmp_TSW) - (tmp_BNE + tmp_BNW + tmp_BSE + tmp_BSW)); + } + else { WALBERLA_ABORT("The chosen stencil type is not implemented in computeNormal()."); } + } + } +} + +template< typename Stencil_T, typename vector_t, typename ScalarFieldIt_T, typename FlagFieldIt_T, typename flag_t > +void computeNormalNearSolidBoundary(vector_t& normal, const ScalarFieldIt_T& fillFieldIt, + const FlagFieldIt_T& flagFieldIt, const flag_t& validNeighborFlagMask, + const flag_t& obstacleFlagMask) +{ + Vector3< real_t > midPoint(real_c(0)); + + // construct the virtual midpoint + for (auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + if (isPartOfMaskSet(flagFieldIt.neighbor(*dir), validNeighborFlagMask) && + isPartOfMaskSet(flagFieldIt.neighbor(dir.inverseDir()), obstacleFlagMask)) + { + if constexpr (Stencil_T::D == uint_t(2)) + { + midPoint[0] += real_c(dir.cx()) * + real_c(stencil::gaussianMultipliers[stencil::D3Q27::idx[stencil::map2Dto3D[2][*dir]]]); + midPoint[1] += real_c(dir.cy()) * + real_c(stencil::gaussianMultipliers[stencil::D3Q27::idx[stencil::map2Dto3D[2][*dir]]]); + midPoint[2] += real_c(0); + } + else + { + midPoint[0] += real_c(dir.cx()) * real_c(stencil::gaussianMultipliers[dir.toIdx()]); + midPoint[1] += real_c(dir.cy()) * real_c(stencil::gaussianMultipliers[dir.toIdx()]); + midPoint[2] += real_c(dir.cz()) * real_c(stencil::gaussianMultipliers[dir.toIdx()]); + } + } + } + + // restrict the displacement of the virtual midpoint to an absolute value of 0.5 + for (uint_t i = uint_c(0); i != uint_c(3); ++i) + { + if (midPoint[i] > real_c(0.0)) { midPoint[i] = real_c(0.5); } + else + { + if (midPoint[i] < real_c(0.0)) { midPoint[i] = real_c(-0.5); } + // else midPoint[i] == 0 + } + } + + normal.set(real_c(0), real_c(0), real_c(0)); + + // use standard Parker-Youngs approximation (PY) for all cells without solid boundary neighbors + // otherwise shift neighboring cell by virtual midpoint (also referred to as narrower PY) + for (auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + // skip directions that have obstacleFlagMask set in this and the opposing direction; this is NOT documented in + // literature, however, it avoids that an undefined fill level is used from the wall cells + if (isPartOfMaskSet(flagFieldIt.neighbor(*dir), obstacleFlagMask) && + isPartOfMaskSet(flagFieldIt.neighbor(dir.inverseDir()), obstacleFlagMask)) + { + continue; + } + + cell_idx_t modCx = dir.cx(); + cell_idx_t modCy = dir.cy(); + cell_idx_t modCz = dir.cz(); + + // shift neighboring cells by midpoint if they are solid boundary cells + if (isPartOfMaskSet(flagFieldIt.neighbor(*dir), obstacleFlagMask) || + isPartOfMaskSet(flagFieldIt.neighbor(dir.inverseDir()), obstacleFlagMask)) + { + // truncate cells towards 0, i.e., make the access pattern narrower + modCx = cell_idx_c(real_c(modCx) + midPoint[0]); + modCy = cell_idx_c(real_c(modCy) + midPoint[1]); + modCz = cell_idx_c(real_c(modCz) + midPoint[2]); + } + + real_t fill; + + if (isPartOfMaskSet(flagFieldIt.neighbor(modCx, modCy, modCz), validNeighborFlagMask)) + { + // compute normal with formula from regular Parker-Youngs approximation + if constexpr (Stencil_T::D == uint_t(2)) + { + fill = fillFieldIt.neighbor(modCx, modCy, modCz) * + real_c(stencil::gaussianMultipliers[stencil::D3Q27::idx[stencil::map2Dto3D[2][*dir]]]); + } + else { fill = fillFieldIt.neighbor(modCx, modCy, modCz) * real_c(stencil::gaussianMultipliers[dir.toIdx()]); } + + normal[0] += real_c(dir.cx()) * fill; + normal[1] += real_c(dir.cy()) * fill; + normal[2] += real_c(dir.cz()) * fill; + } + else { normal = Vector3< real_t >(real_c(0)); } + } +} +} // namespace normal_computation +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/ObstacleFillLevelSweep.h b/src/lbm_generated/free_surface/surface_geometry/ObstacleFillLevelSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..031eca256b8305bf9c48dd5276a45cda0ba7eb34 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/ObstacleFillLevelSweep.h @@ -0,0 +1,91 @@ +//====================================================================================================================== +// +// 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 ObstacleFillLevelSweep.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Reflect fill levels into obstacle cells (for finite difference curvature computation). +// +//====================================================================================================================== + +#pragma once + +#include "blockforest/StructuredBlockForest.h" + +#include "domain_decomposition/BlockDataID.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Reflect fill levels into obstacle cells by averaging the fill levels from fluid cells with weights according to the + * surface normal. + * + * See dissertation of S. Bogner, 2017 (section 4.4.2.1). + * + * IMPORTANT REMARK: If an obstacle is located in a non-periodic outermost ghost layer, the fill level field must have + * two ghost layers. That is, the ObstacleFillLevelSweep computes the fill level obstacle of cells located in the + * outermost global ghost layer. For this, the all neighboring cells' fill levels are required. + * A single ghost layer is not sufficient, because the computed values by ObstacleFillLevelSweep (located in an + * outermost global ghost layer) are not communicated themselves. In the example below, the values A, D, E, and H are + * located in a global outermost ghost layer. Only directions without # shall be communicated and * marks ghost layers + * in the directions to be communicated. In this example, only B, C, F, and G will be communicated as expected. In + * contrast, A, D, E, and H will not be communicated. + * + * Block 1 Block 2 Block 1 Block 2 + * ###### ###### ###### ###### + * # A |* *| E # # A |* *| E # + * # ---- -----# # ---- -----# + * # B |* *| F # ===> communication # B |F B| F # + * # C |* *| G # # C |G C| G # + * # ---- -----# # ---- -----# + * # D |* *| H # # D |* *| H # + * ###### ###### ###### ###### + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class ObstacleFillLevelSweep +{ + protected: + using FlagUIDSet = Set< FlagUID >; + + using vector_t = Vector3<real_t>; + using flag_t = typename std::remove_const< typename FlagField_T::value_type >::type; + + public: + ObstacleFillLevelSweep(const BlockDataID& fillFieldDstID, const ConstBlockDataID& fillFieldSrcID, + const ConstBlockDataID& flagFieldID, const ConstBlockDataID& obstacleNormalFieldID, + const FlagUIDSet& liquidInterfaceGasFlagIDSet, const FlagUIDSet& obstacleFlagIDSet) + : fillFieldDstID_(fillFieldDstID), fillFieldSrcID_(fillFieldSrcID), flagFieldID_(flagFieldID), + obstacleNormalFieldID_(obstacleNormalFieldID), liquidInterfaceGasFlagIDSet_(liquidInterfaceGasFlagIDSet), + obstacleFlagIDSet_(obstacleFlagIDSet) + {} + + void operator()(IBlock* const block); + + private: + BlockDataID fillFieldDstID_; + ConstBlockDataID fillFieldSrcID_; + ConstBlockDataID flagFieldID_; + ConstBlockDataID obstacleNormalFieldID_; + + FlagUIDSet liquidInterfaceGasFlagIDSet_; + FlagUIDSet obstacleFlagIDSet_; +}; // class ObstacleFillLevelSweep + +} // namespace free_surface +} // namespace walberla + +#include "ObstacleFillLevelSweep.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/ObstacleFillLevelSweep.impl.h b/src/lbm_generated/free_surface/surface_geometry/ObstacleFillLevelSweep.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..c6fa9765cbd13319b0d447a4a9c494e2511471b3 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/ObstacleFillLevelSweep.impl.h @@ -0,0 +1,92 @@ +//====================================================================================================================== +// +// 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 ObstacleFillLevelSweep.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Reflect fill levels into obstacle cells (for finite difference curvature computation). +// +//====================================================================================================================== + +#include "core/debug/CheckFunctions.h" +#include "core/math/Utility.h" +#include "core/math/Vector3.h" + +#include "field/FlagField.h" + +#include <algorithm> +#include <cmath> + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void ObstacleFillLevelSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T >::operator()(IBlock* const block) +{ + // get fields + const ScalarField_T* const fillFieldSrc = block->getData< const ScalarField_T >(fillFieldSrcID_); + ScalarField_T* const fillFieldDst = block->getData< ScalarField_T >(fillFieldDstID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + const VectorField_T* const obstacleNormalField = block->getData< const VectorField_T >(obstacleNormalFieldID_); + + // get flags + const flag_t liquidInterfaceGasFlagMask = flagField->getMask(liquidInterfaceGasFlagIDSet_); + const flag_t obstacleFlagMask = flagField->getMask(obstacleFlagIDSet_); + + // equation (4.22) in dissertation of S. Bogner, 2017 (section 4.4.2.1); include ghost layer because solid cells + // might be located in the (outermost global) ghost layer + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(fillFieldDst, uint_c(1), { + // IMPORTANT REMARK: do not restrict this algorithm to obstacle cells that are direct neighbors of interface + // cells; the succeeding SmoothingSweep uses the values computed here and must be executed for an at least + // two-cell neighborhood of interface cells + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + const typename ScalarField_T::ConstPtr fillFieldSrcPtr(*fillFieldSrc, x, y, z); + const typename ScalarField_T::Ptr fillFieldDstPtr(*fillFieldDst, x, y, z); + + if (isPartOfMaskSet(flagFieldPtr, obstacleFlagMask) && + isFlagInNeighborhood< Stencil_T >(flagFieldPtr, liquidInterfaceGasFlagMask)) + { + vector_t obstacleNormal = Vector3(obstacleNormalField->get(x, y, z, 0), + obstacleNormalField->get(x, y, z, 1), + obstacleNormalField->get(x, y, z, 2)) ; + + WALBERLA_CHECK_GREATER(obstacleNormal.length(), real_c(0), + "An obstacleNormal of an obstacle cell was found to be zero in obstacleNormalSweep. " + "This is not plausible."); + + real_t sum = real_c(0); + real_t weightSum = real_c(0); + for (auto dir = Stencil_T::beginNoCenter(); dir != Stencil_T::end(); ++dir) + { + if (isPartOfMaskSet(flagFieldPtr.neighbor(*dir), liquidInterfaceGasFlagMask)) + { + const Vector3< real_t > dirVector = + Vector3< real_t >(real_c(dir.cx()), real_c(dir.cy()), real_c(dir.cz())).getNormalized(); + + const real_t weight = std::abs(obstacleNormal * dirVector); + + sum += weight * fillFieldSrcPtr.neighbor(*dir); + + weightSum += weight; + } + } + + *fillFieldDstPtr = weightSum > real_c(0) ? sum / weightSum : real_c(0); + } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ +} +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/ObstacleNormalSweep.h b/src/lbm_generated/free_surface/surface_geometry/ObstacleNormalSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..579f3a23f6d206681b2f0d568ecee7d35e8d60e7 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/ObstacleNormalSweep.h @@ -0,0 +1,98 @@ +//====================================================================================================================== +// +// 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 ObstacleNormalSweep.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Compute a mean obstacle normal in interface cells near solid boundary cells. +// +//====================================================================================================================== + +#pragma once + +#include "core/logging/Logging.h" + +#include "domain_decomposition/BlockDataID.h" + +#include "field/FlagField.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Compute a mean obstacle normal in interface cells near obstacle cells, and/or in obstacle cells. This reduces the + * influence of a stair-case approximated wall in the wetting model. + * + * - computeInInterfaceCells: Compute the obstacle normal in interface cells. Required when using curvature computation + * based on local triangulation (not with finite difference method). + * - computeInObstacleCells: Compute the obstacle normal in obstacle cells. Required when using curvature computation + * based on the finite difference method (not with local triangulation). + * + * Details can be found in the dissertation of S. Donath, 2011 section 6.3.5.2. + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T, typename VectorField_T > +class ObstacleNormalSweep +{ + protected: + using vector_t = Vector3<real_t>; + using flag_t = typename std::remove_const< typename FlagField_T::value_type >::type; + + public: + ObstacleNormalSweep(const BlockDataID& obstacleNormalFieldID, const ConstBlockDataID& flagFieldID, + const FlagUID& interfaceFlagID, const Set< FlagUID >& liquidInterfaceGasFlagIDSet, + const Set< FlagUID >& obstacleFlagIDSet, bool computeInInterfaceCells, + bool computeInObstacleCells, bool computeInGhostLayer) + : obstacleNormalFieldID_(obstacleNormalFieldID), flagFieldID_(flagFieldID), interfaceFlagID_(interfaceFlagID), + liquidInterfaceGasFlagIDSet_(liquidInterfaceGasFlagIDSet), obstacleFlagIDSet_(obstacleFlagIDSet), + computeInInterfaceCells_(computeInInterfaceCells), computeInObstacleCells_(computeInObstacleCells), + computeInGhostLayer_(computeInGhostLayer) + { + if (!computeInInterfaceCells_ && !computeInObstacleCells_) + { + WALBERLA_LOG_WARNING_ON_ROOT( + "In ObstacleNormalSweep, you specified to neither compute the obstacle normal in interface cells, nor in " + "obstacle cells. That is, ObstacleNormalSweep will do nothing. Please check if this is what you really " + "want."); + } + } + + void operator()(IBlock* const block); + + private: + template< typename FlagFieldIt_T > + void computeObstacleNormalInInterfaceCell(vector_t& obstacleNormal, const FlagFieldIt_T& flagFieldIt, + const flag_t& validNeighborFlagMask); + + template< typename FlagFieldIt_T > + void computeObstacleNormalInObstacleCell(vector_t& obstacleNormal, const FlagFieldIt_T& flagFieldIt, + const flag_t& liquidInterfaceGasFlagMask); + + BlockDataID obstacleNormalFieldID_; + ConstBlockDataID flagFieldID_; + + FlagUID interfaceFlagID_; + Set< FlagUID > liquidInterfaceGasFlagIDSet_; + Set< FlagUID > obstacleFlagIDSet_; + + bool computeInInterfaceCells_; + bool computeInObstacleCells_; + bool computeInGhostLayer_; +}; // class ObstacleNormalSweep + +} // namespace free_surface +} // namespace walberla + +#include "ObstacleNormalSweep.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/ObstacleNormalSweep.impl.h b/src/lbm_generated/free_surface/surface_geometry/ObstacleNormalSweep.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..aacc785076ad3ea617404404be062ffba89a8491 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/ObstacleNormalSweep.impl.h @@ -0,0 +1,149 @@ +//====================================================================================================================== +// +// 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 ObstacleNormalSweep.impl.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Compute a mean obstacle normal in interface cells near solid boundary cells. +// +//====================================================================================================================== + +#include "core/math/Vector3.h" + +#include "field/iterators/IteratorMacros.h" + +#include "stencil/D3Q27.h" + +#include "ObstacleNormalSweep.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename Stencil_T, typename FlagField_T, typename VectorField_T > +void ObstacleNormalSweep< Stencil_T, FlagField_T, VectorField_T >::operator()(IBlock* const block) +{ + // do nothing if obstacle normal must not be computed anywhere + if (!computeInInterfaceCells_ && !computeInObstacleCells_) { return; } + + // fetch fields + VectorField_T* const obstacleNormalField = block->getData< VectorField_T >(obstacleNormalFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + + // two ghost layers are required in the flag field + WALBERLA_ASSERT_EQUAL(flagField->nrOfGhostLayers(), uint_c(2)); + + // get flags + const flag_t interfaceFlag = flagField->getFlag(interfaceFlagID_); + const flag_t liquidInterfaceGasFlagMask = flagField->getMask(liquidInterfaceGasFlagIDSet_); + const flag_t obstacleFlagMask = flagField->getMask(obstacleFlagIDSet_); + + // include ghost layer because solid cells might be located in the (outermost global) ghost layer + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(obstacleNormalField, uint_c(1), { + if (!computeInGhostLayer_ && (!flagField->isInInnerPart(Cell(x, y, z)))) { continue; } + + const typename FlagField_T::ConstPtr flagFieldPtr(*flagField, x, y, z); + + const bool computeInInterfaceCell = computeInInterfaceCells_ && isPartOfMaskSet(flagFieldPtr, interfaceFlag) && + isFlagInNeighborhood< Stencil_T >(flagFieldPtr, obstacleFlagMask); + + const bool computeInObstacleCell = computeInObstacleCells_ && isPartOfMaskSet(flagFieldPtr, obstacleFlagMask) && + isFlagInNeighborhood< Stencil_T >(flagFieldPtr, liquidInterfaceGasFlagMask); + + // IMPORTANT REMARK: do not restrict this algorithm to obstacle cells that are direct neighbors of interface + // cells; the succeeding ObstacleFillLevelSweep and SmoothingSweep use the values computed here and the latter + // must work on an at least two-cell neighborhood of interface cells + + vector_t obstacleNormal = Vector3(obstacleNormalField->get(x, y, z, 0), + obstacleNormalField->get(x, y, z, 1), + obstacleNormalField->get(x, y, z, 2)) ; + + if (computeInInterfaceCell) + { + WALBERLA_ASSERT(!computeInObstacleCell); + + // compute mean obstacle, i.e., mean wall normal in interface cell (see dissertation of S. Donath, 2011, + // section 6.3.5.2) + computeObstacleNormalInInterfaceCell(obstacleNormal, flagFieldPtr, obstacleFlagMask); + } + else + { + if (computeInObstacleCell) + { + WALBERLA_ASSERT(!computeInInterfaceCell); + + // compute mean obstacle normal in obstacle cell + computeObstacleNormalInObstacleCell(obstacleNormal, flagFieldPtr, liquidInterfaceGasFlagMask); + } + else + { + // set obstacle normal of all other cells to zero + obstacleNormal.set(real_c(0), real_c(0), real_c(0)); + } + } + + // normalize mean obstacle normal + const real_t sqrObstNormal = obstacleNormal.sqrLength(); + if (sqrObstNormal > real_c(0)) + { + const real_t invlength = -real_c(1) / real_c(std::sqrt(sqrObstNormal)); + obstacleNormal *= invlength; + } + }); // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ +} + +template< typename Stencil_T, typename FlagField_T, typename VectorField_T > +template< typename FlagFieldIt_T > +void ObstacleNormalSweep< Stencil_T, FlagField_T, VectorField_T >::computeObstacleNormalInInterfaceCell( + vector_t& obstacleNormal, const FlagFieldIt_T& flagFieldIt, const flag_t& obstacleFlagMask) +{ + uint_t obstCount = uint_c(0); + obstacleNormal = vector_t(real_c(0)); + + for (auto i = Stencil_T::beginNoCenter(); i != Stencil_T::end(); ++i) + { + // only consider directions in which there is an obstacle cell + if (isPartOfMaskSet(flagFieldIt.neighbor(*i), obstacleFlagMask)) + { + obstacleNormal += vector_t(real_c(-i.cx()), real_c(-i.cy()), real_c(-i.cz())); + ++obstCount; + } + } + obstacleNormal = obstCount > uint_c(0) ? obstacleNormal / real_c(obstCount) : vector_t(real_c(0)); +} + +template< typename Stencil_T, typename FlagField_T, typename VectorField_T > +template< typename FlagFieldIt_T > +void ObstacleNormalSweep< Stencil_T, FlagField_T, VectorField_T >::computeObstacleNormalInObstacleCell( + vector_t& obstacleNormal, const FlagFieldIt_T& flagFieldIt, const flag_t& liquidInterfaceGasFlagMask) +{ + uint_t obstCount = uint_c(0); + obstacleNormal = vector_t(real_c(0)); + + for (auto i = Stencil_T::beginNoCenter(); i != Stencil_T::end(); ++i) + { + // only consider directions in which there is a liquid, interface, or gas cell + if (isPartOfMaskSet(flagFieldIt.neighbor(*i), liquidInterfaceGasFlagMask)) + { + obstacleNormal += vector_t(real_c(i.cx()), real_c(i.cy()), real_c(i.cz())); + + ++obstCount; + } + } + obstacleNormal = obstCount > uint_c(0) ? obstacleNormal / real_c(obstCount) : vector_t(real_c(0)); +} + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/SmoothingSweep.h b/src/lbm_generated/free_surface/surface_geometry/SmoothingSweep.h new file mode 100644 index 0000000000000000000000000000000000000000..b849f0334099f8b72e103161e033fecc1b1a02a0 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/SmoothingSweep.h @@ -0,0 +1,113 @@ +//====================================================================================================================== +// +// 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 SmoothingSweep.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Smooth fill levels (used for finite difference curvature computation). +// +//====================================================================================================================== + +#pragma once + +#include "blockforest/StructuredBlockForest.h" + +#include "domain_decomposition/BlockDataID.h" + +namespace walberla +{ +namespace free_surface_generated +{ +// forward declaration +template< typename Stencil_T > +class KernelK8; + +/*********************************************************************************************************************** + * Smooth fill levels such that interface-neighboring cells get assigned a new fill level. This is required for + * computing the interface curvature using the finite difference method. + * + * The same smoothing kernel is used as in the dissertation of S. Bogner, 2017, i.e., the K8 kernel with support + * radius 2 from + * Williams, Kothe and Puckett, "Accuracy and Convergence of Continuum Surface Tension Models", 1998. + **********************************************************************************************************************/ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +class SmoothingSweep +{ + protected: + using vector_t = typename std::remove_const< typename VectorField_T::value_type >::type; + using flag_t = typename std::remove_const< typename FlagField_T::value_type >::type; + + public: + SmoothingSweep(const BlockDataID& smoothFillFieldID, const ConstBlockDataID& fillFieldID, + const ConstBlockDataID& flagFieldID, const Set< FlagUID >& liquidInterfaceGasFlagIDSet, + const Set< FlagUID >& obstacleFlagIDSet, bool includeObstacleNeighbors) + : smoothFillFieldID_(smoothFillFieldID), fillFieldID_(fillFieldID), flagFieldID_(flagFieldID), + liquidInterfaceGasFlagIDSet_(liquidInterfaceGasFlagIDSet), obstacleFlagIDSet_(obstacleFlagIDSet), + includeObstacleNeighbors_(includeObstacleNeighbors), smoothingKernel_(KernelK8< Stencil_T >(real_c(2.0))) + {} + + void operator()(IBlock* const block); + + private: + BlockDataID smoothFillFieldID_; + ConstBlockDataID fillFieldID_; + ConstBlockDataID flagFieldID_; + + Set< FlagUID > liquidInterfaceGasFlagIDSet_; + Set< FlagUID > obstacleFlagIDSet_; + + bool includeObstacleNeighbors_; + + KernelK8< Stencil_T > smoothingKernel_; +}; // class SmoothingSweep + +/*********************************************************************************************************************** + * K8 kernel from Williams, Kothe and Puckett, "Accuracy and Convergence of Continuum Surface Tension Models", 1998. + **********************************************************************************************************************/ +template< typename Stencil_T > +class KernelK8 +{ + public: + KernelK8(real_t epsilon) : epsilon_(epsilon) { stencilSize_ = uint_c(std::ceil(epsilon_) - real_c(1)); } + + // equation (11) in Williams et al. (normalization constant A=1 here, result must be normalized outside the kernel) + inline real_t kernelFunction(const Vector3< real_t >& dirVec) const + { + const real_t r_sqr = dirVec.sqrLength(); + const real_t eps_sqr = epsilon_ * epsilon_; + + if (r_sqr < eps_sqr) + { + real_t result = real_c(1.0) - (r_sqr / (eps_sqr)); + result = result * result * result * result; + + return result; + } + else { return real_c(0.0); } + } + + inline real_t getSupportRadius() const { return epsilon_; } + inline uint_t getStencilSize() const { return static_cast< uint_t >(stencilSize_); } + + private: + real_t epsilon_; // support radius of the kernel + uint_t stencilSize_; // size of the stencil which defines included neighbors in smoothing + +}; // class KernelK8 + +} // namespace free_surface +} // namespace walberla + +#include "SmoothingSweep.impl.h" \ No newline at end of file diff --git a/src/lbm_generated/free_surface/surface_geometry/SmoothingSweep.impl.h b/src/lbm_generated/free_surface/surface_geometry/SmoothingSweep.impl.h new file mode 100644 index 0000000000000000000000000000000000000000..eab1f39df1aab47c83f2a02f1be1047d338e5794 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/SmoothingSweep.impl.h @@ -0,0 +1,159 @@ +//====================================================================================================================== +// +// 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 SmoothingSweep.impl.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Smooth fill levels (used for finite difference curvature computation). +// +//====================================================================================================================== + +#include "core/debug/CheckFunctions.h" +#include "core/math/Utility.h" +#include "core/math/Vector3.h" + +#include "field/FlagField.h" + +#include <algorithm> +#include <cmath> + +#include "ContactAngle.h" +#include "SmoothingSweep.h" +#include "Utility.h" + +namespace walberla +{ +namespace free_surface_generated +{ +template< typename Stencil_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T > +void SmoothingSweep< Stencil_T, FlagField_T, ScalarField_T, VectorField_T >::operator()(IBlock* const block) +{ + // get fields + ScalarField_T* const smoothFillField = block->getData< ScalarField_T >(smoothFillFieldID_); + const ScalarField_T* const fillField = block->getData< const ScalarField_T >(fillFieldID_); + const FlagField_T* const flagField = block->getData< const FlagField_T >(flagFieldID_); + + // get flags + const flag_t liquidInterfaceGasFlagMask = flagField->getMask(liquidInterfaceGasFlagIDSet_); + const flag_t obstacleFlagMask = flagField->getMask(obstacleFlagIDSet_); + + // const KernelK8< Stencil_T > smoothingKernel(real_c(2.0)); + + const uint_t kernelSize = smoothingKernel_.getStencilSize(); + + WALBERLA_CHECK_GREATER_EQUAL( + smoothFillField->nrOfGhostLayers(), kernelSize, + "Support radius of smoothing kernel results in a smoothing stencil size that exceeds the ghost layers."); + + // including ghost layers is not necessary, even if solid cells are located in the (outermost global) ghost layer, + // because fill level in obstacle cells is set by ObstacleFillLevelSweep + WALBERLA_FOR_ALL_CELLS(smoothFillFieldIt, smoothFillField, fillFieldIt, fillField, flagFieldIt, flagField, { + // IMPORTANT REMARK: do not restrict this algorithm to interface cells and their neighbors; when the normals are + // computed in the neighborhood of interface cells, the second neighbors of interface cells are also required + + // mollify fill level in interface, liquid, and gas according to equation (9) in Williams et al. + if (isPartOfMaskSet(flagFieldIt, liquidInterfaceGasFlagMask)) + { + real_t normalizationConstant = real_c(0); + real_t smoothedFillLevel = real_c(0); + if constexpr (Stencil_T::D == uint_t(2)) + { + for (int j = -int_c(kernelSize); j <= int_c(kernelSize); ++j) + { + for (int i = -int_c(kernelSize); i <= int_c(kernelSize); ++i) + { + const Vector3< real_t > dirVector(real_c(i), real_c(j), real_c(0)); + + if (isPartOfMaskSet(flagFieldIt.neighbor(cell_idx_c(i), cell_idx_c(j), cell_idx_c(0)), + obstacleFlagMask)) + { + if (includeObstacleNeighbors_) + { + // in solid cells, use values from smoothed fill field (instead of regular fill field) that have + // been set by ObstacleFillLevelSweep + smoothedFillLevel += smoothingKernel_.kernelFunction(dirVector) * + smoothFillFieldIt.neighbor(cell_idx_c(i), cell_idx_c(j), cell_idx_c(0)); + + if (dirVector.length() < smoothingKernel_.getSupportRadius()) + { + normalizationConstant += smoothingKernel_.kernelFunction(dirVector); + } + } // else: do not include this direction in smoothing + } + else + { + smoothedFillLevel += smoothingKernel_.kernelFunction(dirVector) * + fillFieldIt.neighbor(cell_idx_c(i), cell_idx_c(j), cell_idx_c(0)); + + if (dirVector.length() < smoothingKernel_.getSupportRadius()) + { + normalizationConstant += smoothingKernel_.kernelFunction(dirVector); + } + } + } + } + } + else + { + if constexpr (Stencil_T::D == uint_t(3)) + { + for (int k = -int_c(kernelSize); k <= int_c(kernelSize); ++k) + { + for (int j = -int_c(kernelSize); j <= int_c(kernelSize); ++j) + { + for (int i = -int_c(kernelSize); i <= int_c(kernelSize); ++i) + { + const Vector3< real_t > dirVector(real_c(i), real_c(j), real_c(k)); + + if (isPartOfMaskSet(flagFieldIt.neighbor(cell_idx_c(i), cell_idx_c(j), cell_idx_c(k)), + obstacleFlagMask)) + { + if (includeObstacleNeighbors_) + { + // in solid cells, use values from smoothed fill field (instead of regular fill field) + // that have been set by ObstacleFillLevelSweep + smoothedFillLevel += + smoothingKernel_.kernelFunction(dirVector) * + smoothFillFieldIt.neighbor(cell_idx_c(i), cell_idx_c(j), cell_idx_c(k)); + + if (dirVector.length() < smoothingKernel_.getSupportRadius()) + { + normalizationConstant += smoothingKernel_.kernelFunction(dirVector); + } + } // else: do not include this direction in smoothing + } + else + { + smoothedFillLevel += smoothingKernel_.kernelFunction(dirVector) * + fillFieldIt.neighbor(cell_idx_c(i), cell_idx_c(j), cell_idx_c(k)); + + if (dirVector.length() < smoothingKernel_.getSupportRadius()) + { + normalizationConstant += smoothingKernel_.kernelFunction(dirVector); + } + } + } + } + } + } + } + + smoothedFillLevel /= normalizationConstant; + *smoothFillFieldIt = smoothedFillLevel; + } + }) // WALBERLA_FOR_ALL_CELLS +} +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/SurfaceGeometryHandler.h b/src/lbm_generated/free_surface/surface_geometry/SurfaceGeometryHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..db78bf3c0cf3b98f311290dfb8cad0a411eb9373 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/SurfaceGeometryHandler.h @@ -0,0 +1,225 @@ +//====================================================================================================================== +// +// 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 SurfaceGeometryHandler.h +//! \ingroup surface_geometry +//! \author Matthias Markl <matthias.markl@fau.de> +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Handles the surface geometry (normal and curvature computation) by creating fields and adding sweeps. +// +//====================================================================================================================== + +#pragma once + +#include "core/StringUtility.h" + +#include "domain_decomposition/StructuredBlockStorage.h" + +#include "field/AddToStorage.h" +#include "field/FlagField.h" + +#include "lbm_generated/blockforest/SimpleCommunication.h" +#include "lbm_generated/free_surface/BlockStateDetectorSweep.h" +#include "lbm_generated/free_surface/FlagInfo.h" + +#include "stencil/D2Q9.h" +#include "stencil/D3Q27.h" + +#include "timeloop/SweepTimeloop.h" + +#include <type_traits> +#include <vector> + +#include "CurvatureModel.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Handles the surface geometry (normal and curvature computation) by creating fields and adding sweeps. + **********************************************************************************************************************/ +template< typename StorageSpecification_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T, typename FlagInfo_T > +class SurfaceGeometryHandler +{ + protected: + using vector_t = Vector3<real_t>; + + // explicitly use either D2Q9 or D3Q27 here, as the geometry operations require (or are most accurate with) the full + // neighborhood; + using Stencil_T = typename std::conditional< StorageSpecification_T::Stencil::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type; + using Communication_T = lbm_generated::SimpleCommunication< Stencil_T >; + using StateSweep = BlockStateDetectorSweep< FlagField_T >; // used in friend classes + + public: + SurfaceGeometryHandler(const std::shared_ptr< StructuredBlockForest >& blockForest, + FlagInfo_T& flagInfo, + const BlockDataID& flagFieldID, + const BlockDataID& fillFieldID, const std::string& curvatureModel, bool computeCurvature, + bool enableWetting, real_t contactAngleInDegrees) + : blockForest_(blockForest), flagInfo_(flagInfo), flagFieldID_(flagFieldID), fillFieldID_(fillFieldID), + curvatureModel_(curvatureModel), computeCurvature_(computeCurvature), enableWetting_(enableWetting), + contactAngle_(ContactAngle(contactAngleInDegrees)) + { + curvatureFieldID_ = field::addToStorage< ScalarField_T >(blockForest_, "Curvature field", real_c(0), field::fzyx, uint_c(1)); + normalFieldID_ = field::addToStorage< VectorField_T >(blockForest_, "Normal field", real_c(0), field::fzyx, uint_c(1)); + obstacleNormalFieldID_ = field::addToStorage< VectorField_T >(blockForest_, "Obstacle normal field", real_c(0), field::fzyx, uint_c(1)); + + obstacleFlagIDSet_ = flagInfo_.getObstacleIDSet(); + + if (StorageSpecification_T::Stencil::D == uint_t(2)) + { + WALBERLA_LOG_INFO_ON_ROOT( + "IMPORTANT REMARK: You are using a D2Q9 stencil in SurfaceGeometryHandler. Be aware that the " + "results might slightly differ when compared to a D3Q19 stencil and periodicity in the third direction. " + "This is caused by the smoothing of the fill level field, where the additional directions in the D3Q27 add " + "additional weights to the smoothing kernel. Therefore, the resulting smoothed fill level will be " + "different.") + } + } + + ConstBlockDataID getConstCurvatureFieldID() const { return curvatureFieldID_; } + ConstBlockDataID getConstNormalFieldID() const { return normalFieldID_; } + ConstBlockDataID getConstObstNormalFieldID() const { return obstacleNormalFieldID_; } + + BlockDataID getCurvatureFieldID() const { return curvatureFieldID_; } + BlockDataID getNormalFieldID() const { return normalFieldID_; } + BlockDataID getObstNormalFieldID() const { return obstacleNormalFieldID_; } + FlagInfo_T getFlagInfo() const {return flagInfo_;} + Vector3< bool > isObstacleInGlobalGhostLayer() const + { + Vector3< bool > isObstacleInGlobalGhostLayer(false, false, false); + + for (auto blockIt = blockForest_->begin(); blockIt != blockForest_->end(); ++blockIt) + { + const FlagField_T* const flagField = blockIt->template getData< const FlagField_T >(flagFieldID_); + + const CellInterval domainCellBB = blockForest_->getDomainCellBB(); + + // disable OpenMP such that loop termination works correctly + WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP(flagField, uint_c(1), omp critical, { + // get cell in global coordinates + Cell globalCell = Cell(x, y, z); + blockForest_->transformBlockLocalToGlobalCell(globalCell, *blockIt); + + // check if the current cell is located in a global ghost layer + const bool isCellInGlobalGhostLayerX = + globalCell[0] < domainCellBB.xMin() || globalCell[0] > domainCellBB.xMax(); + + const bool isCellInGlobalGhostLayerY = + globalCell[1] < domainCellBB.yMin() || globalCell[1] > domainCellBB.yMax(); + + const bool isCellInGlobalGhostLayerZ = + globalCell[2] < domainCellBB.zMin() || globalCell[2] > domainCellBB.zMax(); + + // skip corners, as they do not influence periodic communication + if ((isCellInGlobalGhostLayerX && (isCellInGlobalGhostLayerY || isCellInGlobalGhostLayerZ)) || + (isCellInGlobalGhostLayerY && isCellInGlobalGhostLayerZ)) + { + continue; + } + + if (!isObstacleInGlobalGhostLayer[0] && isCellInGlobalGhostLayerX && + isPartOfMaskSet(flagField->get(x, y, z), flagField->getMask(flagInfo_.getObstacleIDSet()))) + { + isObstacleInGlobalGhostLayer[0] = true; + } + + if (!isObstacleInGlobalGhostLayer[1] && isCellInGlobalGhostLayerY && + isPartOfMaskSet(flagField->get(x, y, z), flagField->getMask(flagInfo_.getObstacleIDSet()))) + { + isObstacleInGlobalGhostLayer[1] = true; + } + + if (!isObstacleInGlobalGhostLayer[2] && isCellInGlobalGhostLayerZ && + isPartOfMaskSet(flagField->get(x, y, z), flagField->getMask(flagInfo_.getObstacleIDSet()))) + { + isObstacleInGlobalGhostLayer[2] = true; + } + + if (isObstacleInGlobalGhostLayer[0] && isObstacleInGlobalGhostLayer[1] && isObstacleInGlobalGhostLayer[2]) + { + break; // there is no need to check other cells on this block + } + }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ_OMP + } + + mpi::allReduceInplace(isObstacleInGlobalGhostLayer, mpi::LOGICAL_OR); + + return isObstacleInGlobalGhostLayer; + }; + + void addSweeps(SweepTimeloop& timeloop) const + { + auto blockStateUpdate = StateSweep(blockForest_, flagInfo_, flagFieldID_); + + if (!string_icompare(curvatureModel_, "FiniteDifferenceMethod")) + { + curvature_model::FiniteDifferenceMethod< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo_T > + model; + model.addSweeps(timeloop, *this); + } + else + { + if (!string_icompare(curvatureModel_, "LocalTriangulation")) + { + curvature_model::LocalTriangulation< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, VectorField_T, FlagInfo_T > + model; + model.addSweeps(timeloop, *this); + } + else + { + if (!string_icompare(curvatureModel_, "SimpleFiniteDifferenceMethod")) + { + curvature_model::SimpleFiniteDifferenceMethod< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T, FlagInfo_T > + model; + model.addSweeps(timeloop, *this); + } + else { WALBERLA_ABORT("The specified curvature model is unknown.") } + } + } + } + + protected: + std::shared_ptr< StructuredBlockForest > blockForest_; // used by friend classes + + FlagInfo_T& flagInfo_; + + BlockDataID flagFieldID_; + ConstBlockDataID fillFieldID_; + + BlockDataID curvatureFieldID_; + BlockDataID normalFieldID_; + BlockDataID obstacleNormalFieldID_; // mean normal in obstacle cells required for e.g. artificial curvature contact + // model (dissertation of S. Donath, 2011, section 6.5.3.2) + + Set< FlagUID > obstacleFlagIDSet_; // used by friend classes (see CurvatureModel.impl.h) + + std::string curvatureModel_; + bool computeCurvature_; // allows to not compute curvature (just normal) when e.g. the surface tension is 0 + bool enableWetting_; // used by friend classes + ContactAngle contactAngle_; // used by friend classes + + friend class curvature_model::FiniteDifferenceMethod< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T, FlagInfo_T >; + friend class curvature_model::LocalTriangulation< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T, FlagInfo_T >; + friend class curvature_model::SimpleFiniteDifferenceMethod< Stencil_T, StorageSpecification_T, FlagField_T, ScalarField_T, + VectorField_T, FlagInfo_T >; +}; // class SurfaceGeometryHandler + +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/Utility.cpp b/src/lbm_generated/free_surface/surface_geometry/Utility.cpp new file mode 100644 index 0000000000000000000000000000000000000000..08711fd8b85584a963e3c5197110fe426aedceac --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/Utility.cpp @@ -0,0 +1,547 @@ +//====================================================================================================================== +// +// 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 Utility.cpp +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Helper functions for surface geometry computations. +// +//====================================================================================================================== + +#include "Utility.h" + +#include "core/math/Constants.h" +#include "core/math/Matrix3.h" +#include "core/math/Vector3.h" + +#include <cmath> +#include <vector> + +#include "ContactAngle.h" + +namespace walberla +{ +namespace free_surface_generated +{ +bool computeArtificalWallPoint(const Vector3< real_t >& globalInterfacePointLocation, + const Vector3< real_t >& globalCellCoordinate, const Vector3< real_t >& normal, + const Vector3< real_t >& wallNormal, const Vector3< real_t >& obstacleNormal, + const ContactAngle& contactAngle, Vector3< real_t >& artificialWallPointCoord, + Vector3< real_t >& artificialWallNormal) +{ + // get local interface point location (location of interface point inside cell with origin (0.5,0.5,0.5)) + const Vector3< real_t > interfacePointLocation = globalInterfacePointLocation - globalCellCoordinate; + + // check whether the interface plane intersects one of the cell's edges; exit this function if it does not intersect + // any edge in at least one direction (see dissertation of S. Donath, 2011, section 6.4.5.4) + if (wallNormal[0] < real_c(0) && + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(1)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, + interfacePointLocation) < real_c(0))) + { + return false; + } + + if (wallNormal[0] > real_c(0) && + (getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(1), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(1)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, + interfacePointLocation) < real_c(0))) + { + return false; + } + + if (wallNormal[1] < real_c(0) && + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(1)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, + interfacePointLocation) < real_c(0))) + { + return false; + } + + if (wallNormal[1] > real_c(0) && + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(0)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(1), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(1)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, + interfacePointLocation) < real_c(0))) + { + return false; + } + + if (wallNormal[2] < real_c(0) && + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(0)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, + interfacePointLocation) < real_c(0))) + { + return false; + } + + if (wallNormal[2] > real_c(0) && + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(1)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(1)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(1)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, + interfacePointLocation) < real_c(0)) && + + (getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(1)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, + interfacePointLocation) < real_c(0))) + { + return false; + } + + // line 1 in Algorithm 6.2 in dissertation of S. Donath, 2011 + real_t cosAlpha = dot(normal, obstacleNormal); + + // determine sin(alpha) via orthogonal projections to compute alpha in the correct quadrant + const Vector3< real_t > projector = normal - cosAlpha * obstacleNormal; + const Vector3< real_t > baseVec = cross(obstacleNormal, normal); + const real_t sinAlpha = projector * cross(baseVec, obstacleNormal).getNormalized(); + + // compute alpha (angle between surface plane and wall) + real_t alpha; + if (sinAlpha >= real_c(0)) { alpha = real_c(std::acos(cosAlpha)); } + else { alpha = real_c(2) * math::pi - real_c(std::acos(cosAlpha)); } + + // line 2 in Algorithm 6.2 in dissertation of S. Donath, 2011 + const real_t theta = contactAngle.getInRadians(); + const real_t delta = theta - alpha; + + // determine distance from interface point to wall plane + const real_t wallDistance = dot(fabs(interfacePointLocation), wallNormal); + + // line 3-4 in Algorithm 6.2 in dissertation of S. Donath, 2011 + // correct contact angle is already reached + if (realIsEqual(delta, real_c(0), real_c(1e-14))) + { + // IMPORTANT: the following approach is in contrast to the dissertation of S. Donath, 2011 + // - Dissertation: only the intersection of the interface surface with the wall is computed; it is known that this + // could in rare cases lead to unstable configurations + // - Here: extrapolate (extend) the wall point such that the triangle is considered valid during curvature + // computation; + // This has been adapted from the old waLBerla source code. While delta is considered to be zero, there is + // still a division by delta below. This has not been found problematic when using double precision, however it + // leads to invalid values in single precision. Therefore, the following macro exits the function prematurely when + // using single precision and returns false such that the wall point will not be used. +#ifndef WALBERLA_DOUBLE_ACCURACY + return false; +#endif + + // determine the direction in which the artifical wall point is to be expected + // expression "dot(wallNormal,normal)*wallNormal" is orthogonal projection of normal on wallNormal + // (wallNormal has already been normalized => no division by vector length required) + const Vector3< real_t > targetDir = (normal - dot(wallNormal, normal) * wallNormal).getNormalized(); + + const real_t sinTheta = contactAngle.getSin(); + + // distance of interface point to wall in direction of wallNormal + real_t wallPointDistance = wallDistance / sinTheta; // d_WP in dissertation of S. Donath, 2011 + + // wall point must not be too close or far away from interface point too avoid degenerated triangles + real_t virtWallDistance; + if (wallPointDistance < real_c(0.8)) + { + // extend distance with a heuristically chosen tolerance such that the point is not thrown away when checking + // for degenerated triangles in the curvature computation + wallPointDistance = real_c(0.801); + virtWallDistance = wallPointDistance * sinTheta; // d_W' + } + else + { + if (wallPointDistance > real_c(1.8)) + { + // reduce distance with heuristically chosen tolerance + wallPointDistance = real_c(1.799); + virtWallDistance = wallPointDistance * sinTheta; // d_W' + } + else + { + virtWallDistance = wallDistance; // d_W' + } + } + + const real_t virtPointDistance = virtWallDistance / sinTheta; // d_WP' + + // compute point by shifting virtWallDistance along wallNormal starting from globalInterfacePointLocation + // virtWallProjection is given in global coordinates + const Vector3< real_t > virtWallProjection = globalInterfacePointLocation - virtWallDistance * wallNormal; + + const real_t cosTheta = contactAngle.getCos(); + + // compute artificial wall point by starting from virtWallProjection and shifting "virtPointDistance*cosTheta" in + // targetDir (vritualWallPointCoord is r_W in dissertation of S. Donath, 2011) + artificialWallPointCoord = virtWallProjection + virtPointDistance * cosTheta * targetDir; + + // radius r of the artificial circle "virtPointDistance*0.5/(sin(0.5)*delta)" + // midpoint M of the artificial circle "normal*r+globalInterfacePointLocation" + // normal of the virtual wall point "M-artificialWallPointCoord" + artificialWallNormal = normal * virtPointDistance * real_c(0.5) / (std::sin(real_c(0.5) * delta)) + + globalInterfacePointLocation - artificialWallPointCoord; + artificialWallNormal = artificialWallNormal.getNormalized(); + + return true; + } + else + { + // compute base angles of triangle; line 6 in Algorithm 6.2 in dissertation of S. Donath, 2011 + const real_t beta = real_c(0.5) * (math::pi - real_c(std::fabs(delta))); + + real_t gamma; + // line 7 in Algorithm 6.2 in dissertation of S. Donath, 2011 + if (theta < alpha) { gamma = beta - theta; } + else { gamma = beta + theta - math::pi; } + + const real_t wallPointDistance = + wallDistance / real_c(std::cos(std::fabs(gamma))); // d_WP in dissertation of S. Donath, 2011 + + // line 9 in Algorithm 6.2 in dissertation of S. Donath, 2011 + // division by zero not possible as delta==0 is caught above + real_t radius = real_c(0.5) * wallPointDistance / std::sin(real_c(0.5) * real_c(std::fabs(delta))); + + // check wallPointDistance for being in a valid range (to avoid degenerated triangles) + real_t artificialWallPointDistance = wallPointDistance; // d'_WP in dissertation of S. Donath, 2011 + + if (wallPointDistance < real_c(0.8)) + { + // extend distance with a heuristically chosen tolerance such that the point is not thrown away when checking + // for degenerated triangles in the curvature computation + artificialWallPointDistance = real_c(0.801); + + // if extended distance exceeds circle diameter, assume delta=90 degrees + if (artificialWallPointDistance > real_c(2) * radius) + { + radius = artificialWallPointDistance * math::one_div_root_two; + } + } + else + { + // reduce distance with heuristically chosen tolerance + if (wallPointDistance > real_c(1.8)) { artificialWallPointDistance = real_c(1.799); } + } + + // line 17 in Algorithm 6.2 in dissertation of S. Donath, 2011 + const real_t artificialDelta = + real_c(2) * real_c(std::asin(real_c(0.5) * artificialWallPointDistance / + real_c(radius))); // delta' in dissertation of S. Donath, 2011 + + // line 18 in Algorithm 6.2 in dissertation of S. Donath, 2011 + Vector3< real_t > rotVec = cross(normal, obstacleNormal); + + // change direction of rotation axis for delta>0; this is in contrast to Algorithm 6.2 in dissertation of Stefan + // Donath, 2011 but was found to be necessary as otherwise the artificialWallNormal points in the wrong direction + if (delta > real_c(0)) { rotVec *= real_c(-1); } + + // line 19 in Algorithm 6.2 in dissertation of S. Donath, 2011 + const Matrix3< real_t > rotMat(rotVec, artificialDelta); + + // line 20 in Algorithm 6.2 in dissertation of S. Donath, 2011 + artificialWallNormal = rotMat * normal; + + // line 21 in Algorithm 6.2 in dissertation of S. Donath, 2011 + if (theta < alpha) + { + artificialWallPointCoord = globalInterfacePointLocation + radius * (normal - artificialWallNormal); + } + else { artificialWallPointCoord = globalInterfacePointLocation - radius * (normal - artificialWallNormal); } + + return true; + } +} + +Vector3< real_t > getInterfacePoint(const Vector3< real_t >& normal, real_t fillLevel) +{ + // exploit symmetries of cubic cell to simplify the algorithm, i.e., restrict the fill level to the interval + // [0,0.5] (see dissertation of T. Pohl, 2008, p. 22f) + bool fillMirrored = false; + if (fillLevel >= real_c(0.5)) + { + fillMirrored = true; + fillLevel = real_c(1) - fillLevel; + } + + // sort normal components such that nx, ny, nz >= 0 and nx <= ny <= nz to simplify the algorithm + Vector3< real_t > normalSorted = fabs(normal); + if (normalSorted[0] > normalSorted[1]) + { + // swap nx and ny + real_t tmp = normalSorted[0]; + normalSorted[0] = normalSorted[1]; + normalSorted[1] = tmp; + } + + if (normalSorted[1] > normalSorted[2]) + { + // swap ny and nz + real_t tmp = normalSorted[1]; + normalSorted[1] = normalSorted[2]; + normalSorted[2] = tmp; + + if (normalSorted[0] > normalSorted[1]) + { + // swap nx and ny + tmp = normalSorted[0]; + normalSorted[0] = normalSorted[1]; + normalSorted[1] = tmp; + } + } + + // minimal and maximal plane offset chosen as in the dissertation of T. Pohl, 2008, p. 22 + real_t offsetMin = real_c(-0.866025); // sqrt(3/4), lowest possible value + real_t offsetMax = real_c(0); + + // find correct interface position by bisection (Algorithm 2.1, p. 22 in dissertation of T. Pohl, 2008) + const uint_t numBisections = + uint_c(10); // number of bisections, =10 in dissertation of T. Pohl, 2008 (heuristically chosen) + + for (uint_t i = uint_c(0); i <= numBisections; ++i) + { + const real_t offsetTmp = real_c(0.5) * (offsetMin + offsetMax); + const real_t newFillLevel = computeCellFluidVolume(normalSorted, offsetTmp); + + // volume is too small, reduce upper bound + if (newFillLevel > fillLevel) { offsetMax = offsetTmp; } + + // volume is too large, reduce lower bound + else { offsetMin = offsetTmp; } + } + real_t offset = real_c(0.5) * (offsetMin + offsetMax); + + if (fillMirrored) { offset *= real_c(-1); } + const Vector3< real_t > interfacePoint = Vector3< real_t >(real_c(0.5)) + offset * normal; + + return interfacePoint; +} + +real_t getCellEdgeIntersection(const Vector3< real_t >& edgePoint, const Vector3< real_t >& edgeDirection, + const Vector3< real_t >& normal, const Vector3< real_t >& surfacePoint) +{ + // #ifndef BELOW_CELL + // # define BELOW_CELL (-10) + // #endif + +#ifndef ABOVE_CELL +# define ABOVE_CELL (-20) +#endif + // mathematical description: + // surface plane in coordinate form: x * normal = surfacePoint * normal + // cell edge line: edgePoint + lambda * edgeDirection + // => point of intersection: lambda = (interfacePoint * normal - edgePoint * normal) / edgeDirection * normal + + // compute angle between normal and cell edge + real_t cosAngle = dot(edgeDirection, normal); + + real_t intersection = real_c(0); + + // intersection exists only if angle is not 90°, i.e., (intersection != 0) + if (std::fabs(cosAngle) >= real_c(1e-14)) + { + intersection = ((surfacePoint - edgePoint) * normal) / cosAngle; + + // // intersection is below cell + // if (intersection < real_c(0)) { intersection = real_c(BELOW_CELL); } + + // intersection is above cell + if (intersection > real_c(1)) { intersection = real_c(ABOVE_CELL); } + } + else // no intersection if angle is 90° (intersection == 0) + { + intersection = real_c(-1); + } + + return intersection; +} + +real_t computeCellFluidVolume(const Vector3< real_t >& normal, real_t offset) +{ + const Vector3< real_t > interfacePoint = Vector3< real_t >(real_c(0.5)) + offset * normal; + + real_t volume = real_c(0); + + // stores points of intersection with cell edges; points are shifted along normal such that the points lay + // on one plane and surface area can be calculated + std::vector< Vector3< real_t > > points; + + // SW: south west, EB: east bottom, etc. + real_t iSW = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, interfacePoint); + + // if no intersection with edge SW, volume is zero + if (iSW > real_c(0) || realIsIdentical(iSW, ABOVE_CELL)) + { + real_t iSE = getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, interfacePoint); + real_t iNW = getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, interfacePoint); + real_t iNE = getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(1), real_c(0)), + Vector3< real_t >(real_c(0), real_c(0), real_c(1)), normal, interfacePoint); + + // simple case: all four vertices are included in fluid domain (see Figure 2.12, p. 24 in dissertation of Thomas + // Pohl, 2008) + if (iNE > real_c(0)) { volume = real_c(0.25) * (iSW + iSE + iNW + iNE); } + else + { + if (iSE >= real_c(0)) + { + real_t iEB = + getCellEdgeIntersection(Vector3< real_t >(real_c(1), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, interfacePoint); + + // shift intersection points along normal and store points + points.push_back(Vector3< real_t >(real_c(1), real_c(0), real_c(iSE)) - interfacePoint); + points.push_back(Vector3< real_t >(real_c(1), real_c(iEB), real_c(0)) - interfacePoint); + + volume += iSE * iEB; + } + else + { + real_t iSB = + getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, interfacePoint); + + points.push_back(Vector3< real_t >(real_c(iSB), real_c(0), real_c(0)) - interfacePoint); + } + + if (iNW >= real_c(0)) + { + real_t iNB = + getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(1), real_c(0)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, interfacePoint); + + points.push_back(Vector3< real_t >(real_c(iNB), real_c(1), real_c(0)) - interfacePoint); + points.push_back(Vector3< real_t >(real_c(0), real_c(1), real_c(iNW)) - interfacePoint); + + volume += iNB * iNW; + } + else + { + real_t iWB = + getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(0)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, interfacePoint); + + points.push_back(Vector3< real_t >(real_c(0), real_c(iWB), real_c(0)) - interfacePoint); + } + + real_t iWT = + getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(1)), + Vector3< real_t >(real_c(0), real_c(1), real_c(0)), normal, interfacePoint); + if (iWT >= real_c(0)) + { + real_t iST = + getCellEdgeIntersection(Vector3< real_t >(real_c(0), real_c(0), real_c(1)), + Vector3< real_t >(real_c(1), real_c(0), real_c(0)), normal, interfacePoint); + + points.push_back(Vector3< real_t >(real_c(0), real_c(iWT), real_c(1)) - interfacePoint); + points.push_back(Vector3< real_t >(real_c(iST), real_c(0), real_c(1)) - interfacePoint); + + volume += iWT * iST; + } + else { points.push_back(Vector3< real_t >(real_c(0), real_c(0), real_c(iSW)) - interfacePoint); } + + Vector3< real_t > vectorProduct(real_c(0)); + real_t area = real_c(0); + size_t j = points.size() - 1; + + // compute the vector product, the length of which gives the surface area of the corresponding + // parallelogram; + for (size_t i = 0; i != points.size(); ++i) + { + vectorProduct[0] = points[i][1] * points[j][2] - points[i][2] * points[j][1]; + vectorProduct[1] = points[i][2] * points[j][0] - points[i][0] * points[j][2]; + vectorProduct[2] = points[i][0] * points[j][1] - points[i][1] * points[j][0]; + + // area of the triangle is obtained through division by 2; this is done below in the volume + // calculation (where the division is then by 6 instead of by 3) + area += vectorProduct.length(); + j = i; + } + + // compute and sum the volumes of each resulting pyramid: V = area * height * 1/3 + volume += area * (normal * interfacePoint); + volume /= real_c(6.0); // division by 6 since the area was not divided by 2 above + } + } + else // no intersection with edge SW, volume is zero + { + volume = real_c(0); + } + + return volume; +} +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/free_surface/surface_geometry/Utility.h b/src/lbm_generated/free_surface/surface_geometry/Utility.h new file mode 100644 index 0000000000000000000000000000000000000000..ffcf48bbde8a84d4e8409a6e5d596d60f6fec175 --- /dev/null +++ b/src/lbm_generated/free_surface/surface_geometry/Utility.h @@ -0,0 +1,68 @@ +//====================================================================================================================== +// +// 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 Utility.h +//! \ingroup surface_geometry +//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de> +//! \brief Helper functions for surface geometry computations. +// +//====================================================================================================================== + +#pragma once + +#include "core/math/Vector3.h" + +#include "ContactAngle.h" + +namespace walberla +{ +namespace free_surface_generated +{ +/*********************************************************************************************************************** + * Compute the point p that lays on the plane of the interface surface (see Algorithm 2.1 in dissertation of Thomas + * Pohl, 2008). p = (0.5, 0.5, 0.5) + offset * normal. + **********************************************************************************************************************/ +Vector3< real_t > getInterfacePoint(const Vector3< real_t >& normal, real_t fillLevel); + +/*********************************************************************************************************************** + * Compute the intersection point of a surface (defined by normal and surfacePoint) with an edge of a cell. + **********************************************************************************************************************/ +real_t getCellEdgeIntersection(const Vector3< real_t >& edgePoint, const Vector3< real_t >& edgeDirection, + const Vector3< real_t >& normal, const Vector3< real_t >& surfacePoint); + +/*********************************************************************************************************************** + * Compute the fluid volume within an interface cell with respect to + * - the interface normal + * - the interface surface offset. + * + * see dissertation of T. Pohl, 2008, section 2.5.3, p. 23-26. + **********************************************************************************************************************/ +real_t computeCellFluidVolume(const Vector3< real_t >& normal, real_t offset); + +/*********************************************************************************************************************** + * Compute an artificial wall point according to the artifical curvature wetting model from the dissertation of Stefan + * Donath, 2011. The artificial wall point and the artificial normal can be used to alter the curvature computation with + * local triangulation. The interface curvature is changed such that the correct laplace pressure with respect to the + * contact angle is assumed near solid cells. + * + * see dissertation of T. Pohl, 2008, section 6.3.3 + **********************************************************************************************************************/ +bool computeArtificalWallPoint(const Vector3< real_t >& globalInterfacePointLocation, + const Vector3< real_t >& globalCellCoordinate, const Vector3< real_t >& normal, + const Vector3< real_t >& wallNormal, const Vector3< real_t >& obstacleNormal, + const ContactAngle& contactAngle, Vector3< real_t >& artificialWallPointCoord, + Vector3< real_t >& artificialWallNormal); +} // namespace free_surface +} // namespace walberla diff --git a/src/lbm_generated/macroscopics/CMakeLists.txt b/src/lbm_generated/macroscopics/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..96b2ebfc7f5392ab2e61e80196db5569b94582ad --- /dev/null +++ b/src/lbm_generated/macroscopics/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources( lbm_generated + PRIVATE + Equilibrium.h + DensityAndMomentumDensity.h + ) diff --git a/src/lbm_generated/macroscopics/DensityAndMomentumDensity.h b/src/lbm_generated/macroscopics/DensityAndMomentumDensity.h new file mode 100644 index 0000000000000000000000000000000000000000..d615e4529b960acca6ccfd02bb08973507e347f4 --- /dev/null +++ b/src/lbm_generated/macroscopics/DensityAndMomentumDensity.h @@ -0,0 +1,62 @@ +//====================================================================================================================== +// +// 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 DensityAndMomentumDensity.h +//! \ingroup lbm_generated +//! \author Markus Holzer<markus.holzer@fau.de> +// +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/math/Vector3.h" + +#include <type_traits> + +namespace walberla::lbm_generated { + +template< typename StorageSpecification_T, typename PdfField_T > +real_t getDensityAndMomentumDensity( Vector3< real_t > & momentumDensity, const PdfField_T & pdf, + const cell_idx_t x, const cell_idx_t y, const cell_idx_t z ) +{ + const auto & xyz0 = pdf(x,y,z,0); + + auto d = StorageSpecification_T::Stencil::begin(); + + const auto & firstPdf = pdf.getF( &xyz0, d.toIdx() ); + + momentumDensity[0] = firstPdf * real_c(d.cx()); + momentumDensity[1] = firstPdf * real_c(d.cy()); + momentumDensity[2] = firstPdf * real_c(d.cz()); + real_t rho = firstPdf + ( ( StorageSpecification_T::zeroCenteredPDFs ) ? real_t(0.0) : real_t(1.0) ); + + ++d; + + while( d != StorageSpecification_T::Stencil::end() ) + { + const auto & pdfValue = pdf.getF( &xyz0, d.toIdx() ); + + momentumDensity[0] += pdfValue * real_c(d.cx()); + momentumDensity[1] += pdfValue * real_c(d.cy()); + momentumDensity[2] += pdfValue * real_c(d.cz()); + rho += pdfValue; + + ++d; + } + + return rho; +} +} // namespace walberla::lbm_generated diff --git a/src/lbm_generated/macroscopics/Equilibrium.h b/src/lbm_generated/macroscopics/Equilibrium.h new file mode 100644 index 0000000000000000000000000000000000000000..c651b67a2aa61cd14435e092cc01530c85d63feb --- /dev/null +++ b/src/lbm_generated/macroscopics/Equilibrium.h @@ -0,0 +1,305 @@ +//====================================================================================================================== +// +// 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 Equilibrium.h +//! \ingroup lbm_generated +//! \author Markus Holzer<markus.holzer@fau.de> +//====================================================================================================================== + +#pragma once + +#include "core/DataTypes.h" +#include "core/math/Vector3.h" + +#include "stencil/D2Q9.h" +#include "stencil/D3Q19.h" +#include "stencil/D3Q27.h" +#include "stencil/Directions.h" + +#include <type_traits> +namespace walberla::lbm_generated +{ + +////////////////////////////////////////// +// set equilibrium distribution (x,y,z) // +////////////////////////////////////////// + + +template< typename StorageSpecification_T > +struct Equilibrium +{ + template< typename FieldPtrOrIterator > + static void set(FieldPtrOrIterator& it, const Vector3< real_t >& velocity = Vector3< real_t >(real_t(0.0)), + const real_t rho = real_t(1.0)) + { + it[0] = rho * -0.33333333333333331 * (velocity[0] * velocity[0]) + + rho * -0.33333333333333331 * (velocity[1] * velocity[1]) + + rho * -0.33333333333333331 * (velocity[2] * velocity[2]) + rho * 0.33333333333333331; + it[1] = rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[1] + rho * 0.16666666666666666 * (velocity[1] * velocity[1]); + it[2] = rho * -0.16666666666666666 * velocity[1] + rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[1] * velocity[1]); + it[3] = rho * -0.16666666666666666 * velocity[0] + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[0] * velocity[0]); + it[4] = rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[0] + rho * 0.16666666666666666 * (velocity[0] * velocity[0]); + it[5] = rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[2] + rho * 0.16666666666666666 * (velocity[2] * velocity[2]); + it[6] = rho * -0.16666666666666666 * velocity[2] + rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[2] * velocity[2]); + it[7] = rho * -0.083333333333333329 * velocity[0] + rho * -0.25 * velocity[0] * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]); + it[8] = rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * velocity[1] + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + rho * 0.25 * velocity[0] * velocity[1]; + it[9] = rho * -0.083333333333333329 * velocity[0] + rho * -0.083333333333333329 * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + rho * 0.25 * velocity[0] * velocity[1]; + it[10] = rho * -0.083333333333333329 * velocity[1] + rho * -0.25 * velocity[0] * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]); + it[11] = rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * velocity[2] + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[1] * velocity[2]; + it[12] = rho * -0.083333333333333329 * velocity[1] + rho * -0.25 * velocity[1] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[2] + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + it[13] = rho * -0.083333333333333329 * velocity[0] + rho * -0.25 * velocity[0] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[2] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + it[14] = rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * velocity[2] + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[0] * velocity[2]; + it[15] = rho * -0.083333333333333329 * velocity[2] + rho * -0.25 * velocity[1] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + it[16] = rho * -0.083333333333333329 * velocity[1] + rho * -0.083333333333333329 * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[1] * velocity[2]; + it[17] = rho * -0.083333333333333329 * velocity[0] + rho * -0.083333333333333329 * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[0] * velocity[2]; + it[18] = rho * -0.083333333333333329 * velocity[2] + rho * -0.25 * velocity[0] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + } + + template< typename PdfField_T > + static void set(PdfField_T& pdf, const cell_idx_t x, const cell_idx_t y, const cell_idx_t z, + const Vector3< real_t >& velocity = Vector3< real_t >(real_t(0.0)), const real_t rho = real_t(1.0)) + { + pdf(x, y, z, 0) = rho * -0.33333333333333331 * (velocity[0] * velocity[0]) + + rho * -0.33333333333333331 * (velocity[1] * velocity[1]) + + rho * -0.33333333333333331 * (velocity[2] * velocity[2]) + rho * 0.33333333333333331; + pdf(x, y, z, 1) = rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[1] + + rho * 0.16666666666666666 * (velocity[1] * velocity[1]); + pdf(x, y, z, 2) = rho * -0.16666666666666666 * velocity[1] + + rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[1] * velocity[1]); + pdf(x, y, z, 3) = rho * -0.16666666666666666 * velocity[0] + + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[0] * velocity[0]); + pdf(x, y, z, 4) = rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[0] + + rho * 0.16666666666666666 * (velocity[0] * velocity[0]); + pdf(x, y, z, 5) = rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[2] + + rho * 0.16666666666666666 * (velocity[2] * velocity[2]); + pdf(x, y, z, 6) = rho * -0.16666666666666666 * velocity[2] + + rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[2] * velocity[2]); + pdf(x, y, z, 7) = rho * -0.083333333333333329 * velocity[0] + rho * -0.25 * velocity[0] * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]); + pdf(x, y, z, 8) = + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * velocity[1] + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + rho * 0.25 * velocity[0] * velocity[1]; + pdf(x, y, z, 9) = rho * -0.083333333333333329 * velocity[0] + rho * -0.083333333333333329 * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.25 * velocity[0] * velocity[1]; + pdf(x, y, z, 10) = rho * -0.083333333333333329 * velocity[1] + rho * -0.25 * velocity[0] * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]); + pdf(x, y, z, 11) = + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * velocity[2] + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[1] * velocity[2]; + pdf(x, y, z, 12) = rho * -0.083333333333333329 * velocity[1] + rho * -0.25 * velocity[1] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[2] + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + pdf(x, y, z, 13) = rho * -0.083333333333333329 * velocity[0] + rho * -0.25 * velocity[0] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[2] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + pdf(x, y, z, 14) = + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * velocity[2] + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[0] * velocity[2]; + pdf(x, y, z, 15) = rho * -0.083333333333333329 * velocity[2] + rho * -0.25 * velocity[1] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + pdf(x, y, z, 16) = rho * -0.083333333333333329 * velocity[1] + rho * -0.083333333333333329 * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + + rho * 0.25 * velocity[1] * velocity[2]; + pdf(x, y, z, 17) = rho * -0.083333333333333329 * velocity[0] + rho * -0.083333333333333329 * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + + rho * 0.25 * velocity[0] * velocity[2]; + pdf(x, y, z, 18) = rho * -0.083333333333333329 * velocity[2] + rho * -0.25 * velocity[0] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + } + + static real_t get(const stencil::Direction direction, + const Vector3< real_t >& velocity = Vector3< real_t >(real_t(0.0)), const real_t rho = real_t(1.0)) + { + switch (direction) + { + case 0: { + return rho * -0.33333333333333331 * (velocity[0] * velocity[0]) + + rho * -0.33333333333333331 * (velocity[1] * velocity[1]) + + rho * -0.33333333333333331 * (velocity[2] * velocity[2]) + rho * 0.33333333333333331; + } + case 1: { + return rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[1] + rho * 0.16666666666666666 * (velocity[1] * velocity[1]); + } + case 2: { + return rho * -0.16666666666666666 * velocity[1] + rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[1] * velocity[1]); + } + case 3: { + return rho * -0.16666666666666666 * velocity[0] + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[0] * velocity[0]); + } + case 4: { + return rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + + rho * -0.16666666666666666 * (velocity[2] * velocity[2]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[0] + rho * 0.16666666666666666 * (velocity[0] * velocity[0]); + } + case 5: { + return rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * velocity[2] + rho * 0.16666666666666666 * (velocity[2] * velocity[2]); + } + case 6: { + return rho * -0.16666666666666666 * velocity[2] + rho * -0.16666666666666666 * (velocity[0] * velocity[0]) + + rho * -0.16666666666666666 * (velocity[1] * velocity[1]) + rho * 0.055555555555555552 + + rho * 0.16666666666666666 * (velocity[2] * velocity[2]); + } + case 7: { + return rho * -0.083333333333333329 * velocity[0] + rho * -0.25 * velocity[0] * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]); + } + case 8: { + return rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * velocity[1] + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + rho * 0.25 * velocity[0] * velocity[1]; + } + case 9: { + return rho * -0.083333333333333329 * velocity[0] + rho * -0.083333333333333329 * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + rho * 0.25 * velocity[0] * velocity[1]; + } + case 10: { + return rho * -0.083333333333333329 * velocity[1] + rho * -0.25 * velocity[0] * velocity[1] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]); + } + case 11: { + return rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * velocity[2] + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[1] * velocity[2]; + } + case 12: { + return rho * -0.083333333333333329 * velocity[1] + rho * -0.25 * velocity[1] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[2] + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + } + case 13: { + return rho * -0.083333333333333329 * velocity[0] + rho * -0.25 * velocity[0] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[2] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + } + case 14: { + return rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * velocity[2] + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[0] * velocity[2]; + } + case 15: { + return rho * -0.083333333333333329 * velocity[2] + rho * -0.25 * velocity[1] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[1] + + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + } + case 16: { + return rho * -0.083333333333333329 * velocity[1] + rho * -0.083333333333333329 * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[1] * velocity[1]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[1] * velocity[2]; + } + case 17: { + return rho * -0.083333333333333329 * velocity[0] + rho * -0.083333333333333329 * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]) + rho * 0.25 * velocity[0] * velocity[2]; + } + case 18: { + return rho * -0.083333333333333329 * velocity[2] + rho * -0.25 * velocity[0] * velocity[2] + + rho * 0.027777777777777776 + rho * 0.083333333333333329 * velocity[0] + + rho * 0.083333333333333329 * (velocity[0] * velocity[0]) + + rho * 0.083333333333333329 * (velocity[2] * velocity[2]); + } + default: + WALBERLA_ABORT("Invalid Stencil direction"); + } + } +}; + +} // namespace walberla::lbm_generated \ No newline at end of file