Palabos程序代码解读 | particlesInCone

定义部分

Box3D injectionDomain;
plint startParticleIter = 1200; // Start iterating the particles after this iteration.


T particleProbabilityPerCell = 4.e-6; // Per-cell rate of particle injection.
T fluidCompliance = 6.e-5;            // The fluid->particle force is equal to the velocity difference times the fluid-compliance.
Array<T,3> gravity(-3.0e-7, 0., 0.);  // A body force acting on each particle.
T forceAmplitude = 1.e-4;             // Amplitude for the pairwise particle interaction force.
T cutOffLength = 3.5;                 // Cutoff length for particle interaction in lattice units (make sure the interaction force
                                      // is negligible at this distance).
plint commEnvelope = (plint)cutOffLength +1;  // Width of communication envelope needed to cope with neighborhoods within the cutoff length.
                                              // Must be bigger than blockSize/2.


typedef DenseParticleField3D<T,DESCRIPTOR> ParticleFieldT;

MultiParticleField3D<ParticleFieldT>* particles    =0;

Particles之间作用的数据处理器。

template<typename T, template<typename U> class Descriptor>
class ParticleInteractionForce : public BoxProcessingFunctional3D
{
public:
    ParticleInteractionForce(T forceAmplitude_, Array<T,3> gravity_)
        : forceAmplitude(forceAmplitude_),
          gravity(gravity_)
    { }
    // This is the central part of the code, where the particle interaction is defined.
    virtual void processGenericBlocks(Box3D domain, std::vector<AtomicBlock3D*> blocks)
    {
        PLB_PRECONDITION( blocks.size()==1 );
        ParticleField3D<T,Descriptor>& particleField = *dynamic_cast<ParticleField3D<T,Descriptor>*>(blocks[0]);
        Dot3D offset = particleField.getLocation();

        std::vector<Particle3D<T,Descriptor>*> particles;
        std::vector<Particle3D<T,Descriptor>*> neighbors;
        particleField.findParticles(domain, particles);
        // Loop over all particles assigned to this data processor.
        for (pluint iParticle=0; iParticle<particles.size(); ++iParticle) {
            Particle3D<T,Descriptor>* nonTypeParticle = particles[iParticle];
            if (nonTypeParticle->getTag()!=0) {  // Exclude the wall particles.
                VerletParticle3D<T,Descriptor>* particle =
                    dynamic_cast<VerletParticle3D<T,Descriptor>*>(nonTypeParticle);
                // Compute a neighborhood, in integer coordinates, which contains at least
                // all particles inside a radius of cutOffLength.
                Array<T,3> position = particle->getPosition();
                // Convert global particle coordinates to local coordinates for the domain
                // treated by this data processor.
                plint x = util::roundToInt(position[0])-offset.x;
                plint y = util::roundToInt(position[1])-offset.y;
                plint z = util::roundToInt(position[2])-offset.z;
                Box3D neighborhood(x-commEnvelope,x+commEnvelope,
                                   y-commEnvelope,y+commEnvelope, z-commEnvelope,z+commEnvelope);
                neighbors.clear();
                T rCritical = 2.0;
                T rCriticalSqr = util::sqr(rCritical);
                // Use the particle hash to find neighboring particles efficiently.
                particleField.findParticles(neighborhood, neighbors);
                Array<T,3> force((T)0.,(T)0.,(T)0.);
                for (pluint iNeighbor=0; iNeighbor<neighbors.size(); ++iNeighbor) {
                    Array<T,3> r = particle->getPosition() - neighbors[iNeighbor]->getPosition();
                    T rSqr = normSqr(r);
                    if (rSqr<util::sqr(cutOffLength) && rSqr>1.e-6) {
                        // The potential is amplitude*r^{-6} for r>rCritical, and r*amplitude for r<=rCritical.
                        // This is to aovid numerical instability due to huge forces when two
                        // particles come too close (for example in a badly designed initial condition).
                        if (rSqr>rCriticalSqr) {
                            // The potential is r^{-6}, so the force is r^{-7}. An additional r^{-1}
                            // is needed to normalize the vector r.
                            force += r/(rSqr*rSqr*rSqr*rSqr) * forceAmplitude;
                        }
                        else {
                            // Inside a radius of rCritical (in lattice units), the force is constant.
                            T rNorm = std::sqrt(rSqr);
                            force += r/(rNorm*rCritical*rCriticalSqr*rCriticalSqr*rCriticalSqr) * forceAmplitude;
                        }
                    }
                }
                // Particle acceleration according to Newton's law.
                particle->set_a (
                        particle->get_a() + (gravity+force) * particle->get_invRho() );
            }
        }
    }
    virtual ParticleInteractionForce<T,Descriptor>* clone() const
    {
        return new ParticleInteractionForce<T,Descriptor>(*this);
    }
    virtual void getTypeOfModification(std::vector<modif::ModifT>& modified) const {
        modified[0] = modif::dynamicVariables;
    }
private:
    T forceAmplitude;
    Array<T,3> gravity;
};

