Problems of Resolved Particles with Periodic Boundary
OpenLB – Open Source Lattice Boltzmann Code › Forums › on OpenLB › General Topics › Problems of Resolved Particles with Periodic Boundary
- This topic has 7 replies, 4 voices, and was last updated 4 weeks, 1 day ago by Rookie.
-
AuthorPosts
-
July 9, 2024 at 5:23 pm #8895yueyqParticipant
Dear Community,
I tried to modify the example code dkt2d.cpp to be periodic along x-axis (left and right), but failed. The fluid part is good, but the solid part has two problems:
1. I set bottom boundary to be wall, but it works out that the left and right boundary exert forces to particles as wall when contacting. It shows that the four boundaries share same “wallID”, even though they have different material number. Plus, I have set defaults::periodicity {true, false}.
2. There is no longer momentum exchanged between particle and fluid after the particles left the domain and entered from the other side (i.e. periodic boundary).
Please give me some advice. Thanks a lot.
July 12, 2024 at 9:19 am #8905janParticipantDear yueyq,
Without having the specific details of your code, I can only make assumptions about the issues you’re facing. Here are a couple of points to consider:
1. It’s possible that you’ve defined the
solidBoundaries
incorrectly. Ensure that the solidBoundaries are not set for the entire surrounding part, but only for the single wall at the bottom of the container. Updating this might resolve the issue with forces being exerted by the left and right boundaries.2. Double-check how you’ve set the periodicity. Also, incorrect definition of the walls might cause the force from the wall contact to override the forces from the fluid. This could be the reason why there’s no momentum exchange between the particle and the fluid after the particles leave and re-enter the domain.
I hope these suggestions help you resolve the issues in your code. If you need further assistance or have any other questions, please don’t hesitate to ask.
Best regards,
JanJuly 12, 2024 at 3:20 pm #8910yueyqParticipantDear jan,
Thanks for your advice. The code is mostly the same as examples/particles/dkt2d. I will put my code then at another reply post for you to facilitate your reading:)
1. I did get puzzled when I define the “SolidBoundaries”. As the “SolidBoundary” code below, I wonder if “std::unique_ptr<IndicatorF<T, D>> _indPtr” was the whole simulation domain, or just the user-defined boundary wall part? I tried to set the bottom of the container as the “_indPtr”, but particles just went out of the container at the beginning. Plus, why is the cuboid extended larger in the dkt2d example?
struct SolidBoundary {
private:
/// Indicator providing the surface and volume information
std::unique_ptr<IndicatorF<T, D>> _indPtr;
/// Material number on the lattice for identification and handling of boundaries
const unsigned _latticeMaterial;
}2. I checked again about the periodicity, but the problem still exists. A speculation is that forces of the “ghostPos” are overwritten by “position” according to the line 190-195 of src/functors/lattice/latticeMomentumExchangeForce.hh:
// Calculate force for the ghost particle
particle.template setField<GENERAL,POSITION>(ghostPos);
evaluate(output, particle, iP);
// Calculate force of actual particle
particle.template setField<GENERAL,POSITION>(position);
evaluate(output, particle, iP);
Of course, incorrect definition of the walls could also be the reason since it has not been solved yet.Really appreciate your suggestions and help. Look forward to your reply.
Sincerely,
yueyqJuly 12, 2024 at 3:23 pm #8911yueyqParticipant#define NEW_FRAMEWORK
#include “olb2D.h”
#include “olb2D.hh”using namespace olb;
using namespace olb::descriptors;
using namespace olb::graphics;
using namespace olb::particles;
using namespace olb::particles::access;
using namespace olb::particles::contact;
using namespace olb::particles::dynamics;
using namespace olb::util;using T = FLOATING_POINT_TYPE;
//Define lattice type
typedef PorousParticleWithContactD2Q9Descriptor DESCRIPTOR;//Define particle type
typedef ResolvedCircleWithContact2D PARTICLETYPE;// Define particle-particle contact type
typedef ParticleContactArbitraryFromOverlapVolume<T, DESCRIPTOR::d, true>
PARTICLECONTACTTYPE;
// Define particle-wall contact type
typedef WallContactArbitraryFromOverlapVolume<T, DESCRIPTOR::d, true>
WALLCONTACTTYPE;#define WriteVTK
#define WriteGnuPlotstd::string gnuplotFilename = “gnuplot.dat”;
// Parameters for the simulation setup
int N = 1;
int M = N;T eps = 0.5; // eps*latticeL: width of transition area
T maxPhysT = 6.; // max. simulation time in s, SI unit
T iTwrite = 0.125; //converter.getLatticeTime(.3);T lengthX = 0.02;
T lengthY = 0.08;T centerX1 = 0.01;
T centerY1 = 0.068;
Vector<T,2> center1 = {centerX1,centerY1};
T centerX2 = 0.00999;
T centerY2 = 0.072;
Vector<T,2> center2 = {centerX2,centerY2};T rhoP = 1010.;
T radiusP = 0.001;
Vector<T,2> accExt = {.0, -T(9.81) * (T(1) – T(1000) / rhoP)};unsigned contactBoxResolutionPerDirection = 8;
unsigned particleContactMaterial = 0;
unsigned wallContactMaterial = 0;
T youngsModulus = 1e6;
T poissonRatio = 0.3;
T coefficientOfRestitution = 0.9;
T coefficientStaticFriction = 0.6;
T coefficientKineticFriction = 0.3;namespace custom{
template <unsigned D>
const auto customperiodicity = [](){
if constexpr (D==3) {
return Vector<bool,3>(true,true,false);
} else {
return Vector<bool,2>(true,false);
}
};
}void prepareGeometry(UnitConverter<T,DESCRIPTOR> const& converter,
SuperGeometry<T,2>& superGeometry)
{
OstreamManager clout(std::cout, “prepareGeometry”);
clout << “Prepare Geometry …” << std::endl;superGeometry.rename(0, 2);
//superGeometry.rename(2, 1, {1, 1});
superGeometry.rename(2, 1, {0, 1});superGeometry.clean();
superGeometry.innerClean();superGeometry.checkForErrors();
superGeometry.getStatistics().print();
clout << “Prepare Geometry … OK” << std::endl;
}void prepareLattice(
SuperLattice<T, DESCRIPTOR>& sLattice, UnitConverter<T,DESCRIPTOR> const& converter,
SuperGeometry<T,2>& superGeometry)
{
OstreamManager clout(std::cout, “prepareLattice”);
clout << “Prepare Lattice …” << std::endl;/// Material=0 –>do nothing
sLattice.defineDynamics<PorousParticleBGKdynamics>(superGeometry, 1);
setBounceBackBoundary(sLattice, superGeometry, 2);sLattice.setParameter<descriptors::OMEGA>(converter.getLatticeRelaxationFrequency());
clout << “Prepare Lattice … OK” << std::endl;
}void setBoundaryValues(SuperLattice<T, DESCRIPTOR>& sLattice,
UnitConverter<T,DESCRIPTOR> const& converter,
SuperGeometry<T,2>& superGeometry)
{
OstreamManager clout(std::cout, “setBoundaryValues”);AnalyticalConst2D<T, T> one(1.);
sLattice.defineField<POROSITY>(superGeometry.getMaterialIndicator({1,2}), one);// Set initial condition
AnalyticalConst2D<T, T> ux(0.);
AnalyticalConst2D<T, T> uy(0.);
AnalyticalConst2D<T, T> rho(1.);
AnalyticalComposed2D<T, T> u(ux, uy);//Initialize all values of distribution functions to their local equilibrium
sLattice.defineRhoU(superGeometry, 1, rho, u);
sLattice.iniEquilibrium(superGeometry, 1, rho, u);// Make the lattice ready for simulation
sLattice.initialize();
}void getResults(SuperLattice<T, DESCRIPTOR>& sLattice,
UnitConverter<T,DESCRIPTOR> const& converter, int iT,
SuperGeometry<T,2>& superGeometry, Timer<double>& timer,
ParticleSystem<T,PARTICLETYPE>& particleSystem )
{
OstreamManager clout(std::cout, “getResults”);#ifdef WriteVTK
SuperVTMwriter2D<T> vtkWriter(“sedimentation”);
SuperLatticePhysVelocity2D<T, DESCRIPTOR> velocity(sLattice, converter);
SuperLatticePhysPressure2D<T, DESCRIPTOR> pressure(sLattice, converter);
SuperLatticePhysExternalPorosity2D<T, DESCRIPTOR> externalPor(sLattice, converter);
SuperLatticeMomentumExchangeForceLocal<T, DESCRIPTOR, PARTICLETYPE> momentumExchange(
sLattice, converter, superGeometry, particleSystem);vtkWriter.addFunctor(velocity);
vtkWriter.addFunctor(pressure);
vtkWriter.addFunctor(externalPor);
vtkWriter.addFunctor(momentumExchange);if (iT == 0) {
converter.write(“dkt”);
SuperLatticeGeometry2D<T, DESCRIPTOR> geometry(sLattice, superGeometry);
SuperLatticeCuboid2D<T, DESCRIPTOR> cuboid(sLattice);
SuperLatticeRank2D<T, DESCRIPTOR> rank(sLattice);
vtkWriter.write(geometry);
vtkWriter.write(cuboid);
vtkWriter.write(rank);
vtkWriter.createMasterFile();
}if (iT % converter.getLatticeTime(iTwrite) == 0) {
vtkWriter.write(iT);
}
#endifauto particleA = particleSystem.get( 0 );
auto particleB = particleSystem.get( 1 );#ifdef WriteGnuPlot
if (iT % converter.getLatticeTime(iTwrite) == 0) {
if (singleton::mpi().getRank() == 0) {std::ofstream myfile;
myfile.open (gnuplotFilename.c_str(), std::ios::app);
T p2PosY = particleB.getField<GENERAL,POSITION>()[1];
T p1PosY = particleA.getField<GENERAL,POSITION>()[1];
T p2PosX = particleB.getField<GENERAL,POSITION>()[0];
T p1PosX = particleA.getField<GENERAL,POSITION>()[0];
myfile
<< converter.getPhysTime(iT) << ” ”
<< std::setprecision(9)
<< p2PosY << ” ”
<< p1PosY << ” ”
<< p2PosX << ” ”
<< p1PosX << std::endl;
myfile.close();
}
}
#endif/// Writes output on the console
if (iT % converter.getLatticeTime(iTwrite) == 0) {
timer.update(iT);
timer.printStep();
sLattice.getStatistics().print(iT, converter.getPhysTime(iT));
for (std::size_t iP=0; iP<particleSystem.size(); ++iP) {
auto particle = particleSystem.get(iP);
io::printResolvedParticleInfo(particle);
}
}return;
}int main(int argc, char* argv[])
{
/// === 1st Step: Initialization ===
olbInit(&argc, &argv);
singleton::directories().setOutputDir(“./tmp/”);
OstreamManager clout(std::cout, “main”);UnitConverter<T,DESCRIPTOR> converter(
( T ) 0.0001/ N, //physDeltaX
( T ) 5.e-4/(N*M), //physDeltaT,
( T ) .002, //charPhysLength
( T ) 0.2, //charPhysVelocity
( T ) 1E-6, //physViscosity
( T ) 1000. //physDensity
);
converter.print();/// === 2nd Step: Prepare Geometry ===
std::vector<T> extend(2, T());
extend[0] = lengthX;
extend[1] = lengthY;
std::vector<T> origin(2, T());
IndicatorCuboid2D<T> cuboid(extend, origin);#ifdef PARALLEL_MODE_MPI
CuboidGeometry2D<T> cuboidGeometry(cuboid, converter.getConversionFactorLength(), singleton::mpi().getSize());
#else
CuboidGeometry2D<T> cuboidGeometry(cuboid, converter.getConversionFactorLength(), 1);
#endifcuboidGeometry.setPeriodicity(true, false);
HeuristicLoadBalancer<T> loadBalancer(cuboidGeometry);
SuperGeometry<T,2> superGeometry(cuboidGeometry, loadBalancer, 2);
prepareGeometry(converter, superGeometry);/// === 3rd Step: Prepare Lattice ===
SuperLattice<T, DESCRIPTOR> sLattice(superGeometry);prepareLattice(sLattice, converter, superGeometry);
/// === 4th Step: Main Loop with Timer ===
Timer<double> timer(converter.getLatticeTime(maxPhysT), superGeometry.getStatistics().getNvoxel());
timer.start();// Create ParticleSystem
ParticleSystem<T,PARTICLETYPE> particleSystem;Vector<bool,DESCRIPTOR::d> period (true, false);
//Create particle manager handling coupling, gravity and particle dynamics
ParticleManager<T,DESCRIPTOR,PARTICLETYPE> particleManager(
particleSystem, superGeometry, sLattice, converter, accExt, period);// Create and assign resolved particle dynamics
particleSystem.defineDynamics<
VerletParticleDynamics<T,PARTICLETYPE>>();// Create solid boundaries for particle interaction
std::vector<SolidBoundary<T, DESCRIPTOR::d>> solidBoundaries;
solidBoundaries.push_back( SolidBoundary<T, DESCRIPTOR::d>(
std::make_unique<IndicInverse<T, DESCRIPTOR::d>>(
cuboid, cuboid.getMin() – 5 * converter.getPhysDeltaX(),
cuboid.getMax() + 5 * converter.getPhysDeltaX()), 2, wallContactMaterial));// Create objects for contact treatment
ContactContainer<T, PARTICLECONTACTTYPE, WALLCONTACTTYPE> contactContainer;
// Generate lookup table for contact properties
ContactProperties<T, 1> contactProperties;
contactProperties.set(particleContactMaterial, wallContactMaterial,
evalEffectiveYoungModulus(youngsModulus, youngsModulus,
poissonRatio, poissonRatio),
coefficientOfRestitution, coefficientKineticFriction, coefficientStaticFriction);T epsilon = eps * converter.getConversionFactorLength();
T radius = radiusP;// Create Particle 1
creators::addResolvedCircle2D( particleSystem, center1,
radius, epsilon, rhoP );// Create Particle 2
creators::addResolvedCircle2D( particleSystem, center2,
radius, epsilon, rhoP );//Check ParticleSystem
particleSystem.checkForErrors();// Set contact material
for (std::size_t iP = 0; iP < particleSystem.size(); ++iP) {
auto particle = particleSystem.get(iP);
setContactMaterial(particle, particleContactMaterial);
}/// === 5th Step: Definition of Initial and Boundary Conditions ===
setBoundaryValues(sLattice, converter, superGeometry);{
auto& communicator = sLattice.getCommunicator(stage::PostPostProcess());
communicator.requestOverlap(sLattice.getOverlap());
communicator.requestFields<POROSITY,VELOCITY_NUMERATOR,VELOCITY_DENOMINATOR>();
communicator.exchangeRequests();
}clout << “MaxIT: ” << converter.getLatticeTime(maxPhysT) << std::endl;
for (std::size_t iT = 0; iT < converter.getLatticeTime(maxPhysT)+10; ++iT) {// Execute particle manager
particleManager.execute<
couple_lattice_to_particles<T,DESCRIPTOR,PARTICLETYPE>,
apply_gravity<T,PARTICLETYPE>
>();const T k = static_cast<T>(4. / (3 * util::sqrt(M_PI)));
decltype(custom::customperiodicity<2>) customperiod = custom::customperiodicity<2>;
// Calculate and apply contact forces
processContacts<T, PARTICLETYPE, PARTICLECONTACTTYPE, WALLCONTACTTYPE, ContactProperties<T, 1>>(
particleSystem, solidBoundaries, contactContainer, contactProperties,
superGeometry, contactBoxResolutionPerDirection, k, customperiod);// Solve equations of motion
particleManager.execute<process_dynamics<T,PARTICLETYPE>>();// Couple particles to lattice (with contact detection)
coupleResolvedParticlesToLattice<T, DESCRIPTOR, PARTICLETYPE, PARTICLECONTACTTYPE, WALLCONTACTTYPE>(
particleSystem, contactContainer, superGeometry, sLattice, converter, solidBoundaries, customperiod);// Get Results
getResults(sLattice, converter, iT, superGeometry, timer, particleSystem);// Collide and stream
sLattice.collideAndStream();
}// Run Gnuplot
if (singleton::mpi().getRank() == 0) {
if (!system(NULL)) {
exit (EXIT_FAILURE);
}
int ret = system(“gnuplot dkt.p”);
if (ret == -1) {
clout << “Writing Gnuplot failed!” << std::endl;
}
}timer.stop();
timer.printSummary();
}July 15, 2024 at 9:12 am #8921janParticipantDear yueyq,
solidBoundaries.push_back( SolidBoundary<T, DESCRIPTOR::d>( std::make_unique<IndicInverse<T, DESCRIPTOR::d>>( cuboid, cuboid.getMin() – 5 * converter.getPhysDeltaX(), cuboid.getMax() + 5 * converter.getPhysDeltaX()), 2, wallContactMaterial));
the above part takes the inverse of the whole computation domain, which means each side is regarded as a wall. If you solely want the bottom to act as a wall, then I suggest that you introduce a
IndicatorCuboid
instead and place it at the bottom wall. Be sure to place it exactly where the bottom wall is and give it a sufficient thickness, because the particles will overlap it.That’s also part of the reason why the
SolidBoundary
in the example is extended. If you’d have a wall with a infinitesimal thickness, it would lead to problems during the contact treatment, as we want to calculate the overlap volume.Best regards,
Jan- This reply was modified 7 months ago by jan.
January 8, 2025 at 12:50 pm #9711RookieParticipantDear Jan,
I want the resolved particles to collide with the walls in the channel, so I need solid boundaries at both the top and bottom. My fluid and particles are set to be periodic in the x and y directions. However, if I use the code mentioned earlier, it indeed causes particle rebound in the x and y directions due to walls surrounding all sides. The problem now is that I don’t know how to properly set up the walls.
I only found “SuperIndicatorMaterial,” but it cannot be directly used to define the walls due to material mismatches. As you mentioned, “IndicatorCuboid” seems to define only one side of the wall, but I need walls on both the top and bottom. Regarding your suggestion to provide sufficient thickness, is it reflected in “5 * converter.getPhysDeltaX()”? However, based on my tests, particles rebound off the surrounding walls, and changing this parameter does not affect the simulation results.
const unsigned latticeMaterial = 2; //Material number of wall
const unsigned contactMaterial = 0; //Material identifier (only relevant for contact model)
SolidBoundary<T,3> wall( std::make_unique<IndicInverse<T, DESCRIPTOR::d>>(
cuboid, cuboid.getMin() – 5 * converter.getPhysDeltaX(),
cuboid.getMax() + 5 * converter.getPhysDeltaX()),
latticeMaterial, contactMaterial );particleSystem.defineDynamics<
VerletParticleDynamicsVelocityWallReflection<T,PARTICLETYPE>>(wall);Best regards,
RookieJanuary 16, 2025 at 11:01 am #9765ChristophParticipantHello Rookie,
You can put the top and bottom boundaries by defining an Indicator for each of them and then assigning the wall material number to them. For the bottom this could be implemented like this:
Vector<T,3> originBottom (0,0,0); //change if the origin is not the bottom point in your domain
Vector<T,3> extendBottom (lengthX, 1.5*converter.getPhysDeltaX, lengthZ);
IndicatorCuboid3D<T> bottomWall( extendBottom,originBottom );
superGeometry.rename( 0,2,bottomWall);and similar for the top, only the origin has to be modified.
Best regards,
ChristophJanuary 17, 2025 at 8:38 am #9768RookieParticipantDear Christoph,
Thanks for your suggestion, I have solved this problem, because this function(VerletParticleDynamicsVelocityWallReflection) only accepts one parameter, so defining two walls is not successful, so I changed the size of the cuboid.
Best regards,
Rookie -
AuthorPosts
- You must be logged in to reply to this topic.