chombo-discharge
CD_ParticleManagementImplem.H
Go to the documentation of this file.
1 /* chombo-discharge
2  * Copyright © 2022 SINTEF Energy Research.
3  * Please refer to Copyright.txt and LICENSE in the chombo-discharge root directory.
4  */
5 
12 #ifndef CD_ParticleManagementImplem_H
13 #define CD_ParticleManagementImplem_H
14 
15 // Std includes
16 #include <utility>
17 #include <type_traits>
18 
19 // Chombo includes
20 #include <CH_Timer.H>
21 
22 // Our includes
23 #include <CD_Random.H>
24 #include <CD_ParticleManagement.H>
25 #include <CD_NamespaceHeader.H>
26 
27 namespace ParticleManagement {
28 
29  template <class P, Real& (P::*weight)(), const RealVect& (P::*position)() const>
30  static inline void
31  partitionAndSplitEqualWeightKD(KDNode<P>& a_node, const BinaryParticleReconcile<P> a_particleReconcile) noexcept
32  {
33  CH_assert(!(a_node.isInteriorNode()));
34  CH_assert(a_node.weight() > 2.0 - std::numeric_limits<Real>::min());
35 
36  constexpr Real splitThresh = 2.0 - std::numeric_limits<Real>::min();
37 
38  // Particles and node weight.
39  typename KDNode<P>::ParticleList& particles = a_node.getParticles();
40 
41  const Real W = a_node.weight();
42 
43  // A. Figure out which coordinate direction we should partition and sort
44  // the particles.
45  RealVect loCorner = +std::numeric_limits<Real>::max() * RealVect::Unit;
46  RealVect hiCorner = -std::numeric_limits<Real>::max() * RealVect::Unit;
47 
48  for (auto& p : particles) {
49  const RealVect& pos = (p.*position)();
50  for (int dir = 0; dir < SpaceDim; dir++) {
51  loCorner[dir] = std::min(pos[dir], loCorner[dir]);
52  hiCorner[dir] = std::max(pos[dir], hiCorner[dir]);
53  }
54  }
55 
56  const int splitDir = (hiCorner - loCorner).maxDir(true);
57 
58  auto sortCrit = [splitDir](const P& p1, const P& p2) -> bool {
59  return (p1.*position)()[splitDir] < (p2.*position)()[splitDir];
60  };
61 
62  std::sort(particles.begin(), particles.end(), sortCrit);
63 
64  // B. Determine the "median particle" and start computing the weight in the
65  // two halves.
66  size_t id = 0;
67  Real wl = 0.0;
68  Real wr = W - (particles[id].*weight)();
69 
70  for (size_t i = 1; i < particles.size(); i++) {
71  const Real& w = (particles[id].*weight)();
72 
73  if (wl + w < wr) {
74  id = i;
75  wl += w;
76  wr = W - wl - (particles[id].*weight)();
77  }
78  else {
79  break;
80  }
81  }
82 
83  // C. Copy the two particle halves to each subnode.
84  P p = particles[id];
85 
86  typename KDNode<P>::ParticleList pl;
87  typename KDNode<P>::ParticleList pr;
88 
89  std::move(particles.begin(), particles.begin() + id, std::back_inserter(pl));
90  std::move(particles.begin() + id + 1, particles.end(), std::back_inserter(pr));
91 
92  const Real& pw = (p.*weight)();
93  const Real dw = wr - wl;
94 
95  CH_assert(wl + wr + pw == W);
96 
97  // D. Assign the median particle; split the particle if we can.
98  if (pw >= splitThresh && pw >= std::abs(dw)) {
99  Real dwl = dw;
100  Real dwr = 0.0;
101  Real ddw = pw - dw;
102 
103  const long long N = (long long)ddw;
104 
105  if (N > 0LL) {
106 
107  const long long Nr = N / 2;
108  const long long Nl = N - Nr;
109 
110  dwl += (ddw / N) * Nl;
111  dwr += (ddw / N) * Nr;
112  }
113 
114  if (dwl > 0.0 && dwr > 0.0) {
115  // Splitting particle.
116 
117  P il(p);
118  P ir(p);
119 
120  CH_assert(dwl >= 1.0);
121  CH_assert(dwr >= 1.0);
122 
123  wl += dwl;
124  wr += dwr;
125 
126  (il.*weight)() = dwl;
127  (ir.*weight)() = dwr;
128 
129  // User can reconcile other particle properties.
130  a_particleReconcile(il, ir, p);
131 
132  pl.emplace_back(std::move(il));
133  pr.emplace_back(std::move(ir));
134  }
135  else if (dwl > 0.0 && dwr == 0.0) {
136  // Particle assigned to left node.
137 
138  P il(p);
139 
140  CH_assert(dwl >= 1.0);
141 
142  wl += dwl;
143  (il.*weight)() = dwl;
144  pl.emplace_back(std::move(il));
145  }
146  else if (dwl == 0.0 && dwr > 0.0) {
147  // Particle assigned to right node.
148 
149  P ir(p);
150 
151  CH_assert(dwr >= 1.0);
152 
153  wr += dwr;
154  (ir.*weight)() = dwr;
155  pr.emplace_back(std::move(ir));
156  }
157  else {
158  // Should not happen.
159 
160  MayDay::Abort("ParticleManagement::partitionAndSplitEqualWeightKD - logic bust");
161  }
162  }
163  else {
164  if (wl <= wr) {
165  wl += pw;
166  pl.emplace_back(std::move(p));
167  }
168  else {
169  wr += pw;
170  pr.emplace_back(std::move(p));
171  }
172  }
173 
174  // E. If this breaks, weight is not conserved or we broke the median particle splitting; the weight difference
175  // between the left/right node should be at most one physical particle.
176  CH_assert(wl + wr == W);
177  CH_assert(std::abs(wl - wr) <= 1.0);
178 
179  // F. Instantiate the child nodes.
180  particles.resize(0);
181 
182  a_node.getLeft() = std::make_shared<KDNode<P>>(pl);
183  a_node.getRight() = std::make_shared<KDNode<P>>(pr);
184 
185  a_node.getLeft()->weight() = wl;
186  a_node.getRight()->weight() = wr;
187 
188  // G. Debug code; make sure particle weights make sense
189 #ifndef NDEBUG
190  Real WL = 0.0;
191  Real WR = 0.0;
192 
193  // Note: Not auto& l/r : pl/pr because the particles were into the child node.
194  for (auto& l : a_node.getLeft()->getParticles()) {
195  CH_assert((l.*weight)() >= 1.0);
196 
197  WL += (l.*weight)();
198  }
199 
200  for (auto& r : a_node.getRight()->getParticles()) {
201  CH_assert((r.*weight)() >= 1.0);
202 
203  WR += (r.*weight)();
204  }
205 
206  CH_assert(WL == wl);
207  CH_assert(WR == wr);
208 #endif
209  }
210 
211  template <class P, Real& (P::*weight)(), const RealVect& (P::*position)() const>
212  inline std::vector<std::shared_ptr<KDNode<P>>>
213  recursivePartitionAndSplitEqualWeightKD(typename KDNode<P>::ParticleList& a_inputParticles,
214  const int a_maxLeaves,
215  const BinaryParticleReconcile<P> a_particleReconcile) noexcept
216  {
217  CH_TIME("ParticleManagement::recursivePartitionAndSplitEqualWeightKD");
218 
219  Real W = 0.0;
220 
221  for (auto& p : a_inputParticles) {
222  W += (p.*weight)();
223  }
224 
225  std::vector<std::shared_ptr<KDNode<P>>> leaves;
226 
227  leaves.emplace_back(std::make_shared<KDNode<P>>(a_inputParticles));
228  leaves[0]->weight() = W;
229 
230  bool keepGoing = true;
231 
232  while (keepGoing && leaves.size() < a_maxLeaves) {
233  keepGoing = false;
234 
235  std::vector<std::shared_ptr<KDNode<P>>> newLeaves;
236 
237  for (const auto& l : leaves) {
238  if (l->weight() > 2.0 - std::numeric_limits<Real>::min()) {
239  ParticleManagement::partitionAndSplitEqualWeightKD<P, weight, position>(*l, a_particleReconcile);
240 
241  newLeaves.emplace_back(l->getLeft());
242  newLeaves.emplace_back(l->getRight());
243 
244  keepGoing = true;
245  }
246  else {
247  newLeaves.emplace_back(l);
248  }
249 
250  // Break out if we have sufficient leaf nodes.
251  if (newLeaves.size() >= a_maxLeaves) {
252  break;
253  }
254  }
255 
256  leaves = newLeaves;
257  }
258 
259  return leaves;
260  }
261 
262  template <typename P, typename T, typename>
263  inline void
264  removePhysicalParticles(List<P>& a_particles, const T a_numPhysPartToRemove) noexcept
265  {
266  CH_TIME("ParticleManagement::removePhysicalParticles");
267 
268  constexpr T zero = (T)0;
269 
270  // Obviously an error that the user should catch.
271  if (a_numPhysPartToRemove < zero) {
272  MayDay::Error("ParticleManagement::removePhysicalParticles - 'a_numPhysPartoToRemove < 0'");
273  }
274 
275  if (a_particles.length() > 0) {
276  ListIterator<P> lit(a_particles);
277 
278  T numRemoved = zero;
279 
280  // 1. Compute the minimum particle weight.
281  T minWeight = std::numeric_limits<T>::max();
282  for (lit.begin(); lit.ok(); ++lit) {
283  minWeight = std::min(minWeight, (T)lit().weight());
284  }
285 
286  // 2. Trim particle weights down to minWeight.
287  for (lit.begin(); lit.ok(); ++lit) {
288  const T diff1 = (T)lit().weight() - minWeight;
289  const T diff2 = a_numPhysPartToRemove - numRemoved;
290 
291  CH_assert(diff1 >= zero);
292  CH_assert(diff2 >= zero);
293 
294  const T r = std::max(0LL, std::min(diff1, diff2));
295 
296  lit().weight() -= 1.0 * r;
297  numRemoved += r;
298  }
299 
300  // 3. "Uniformly" subtract the particle weights.
301  if (a_numPhysPartToRemove - numRemoved > zero) {
302  const T numCompParticles = (T)a_particles.length();
303  const T uniformWeight = (a_numPhysPartToRemove - numRemoved) / numCompParticles;
304  const T uniformRemainder = (a_numPhysPartToRemove - numRemoved) % numCompParticles;
305 
306  // Uniformly remove weight from each particle.
307  if (uniformWeight > zero) {
308  for (lit.begin(); lit.ok(); ++lit) {
309  lit().weight() -= 1.0 * uniformWeight;
310 
311  numRemoved += uniformWeight;
312  }
313  }
314 
315  // May have to remove remainder from multiple particles because their weights might be less
316  // then the actual remainder.
317  if (uniformRemainder > zero) {
318  T W = 0;
319 
320  for (lit.begin(); lit.ok(); ++lit) {
321 
322  // Never remove so that weight is negative.
323  const T w = std::min((T)lit().weight(), uniformRemainder - W);
324 
325  lit().weight() -= 1.0 * w;
326 
327  W += w;
328  numRemoved += w;
329 
330  if (W == uniformRemainder) {
331  break;
332  }
333  }
334  }
335  }
336 
337  // Debug code.
338  CH_assert(numRemoved == a_numPhysPartToRemove);
339 #ifdef NDEBUG
340  for (lit.begin(); lit.ok(); ++lit) {
341  CH_assert(lit().weight() >= 0.0);
342  }
343 #endif
344  }
345  }
346 
347  template <typename P>
348  inline void
349  deleteParticles(List<P>& a_particles, const Real a_weightThresh) noexcept
350  {
351  CH_TIME("ParticleManagement::deleteParticles");
352 
353  for (ListIterator<P> lit(a_particles); lit.ok();) {
354  if (lit().weight() < a_weightThresh) {
355  a_particles.remove(lit);
356  }
357  else {
358  ++lit;
359  }
360  }
361  }
362 
363  template <typename T, typename>
364  inline std::vector<T>
365  partitionParticleWeights(const T a_numPhysicalParticles, const T a_maxCompParticles) noexcept
366  {
367  std::vector<T> ret(0);
368 
369  constexpr T zero = (T)0;
370  constexpr T one = (T)1;
371 
372  if (a_maxCompParticles > zero) {
373  if (a_numPhysicalParticles <= a_maxCompParticles) {
374  ret.resize(a_numPhysicalParticles, one);
375  }
376  else {
377  const T W = a_numPhysicalParticles / a_maxCompParticles;
378  T r = a_numPhysicalParticles % a_maxCompParticles;
379 
380  if (W > zero) {
381  ret.resize(a_maxCompParticles, W);
382 
383  for (int i = 0; i < ret.size() && r > zero; i++) {
384  ret[i] += one;
385  r--;
386  }
387  }
388  else {
389  ret.resize(1, r);
390  }
391  }
392  }
393 
394  return ret;
395  }
396 
397  template <typename T, typename>
398  inline T
399  partitionParticles(const T a_numParticles)
400  {
401 #ifdef CH_MPI
402  const T quotient = a_numParticles / numProc();
403  const T remainder = a_numParticles % numProc();
404 
405  Vector<T> particlesPerRank(numProc(), quotient);
406 
407  for (int i = 0; i < remainder; i++) {
408  particlesPerRank[i]++;
409  }
410 
411  return particlesPerRank[procID()];
412 #else
413  return a_numParticles;
414 #endif
415  }
416 
417  template <typename P, typename T, typename>
418  inline void
419  drawRandomParticles(List<P>& a_particles, const T a_numParticles, const std::function<RealVect()>& a_distribution)
420  {
421 
422  a_particles.clear();
423 
424  const T numParticles = partitionParticles(a_numParticles);
425 
426  for (T t = 0; t < numParticles; t++) {
427  P p;
428  p.position() = a_distribution();
429 
430  a_particles.add(p);
431  }
432  }
433 
434  template <typename P, typename T, typename>
435  inline void
436  drawSphereParticles(List<P>& a_particles,
437  const T a_numParticles,
438  const RealVect a_center,
439  const Real a_radius) noexcept
440  {
441  CH_TIME("ParticleManagement::drawSphereParticles");
442 
443  a_particles.clear();
444 
445  const T numParticles = partitionParticles(a_numParticles);
446 
447  for (T t = 0; t < numParticles; t++) {
448  P p;
449 
450  RealVect& x = p.position();
451 
452  x = std::numeric_limits<Real>::max() * RealVect::Unit;
453 
454  while (x.vectorLength() > a_radius) {
455  for (int d = 0; d < SpaceDim; d++) {
456  x[d] = a_radius * Random::getUniformReal11();
457  }
458  }
459 
460  x += a_center;
461 
462  a_particles.add(p);
463  }
464  }
465 
466  template <typename P, typename T, typename>
467  inline void
468  drawBoxParticles(List<P>& a_particles,
469  const T a_numParticles,
470  const RealVect a_loCorner,
471  const RealVect a_hiCorner) noexcept
472  {
473  CH_TIME("ParticleManagement::drawBoxParticles");
474 
475  CH_assert(a_hiCorner >= a_loCorner);
476 
477  auto ranBox = [&]() -> RealVect {
478  return RealVect(D_DECL(a_loCorner[0] + (a_hiCorner[0] - a_loCorner[0]) * Random::getUniformReal01(),
479  a_loCorner[1] + (a_hiCorner[1] - a_loCorner[1]) * Random::getUniformReal01(),
480  a_loCorner[2] + (a_hiCorner[2] - a_loCorner[2]) * Random::getUniformReal01()));
481  };
482 
483  drawRandomParticles(a_particles, a_numParticles, ranBox);
484  }
485 
486  template <typename P, typename T, typename>
487  inline void
488  drawGaussianParticles(List<P>& a_particles,
489  const T a_numParticles,
490  const RealVect a_center,
491  const Real a_radius) noexcept
492  {
493  CH_TIME("ParticleManagement::drawGaussianParticles");
494 
495  std::normal_distribution<Real> gauss(0.0, a_radius);
496 
497  auto ranGauss = [&]() -> RealVect {
498  return a_center + Random::get(gauss) * Random::getDirection();
499  };
500 
501  drawRandomParticles(a_particles, a_numParticles, ranGauss);
502  }
503 } // namespace ParticleManagement
504 
505 #include <CD_NamespaceFooter.H>
506 
507 #endif
Namespace containing various particle management utilities.
File containing some useful static methods related to random number generation.
Node in a particle-merging KD-tree.
Definition: CD_KDNode.H:33
std::vector< P > ParticleList
List of particles. This is aliased because the KD-tree construction may require both random access an...
Definition: CD_KDNode.H:39
static Real get(T &a_distribution)
For getting a random number from a user-supplied distribution. T must be a distribution for which we ...
Definition: CD_RandomImplem.H:208
static RealVect getDirection()
Get a random direction in space.
Definition: CD_RandomImplem.H:171
static Real getUniformReal11()
Get a uniform real number on the interval [-1,1].
Definition: CD_RandomImplem.H:155
static Real getUniformReal01()
Get a uniform real number on the interval [0,1].
Definition: CD_RandomImplem.H:147
RealVect position(const Location::Cell a_location, const VolIndex &a_vof, const EBISBox &a_ebisbox, const Real &a_dx)
Compute the position (ignoring the "origin) of a Vof.
Definition: CD_LocationImplem.H:20
Real max(const Real &a_input) noexcept
Get the maximum of the input, reduced over MPI ranks (in the Chombo communicator)
Definition: CD_ParallelOpsImplem.H:176
Real min(const Real &a_input) noexcept
Get the minimum of the input, reduced over MPI ranks (in the Chombo communicator)
Definition: CD_ParallelOpsImplem.H:58
Namespace for various particle management tools.
Definition: CD_ParticleManagement.H:31
std::function< void(P &p1, P &p2, const P &p0)> BinaryParticleReconcile
Declaration of a reconciliation function when splitting particles.
Definition: CD_ParticleManagement.H:50