返回一个圆形区域。

class CircularInjection {
public:
    CircularInjection ( T radius_, Array<T,3> center_ )
        : radius(radius_),
          center(center_)
    { }
    bool operator()(Array<T,3> const& pos) const {
        return (util::sqr(pos[1]-center[1]) + util::sqr(pos[2]-center[2]))
               < util::sqr(radius);
    }
private:
    T radius;
    Array<T,3> center;
};

建立区域,使其与流场相等。

MultiBlockManagement3D const& latticeManagement(fluidLattice->getMultiBlockManagement());
    MultiBlockManagement3D particleManagement (
            latticeManagement.getSparseBlockStructure(),
            latticeManagement.getThreadAttribution().clone(),
            commEnvelope, latticeManagement.getRefinementLevel() );

    // Allocation of the data for the particles. This only allocates the particle-hash
    // (a grid on which the particles are stored). The particles themselves are injected
    // later on.
    particles = new MultiParticleField3D<ParticleFieldT> (
        particleManagement, defaultMultiBlockPolicy3D().getCombinedStatistics() );

数据处理器的集成。

 // Add a particle on each vertex of the cone wall. These particles don't move, but
    // they interact with the injected particles, so that these rebounce from the wall.
    addWallParticlesGeneric<T,DESCRIPTOR,ParticleFieldT>(*particles, *triangleBd);

    // In the following the data processors for the equations of motion and the
    // interaction terms are manually added to the particle field. In other situations,
    // data processors are added implicitly, such as for example the data processors
    // for the implementation of a boundary condition in the function call
    // boundaryCondition->insert() above.

    // Prepare the arguments to be provided to the data processors: the particle field
    // for Verlet integration, and particles+fluid for the coupling terms.
    std::vector<MultiBlock3D*> particleArg;
    particleArg.push_back(particles);

    std::vector<MultiBlock3D*> particleFluidArg;
    particleFluidArg.push_back(particles);
    particleFluidArg.push_back(fluidLattice);

    // Calls the function advance() on each particle which, for Verlet particles,
    // updates the position according to v(t+0.5)
    integrateProcessingFunctional (
            new AdvanceParticlesEveryWhereFunctional3D<T,DESCRIPTOR>(-1.0),
            fluidLattice->getBoundingBox(), particleArg, 0);
    // Compute fluid->particle force (which includes friction).
    integrateProcessingFunctional (
            new FluidToParticleCoupling3D<T,DESCRIPTOR>(1.),
            fluidLattice->getBoundingBox(), particleFluidArg, 1 );
    // Compute particle->particle and gravity forces, with help of the data processor
    // ParticleInteractionForce defined above.
    integrateProcessingFunctional (
            new ParticleInteractionForce<T,DESCRIPTOR>(forceAmplitude, gravity),
            fluidLattice->getBoundingBox(), particleArg, 1 );
    // Integrate the velocity according to the Verlet algorithm.
    integrateProcessingFunctional (
            new VerletUpdateVelocitySelective3D<T,DESCRIPTOR>(new util::SelectLargerEqualInt(1)),
            fluidLattice->getBoundingBox(), particleArg, 1 );

    // Definition of a domain from which particles will be injected in the flow field.
    //   The specific domain is close to the inlet of the tube.
    injectionDomain = Box3D(fluidLattice->getBoundingBox());
    injectionDomain.x0 = inletCenter[0] +3;
    injectionDomain.x1 = inletCenter[0] +5;

    // Definition of an absorbtion domains for the particles, one at the bottom and one at the top.
    Box3D absorbtionDomain1(fluidLattice->getBoundingBox());
    absorbtionDomain1.x0 = outletCenter[0] -2;
    absorbtionDomain1.x1 = outletCenter[0] -2;
    Box3D absorbtionDomain2(fluidLattice->getBoundingBox());
    absorbtionDomain2.x0 = inletCenter[0] +1;
    absorbtionDomain2.x1 = inletCenter[0] +1;

    // Functional which absorbs the particles in the specified domains, to avoid that they leave the
    // computational domain.
    integrateProcessingFunctional (
            new AbsorbParticlesFunctionalSelective3D<T,DESCRIPTOR>(new util::SelectLargerEqualInt(1)),
            absorbtionDomain1, particleArg, 0 );
    integrateProcessingFunctional (
            new AbsorbParticlesFunctionalSelective3D<T,DESCRIPTOR>(new util::SelectLargerEqualInt(1)),
            absorbtionDomain2, particleArg, 0 );

    // Execute all data processors once to start the simulation off with well-defined initial values.
    particles->executeInternalProcessors();

