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