Program Listing for File TimeStep.cpp

Return to documentation for file (SPlisHSPlasH/TimeStep.cpp)

#include "TimeStep.h"
#include "TimeManager.h"
#include "SPHKernels.h"
#include "Utilities/Timing.h"
#include "EmitterSystem.h"
#include "Simulation.h"
#include "SPlisHSPlasH/BoundaryModel_Akinci2012.h"
#include "SPlisHSPlasH/BoundaryModel_Koschier2017.h"
#include "SPlisHSPlasH/BoundaryModel_Bender2019.h"

using namespace SPH;
using namespace std;
using namespace GenParam;

//#define USE_FD_NORMAL

int TimeStep::SOLVER_ITERATIONS = -1;
int TimeStep::MIN_ITERATIONS = -1;
int TimeStep::MAX_ITERATIONS = -1;
int TimeStep::MAX_ERROR = -1;


TimeStep::TimeStep()
{
    m_iterations = 0;
    m_minIterations = 2;
    m_maxIterations = 100;
    m_maxError = 0.01;
}

TimeStep::~TimeStep(void)
{
}

void TimeStep::init()
{
    initParameters();
}

void TimeStep::initParameters()
{
    ParameterObject::initParameters();

    SOLVER_ITERATIONS = createNumericParameter("iterations", "Iterations", &m_iterations);
    setGroup(SOLVER_ITERATIONS, "Simulation|Simulation");
    setDescription(SOLVER_ITERATIONS, "Iterations required by the pressure solver.");
    getParameter(SOLVER_ITERATIONS)->setReadOnly(true);

    MIN_ITERATIONS = createNumericParameter("minIterations", "Min. iterations", &m_minIterations);
    setGroup(MIN_ITERATIONS, "Simulation|Simulation");
    setDescription(MIN_ITERATIONS, "Minimal number of iterations of the pressure solver.");
    static_cast<NumericParameter<unsigned int>*>(getParameter(MIN_ITERATIONS))->setMinValue(0);

    MAX_ITERATIONS = createNumericParameter("maxIterations", "Max. iterations", &m_maxIterations);
    setGroup(MAX_ITERATIONS, "Simulation|Simulation");
    setDescription(MAX_ITERATIONS, "Maximal number of iterations of the pressure solver.");
    static_cast<NumericParameter<unsigned int>*>(getParameter(MAX_ITERATIONS))->setMinValue(1);

    MAX_ERROR = createNumericParameter("maxError", "Max. density error(%)", &m_maxError);
    setGroup(MAX_ERROR, "Simulation|Simulation");
    setDescription(MAX_ERROR, "Maximal density error (%).");
    static_cast<RealParameter*>(getParameter(MAX_ERROR))->setMinValue(1e-6);
}

void TimeStep::clearAccelerations(const unsigned int fluidModelIndex)
{
    Simulation *sim = Simulation::getCurrent();
    FluidModel *model = sim->getFluidModel(fluidModelIndex);
    const unsigned int count = model->numActiveParticles();
    const Vector3r grav(sim->getVecValue<Real>(Simulation::GRAVITATION));
    for (unsigned int i=0; i < count; i++)
    {
        // Clear accelerations of dynamic particles
        if (model->getMass(i) != 0.0)
        {
            Vector3r &a = model->getAcceleration(i);
            a = grav;
        }
    }
}

#ifdef USE_AVX