具体调用,定义particles模板。

// Create a template for the particles to be injected.
    VerletParticle3D<T,DESCRIPTOR> *particleTemplate=0;
    particleTemplate = new VerletParticle3D<T,DESCRIPTOR>(1, Array<T,3>(0.,0.,0.));
    particleTemplate->setFluidCompliance(fluidCompliance);
if (i>=startParticleIter) {
            std::vector<MultiBlock3D*> particleArg;
            particleArg.push_back(particles);
            // Progressively increase the injection rate.
            T probability = particleProbabilityPerCell;
            //T probability = particleProbabilityPerCell*(1.+(T)i/6000.);
            Particle3D<T,DESCRIPTOR>* particle = particleTemplate->clone();
            // Attribute a color to the first particles by changing their tags (the tags
            // are written in the VTK file).
            if (i<=startParticleIter+20000) {
                particle->setTag(i-startParticleIter);
            }
            applyProcessingFunctional (
                    new AnalyticalInjectRandomParticlesFunctional3D<T,DESCRIPTOR,CircularInjection> (
                        particle, probability, CircularInjection(radius/2.0, inletCenter) ),
                    injectionDomain, particleArg );
        }
 if (i>=startParticleIter) {
            // Iterate the particles..
            particles->executeInternalProcessors();
        }

小总结

定义时选定为DenseParticleField3D,随后的AdvanceParticlesEveryWhereFunctional3D会执行particleField.advanceParticles(particleField.getBoundingBox(), cutOffValue);,找到Dense Particles对应的源码如下:

template<typename T, template<typename U> class Descriptor>
void DenseParticleField3D<T,Descriptor>::advanceParticles(Box3D domain, T cutOffSpeedSqr) {
    Box3D finalDomain;
    if( intersect(domain, particleGrid.getBoundingBox(), finalDomain) )
    {
        std::vector<std::pair<Dot3D,Particle3D<T,Descriptor>*> > nextCellParticles;
        for (plint iX=finalDomain.x0; iX<=finalDomain.x1; ++iX) {
            for (plint iY=finalDomain.y0; iY<=finalDomain.y1; ++iY) {
                for (plint iZ=finalDomain.z0; iZ<=finalDomain.z1; ++iZ) {
                    std::vector<Particle3D<T,Descriptor>*>& particles = particleGrid.get(iX,iY,iZ);
                    std::vector<Particle3D<T,Descriptor>*> newLocalParticles;
                    for (pluint iParticle=0; iParticle<particles.size(); ++iParticle) {
                        Particle3D<T,Descriptor>* particle = particles[iParticle];
                        Array<T,3> oldPos( particle->getPosition() );
                        particle->advance();
                        if (cutOffSpeedSqr>=T() && normSqr(oldPos-particle->getPosition()) < cutOffSpeedSqr)
                        {
                            delete particle;
                        }
                        else {
                            plint newX, newY, newZ;
                            this->computeGridPosition(particle->getPosition(), newX, newY, newZ);
                            if (newX==iX && newY==iY && newZ==iZ) {
                                newLocalParticles.push_back(particle);
                            }
                            else {
                                if (contained(newX,newY,newZ, finalDomain)) {
                                    nextCellParticles.push_back(std::make_pair(Dot3D(newX,newY,newZ),particle));
                                }
                                else {
                                    delete particle;
                                }
                            }
                        }
                    }
                    newLocalParticles.swap(particles);
                }
            }
        }
        for (pluint i=0; i<nextCellParticles.size(); ++i) {
            plint newX = nextCellParticles[i].first.x;
            plint newY = nextCellParticles[i].first.y;
            plint newZ = nextCellParticles[i].first.z;
            particleGrid.get(newX,newY,newZ).push_back(nextCellParticles[i].second);
        }
    }
}

遍历指定区域lattice格点,根据区域lattice坐标得到particles格点坐标,遍历该坐标下所有particles,利用advance获得新位置,接下来判断particle新位置是否合理,如果新位置就是该格点则不变,如果位置超出则被删除,如果在指定区域内,则利用nextCellParticles存储新位置,它的构成是<Dot3D,Particle3D<T,Descriptor>*> ,first是一个Dot3D,second是Particles3D。

最后的操作也很直观,即新particle区域位置设定一个particle。particleGrid.get(newX,newY,newZ).push_back(nextCellParticles[i].second);

后续是VerletUpdateVelocitySelective3D:

