Program Listing for File TimeStepPF.cpp
↰ Return to documentation for file (SPlisHSPlasH/PF/TimeStepPF.cpp)
#include "TimeStepPF.h"
#include "SimulationDataPF.h"
#include "SPlisHSPlasH/SPHKernels.h"
#include "SPlisHSPlasH/TimeManager.h"
#include "Utilities/Timing.h"
#include "SPlisHSPlasH/Simulation.h"
#include "SPlisHSPlasH/BoundaryModel_Akinci2012.h"
#include "SPlisHSPlasH/BoundaryModel_Koschier2017.h"
#include "SPlisHSPlasH/BoundaryModel_Bender2019.h"
#include <atomic>
#include <iostream>
using namespace SPH;
using namespace std;
using namespace GenParam;
std::string TimeStepPF::METHOD_NAME = "Projective Fluids";
int TimeStepPF::SOLVER_ITERATIONS = -1;
int TimeStepPF::MIN_ITERATIONS = -1;
int TimeStepPF::MAX_ITERATIONS = -1;
int TimeStepPF::MAX_ERROR = -1;
int TimeStepPF::STIFFNESS = -1;
#define Vec3Block(i) template segment<3>(3 * (i))
// helper functions
namespace
{
template <typename T>
struct atomic_wrapper
{
std::atomic<T> _a;
atomic_wrapper() : _a(0) {}
atomic_wrapper(const std::atomic<T> &a) :_a(a.load()) {}
atomic_wrapper(const atomic_wrapper &other) :_a(other._a.load()) {}
atomic_wrapper &operator=(const atomic_wrapper &other)
{
_a.store(other._a.load());
return *this;
}
};
inline void addToAtomicReal(std::atomic<Real> & a, const Real & r)
{
Real current = a;
while (!a.compare_exchange_weak(current, current + r))
;
}
}
using AtomicRealVec = std::vector < atomic_wrapper<Real> >;
TimeStepPF::TimeStepPF() :
TimeStep(),
m_stiffness(50000.0),
m_numActiveParticlesTotal(0)
{
m_simulationData.init();
m_iterations = 0;
m_minIterations = 2;
m_maxIterations = 100;
m_maxError = static_cast<Real>(1e-10);
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({ "oldPosition", METHOD_NAME, FieldType::Vector3, [this, fluidModelIndex](const unsigned int i) -> Real* { return &m_simulationData.getOldPosition(fluidModelIndex, i)[0]; }, true });
model->addField({ "S", METHOD_NAME, FieldType::Vector3, [this, fluidModelIndex](const unsigned int i) -> Real* { return &m_simulationData.getS(fluidModelIndex, i)[0]; } });
model->addField({ "numFluidNeighbors", METHOD_NAME, FieldType::UInt, [this, fluidModelIndex](const unsigned int i) -> unsigned int* { return &m_simulationData.getNumFluidNeighbors(fluidModelIndex, i); } });
model->addField({ "diag", METHOD_NAME, FieldType::Vector3, [this, fluidModelIndex](const unsigned int i) -> Real* { return &m_simulationData.getDiag(fluidModelIndex, i)[0]; } });
}
}
TimeStepPF::~TimeStepPF(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("oldPosition");
model->removeFieldByName("S");
model->removeFieldByName("numFluidNeighbors");
model->removeFieldByName("diag");
}
}
void TimeStepPF::initParameters()
{
TimeStep::initParameters();
SOLVER_ITERATIONS = createNumericParameter("iterations", "Iterations", &m_iterations);
setGroup(SOLVER_ITERATIONS, "Simulation|Projective Fluids");
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|Projective Fluids");
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|Projective Fluids");
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. error", &m_maxError);
setGroup(MAX_ERROR, "Simulation|Projective Fluids");
setDescription(MAX_ERROR, "Maximal error.");
static_cast<RealParameter*>(getParameter(MAX_ERROR))->setMinValue(static_cast<Real>(1e-12));
STIFFNESS = createNumericParameter("stiffness", "Stiffness", &m_stiffness);
setGroup(STIFFNESS, "Simulation|Projective Fluids");
setDescription(STIFFNESS, "Stiffness coefficient.");
static_cast<RealParameter*>(getParameter(STIFFNESS))->setMinValue(static_cast<Real>(1e-6));
}
void TimeStepPF::step()
{
Simulation *sim = Simulation::getCurrent();
const unsigned int nModels = sim->numberOfFluidModels();
TimeManager *tm = TimeManager::getCurrent ();
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
clearAccelerations(fluidModelIndex);
initialGuessForPositions(fluidModelIndex);
}
sim->performNeighborhoodSearch();
#ifdef USE_PERFORMANCE_OPTIMIZATION
precomputeValues();
#endif
if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Bender2019)
computeVolumeAndBoundaryX();
else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
computeDensityAndGradient();
START_TIMING("solvePDConstraints");
solvePDConstraints();
STOP_TIMING_AVG;
if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Bender2019)
computeVolumeAndBoundaryX();
else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
computeDensityAndGradient();
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
computeDensities(fluidModelIndex);
sim->computeNonPressureForces();
addAccellerationToVelocity();
// update emitters
sim->emitParticles();
sim->animateParticles();
// Compute new time
sim->updateTimeStepSize();
tm->setTime(tm->getTime () + tm->getTimeStepSize());
}
void TimeStepPF::reset()
{
TimeStep::reset();
m_simulationData.reset();
m_iterations = 0;
}
void TimeStepPF::initialGuessForPositions(const unsigned int fluidModelIndex)
{
Simulation *sim = Simulation::getCurrent();
FluidModel *model = sim->getFluidModel(fluidModelIndex);
const auto numParticles = model->numActiveParticles();
const auto h = TimeManager::getCurrent()->getTimeStepSize();
#pragma omp parallel for
for (int i = 0; i < (int)numParticles; i++)
{
m_simulationData.setOldPosition(fluidModelIndex, i, model->getPosition(i));
if (model->getParticleState(i) == ParticleState::Active)
{
const auto newPos = (model->getPosition(i) + h * model->getVelocity(i) + (h * h) * model->getAcceleration(i)).eval();
model->setPosition(i, newPos);
m_simulationData.setS(fluidModelIndex, i, newPos);
}
else
{
m_simulationData.setS(fluidModelIndex, i, model->getPosition(i));
}
}
}
void TimeStepPF::solvePDConstraints()
{
Simulation *sim = Simulation::getCurrent();
const unsigned int nFluids = sim->numberOfFluidModels();
if (nFluids == 0)
return;
// total number of active fluid particles
m_numActiveParticlesTotal = m_simulationData.getParticleOffset(nFluids - 1) + sim->getFluidModel(nFluids - 1)->numActiveParticles();
VectorXr x(3 * m_numActiveParticlesTotal);
VectorXr b(3 * m_numActiveParticlesTotal);
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nFluids; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
const unsigned int offset = m_simulationData.getParticleOffset(fluidModelIndex);
#pragma omp parallel for schedule(static)
for (int i = 0; i < (int)model->numActiveParticles(); i++)
{
// initialize positions
x.Vec3Block(offset + i) = m_simulationData.getS(fluidModelIndex, i);
// count number of fluid neighbors for relaxation
unsigned int nNeighbors = 0;
for (unsigned int pid = 0; pid < nFluids; pid++)
nNeighbors += sim->numberOfNeighbors(fluidModelIndex, pid, i);
m_simulationData.setNumFluidNeighbors(fluidModelIndex, i, nNeighbors + 1u);
}
}
// Init linear system solver and preconditioner
MatrixReplacement A(3 * m_numActiveParticlesTotal, matrixVecProd, (void*) this);
#ifdef PD_USE_DIAGONAL_PRECONDITIONER
preparePreconditioner();
m_solver.preconditioner().init(m_numActiveParticlesTotal, diagonalMatrixElement, (void*)this);
#endif
m_solver.setMaxIterations(m_maxIterations);
m_solver.compute(A);
for (m_iterations = 0u; m_iterations < m_maxIterations; m_iterations++)
{
// Compute RHS
matrixFreeRHS(x,b);
// Solve linear system
#ifdef PD_USE_DIAGONAL_PRECONDITIONER
// hack to make the solver perform at least min_iter CG iterations for stability
const unsigned int min_iter = m_minIterations;
m_solver.setTolerance(m_iterations < min_iter ? static_cast<Real>(1e-32) : static_cast<Real>(1e-10));
#endif
x = m_solver.solveWithGuess(b, x);
if (m_solver.iterations() == 0)
break;
}
updatePositionsAndVelocity(x);
}
void SPH::TimeStepPF::updatePositionsAndVelocity(const VectorXr & x)
{
Simulation *sim = Simulation::getCurrent();
const unsigned int nModels = sim->numberOfFluidModels();
const Real h_inv = 1 / TimeManager::getCurrent()->getTimeStepSize();
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
const unsigned int numParticles = model->numActiveParticles();
const unsigned int offset = m_simulationData.getParticleOffset(fluidModelIndex);
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
if (model->getParticleState(i) == ParticleState::Active)
{
const Vector3r vel = h_inv * (x.Vec3Block(offset + i) - m_simulationData.getOldPosition(fluidModelIndex, i));
model->setPosition(i, x.Vec3Block(offset + i));
model->setVelocity(i, vel);
}
}
}
}
}
#ifdef PD_USE_DIAGONAL_PRECONDITIONER
FORCE_INLINE void SPH::TimeStepPF::diagonalMatrixElement(const unsigned int row, Vector3r &result, void *userData)
{
TimeStepPF *timeStepPF = static_cast<TimeStepPF*>(userData);
Simulation *sim = Simulation::getCurrent();
const unsigned int nModels = sim->numberOfFluidModels();
// TODO: use m_simulationData->getParticleOffset instead of accessing FluidModels
// find corresponding fluid model and particle index for the current row
unsigned int fluidRow = row;
unsigned int fluidModelIndex;
for (fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
if (fluidRow >= model->numActiveParticles())
fluidRow -= model->numActiveParticles();
else
break;
}
result = timeStepPF->m_simulationData.getDiag(fluidModelIndex, fluidRow);
}
void SPH::TimeStepPF::preparePreconditioner()
{
Simulation *sim = Simulation::getCurrent();
const unsigned int nModels = sim->numberOfFluidModels();
const auto h = TimeManager::getCurrent()->getTimeStepSize();
const auto system_scale = h * h * m_stiffness;
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
const auto numParticles = model->numActiveParticles();
const Real density0_inv = 1 / model->getDensity0();
#pragma omp parallel for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
Real density_scale = 1;
for (unsigned int pid = 0; pid < nModels; pid++)
{
density_scale += density0_inv * sim->getFluidModelFromPointSet(pid)->getDensity0() * sim->numberOfNeighbors(fluidModelIndex, pid, i);
}
m_simulationData.setDiag(fluidModelIndex, i, Vector3r::Constant(system_scale * density_scale + model->getMass(i)));
}
}
}
#endif
void SPH::TimeStepPF::addAccellerationToVelocity()
{
Simulation *sim = Simulation::getCurrent();
const auto h = TimeManager::getCurrent()->getTimeStepSize();
const unsigned int nModels = sim->numberOfFluidModels();
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
const auto numParticles = model->numActiveParticles();
#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->setVelocity(i, model->getVelocity(i) + h * model->getAcceleration(i));
}
}
}
}
void SPH::TimeStepPF::matrixFreeRHS(const VectorXr & x, VectorXr & result)
{
Simulation *sim = Simulation::getCurrent();
const Real h = TimeManager::getCurrent()->getTimeStepSize();;
const unsigned int nFluids = sim->numberOfFluidModels();
const unsigned int nBoundaries = sim->numberOfBoundaryModels();
// helper functions
// constraint value
const auto calculateC = [&](const unsigned int fluidModelIndex, const unsigned int i, std::vector<Vector3r> & p) -> Real
{
const FluidModel * model = sim->getFluidModel(fluidModelIndex);
// Compute current density for particle i
Real density = model->getVolume(i) * sim->W_zero();
const Vector3r &xi = p[0];
unsigned int counter = 1;
for (unsigned int pid = 0; pid < nFluids; pid++)
{
const FluidModel *fm_neighbor = sim->getFluidModelFromPointSet(pid);
for (unsigned int j = 0; j < sim->numberOfNeighbors(fluidModelIndex, pid, i); j++)
{
const unsigned int neighborIndex = sim->getNeighbor(fluidModelIndex, pid, i, j);
const Vector3r & xj = p[counter++];
density += fm_neighbor->getVolume(neighborIndex) * sim->W(xi - xj);
}
}
// influence of boundary on density
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 if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Bender2019)
{
forall_volume_maps(
density += Vj * sim->W(xi - xj);
);
}
// constraint value = density / density0 - 1
const auto C = density - 1;
// pressure clamping
return (C < 0) ? 0 : C;
};
// constraint gradient
const auto calculateNablaC = [&](const unsigned int fluidModelIndex, const unsigned int i, std::vector<Vector3r> & p) -> std::vector<Vector3r>
{
std::vector<Vector3r> nablaC(p.size());
nablaC[0].setZero();
const Vector3r &xi = p[0];
unsigned int counter = 1;
for (unsigned int pid = 0; pid < nFluids; pid++)
{
const FluidModel *fm_neighbor = sim->getFluidModelFromPointSet(pid);
for (unsigned int j = 0; j < sim->numberOfNeighbors(fluidModelIndex, pid, i); j++)
{
const unsigned int neighborIndex = sim->getNeighbor(fluidModelIndex, pid, i, j);
const Vector3r & xj = p[counter];
nablaC[counter] = -fm_neighbor->getVolume(neighborIndex) * sim->gradW(xi - xj);
nablaC[0] -= nablaC[counter];
counter++;
}
}
if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
{
// influence of boundary on gradient
forall_boundary_neighbors(
// Boundary: Akinci2012
nablaC[0] += bm_neighbor->getVolume(neighborIndex) * sim->gradW(xi - xj);
)
}
else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
{
forall_density_maps(
nablaC[0] -= gradRho;
);
}
else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Bender2019)
{
forall_volume_maps(
nablaC[0] += Vj * sim->gradW(xi - xj);
);
}
return nablaC;
};
AtomicRealVec accumulator(3 * m_numActiveParticlesTotal);
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nFluids; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
const unsigned int numParticles = model->numActiveParticles();
const unsigned int offset = m_simulationData.getParticleOffset(fluidModelIndex);
const Real density0 = model->getDensity0();
// influence of pressure
#pragma omp parallel default(shared)
{
// local step for fluid constraints
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
// total number of neighbors in all point sets (fluids and boundaries)
unsigned int numParticlesInConstraint = 1;
for (unsigned int pid = 0; pid < nFluids; pid++)
{
numParticlesInConstraint += sim->numberOfNeighbors(fluidModelIndex, pid, i);
}
// particle positions in current constraint, will be projected
std::vector<Vector3r> p;
p.reserve(numParticlesInConstraint);
// the i'th particle itself
p.emplace_back(x.Vec3Block(offset + i));
// fluid neighbors
for (unsigned int pid = 0; pid < nFluids; pid++)
{
const unsigned int neighborOffset = m_simulationData.getParticleOffset(pid);
for (unsigned int j = 0; j < sim->numberOfNeighbors(fluidModelIndex, pid, i); j++)
{
const unsigned int neighborIndex = sim->getNeighbor(fluidModelIndex, pid, i, j);
p.emplace_back(x.Vec3Block(neighborOffset + neighborIndex));
}
}
// constraint projection
const auto C_goal = Real(1e-14);
const auto max_steps = 100u;
auto it = 0u;
auto C = calculateC(fluidModelIndex, i, p);
while ((std::abs(C) > C_goal) && it++ < max_steps)
{
const auto g = calculateNablaC(fluidModelIndex, i, p);
const Real dg = [&g]() { Real s = 0; for (const auto & x : g) { s += x.squaredNorm(); } return s; }();
if (dg == 0) break; // found a minimum
const Real cdg = -C / (dg + 1e-6f); // add regularization factor
// move fluid particles along constraint gradient
p[0] += (cdg * m_simulationData.getNumFluidNeighbors(fluidModelIndex, i)) * g[0];
// fluid particles are projected
unsigned int counter = 1;
for (unsigned int pid = 0; pid < nFluids; pid++)
{
for (unsigned int j = 0; j < sim->numberOfNeighbors(fluidModelIndex, pid, i); j++)
{
const unsigned int neighborIndex = sim->getNeighbor(fluidModelIndex, pid, i, j);
const unsigned int nfn = m_simulationData.getNumFluidNeighbors(pid, neighborIndex);
p[counter] += (cdg * nfn) * g[counter];
counter++;
}
}
if (it + 1 < max_steps)
{
if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
computeDensityAndGradient(fluidModelIndex, i, p[0]);
C = calculateC(fluidModelIndex, i, p);
}
}
// update RHS
const unsigned int index_i = offset + i;
for (auto c = 0u; c < 3; c++)
addToAtomicReal(accumulator[3 * index_i + c]._a, density0 * p[0][c]);
unsigned int counter = 1;
for (unsigned int pid = 0; pid < nFluids; pid++)
{
const unsigned int neighborOffset = m_simulationData.getParticleOffset(pid);
for (unsigned int j = 0; j < sim->numberOfNeighbors(fluidModelIndex, pid, i); j++)
{
const unsigned int neighborIndex = sim->getNeighbor(fluidModelIndex, pid, i, j);
const unsigned int index_j = neighborOffset + neighborIndex;
for (auto c = 0u; c < 3; c++)
addToAtomicReal(accumulator[3 * index_j + c]._a, density0 * p[counter][c]);
counter++;
}
}
}
}
}
const Real system_scale = h * h * m_stiffness;
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nFluids; fluidModelIndex++)
{
FluidModel *model = sim->getFluidModel(fluidModelIndex);
const unsigned int numParticles = model->numActiveParticles();
const unsigned int offset = m_simulationData.getParticleOffset(fluidModelIndex);
const Real density_scaled_system = system_scale / model->getDensity0();
// influence of momentum
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
const auto m = model->getMass(i);
const auto& s = m_simulationData.getS(fluidModelIndex, i);
for (auto c = 0u; c < 3; c++)
{
const auto id = 3 * (offset + i) + c;
result[id] = density_scaled_system * accumulator[id]._a + m * s[c];
}
}
}
}
}
void SPH::TimeStepPF::matrixVecProd(const Real* vec, Real *result, void *userData)
{
TimeStepPF *timeStepPF = static_cast<TimeStepPF*>(userData);
Simulation *sim = Simulation::getCurrent();
const Real h = TimeManager::getCurrent()->getTimeStepSize();
const unsigned int nFluids = sim->numberOfFluidModels();
const Real system_scale = h * h * timeStepPF->m_stiffness;
for (unsigned int fluidModelIndex = 0; fluidModelIndex < nFluids; fluidModelIndex++)
{
const FluidModel *model = sim->getFluidModel(fluidModelIndex);
const Real density0 = model->getDensity0();
const Real density_scaled_system = system_scale / density0;
const unsigned int numParticles = model->numActiveParticles();
const unsigned int offset = timeStepPF->m_simulationData.getParticleOffset(fluidModelIndex);
#pragma omp parallel for schedule(static)
for (int i = 0; i < (int) numParticles; i++)
{
const unsigned int index_i = i + offset;
Eigen::Map<Vector3r> ri(result + 3 * index_i, 3, 1);
Eigen::Map<const Vector3r> xi(vec + 3 * index_i, 3, 1);
// the particle itself
Real accumulator = density0;
// fluid neighbors
for (unsigned int pid = 0; pid < nFluids; pid++)
{
const FluidModel *fm_neighbor = sim->getFluidModelFromPointSet(pid);
accumulator += fm_neighbor->getDensity0() * sim->numberOfNeighbors(fluidModelIndex, pid, i);
}
// boundary neighbors are not part of the linear system,
// they just influence the right hand side in the projection
ri = (density_scaled_system * accumulator + model->getMass(i)) * xi;
}
}
}
void TimeStepPF::performNeighborhoodSearchSort()
{
m_simulationData.performNeighborhoodSearchSort();
}
void TimeStepPF::emittedParticles(FluidModel *model, const unsigned int startIndex)
{
m_simulationData.emittedParticles(model, startIndex);
}
void TimeStepPF::resize()
{
m_simulationData.init();
}