iMSTK
Interactive Medical Simulation Toolkit
imstkSphModel.cpp
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #include "imstkSphModel.h"
8 #include "imstkParallelUtils.h"
9 #include "imstkPointSet.h"
10 #include "imstkTaskGraph.h"
11 #include "imstkVTKMeshIO.h"
12 
13 namespace imstk
14 {
15 SphModelConfig::SphModelConfig(const double particleRadius)
16 {
17  // \todo Warning in all paths?
18  if (std::abs(particleRadius) > 1.0e-6)
19  {
20  LOG_IF(WARNING, (particleRadius < 0)) << "Particle radius supplied is negative! Using absolute value of the supplied radius.";
21  m_particleRadius = std::abs(particleRadius);
22  }
23  else
24  {
25  LOG(WARNING) << "Particle radius too small! Setting to 1.e-6";
26  m_particleRadius = 1.e-6;
27  }
28  initialize();
29 }
30 
31 SphModelConfig::SphModelConfig(const double particleRadius, const double speedOfSound, const double restDensity)
32 {
33  if (std::abs(particleRadius) > 1.0e-6)
34  {
35  LOG_IF(WARNING, (particleRadius < 0)) << "Particle radius supplied is negative! Using absolute value of the supplied radius.";
36  m_particleRadius = std::abs(particleRadius);
37  }
38  else
39  {
40  LOG(WARNING) << "Particle radius too small! Setting to 1.e-6";
41  m_particleRadius = 1.e-6;
42  }
43 
44  if (speedOfSound < 0)
45  {
46  LOG(WARNING) << "Speed of sound is negative! Setting speed of sound to default value.";
47  }
48  else
49  {
50  m_speedOfSound = speedOfSound;
51  }
52 
53  if (restDensity < 0)
54  {
55  LOG(WARNING) << "Rest density is negative! Setting rest density to default value.";
56  }
57  else
58  {
59  m_restDensity = restDensity;
60  }
61  initialize();
62 }
63 
64 void
65 SphModelConfig::initialize()
66 {
67  // Compute the derived quantities
68  m_particleRadiusSqr = m_particleRadius * m_particleRadius;
69 
70  m_particleMass = std::pow(2.0 * m_particleRadius, 3) * m_restDensity * m_particleMassScale;
71  m_restDensitySqr = m_restDensity * m_restDensity;
72  m_restDensityInv = 1.0 / m_restDensity;
73 
74  m_kernelRadius = m_particleRadius * m_kernelOverParticleRadiusRatio;
76 
77  m_pressureStiffness = m_restDensity * m_speedOfSound * m_speedOfSound / 7.0;
78 }
79 
80 SphModel::SphModel() : DynamicalModel<SphState>(DynamicalModelType::SmoothedParticleHydrodynamics)
81 {
82  m_validGeometryTypes = { "PointSet" };
83 
84  m_findParticleNeighborsNode = m_taskGraph->addFunction("SPHModel_Partition", std::bind(&SphModel::findParticleNeighbors, this));
85  m_computeDensityNode = m_taskGraph->addFunction("SPHModel_ComputeDensity", [&]()
86  {
87  computeNeighborRelativePositions();
88  computeDensity();
89  });
90 
91  m_normalizeDensityNode = m_taskGraph->addFunction("SPHModel_NormalizeDensity", std::bind(&SphModel::normalizeDensity, this));
92 
93  m_collectNeighborDensityNode = m_taskGraph->addFunction("SPHModel_CollectNeighborDensity", std::bind(&SphModel::collectNeighborDensity, this));
94 
95  m_computeTimeStepSizeNode =
96  m_taskGraph->addFunction("SPHModel_ComputeTimestep", std::bind(&SphModel::computeTimeStepSize, this));
97 
98  m_computePressureAccelNode =
99  m_taskGraph->addFunction("SPHModel_ComputePressureAccel", std::bind(&SphModel::computePressureAcceleration, this));
100 
101  m_computeSurfaceTensionNode =
102  m_taskGraph->addFunction("SPHModel_ComputeSurfaceTensionAccel", std::bind(&SphModel::computeSurfaceTension, this));
103 
104  m_computeViscosityNode =
105  m_taskGraph->addFunction("SPHModel_ComputeViscosity", std::bind(&SphModel::computeViscosity, this));
106 
107  m_integrateNode =
108  m_taskGraph->addFunction("SPHModel_Integrate", std::bind(&SphModel::sumAccels, this));
109 
110  m_updateVelocityNode =
111  m_taskGraph->addFunction("SPHModel_UpdateVelocity", [&]()
112  {
113  updateVelocity(getTimeStep());
114  });
115 
116  m_moveParticlesNode =
117  m_taskGraph->addFunction("SPHModel_MoveParticles", [&]()
118  {
119  moveParticles(getTimeStep());
120  });
121 
122  //m_computePositionNode =
123  // m_taskGraph->addFunction("SPHModel_ComputePositions", [&]()
124  // {
125  // moveParticles(getTimeStep());
126  // });
127 }
128 
129 bool
131 {
132  LOG_IF(FATAL, (!this->getModelGeometry())) << "Model geometry is not yet set! Cannot initialize without model geometry.";
133  m_pointSetGeometry = std::dynamic_pointer_cast<PointSet>(m_geometry);
134  const int numParticles = m_pointSetGeometry->getNumVertices();
135 
136  // Allocate init and current state
137  m_initialState = std::make_shared<SphState>(numParticles);
138  m_currentState = std::make_shared<SphState>(numParticles);
139 
140  // If there were initial velocities (set them)
141  if (m_initialVelocities != nullptr)
142  {
143  m_currentState->setVelocities(m_initialVelocities);
144  }
145 
146  // Copy current to initial
147  m_initialState->setState(m_currentState);
148 
149  // Share geometry and state position arrays
150  m_currentState->setPositions(m_pointSetGeometry->getVertexPositions());
151  m_initialState->setPositions(m_pointSetGeometry->getInitialVertexPositions());
152 
153  // Initialize simulation dependent parameters and kernel data
154  m_kernels.initialize(m_modelParameters->m_kernelRadius);
155 
156  // Initialize neighbor searcher
157  m_neighborSearcher = std::make_shared<NeighborSearch>(m_modelParameters->m_neighborSearchMethod,
158  m_modelParameters->m_kernelRadius);
159 
160  m_pressureAccels = std::make_shared<VecDataArray<double, 3>>(numParticles);
161  std::fill_n(m_pressureAccels->getPointer(), m_pressureAccels->size(), Vec3d(0, 0, 0));
162 
163  // initialize surface tension to 0 in case you remove the surface tension node
164  m_surfaceTensionAccels = std::make_shared<VecDataArray<double, 3>>(numParticles);
165  std::fill_n(m_surfaceTensionAccels->getPointer(), m_surfaceTensionAccels->size(), Vec3d(0, 0, 0));
166 
167  m_viscousAccels = std::make_shared<VecDataArray<double, 3>>(numParticles);
168  std::fill_n(m_viscousAccels->getPointer(), m_viscousAccels->size(), Vec3d(0, 0, 0));
169 
170  m_neighborVelContr = std::make_shared<VecDataArray<double, 3>>(numParticles);
171  std::fill_n(m_neighborVelContr->getPointer(), m_neighborVelContr->size(), Vec3d(0, 0, 0));
172 
173  m_particleShift = std::make_shared<VecDataArray<double, 3>>(numParticles);
174  std::fill_n(m_particleShift->getPointer(), m_particleShift->size(), Vec3d(0, 0, 0));
175 
176  // Add all the attributes to the geometry
177  m_pointSetGeometry->setVertexAttribute("Pressure Accels", m_pressureAccels);
178  m_pointSetGeometry->setVertexAttribute("Surface Tension Accels", m_surfaceTensionAccels);
179  m_pointSetGeometry->setVertexAttribute("Viscous Accels", m_viscousAccels);
180  m_pointSetGeometry->setVertexAttribute("Densities", m_currentState->getDensities());
181  m_pointSetGeometry->setVertexAttribute("Velocities", m_currentState->getVelocities());
182  m_pointSetGeometry->setVertexAttribute("Diffuse Velocities", m_currentState->getDiffuseVelocities());
183  m_pointSetGeometry->setVertexAttribute("Normals", m_currentState->getNormals());
184  m_pointSetGeometry->setVertexAttribute("Accels", m_currentState->getAccelerations());
185 
186  return true;
187 }
188 
189 void
190 SphModel::initGraphEdges(std::shared_ptr<TaskNode> source, std::shared_ptr<TaskNode> sink)
191 {
192  // Setup graph connectivity
193  m_taskGraph->addEdge(source, m_findParticleNeighborsNode);
194  m_taskGraph->addEdge(m_findParticleNeighborsNode, m_computeDensityNode);
195  m_taskGraph->addEdge(m_computeDensityNode, m_normalizeDensityNode);
196  m_taskGraph->addEdge(m_normalizeDensityNode, m_collectNeighborDensityNode);
197 
198  // Pressure, Surface Tension, and time step size can be done in parallel
199  m_taskGraph->addEdge(m_collectNeighborDensityNode, m_computePressureAccelNode);
200  m_taskGraph->addEdge(m_collectNeighborDensityNode, m_computeSurfaceTensionNode);
201  m_taskGraph->addEdge(m_collectNeighborDensityNode, m_computeViscosityNode);
202  m_taskGraph->addEdge(m_collectNeighborDensityNode, m_computeTimeStepSizeNode);
203 
204  m_taskGraph->addEdge(m_computePressureAccelNode, m_integrateNode);
205  m_taskGraph->addEdge(m_computeSurfaceTensionNode, m_integrateNode);
206  m_taskGraph->addEdge(m_computeViscosityNode, m_integrateNode);
207  m_taskGraph->addEdge(m_computeTimeStepSizeNode, m_integrateNode);
208 
209  m_taskGraph->addEdge(m_integrateNode, m_updateVelocityNode);
210  m_taskGraph->addEdge(m_updateVelocityNode, m_moveParticlesNode);
211  m_taskGraph->addEdge(m_moveParticlesNode, sink);
212 }
213 
214 void
215 SphModel::computeTimeStepSize()
216 {
217  m_dt = (this->m_timeStepSizeType == TimeSteppingType::Fixed) ? m_defaultDt : computeCFLTimeStepSize();
218 }
219 
220 double
221 SphModel::computeCFLTimeStepSize()
222 {
223  auto maxVel = ParallelUtils::findMaxL2Norm(*getCurrentState()->getFullStepVelocities());
224 
225  // dt = CFL * 2r / (speed of sound + max{|| v ||})
226  double timestep = maxVel > 1.0e-6 ?
227  m_modelParameters->m_cflFactor * (2.0 * m_modelParameters->m_particleRadius / (m_modelParameters->m_speedOfSound + maxVel)) :
228  m_modelParameters->m_maxTimestep;
229 
230  // clamp the time step size to be within a given range
231  if (timestep > m_modelParameters->m_maxTimestep)
232  {
233  timestep = m_modelParameters->m_maxTimestep;
234  }
235  else if (timestep < m_modelParameters->m_minTimestep)
236  {
237  timestep = m_modelParameters->m_minTimestep;
238  }
239  return timestep;
240 }
241 
242 void
243 SphModel::findParticleNeighbors()
244 {
245  m_neighborSearcher->getNeighbors(getCurrentState()->getFluidNeighborLists(), *getCurrentState()->getPositions());
246 
247  if (m_modelParameters->m_bDensityWithBoundary) // if considering boundary particles for computing fluid density
248  {
249  m_neighborSearcher->getNeighbors(getCurrentState()->getBoundaryNeighborLists(),
250  *getCurrentState()->getPositions(),
251  *getCurrentState()->getBoundaryParticlePositions());
252  }
253 }
254 
255 void
256 SphModel::computeNeighborRelativePositions()
257 {
258  auto computeRelativePositions = [&](const Vec3d& ppos, const std::vector<size_t>& neighborList,
259  const VecDataArray<double, 3>& allPositions, std::vector<NeighborInfo>& neighborInfo)
260  {
261  for (const size_t q : neighborList)
262  {
263  const Vec3d& qpos = allPositions[q];
264  const Vec3d r = ppos - qpos;
265  neighborInfo.push_back({ r, m_modelParameters->m_restDensity });
266  }
267  };
268 
269  std::shared_ptr<VecDataArray<double, 3>> positionsPtr = getCurrentState()->getPositions();
270  const VecDataArray<double, 3>& positions = *positionsPtr;
271 
272  std::vector<std::vector<NeighborInfo>>& neighborInfos = getCurrentState()->getNeighborInfo();
273 
274  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
275  [&](const size_t p)
276  {
277  if (m_sphBoundaryConditions
278  && m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer)
279  {
280  return;
281  }
282 
283  const Vec3d& ppos = positions[p];
284  std::vector<NeighborInfo>& neighborInfo = neighborInfos[p];
285  neighborInfo.resize(0);
286  neighborInfo.reserve(48);
287 
288  computeRelativePositions(ppos, getCurrentState()->getFluidNeighborLists()[p], *getCurrentState()->getPositions(), neighborInfo);
289  // if considering boundary particles then also cache relative positions with them
290  if (m_modelParameters->m_bDensityWithBoundary)
291  {
292  computeRelativePositions(ppos, getCurrentState()->getBoundaryNeighborLists()[p], *getCurrentState()->getBoundaryParticlePositions(), neighborInfo);
293  }
294  });
295 }
296 
297 void
298 SphModel::collectNeighborDensity()
299 {
300  // After computing particle densities, cache them into neighborInfo variable, next to relative positions
301  // this is useful because relative positions and densities are accessed together multiple times
302  // caching relative positions and densities therefore can reduce computation time significantly (tested)
303  std::shared_ptr<DataArray<double>> densitiesPtr = getCurrentState()->getDensities();
304  DataArray<double>& densities = *densitiesPtr;
305 
306  const std::vector<std::vector<size_t>>& neighborLists = getCurrentState()->getFluidNeighborLists();
307  const std::vector<SphBoundaryConditions::ParticleType>& particleTypes = m_sphBoundaryConditions->getParticleTypes();
308 
309  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
310  [&](const size_t p)
311  {
312  if (m_sphBoundaryConditions && particleTypes[p] == SphBoundaryConditions::ParticleType::Buffer)
313  {
314  return;
315  }
316 
317  auto& neighborInfo = getCurrentState()->getNeighborInfo()[p];
318  if (neighborInfo.size() <= 1)
319  {
320  return; // the particle has no neighbor
321  }
322 
323  const auto& fluidNeighborList = neighborLists[p];
324  for (size_t i = 0; i < fluidNeighborList.size(); ++i)
325  {
326  auto q = fluidNeighborList[i];
327  neighborInfo[i].density = densities[q];
328  }
329  });
330 }
331 
332 void
333 SphModel::computeDensity()
334 {
335  std::shared_ptr<DataArray<double>> densitiesPtr = getCurrentState()->getDensities();
336  DataArray<double>& densities = *densitiesPtr;
337 
338  const std::vector<std::vector<NeighborInfo>>& neighborInfos = getCurrentState()->getNeighborInfo();
339 
340  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
341  [&](const size_t p)
342  {
343  if (m_sphBoundaryConditions && m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer)
344  {
345  return;
346  }
347 
348  const std::vector<NeighborInfo>& neighborInfo = neighborInfos[p];
349  if (neighborInfo.size() <= 1)
350  {
351  return; // the particle has no neighbor
352  }
353 
354  double pdensity = 0.0;
355  for (const auto& qInfo : neighborInfo)
356  {
357  pdensity += m_kernels.W(qInfo.relativePos);
358  }
359  pdensity *= m_modelParameters->m_particleMass;
360  densities[p] = pdensity;
361  });
362 }
363 
364 //void
365 //SPHModel::computePressureOutlet()
366 //{
367 // ParallelUtils::parallelFor(getState().getNumParticles(),
368 // [&](const size_t p) {
369 // if (getState().getPositions()[p].x() > m_outletRegionXCoord && getState().getPositions()[p].x() < m_maxXCoord)
370 // {
371 // getState().getDensities()[p] = m_outletDensity;
372 // }
373 // });
374 //}
375 
376 void
377 SphModel::normalizeDensity()
378 {
379  if (!m_modelParameters->m_bNormalizeDensity)
380  {
381  return;
382  }
383 
384  std::shared_ptr<DataArray<double>> densitiesPtr = getCurrentState()->getDensities();
385  DataArray<double>& densities = *densitiesPtr;
386 
387  const std::vector<std::vector<size_t>>& neighborLists = getCurrentState()->getFluidNeighborLists();
388  const std::vector<std::vector<NeighborInfo>>& neighborInfos = getCurrentState()->getNeighborInfo();
389  const std::vector<SphBoundaryConditions::ParticleType>& particleTypes = m_sphBoundaryConditions->getParticleTypes();
390 
391  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
392  [&](const size_t p)
393  {
394  if (m_sphBoundaryConditions && particleTypes[p] == SphBoundaryConditions::ParticleType::Buffer)
395  {
396  return;
397  }
398 
399  const std::vector<NeighborInfo>& neighborInfo = neighborInfos[p];
400  if (neighborInfo.size() <= 1)
401  {
402  return; // the particle has no neighbor
403  }
404 
405  const auto& fluidNeighborList = neighborLists[p];
406  double tmp = 0.0;
407 
408  for (size_t i = 0; i < fluidNeighborList.size(); ++i)
409  {
410  const auto& qInfo = neighborInfo[i];
411 
412  // because we're not done with density computation, qInfo does not contain desity of particle q yet
413  const auto q = fluidNeighborList[i];
414  const auto qdensity = densities[q];
415  tmp += m_kernels.W(qInfo.relativePos) / qdensity;
416  }
417 
418  densities[p] /= (tmp * m_modelParameters->m_particleMass);
419  });
420 }
421 
422 void
423 SphModel::computePressureAcceleration()
424 {
425  std::shared_ptr<DataArray<double>> densitiesPtr = getCurrentState()->getDensities();
426  const DataArray<double>& densities = *densitiesPtr;
427  VecDataArray<double, 3>& pressureAccels = *m_pressureAccels;
428 
429  const std::vector<std::vector<NeighborInfo>>& neighborInfos = getCurrentState()->getNeighborInfo();
430  const std::vector<SphBoundaryConditions::ParticleType>& particleTypes = m_sphBoundaryConditions->getParticleTypes();
431 
432  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
433  [&](const size_t p)
434  {
435  if (m_sphBoundaryConditions && particleTypes[p] == SphBoundaryConditions::ParticleType::Buffer)
436  {
437  return;
438  }
439 
440  Vec3d accel = Vec3d::Zero();
441  const std::vector<NeighborInfo>& neighborInfo = neighborInfos[p];
442  if (neighborInfo.size() <= 1)
443  {
444  pressureAccels[p] = accel;
445  return;
446  }
447 
448  const auto pdensity = densities[p];
449  const auto ppressure = getParticlePressure(pdensity);
450 
451  for (size_t idx = 0; idx < neighborInfo.size(); ++idx)
452  {
453  const auto& qInfo = neighborInfo[idx];
454  const auto r = qInfo.relativePos;
455  const auto qdensity = qInfo.density;
456  const auto qpressure = getParticlePressure(qdensity);
457  // pressure forces
458  accel += -(ppressure / (pdensity * pdensity) + qpressure / (qdensity * qdensity)) * m_kernels.gradW(r);
459  }
460 
461  accel *= m_modelParameters->m_particleMass;
462 
463  //getState().getAccelerations()[p] = accel;
464  pressureAccels[p] = accel;
465  });
466 }
467 
468 void
469 SphModel::computeViscosity()
470 {
471  VecDataArray<double, 3>& viscousAccels = *m_viscousAccels;
472  VecDataArray<double, 3>& neighborVelContr = *m_neighborVelContr;
473  VecDataArray<double, 3>& particleShift = *m_particleShift;
474  const VecDataArray<double, 3>& halfStepVelocities = *getCurrentState()->getHalfStepVelocities();
475 
476  const std::vector<std::vector<NeighborInfo>>& neighborInfos = getCurrentState()->getNeighborInfo();
477  const std::vector<std::vector<size_t>>& neighborLists = getCurrentState()->getFluidNeighborLists();
478 
479  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
480  [&](const size_t p)
481  {
482  if (m_sphBoundaryConditions
483  && (m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer
484  || m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Wall))
485  {
486  return;
487  }
488 
489  const std::vector<NeighborInfo>& neighborInfo = neighborInfos[p];
490  if (neighborInfo.size() <= 1)
491  {
492  neighborVelContr[p] = Vec3d::Zero();
493  viscousAccels[p] = Vec3d::Zero();
494  return;
495  }
496 
497  Vec3d neighborVelContributionsNumerator = Vec3d::Zero();
498  double neighborVelContributionsDenominator = 0.0;
499  Vec3d particleShifts = Vec3d::Zero();
500 
501  const Vec3d& pvel = halfStepVelocities[p];
502  const std::vector<size_t>& fluidNeighborList = neighborLists[p];
503 
504  Vec3d diffuseFluid = Vec3d::Zero();
505  for (size_t i = 0; i < fluidNeighborList.size(); ++i)
506  {
507  const auto q = fluidNeighborList[i];
508  const auto& qvel = halfStepVelocities[q];
509  const auto& qInfo = neighborInfo[i];
510  const auto r = qInfo.relativePos;
511  const auto qdensity = qInfo.density;
512  diffuseFluid += (1.0 / qdensity) * m_kernels.laplace(r) * (qvel - pvel);
513 
514  neighborVelContributionsNumerator += (qvel - pvel) * m_kernels.W(r);
515  neighborVelContributionsDenominator += m_kernels.W(r);
516  particleShifts += m_kernels.gradW(r);
517  //diffuseFluid += (Real(1.0) / qdensity) * m_kernels.W(r) * (qvel - pvel);
518  }
519  //diffuseFluid *= m_modelParameters->m_dynamicViscosityCoeff / getState().getDensities()[p];
520  const double particleRadius = m_modelParameters->m_particleRadius;
521  particleShifts *= 4 / 3 * PI * particleRadius * particleRadius * particleRadius * 0.5 * m_modelParameters->m_kernelRadius * halfStepVelocities[p].norm();
522  diffuseFluid *= m_modelParameters->m_dynamicViscosityCoeff * m_modelParameters->m_particleMass;
523  neighborVelContr[p] = neighborVelContributionsNumerator * m_modelParameters->m_eta / neighborVelContributionsDenominator;
524  particleShift[p] = -particleShifts;
525 
526  viscousAccels[p] = diffuseFluid;
527  });
528 }
529 
530 void
531 SphModel::computeSurfaceTension()
532 {
533  VecDataArray<double, 3>& surfaceNormals = *getCurrentState()->getNormals();
534 
535  const std::vector<std::vector<NeighborInfo>>& neighborInfos = getCurrentState()->getNeighborInfo();
536 
537  // First, compute surface normal for all particles
538  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
539  [&](const size_t p)
540  {
541  if (m_sphBoundaryConditions && m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer)
542  {
543  return;
544  }
545 
546  Vec3d n(0.0, 0.0, 0.0);
547  const std::vector<NeighborInfo>& neighborInfo = neighborInfos[p];
548  if (neighborInfo.size() <= 1)
549  {
550  surfaceNormals[p] = n;
551  return;
552  }
553 
554  for (size_t i = 0; i < neighborInfo.size(); ++i)
555  {
556  const auto& qInfo = neighborInfo[i];
557  const auto r = qInfo.relativePos;
558  const auto qdensity = qInfo.density;
559  n += (1.0 / qdensity) * m_kernels.gradW(r);
560  }
561 
562  n *= m_modelParameters->m_kernelRadius * m_modelParameters->m_particleMass;
563  surfaceNormals[p] = n;
564  });
565 
566  VecDataArray<double, 3>& surfaceTensionAccels = *m_surfaceTensionAccels;
567  const DataArray<double>& densities = *getCurrentState()->getDensities();
568 
569  const std::vector<std::vector<size_t>>& neighborLists = getCurrentState()->getFluidNeighborLists();
570 
571  // Second, compute surface tension acceleration
572  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
573  [&](const size_t p)
574  {
575  if (m_sphBoundaryConditions
576  && (m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer
577  || m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Wall))
578  {
579  return;
580  }
581 
582  const std::vector<size_t>& fluidNeighborList = neighborLists[p];
583  if (fluidNeighborList.size() <= 1)
584  {
585  return; // the particle has no neighbor
586  }
587 
588  const Vec3d& ni = surfaceNormals[p];
589  const double pdensity = densities[p];
590  const auto& neighborInfo = neighborInfos[p];
591 
592  Vec3d accel = Vec3d::Zero();
593  for (size_t i = 0; i < fluidNeighborList.size(); ++i)
594  {
595  const size_t q = fluidNeighborList[i];
596  if (p == q)
597  {
598  continue;
599  }
600  const NeighborInfo& qInfo = neighborInfo[i];
601  const double qdensity = qInfo.density;
602 
603  // Correction factor
604  const double K_ij = 2.0 * m_modelParameters->m_restDensity / (pdensity + qdensity);
605 
606  // Cohesion acc
607  const Vec3d& r = qInfo.relativePos;
608  const double d2 = r.squaredNorm();
609  if (d2 > 1.0e-20)
610  {
611  accel -= K_ij * m_modelParameters->m_particleMass * (r / std::sqrt(d2)) * m_kernels.cohesionW(r);
612  }
613 
614  // Curvature acc
615  const Vec3d& nj = surfaceNormals[q];
616  accel -= K_ij * (ni - nj);
617  }
618 
619  accel *= m_modelParameters->m_surfaceTensionStiffness;
620  //getState().getAccelerations()[p] += accel;
621  surfaceTensionAccels[p] = accel;
622  });
623 }
624 
625 void
626 SphModel::sumAccels()
627 {
628  const VecDataArray<double, 3>& pressureAccels = *m_pressureAccels;
629  const VecDataArray<double, 3>& surfaceTensionAccels = *m_surfaceTensionAccels;
630  const VecDataArray<double, 3>& viscousAccels = *m_viscousAccels;
631  VecDataArray<double, 3>& accels = *getCurrentState()->getAccelerations();
632 
633  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
634  [&](const size_t p)
635  {
636  if (m_sphBoundaryConditions
637  && (m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer
638  || m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Wall))
639  {
640  return;
641  }
642 
643  accels[p] = pressureAccels[p] + surfaceTensionAccels[p] + viscousAccels[p];
644  });
645 }
646 
647 void
648 SphModel::updateVelocity(const double timestep)
649 {
650  VecDataArray<double, 3>& halfStepVelocities = *getCurrentState()->getHalfStepVelocities();
651  VecDataArray<double, 3>& fullStepVelocities = *getCurrentState()->getFullStepVelocities();
652  const VecDataArray<double, 3>& positions = *getCurrentState()->getPositions();
653  const VecDataArray<double, 3>& accels = *getCurrentState()->getAccelerations();
654 
655  ParallelUtils::parallelFor(getCurrentState()->getNumParticles(),
656  [&](const size_t p)
657  {
658  if (m_sphBoundaryConditions
659  && (m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer
660  || m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Wall))
661  {
662  return;
663  }
664 
665  // todo - simply run SPH for half a time step to start to we don't need to perform this check at every time step
666  if (m_timeStepCount == 0)
667  {
668  halfStepVelocities[p] = fullStepVelocities[p] + (m_modelParameters->m_gravity + accels[p]) * timestep * 0.5;
669  fullStepVelocities[p] += (m_modelParameters->m_gravity + accels[p]) * timestep;
670  }
671  else
672  {
673  halfStepVelocities[p] += (m_modelParameters->m_gravity + accels[p]) * timestep;
674  fullStepVelocities[p] = halfStepVelocities[p] + (m_modelParameters->m_gravity + accels[p]) * timestep * 0.5;
675  }
676  if (m_sphBoundaryConditions && m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Inlet)
677  {
678  halfStepVelocities[p] = m_sphBoundaryConditions->computeParabolicInletVelocity(positions[p]);
679  fullStepVelocities[p] = m_sphBoundaryConditions->computeParabolicInletVelocity(positions[p]);
680  }
681  });
682 }
683 
684 void
685 SphModel::moveParticles(const double timestep)
686 {
687  //ParallelUtils::parallelFor(getState().getNumParticles(),
688  // [&](const size_t p) {
689 
690  VecDataArray<double, 3>& neighborVelContr = *m_neighborVelContr;
691  VecDataArray<double, 3>& particleShift = *m_particleShift;
692  VecDataArray<double, 3>& positions = *getCurrentState()->getPositions();
693  VecDataArray<double, 3>& halfStepVelocities = *getCurrentState()->getHalfStepVelocities();
694  VecDataArray<double, 3>& fullStepVelocities = *getCurrentState()->getFullStepVelocities();
695 
696  for (int p = 0; p < static_cast<int>(getCurrentState()->getNumParticles()); p++)
697  {
698  if (m_sphBoundaryConditions
699  && (m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer
700  || m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Wall))
701  {
702  continue;
703  }
704 
705  Vec3d oldPosition = positions[p];
706  Vec3d newPosition = oldPosition + particleShift[p] * timestep + (halfStepVelocities[p] + neighborVelContr[p]) * timestep;
707 
708  positions[p] = newPosition;
709 
710  if (m_sphBoundaryConditions)
711  {
712  std::vector<SphBoundaryConditions::ParticleType>& particleTypes = m_sphBoundaryConditions->getParticleTypes();
713  if (particleTypes[p] == SphBoundaryConditions::ParticleType::Inlet
714  && !m_sphBoundaryConditions->isInInletDomain(newPosition))
715  {
716  // change particle type to fluid
717  particleTypes[p] = SphBoundaryConditions::ParticleType::Fluid;
718  // insert particle into inlet domain from buffer domain
719  // todo: come up with a better way to find buffer indices
720  // right now, the buffer index is limiting the parallel ability of this function
721  const size_t bufferParticleIndex = m_sphBoundaryConditions->getBufferIndices().back();
722  m_sphBoundaryConditions->getBufferIndices().pop_back();
723  particleTypes[bufferParticleIndex] = SphBoundaryConditions::ParticleType::Inlet;
724 
725  positions[bufferParticleIndex] = m_sphBoundaryConditions->placeParticleAtInlet(oldPosition);
726  halfStepVelocities[bufferParticleIndex] = m_sphBoundaryConditions->computeParabolicInletVelocity(positions[bufferParticleIndex]);
727  fullStepVelocities[bufferParticleIndex] = m_sphBoundaryConditions->computeParabolicInletVelocity(positions[bufferParticleIndex]);
728  }
729  else if (particleTypes[p] == SphBoundaryConditions::ParticleType::Outlet
730  && !m_sphBoundaryConditions->isInOutletDomain(newPosition))
731  {
732  particleTypes[p] = SphBoundaryConditions::ParticleType::Buffer;
733  // insert particle into buffer domain after it leaves outlet domain
734  positions[p] = m_sphBoundaryConditions->getBufferCoord();
735  m_sphBoundaryConditions->getBufferIndices().push_back(p);
736  }
737  else if (particleTypes[p] == SphBoundaryConditions::ParticleType::Fluid
738  && m_sphBoundaryConditions->isInOutletDomain(newPosition))
739  {
740  particleTypes[p] = SphBoundaryConditions::ParticleType::Outlet;
741  }
742  else if (particleTypes[p] == SphBoundaryConditions::ParticleType::Fluid
743  && !m_sphBoundaryConditions->isInFluidDomain(newPosition))
744  {
745  particleTypes[p] = SphBoundaryConditions::ParticleType::Buffer;
746  positions[p] = m_sphBoundaryConditions->getBufferCoord();
747  m_sphBoundaryConditions->getBufferIndices().push_back(p);
748  }
749  }
750  }
751  m_timeStepCount++;
752 }
753 
754 double
755 SphModel::getParticlePressure(const double density)
756 {
757  const double d = density / m_modelParameters->m_restDensity;
758  const double d2 = d * d;
759  const double d4 = d2 * d2;
760  const double error = m_modelParameters->m_pressureStiffness * (d4 * d2 * d - 1.0);
761  // clamp pressure error to zero to maintain stability
762  return error > 0.0 ? error : 0.0;
763 }
764 
765 void
766 SphModel::setInitialVelocities(const size_t numParticles, const Vec3d& initialVelocity)
767 {
768  m_initialVelocities->clear();
769  m_initialVelocities->reserve(static_cast<int>(numParticles));
770  for (size_t p = 0; p < numParticles; p++)
771  {
772  if (m_sphBoundaryConditions
773  && (m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Buffer
774  || m_sphBoundaryConditions->getParticleTypes()[p] == SphBoundaryConditions::ParticleType::Wall))
775  {
776  m_initialVelocities->push_back(Vec3d::Zero());
777  }
778  else
779  {
780  m_initialVelocities->push_back(initialVelocity);
781  }
782  }
783 }
784 
785 void
786 SphModel::findNearestParticleToVertex(const VecDataArray<double, 3>& points, const std::vector<std::vector<size_t>>& indices)
787 {
788  const VecDataArray<double, 3>& positions = *getCurrentState()->getPositions();
789  for (size_t i = 0; i < static_cast<size_t>(points.size()); i++)
790  {
791  double minDistance = 1e10;
792  size_t minIndex = 0;
793  for (const size_t j : indices[i])
794  {
795  const Vec3d p1 = positions[j];
796  const double distance = (points[i] - p1).norm();
797  if (distance < minDistance)
798  {
799  minDistance = distance;
800  minIndex = j;
801  }
802  }
803  m_minIndices[i] = minIndex;
804  }
805 }
806 } // namespace imstk
Base class for all geometries represented by discrete points and elements The pointsets follow a pipe...
Definition: imstkPointSet.h:25
double m_particleMassScale
scale particle mass to a smaller value to maintain stability
Definition: imstkSphModel.h:47
Compound Geometry.
void resize(const int size) override
Resize data array to hold exactly size number of values.
void push_back(const ValueType &val)
Append the data array to hold the new value, resizes if neccesary.
DynamicalModelType
Type of the time dependent mathematical model.
void findNearestParticleToVertex(const VecDataArray< double, 3 > &points, const std::vector< std::vector< size_t >> &indices)
Write the state to external file.
double density
density of neighbor particle
Definition: imstkSphState.h:24
Vec3d relativePos
relative position
Definition: imstkSphState.h:23
int getNumVertices() const
Returns the number of total vertices in the mesh.
bool initialize() override
Initialize the dynamical model.
The helper struct to store relative positions and densities of neighbor particlcles.
Definition: imstkSphState.h:21
void initGraphEdges()
Initializes the edges of the graph.