template<typename T, template<typename U> class Descriptor>
void VerletUpdateVelocitySelective3D<T,Descriptor>::processGenericBlocks (
        Box3D domain, std::vector<AtomicBlock3D*> blocks )
{
    PLB_PRECONDITION( blocks.size()==1 );
    ParticleField3D<T,Descriptor>& particleField =
        *dynamic_cast<ParticleField3D<T,Descriptor>*>(blocks[0]);

    std::vector<Particle3D<T,Descriptor>*> found;
    particleField.findParticles(domain, found);

    for (pluint iParticle=0; iParticle<found.size(); ++iParticle) {
        Particle3D<T,Descriptor>* nonTypeParticle = found[iParticle];
        if ((*tags)(nonTypeParticle->getTag())) {
            VerletParticle3D<T,Descriptor>* particle =
                dynamic_cast<VerletParticle3D<T,Descriptor>*>(nonTypeParticle);
            PLB_ASSERT( particle );

            Array<T,3> a(particle->get_a());
            particle->set_v(particle->get_vHalfTime() + (T)0.5*a);
            PLB_ASSERT( norm(particle->get_v()) < 1. );
        }
    }
}

其主要作用:

Array<T,3> a(particle->get_a());
particle->set_v(particle->get_vHalfTime() + (T)0.5*a);

这部分作用与particle3D.hh如下代码几乎一样,比起整个advance,实际上略去了更新坐标的内容,毕竟在这个算例里坐标更新是根据AdvanceParticlesEveryWhereFunctional3D来进行的。

template<typename T, template<typename U> class Descriptor>
void VerletParticle3D<T,Descriptor>::advance() {
    vHalfTime = v + (T)0.5*a;
    this->getPosition() += vHalfTime;
}

而这里的a,在particle3D.hh里有如下调用:

Array<T,3> force( (fluidVelocity-this->get_v()) *fluidCompliance);
    this->set_a(force / this->get_rho());

可以发现这里根据速度,fluidCompliance,rho一起计算的数值。

在particlesInCone算例注释里提到advance是根据v(t+0.5)来更新位置的,我想就是这里的部分吧,在算例自带的数据处理器也有set_a部分,为:

 particle->set_a (
particle->get_a() + (gravity+force) * particle->get_invRho() );

因为是rho*g的形式,我想它应该是作为体积力产生作用的,我最近没有找到particles部分对应的参考文献,所以具体理论暂时不清楚。

这个算例主要的不同点是它自己写了一个particles之间相互作用的数据处理器。

    integrateProcessingFunctional (
            new ParticleInteractionForce<T,DESCRIPTOR>(forceAmplitude, gravity),
            fluidLattice->getBoundingBox(), particleArg, 1 );
    // Integrate the velocity according to the Verlet algorithm.

毕竟是需要包含重力影响,所以就没有使用src里数据处理器的坐标更新方式。

附录:关于a的计算

通过velocity和rhobarJ都可以获得a。

template<typename T, template<typename U> class Descriptor>
void VerletParticle3D<T,Descriptor>::velocityToParticle(TensorField3D<T,3>& velocityField, T scaling)
{
    Array<T,3> position(this->getPosition());
    std::vector<Dot3D> pos(8);
    std::vector<T> weights(8);
    linearInterpolationCoefficients(velocityField, position, pos, weights);
    Array<T,3> fluidVelocity;
    fluidVelocity.resetToZero();
    for (plint iCell=0; iCell<8; ++iCell) {
        fluidVelocity += weights[iCell]*velocityField.get(pos[iCell].x,pos[iCell].y,pos[iCell].z)*scaling;
    }

    Array<T,3> force( (fluidVelocity-this->get_v()) *fluidCompliance);
    this->set_a(force / this->get_rho());
}

template<typename T, template<typename U> class Descriptor>
void VerletParticle3D<T,Descriptor>::velocityToParticle(NTensorField3D<T>& velocityField, T scaling)
{
    Array<T,3> position(this->getPosition());
    std::vector<Dot3D> pos(8);
    std::vector<T> weights(8);
    linearInterpolationCoefficients(velocityField, position, pos, weights);
    Array<T,3> fluidVelocity;
    fluidVelocity.resetToZero();
    for (plint iCell=0; iCell<8; ++iCell) {
        T* data = velocityField.get(pos[iCell].x,pos[iCell].y,pos[iCell].z);
        fluidVelocity[0] += weights[iCell]*scaling * data[0];
        fluidVelocity[1] += weights[iCell]*scaling * data[1];
        fluidVelocity[2] += weights[iCell]*scaling * data[2];
    }

    Array<T,3> force( (fluidVelocity-this->get_v()) *fluidCompliance);
    this->set_a(force / this->get_rho());
}

它的具体过程是先通过流场速度插值计算出流速,如果是velocity方式则接着乘以一个系数scaling,最后乘以fluidCompliance,将其设定为a。

至于为什么要这样做,大家可以自行搜索verlet算法原理。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值