void TimeStep::computeDensities(const unsigned int fluidModelIndex)
{
    Simulation *sim = Simulation::getCurrent();
    FluidModel *model = sim->getFluidModel(fluidModelIndex);
    const Real density0 = model->getDensity0();
    const unsigned int numParticles = model->numActiveParticles();
    const unsigned int nFluids = sim->numberOfFluidModels();
    const unsigned int nBoundaries = sim->numberOfBoundaryModels();

    #pragma omp parallel default(shared)
    {
        #pragma omp for schedule(static)
        for (int i = 0; i < (int) numParticles; i++)
        {
            const Vector3r &xi = model->getPosition(i);
            Real &density = model->getDensity(i);
            density = model->getVolume(i) * CubicKernel_AVX::W_zero();

            Scalarf8 density_avx(0.0f);
            Vector3f8 xi_avx(xi);

            // Fluid
            forall_fluid_neighbors_avx(
                const Scalarf8 Vj_avx = convert_zero(fm_neighbor->getVolume(0), count);
                density_avx += Vj_avx * CubicKernel_AVX::W(xi_avx - xj_avx);
            );

            // Boundary
            if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
            {
                forall_boundary_neighbors_avx(
                    const Scalarf8 V_avx = convert_zero(&sim->getNeighborList(fluidModelIndex, pid, i)[j], &bm_neighbor->getVolume(0), count);
                    density_avx += V_avx * CubicKernel_AVX::W(xi_avx - xj_avx);
                );
                density += density_avx.reduce();
            }
            else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
            {
                density += density_avx.reduce();
                forall_density_maps(
                    density += rho;
                );
            }
            else   // Bender2019
            {
                density += density_avx.reduce();
                forall_volume_maps(
                    density += Vj * sim->W(xi - xj);
                );
            }

            density *= density0;
        }
    }
}

#else

void TimeStep::computeDensities(const unsigned int fluidModelIndex)
{
    Simulation *sim = Simulation::getCurrent();
    FluidModel *model = sim->getFluidModel(fluidModelIndex);
    const Real density0 = model->getDensity0();
    const unsigned int numParticles = model->numActiveParticles();
    const unsigned int nFluids = sim->numberOfFluidModels();
    const unsigned int nBoundaries = sim->numberOfBoundaryModels();

    #pragma omp parallel default(shared)
    {
        #pragma omp for schedule(static)
        for (int i = 0; i < (int) numParticles; i++)
        {
            Real &density = model->getDensity(i);

            // Compute current density for particle i
            density = model->getVolume(i) * sim->W_zero();
            const Vector3r &xi = model->getPosition(i);

            // Fluid
            forall_fluid_neighbors(
                density += fm_neighbor->getVolume(neighborIndex) * sim->W(xi - xj);
            );

            // Boundary
            if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
            {
                forall_boundary_neighbors(
                    // Boundary: Akinci2012
                    density += bm_neighbor->getVolume(neighborIndex) * sim->W(xi - xj);
                );
            }
            else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
            {
                forall_density_maps(
                    density += rho;
                );
            }
            else   // Bender2019
            {
                forall_volume_maps(
                    density += Vj * sim->W(xi - xj);
                );
            }

            density *= density0;
        }
    }
}

#endif

void TimeStep::reset()
{
    m_iterations = 0;
}

void TimeStep::computeVolumeAndBoundaryX()
{
    START_TIMING("computeVolumeAndBoundaryX");
    Simulation *sim = Simulation::getCurrent();
    const unsigned int nFluids = sim->numberOfFluidModels();
    for (unsigned int fluidModelIndex = 0; fluidModelIndex < nFluids; fluidModelIndex++)
    {
        FluidModel *model = sim->getFluidModel(fluidModelIndex);
        const unsigned int numParticles = model->numActiveParticles();

        #pragma omp parallel default(shared)
        {
            #pragma omp for schedule(static)
            for (int i = 0; i < (int)numParticles; i++)
            {
                const Vector3r &xi = model->getPosition(i);
                computeVolumeAndBoundaryX(fluidModelIndex, i, xi);
            }
        }
    }
    STOP_TIMING_AVG;
}

void TimeStep::approximateNormal(Discregrid::DiscreteGrid* map, const Eigen::Vector3d &x, Eigen::Vector3d &n, const unsigned int dim)
{
    // approximate gradient
    double eps = 0.1*Simulation::getCurrent()->getSupportRadius();
    n.setZero();
    Eigen::Vector3d xTmp = x;
    for (unsigned int j = 0; j < dim; j++)
    {
        xTmp[j] += eps;

        double e_p, e_m;
        e_p = map->interpolate(0, xTmp);
        xTmp[j] = x[j] - eps;
        e_m = map->interpolate(0, xTmp);
        xTmp[j] = x[j];

        double res = (e_p - e_m) * (1.0 / (2.0*eps));

        n[j] = res;
    }
}


