Program Listing for File TimeStepPBF.cpp
↰ Return to documentation for file (SPlisHSPlasH/PBF/TimeStepPBF.cpp)
#include "TimeStepPBF.h"
#include "SPlisHSPlasH/TimeManager.h"
#include "TimeIntegration.h"
#include "SPlisHSPlasH/SPHKernels.h"
#include "SimulationDataPBF.h"
#include <iostream>
#include "Utilities/Timing.h"
#include "SPlisHSPlasH/Simulation.h"
#include "Utilities/Counting.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;
std::string TimeStepPBF::METHOD_NAME = "PBF";
int TimeStepPBF::SOLVER_ITERATIONS = -1;
int TimeStepPBF::MIN_ITERATIONS = -1;
int TimeStepPBF::MAX_ITERATIONS = -1;
int TimeStepPBF::MAX_ERROR = -1;
int TimeStepPBF::VELOCITY_UPDATE_METHOD = -1;
int TimeStepPBF::ENUM_PBF_FIRST_ORDER = -1;
int TimeStepPBF::ENUM_PBF_SECOND_ORDER = -1;
TimeStepPBF::TimeStepPBF() :
TimeStep()
{
m_simulationData.init();
m_iterations = 0;
m_minIterations = 2;
m_maxIterations = 100;
m_maxError = static_cast<Real>(0.01);
m_velocityUpdateMethod = 0;
Simulation *sim = Simulation::getCurrent();
const unsigned int nModels = sim->numberOfFluidModels();
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
model->addField({ "lambda", METHOD_NAME, FieldType::Scalar, [this, fluidModelIndex](const unsigned int i) -> Real* { return &m_simulationData.getLambda(fluidModelIndex, i); } });
model->addField({ "deltaX", METHOD_NAME, FieldType::Vector3, [this, fluidModelIndex](const unsigned int i) -> Real* { return &m_simulationData.getDeltaX(fluidModelIndex, i)[0]; } });
}
}
TimeStepPBF::~TimeStepPBF(void)
{
Simulation *sim = Simulation::getCurrent();
const unsigned int nModels = sim->numberOfFluidModels();
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
model->removeFieldByName("lambda");
model->removeFieldByName("deltaX");
}
}
void TimeStepPBF::initParameters()
{
TimeStep::initParameters();
SOLVER_ITERATIONS = createNumericParameter("iterations", "Iterations", &m_iterations);
setGroup(SOLVER_ITERATIONS, "Simulation|PBF");
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|PBF");
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|PBF");
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|PBF");
setDescription(MAX_ERROR, "Maximal density error (%).");
static_cast<RealParameter*>(getParameter(MAX_ERROR))->setMinValue(static_cast<Real>(1e-6));
VELOCITY_UPDATE_METHOD = createEnumParameter("velocityUpdateMethod", "Velocity update method", &m_velocityUpdateMethod);
setGroup(VELOCITY_UPDATE_METHOD, "Simulation|PBF");
setDescription(VELOCITY_UPDATE_METHOD, "Method for the velocity integration.");
EnumParameter *enumParam = static_cast<EnumParameter*>(getParameter(VELOCITY_UPDATE_METHOD));
enumParam->addEnumValue("First Order Update", ENUM_PBF_FIRST_ORDER);
enumParam->addEnumValue("Second Order Update", ENUM_PBF_SECOND_ORDER);
}
void TimeStepPBF::step()
{
Simulation *sim = Simulation::getCurrent();
const unsigned int nModels = sim->numberOfFluidModels();
TimeManager *tm = TimeManager::getCurrent ();
const Real h = tm->getTimeStepSize();
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
clearAccelerations(fluidModelIndex);
// Time integration
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int) model->numActiveParticles(); i++)
{
m_simulationData.getLastPosition(fluidModelIndex, i) = m_simulationData.getOldPosition(fluidModelIndex, i);
m_simulationData.getOldPosition(fluidModelIndex, i) = model->getPosition(i);
if (model->getParticleState(i) == ParticleState::Active)
TimeIntegration::semiImplicitEuler(h, model->getMass(i), model->getPosition(i), model->getVelocity(i), model->getAcceleration(i));
}
}
}
// Perform neighborhood search
sim->performNeighborhoodSearch();
#ifdef USE_PERFORMANCE_OPTIMIZATION
precomputeValues();
#endif
// Solve density constraint
START_TIMING("pressureSolve");
pressureSolve();
STOP_TIMING_AVG;
// Update velocities
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
if (m_velocityUpdateMethod == ENUM_PBF_FIRST_ORDER)
{
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)model->numActiveParticles(); i++)
{
if (model->getParticleState(i) == ParticleState::Active)
TimeIntegration::velocityUpdateFirstOrder(h, model->getMass(i), model->getPosition(i), m_simulationData.getOldPosition(fluidModelIndex, i), model->getVelocity(i));
model->getAcceleration(i).setZero();
}
}
}
else
{
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)model->numActiveParticles(); i++)
{
if (model->getParticleState(i) == ParticleState::Active)
TimeIntegration::velocityUpdateSecondOrder(h, model->getMass(i), model->getPosition(i), m_simulationData.getOldPosition(fluidModelIndex, i), m_simulationData.getLastPosition(fluidModelIndex, i), model->getVelocity(i));
model->getAcceleration(i).setZero();
}
}
}
}
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
computeDensities(fluidModelIndex);
sim->computeNonPressureForces();
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)model->numActiveParticles(); i++)
{
if (model->getParticleState(i) == ParticleState::Active)
model->getVelocity(i) += h * model->getAcceleration(i);
}
}
}
sim->updateTimeStepSize();
sim->emitParticles();
sim->animateParticles();
// Compute new time
tm->setTime (tm->getTime () + h);
}
void TimeStepPBF::reset()
{
TimeStep::reset();
m_simulationData.reset();
m_iterations = 0;
}
void TimeStepPBF::pressureSolve()
{
Simulation *sim = Simulation::getCurrent();
const unsigned int nFluids = sim->numberOfFluidModels();
Real avg_density_err = 0;
m_iterations = 0;
bool chk = false;
while ((!chk || (m_iterations < m_minIterations)) && (m_iterations < m_maxIterations))
{
chk = true;
for (unsigned int i = 0; i < nFluids; i++)
{
FluidModel *model = sim->getFluidModel(i);
const Real density0 = model->getDensity0();
avg_density_err = 0.0;
pressureSolveIteration(i, avg_density_err);
// Maximal allowed density fluctuation
const Real eta = m_maxError * static_cast<Real>(0.01) * density0; // maxError is given in percent
chk = chk && (avg_density_err <= eta);
}
m_iterations++;
}
INCREASE_COUNTER("PBF - iterations", static_cast<Real>(m_iterations));
}
void TimeStepPBF::pressureSolveIteration(const unsigned int fluidModelIndex, Real &avg_density_err)
{
Simulation *sim = Simulation::getCurrent();
FluidModel *model = sim->getFluidModel(fluidModelIndex);
const unsigned int numParticles = model->numActiveParticles();
if (numParticles == 0)
return;
const Real invH = static_cast<Real>(1.0) / TimeManager::getCurrent()->getTimeStepSize();
const Real invH2 = invH*invH;
const unsigned int nFluids = sim->numberOfFluidModels();
const unsigned int nBoundaries = sim->numberOfBoundaryModels();
const Real eps = static_cast<Real>(1.0e-6);
const Real density0 = model->getDensity0();
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int) numParticles; i++)
{
Real &density = model->getDensity(i);
m_simulationData.getLambda(fluidModelIndex, i) = 0.0;
// Compute current density for particle i
density = model->getVolume(i) * sim->W_zero();
const Vector3r &xi = model->getPosition(i);
if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Bender2019)
computeVolumeAndBoundaryX(fluidModelIndex, i, xi);
else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
computeDensityAndGradient(fluidModelIndex, i, xi);
// Fluid
forall_fluid_neighbors(
density += fm_neighbor->getVolume(neighborIndex) * sim->W(xi - xj);
);
// Boundary
if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
{
forall_boundary_neighbors(
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);
);
}
const Real density_err = density0 * (max(density, static_cast<Real>(1.0)) - static_cast<Real>(1.0));
#pragma omp atomic
avg_density_err += density_err;
// Evaluate constraint function
const Real C = std::max(density - static_cast<Real>(1.0), static_cast<Real>(0.0)); // clamp to prevent particle clumping at surface
if (C != 0.0)
{
// Compute gradients dC/dx_j
Real sum_grad_C2 = 0.0;
Vector3r gradC_i(0.0, 0.0, 0.0);
// Fluid
forall_fluid_neighbors(
const Vector3r gradC_j = -fm_neighbor->getVolume(neighborIndex) * sim->gradW(xi - xj);
sum_grad_C2 += gradC_j.squaredNorm();
gradC_i -= gradC_j;
);
// Boundary
if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
{
forall_boundary_neighbors(
// Boundary: Akinci2012
const Vector3r gradC_j = -bm_neighbor->getVolume(neighborIndex) * sim->gradW(xi - xj);
gradC_i -= gradC_j;
);
}
else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
{
forall_density_maps(
gradC_i -= gradRho;
);
}
else // Bender2019
{
forall_volume_maps(
const Vector3r gradC_j = -Vj * sim->gradW(xi - xj);
gradC_i -= gradC_j;
);
}
sum_grad_C2 += gradC_i.squaredNorm();
// Compute lambda
Real &lambda = m_simulationData.getLambda(fluidModelIndex, i);
lambda = -C / (sum_grad_C2 + eps);
}
else
m_simulationData.getLambda(fluidModelIndex, i) = 0.0;
}
}
avg_density_err /= numParticles;
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
Vector3r &corr = m_simulationData.getDeltaX(fluidModelIndex, i);
// Compute position correction
corr.setZero();
const Vector3r &xi = model->getPosition(i);
// Fluid
forall_fluid_neighbors(
const Vector3r gradC_j = -fm_neighbor->getVolume(neighborIndex) * sim->gradW(xi - xj);
corr -= (m_simulationData.getLambda(fluidModelIndex, i) + (fm_neighbor->getDensity0() / density0) * m_simulationData.getLambda(pid, neighborIndex)) * gradC_j;
);
// Boundary
if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
{
forall_boundary_neighbors(
// Boundary: Akinci2012
const Vector3r gradC_j = -bm_neighbor->getVolume(neighborIndex) * sim->gradW(xi - xj);
const Vector3r dx = m_simulationData.getLambda(fluidModelIndex, i) * gradC_j;
corr -= dx;
bm_neighbor->addForce(xj, model->getMass(i) * dx * invH2);
);
}
else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
{
forall_density_maps(
const Vector3r dx = m_simulationData.getLambda(fluidModelIndex, i) * gradRho;
corr -= dx;
bm_neighbor->addForce(xj, model->getMass(i) * dx * invH2);
);
}
else // Bender2019
{
forall_volume_maps(
const Vector3r gradC_j = -Vj * sim->gradW(xi - xj);
const Vector3r dx = m_simulationData.getLambda(fluidModelIndex, i) * gradC_j;
corr -= dx;
bm_neighbor->addForce(xj, model->getMass(i) * dx * invH2);
);
}
}
}
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
if (model->getParticleState(i) == ParticleState::Active)
model->getPosition(i) += m_simulationData.getDeltaX(fluidModelIndex, i);
}
}
}
void TimeStepPBF::performNeighborhoodSearchSort()
{
m_simulationData.performNeighborhoodSearchSort();
}
void TimeStepPBF::emittedParticles(FluidModel *model, const unsigned int startIndex)
{
m_simulationData.emittedParticles(model, startIndex);
}
void TimeStepPBF::resize()
{
m_simulationData.init();
}