void TimeStep::computeVolumeAndBoundaryX(const unsigned int fluidModelIndex, const unsigned int i, const Vector3r &xi)
{
    Simulation *sim = Simulation::getCurrent();
    const unsigned int nFluids = sim->numberOfFluidModels();
    const unsigned int nBoundaries = sim->numberOfBoundaryModels();
    const bool sim2D = sim->is2DSimulation();
    const Real supportRadius = sim->getSupportRadius();
    const Real particleRadius = sim->getParticleRadius();
    const Real dt = TimeManager::getCurrent()->getTimeStepSize();
    FluidModel* model = sim->getFluidModel(fluidModelIndex);

    for (unsigned int pid = 0; pid < nBoundaries; pid++)
    {
        BoundaryModel_Bender2019* bm = static_cast<BoundaryModel_Bender2019*>(sim->getBoundaryModel(pid));

        Vector3r& boundaryXj = bm->getBoundaryXj(fluidModelIndex, i);
        boundaryXj.setZero();
        Real& boundaryVolume = bm->getBoundaryVolume(fluidModelIndex, i);
        boundaryVolume = 0.0;

        const Vector3r& t = bm->getRigidBodyObject()->getPosition();
        const Matrix3r& R = bm->getRigidBodyObject()->getRotation().toRotationMatrix();

        Eigen::Vector3d normal;
        const Eigen::Vector3d localXi = (R.transpose() * (xi - t)).cast<double>();


        std::array<unsigned int, 32> cell;
        Eigen::Vector3d c0;
        Eigen::Matrix<double, 32, 1> N;
#ifdef USE_FD_NORMAL
        bool chk = bm->getMap()->determineShapeFunctions(0, localXi, cell, c0, N);
#else
        Eigen::Matrix<double, 32, 3> dN;
        bool chk = bm->getMap()->determineShapeFunctions(0, localXi, cell, c0, N, &dN);
#endif
        double dist = numeric_limits<double>::max();
        if (chk)
#ifdef USE_FD_NORMAL
            dist = bm->getMap()->interpolate(0, localXi, cell, c0, N);
#else
            dist = bm->getMap()->interpolate(0, localXi, cell, c0, N, &normal, &dN);
#endif

        bool animateParticle = false;
        if (model->getParticleState(i) == ParticleState::Active)
        {
            if ((dist > 0.0) && (static_cast<Real>(dist) < supportRadius))
            {
                const double volume = bm->getMap()->interpolate(1, localXi, cell, c0, N);
                if ((volume > 0.0) && (volume != numeric_limits<double>::max()))
                {
                    boundaryVolume = static_cast<Real>(volume);

#ifdef USE_FD_NORMAL
                    if (sim2D)
                        approximateNormal(bm->getMap(), localXi, normal, 2);
                    else
                        approximateNormal(bm->getMap(), localXi, normal, 3);
#endif
                    normal = R.cast<double>() * normal;
                    const double nl = normal.norm();
                    if (nl > 1.0e-9)
                    {
                        normal /= nl;
                        //const Real d = static_cast<Real>(dist);
                        const Real d = max((static_cast<Real>(dist) + static_cast<Real>(0.5) * particleRadius), static_cast<Real>(2.0)*particleRadius);
                                                                                                // boundary point is 0.5*particleRadius below the surface.
                                                                                                // Ensure that the particle is at least one particle diameter away
                                                                                                // from the boundary X to avoid strong pressure forces.
                        boundaryXj = (xi - d * normal.cast<Real>());
                    }
                    else
                    {
                        boundaryVolume = 0.0;
                    }
                }
                else
                {
                    boundaryVolume = 0.0;
                }
            }
            else if (dist <= 0.0)
            {
                // if a particle is in the boundary, animate the particle back
                LOG_DEBUG << "Particle in boundary.";
                animateParticle = true;
                boundaryVolume = 0.0;
            }
            else
            {
                boundaryVolume = 0.0;
            }
        }

        // Animate particles that are in the boundary back to the surface.
        // Typically this never happens, but this is a fallback solution if
        // too large time steps are used.
        if (animateParticle)
        {
            if (dist != numeric_limits<double>::max())              // if dist is numeric_limits<double>::max(), then the particle is not close to the current boundary
            {
                normal = R.cast<double>() * normal;
                const double nl = normal.norm();

                if (nl > 1.0e-5)
                {
                    normal /= nl;
                    // project to surface
                    Real delta = static_cast<Real>(2.0) * particleRadius - static_cast<Real>(dist);
                    delta = std::min(delta, static_cast<Real>(0.1) * particleRadius);       // get up in small steps
                    model->getPosition(i) = (xi + delta * normal.cast<Real>());
                    // adapt velocity in normal direction
                    //model->getVelocity(i) = 1.0/dt * delta * normal.cast<Real>();
                    model->getVelocity(i).setZero();
                }
            }
            boundaryVolume = 0.0;
        }
    }
}


void TimeStep::computeDensityAndGradient()
{
    START_TIMING("computeDensityAndGradient");
    Simulation *sim = Simulation::getCurrent();
    const unsigned int nFluids = sim->numberOfFluidModels();
    for (unsigned int fluidModelIndex = 0; fluidModelIndex < nFluids; fluidModelIndex++)
    {
        FluidModel *model = sim->getFluidModel(fluidModelIndex);
        const unsigned int numParticles = model->numActiveParticles();

        #pragma omp parallel default(shared)
        {
            #pragma omp for schedule(static)
            for (int i = 0; i < (int)numParticles; i++)
            {
                const Vector3r &xi = model->getPosition(i);
                computeDensityAndGradient(fluidModelIndex, i, xi);
            }
        }
    }
    STOP_TIMING_AVG;
}

void TimeStep::computeDensityAndGradient(const unsigned int fluidModelIndex, const unsigned int i, const Vector3r &xi)
{
    Simulation *sim = Simulation::getCurrent();
    const bool sim2D = sim->is2DSimulation();
    const unsigned int nFluids = sim->numberOfFluidModels();
    const unsigned int nBoundaries = sim->numberOfBoundaryModels();
    const Real supportRadius = sim->getSupportRadius();
    const Real particleRadius = sim->getParticleRadius();
    const Real dt = TimeManager::getCurrent()->getTimeStepSize();

    for (unsigned int pid = 0; pid < nBoundaries; pid++)
    {
        BoundaryModel_Koschier2017 *bm = static_cast<BoundaryModel_Koschier2017*>(sim->getBoundaryModel(pid));

        Vector3r &boundaryDensityGradient = bm->getBoundaryDensityGradient(fluidModelIndex, i);
        Real &boundaryDensity = bm->getBoundaryDensity(fluidModelIndex, i);

        const Vector3r &t = bm->getRigidBodyObject()->getPosition();
        const Matrix3r &R = bm->getRigidBodyObject()->getRotation().toRotationMatrix();

        Eigen::Vector3d normal;
        const Eigen::Vector3d localXi = (R.transpose() * (xi - t)).cast<double>();

        Vector3r &boundaryXj = bm->getBoundaryXj(fluidModelIndex, i);
        std::array<unsigned int, 32> cell;
        Eigen::Vector3d c0;
        Eigen::Matrix<double, 32, 1> N;
        Eigen::Matrix<double, 32, 3> dN;
        bool chk = bm->getMap()->determineShapeFunctions(0, localXi, cell, c0, N, &dN);
        Real dist = numeric_limits<Real>::max();
        if (chk)
#ifdef USE_FD_NORMAL
            dist = static_cast<Real>(bm->getMap()->interpolate(0, localXi, cell, c0, N));
#else
            dist = static_cast<Real>(bm->getMap()->interpolate(0, localXi, cell, c0, N, &normal, &dN));
#endif

        if ((dist > 0.1*particleRadius) && (dist < sim->getSupportRadius()))
        {
            Eigen::Vector3d gradD;
            const Real d = static_cast<Real>(bm->getMap()->interpolate(1, localXi, cell, c0, N, &gradD, &dN));
            if ((d > 1e-6) && (d != numeric_limits<Real>::max()))
            {
                boundaryDensity = d;

#ifdef USE_FD_NORMAL
                if (sim2D)
                    approximateNormal(bm->getMap(), localXi, normal, 2);
                else
                    approximateNormal(bm->getMap(), localXi, normal, 3);
#endif
                boundaryDensityGradient = -R * gradD.cast<Real>();
                normal = R.cast<double>() * normal;
                const double nl = normal.norm();
                if (nl > 1.0e-6)
                {
                    normal /= nl;
                    // Move the boundary point below the surface. Otherwise
                    // the particles are too close which causes large forces.
                    boundaryXj = (xi - (dist+0.5*supportRadius) * normal.cast<Real>());
                }
                else
                {
                    boundaryDensityGradient.setZero();
                    boundaryDensity = 0.0;
                }
            }
            else
            {
                boundaryDensityGradient.setZero();
                boundaryDensity = 0.0;
            }
        }
        else if (dist <= 0.1*particleRadius)
        {
            normal = R.cast<double>() * normal;
            const double nl = normal.norm();
            if (nl > 1.0e-5)
            {
                normal /= nl;
                // project to surface
                Real d = -dist;
                d = std::min(d, static_cast<Real>(0.25 / 0.005) * particleRadius * dt);     // get up in small steps
                sim->getFluidModel(fluidModelIndex)->getPosition(i) = (xi + d * normal.cast<Real>());
                // adapt velocity in normal direction
                sim->getFluidModel(fluidModelIndex)->getVelocity(i) += (0.05 - sim->getFluidModel(fluidModelIndex)->getVelocity(i).dot(normal.cast<Real>())) * normal.cast<Real>();
            }
            boundaryDensityGradient.setZero();
            boundaryDensity = 0.0;
        }
        else
        {
            boundaryDensityGradient.setZero();
            boundaryDensity = 0.0;
        }
    }
}


#ifdef USE_PERFORMANCE_OPTIMIZATION

void TimeStep::precomputeValues()
{
    Simulation* sim = Simulation::getCurrent();
    const unsigned int nFluids = sim->numberOfFluidModels();

    for (unsigned int fluidModelIndex = 0; fluidModelIndex < nFluids; fluidModelIndex++)
    {
        FluidModel* model = sim->getFluidModel(fluidModelIndex);
        model->get_precomputed_indices().clear();
        model->get_precomputed_indices_same_phase().clear();
        model->get_precomputed_V_gradW().clear();
        const int numParticles = (int)model->numActiveParticles();

        auto& precomputed_indices = model->get_precomputed_indices();
        auto& precomputed_indices_same_phase = model->get_precomputed_indices_same_phase();
        auto& precomputed_V_gradW = model->get_precomputed_V_gradW();
        precomputed_indices.reserve(numParticles+1);
        precomputed_indices.push_back(0);

        precomputed_indices_same_phase.reserve(numParticles);

        unsigned int sumNeighborParticles = 0;
        unsigned int sumNeighborParticlesSamePhase = 0;
        for (int i = 0; i < numParticles; i++)
        {
            for (unsigned int pid = 0; pid < nFluids; pid++)
            {
                const unsigned int maxN = sim->numberOfNeighbors(fluidModelIndex, pid, i);

                // same phase
                if (pid == fluidModelIndex)
                    precomputed_indices_same_phase.push_back(sumNeighborParticles);

                // steps of 8 values due to avx
                sumNeighborParticles += maxN / 8;
                if (maxN % 8 != 0)
                    sumNeighborParticles++;
            }
            precomputed_indices.push_back(sumNeighborParticles);
        }

        if (sumNeighborParticles > precomputed_V_gradW.capacity())
            precomputed_V_gradW.reserve(static_cast<int>(1.5 * sumNeighborParticles));
        precomputed_V_gradW.resize(sumNeighborParticles);

        #pragma omp parallel default(shared)
        {
            #pragma omp for schedule(static)
            for (int i = 0; i < (int)numParticles; i++)
            {
                const Vector3r& xi = model->getPosition(i);
                const Vector3f8 xi_avx(xi);
                const unsigned int base = precomputed_indices[i];
                unsigned int idx = 0;
                forall_fluid_neighbors_avx(
                    const Scalarf8 Vj_avx = convert_zero(fm_neighbor->getVolume(0), count);
                    precomputed_V_gradW[base + idx] = CubicKernel_AVX::gradW(xi_avx - xj_avx) * Vj_avx;
                    idx++;
                );
            }
        }
    }
}

#endif