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 <algorithm>
18#include <array>
19#include <cstdint>
20#include <functional>
21#include <limits>
22#include <utility>
23#include <type_traits>
24
25// Chombo includes
26#include <CH_Timer.H>
27
28// Our includes
29#include <CD_Random.H>
30#include <CD_ParticleLoops.H>
31#include <CD_LoadBalancing.H>
33#include <CD_NamespaceHeader.H>
34
35namespace ParticleManagement {
36
38mergeMethodFromString(const std::string& a_str) noexcept
39{
40 if (a_str == "none") {
42 }
43 else if (a_str == "equal_weight_kd") {
45 }
46 else if (a_str == "reinitialize") {
48 }
49 else if (a_str == "reinitialize_bvh") {
51 }
52 else if (a_str == "nn_sfc") {
54 }
55 else if (a_str == "nn_pair_tree") {
57 }
58 else if (a_str == "nn_pair_onecell") {
60 }
61 else if (a_str == "nn_pair_hash") {
63 }
64 else if (a_str == "kd_carve") {
66 }
67 else if (a_str == "kd_patch") {
69 }
70 else if (a_str == "kd_skin_nn") {
72 }
73 else if (a_str == "external") {
75 }
76 else {
77 MayDay::Abort(("ParticleManagement::mergeMethodFromString - unknown merge method '" + a_str + "'").c_str());
78
80 }
81}
82
83namespace detail {
84
85template <class P, Real P::*weight, RealVect P::*position>
86inline void
87buildEqualWeightKDLeaves(const std::vector<P>& a_particles,
88 const int a_maxLeaves,
89 const BinaryParticleReconcile<P>& a_particleReconcile,
90 std::vector<std::pair<const P*, const P*>>& a_leaves) noexcept
91{
92 CH_TIME("ParticleManagement::buildEqualWeightKDLeaves");
93
94 // Half-open particle range [lo, hi) into the flat working buffer, plus the node weight. This replaces
95 // the old heap-allocated, shared_ptr-linked KDNode<P> (one alloc + atomic refcount per node).
96 struct NodeRange
97 {
98 std::size_t lo;
99 std::size_t hi;
100 Real w;
101 };
102
103 // Reusable per-thread scratch: capacity is retained across calls (cleared, never freed), so after the
104 // first cell the merge does no heap allocation. The two particle buffers are ping-ponged level by level,
105 // so each node's particles are always a contiguous span -- no per-node particle list.
106 thread_local std::vector<P> s_cur;
107 thread_local std::vector<P> s_nxt;
108 thread_local std::vector<NodeRange> s_curNodes;
109 thread_local std::vector<NodeRange> s_nxtNodes;
110
111 a_leaves.clear();
112
113 const std::size_t numInput = a_particles.size();
114 if (numInput == 0 || a_maxLeaves <= 0) {
115 return;
116 }
117
118 constexpr Real splitThresh = 2.0 - std::numeric_limits<Real>::min();
119 const std::size_t maxLeaves = static_cast<std::size_t>(a_maxLeaves);
120
121 // Seed the root level. Reserve room for split products: the median split adds at most one particle per
122 // internal node, i.e. fewer than maxLeaves extra particles over the whole build.
123 s_cur.clear();
124 s_cur.reserve(numInput + maxLeaves);
125 s_cur.insert(s_cur.end(), a_particles.begin(), a_particles.end());
126
127 // Total weight, read from the working copy.
128 Real W = 0.0;
129 for (const P& p : s_cur) {
130 W += p.*weight;
131 }
132
133 s_curNodes.clear();
134 s_curNodes.push_back({static_cast<std::size_t>(0), numInput, W});
135
136 // Breadth-first: split every splittable leaf of the current level, streaming the children contiguously
137 // into the next buffer, until we reach maxLeaves leaves or nothing can be split further.
138 bool keepGoing = true;
139 while (keepGoing && s_curNodes.size() < maxLeaves) {
140 keepGoing = false;
141
142 s_nxt.clear();
143 s_nxtNodes.clear();
144
145 // Each split turns one leaf into two (+1 leaf), so this level may split at most this many nodes
146 // before reaching maxLeaves. Nodes beyond the budget are carried forward unchanged -- they must NOT
147 // be dropped (doing so would discard their particles and break weight conservation).
148 std::size_t splitBudget = maxLeaves - s_curNodes.size();
149
150 for (std::size_t ni = 0; ni < s_curNodes.size(); ni++) {
151 const NodeRange node = s_curNodes[ni];
152
153 if (node.w > splitThresh && splitBudget > 0) {
154 splitBudget--;
155
156 // ---- split this node (equal-weight median split) into s_nxt ----
157 P* const beg = s_cur.data() + node.lo;
158 P* const end = s_cur.data() + node.hi;
159 const std::size_t n = node.hi - node.lo;
160 const Real Wn = node.w;
161
162 // A. Split along the longest bounding-box extent.
163 RealVect loCorner = +std::numeric_limits<Real>::max() * RealVect::Unit;
164 RealVect hiCorner = -std::numeric_limits<Real>::max() * RealVect::Unit;
165
166 for (P* p = beg; p != end; ++p) {
167 const RealVect& pos = (*p).*position;
168
169 for (int dir = 0; dir < SpaceDim; dir++) {
170 loCorner[dir] = std::min(pos[dir], loCorner[dir]);
171 hiCorner[dir] = std::max(pos[dir], hiCorner[dir]);
172 }
173 }
174
175 const int splitDir = (hiCorner - loCorner).maxDir(true);
176
177 std::sort(beg, end, [splitDir](const P& p1, const P& p2) -> bool {
178 return (p1.*position)[splitDir] < (p2.*position)[splitDir];
179 });
180
181 // B. Weight-balanced median particle.
182 std::size_t id = 0;
183 Real wl = 0.0;
184 Real wr = Wn - beg[0].*weight;
185
186 for (std::size_t i = 1; i < n; i++) {
187 const Real& w = beg[id].*weight;
188 if (wl + w < wr) {
189 id = i;
190 wl += w;
191 wr = Wn - wl - beg[id].*weight;
192 }
193 else {
194 break;
195 }
196 }
197
198 const P med = beg[id];
199 const Real pw = med.*weight;
200 const Real dw = wr - wl;
201
202 CH_assert(wl + wr + pw == Wn);
203
204 // C. Decide how the median is distributed; produce at most one extra particle per side.
205 bool hasExtraL = false;
206 bool hasExtraR = false;
207 P extraL;
208 P extraR;
209
210 if (pw >= splitThresh && pw >= std::abs(dw)) {
211 Real dwl = dw;
212 Real dwr = 0.0;
213
214 const Real ddw = pw - dw;
215 const long long Nsplit = (long long)ddw;
216
217 if (Nsplit > 0LL) {
218 const long long Nr = Nsplit / 2;
219 const long long Nl = Nsplit - Nr;
220
221 dwl += (ddw / Nsplit) * Nl;
222 dwr += (ddw / Nsplit) * Nr;
223 }
224
225 if (dwl > 0.0 && dwr > 0.0) {
226 // Split the median particle across both children.
227 extraL = med;
228 extraR = med;
229
230 CH_assert(dwl >= 1.0);
231 CH_assert(dwr >= 1.0);
232
233 wl += dwl;
234 wr += dwr;
235
236 extraL.*weight = dwl;
237 extraR.*weight = dwr;
238
239 a_particleReconcile(extraL, extraR, med);
240
241 hasExtraL = true;
242 hasExtraR = true;
243 }
244 else if (dwl > 0.0 && dwr == 0.0) {
245 extraL = med;
246 CH_assert(dwl >= 1.0);
247 wl += dwl;
248 extraL.*weight = dwl;
249 hasExtraL = true;
250 }
251 else if (dwl == 0.0 && dwr > 0.0) {
252 extraR = med;
253 CH_assert(dwr >= 1.0);
254 wr += dwr;
255 extraR.*weight = dwr;
256 hasExtraR = true;
257 }
258 else {
259 MayDay::Abort("ParticleManagement::buildEqualWeightKDLeaves - logic bust");
260 }
261 }
262 else {
263 // Median assigned whole to the lighter side (weight unchanged).
264 if (wl <= wr) {
265 wl = wl + pw;
266 extraL = med;
267 hasExtraL = true;
268 }
269 else {
270 wr = wr + pw;
271 extraR = med;
272 hasExtraR = true;
273 }
274 }
275
276 CH_assert(std::abs(wl + wr - Wn) <= Wn * std::numeric_limits<Real>::epsilon() * 16);
277 CH_assert(std::abs(wl - wr) <= 1.0);
278
279 // D. Stream the two children contiguously into s_nxt. Particle order within each child matches the
280 // old implementation (base half first, then the median's piece), so the leaf reductions are
281 // bit-for-bit identical. NOTE: reading from s_cur (stable) and writing to s_nxt (separate buffer).
282 const std::size_t leftLo = s_nxt.size();
283 s_nxt.insert(s_nxt.end(), beg, beg + id);
284 if (hasExtraL) {
285 s_nxt.push_back(std::move(extraL));
286 }
287 s_nxtNodes.push_back({leftLo, s_nxt.size(), wl});
288
289 const std::size_t rightLo = s_nxt.size();
290 s_nxt.insert(s_nxt.end(), beg + id + 1, end);
291 if (hasExtraR) {
292 s_nxt.push_back(std::move(extraR));
293 }
294 s_nxtNodes.push_back({rightLo, s_nxt.size(), wr});
295
296 keepGoing = true;
297 }
298 else {
299 // Cannot split further -- carry this leaf forward unchanged.
300 const std::size_t lo = s_nxt.size();
301 s_nxt.insert(s_nxt.end(), s_cur.begin() + node.lo, s_cur.begin() + node.hi);
302 s_nxtNodes.push_back({lo, s_nxt.size(), node.w});
303 }
304 }
305
306 s_cur.swap(s_nxt);
307 s_curNodes.swap(s_nxtNodes);
308 }
309
310 // Emit the leaves as contiguous ranges into the (now stable) working buffer.
311 a_leaves.reserve(s_curNodes.size());
312 for (const NodeRange& r : s_curNodes) {
313 const P* const base = s_cur.data() + r.lo;
314 a_leaves.emplace_back(base, base + (r.hi - r.lo));
315 }
316}
317
318template <class P, RealVect P::*position>
319inline void
320mergeAdjacentNearest(std::vector<P>& a_particles,
321 const std::size_t a_target,
322 const std::function<void(P&, const P&)>& a_combine) noexcept
323{
324 CH_TIME("ParticleManagement::mergeAdjacentNearest");
325
326 const std::size_t n = a_particles.size();
327 if (a_target == 0 || n <= a_target) {
328 return;
329 }
330
331 auto dist2 = [&a_particles](const std::size_t i, const std::size_t j) -> Real {
332 const RealVect& pi = a_particles[i].*position;
333 const RealVect& pj = a_particles[j].*position;
334
335 Real s = 0.0;
336
337 for (int d = 0; d < SpaceDim; d++) {
338 const Real dd = pi[d] - pj[d];
339
340 s += dd * dd;
341 }
342
343 return s;
344 };
345
346 // Index-based doubly-linked chain over the (spatially-ordered) list + liveness + version tags. No
347 // pointers and no per-call allocation after warmup.
348 thread_local std::vector<int> prev, next;
349 thread_local std::vector<uint8_t> alive;
350 thread_local std::vector<uint32_t> ver;
351
352 prev.assign(n, -1);
353 next.assign(n, -1);
354 alive.assign(n, 1);
355 ver.assign(n, 0);
356
357 for (std::size_t i = 0; i < n; i++) {
358 prev[i] = (i == 0) ? -1 : static_cast<int>(i - 1);
359 next[i] = (i + 1 == n) ? -1 : static_cast<int>(i + 1);
360 }
361
362 struct Edge
363 {
364 Real d;
365 int a;
366 int b;
367 uint32_t va;
368 uint32_t vb;
369 };
370
371 const auto cmp = [](const Edge& x, const Edge& y) {
372 return x.d > y.d;
373 }; // min-heap
374
375 thread_local std::vector<Edge> heap;
376
377 heap.clear();
378
379 for (std::size_t i = 0; i + 1 < n; i++) {
380 heap.push_back({dist2(i, i + 1), static_cast<int>(i), static_cast<int>(i + 1), 0, 0});
381 }
382
383 std::make_heap(heap.begin(), heap.end(), cmp);
384
385 std::size_t count = n;
386 while (count > a_target && !heap.empty()) {
387 std::pop_heap(heap.begin(), heap.end(), cmp);
388 const Edge edge = heap.back();
389 heap.pop_back();
390
391 const int a = edge.a;
392 const int b = edge.b;
393
394 if (!alive[a] || !alive[b] || ver[a] != edge.va || ver[b] != edge.vb || next[a] != b) {
395 continue; // stale entry
396 }
397
398 // Combine b into a; a's centroid (and one neighbour) changes, so bump a's version.
399 a_combine(a_particles[a], a_particles[b]);
400 alive[b] = 0;
401 const int nb = next[b];
402 next[a] = nb;
403 if (nb >= 0) {
404 prev[nb] = a;
405 }
406
407 count--;
408 ver[a]++;
409
410 if (prev[a] >= 0) {
411 heap.push_back(
412 {dist2(static_cast<std::size_t>(prev[a]), static_cast<std::size_t>(a)), prev[a], a, ver[prev[a]], ver[a]});
413 std::push_heap(heap.begin(), heap.end(), cmp);
414 }
415 if (next[a] >= 0) {
416 heap.push_back(
417 {dist2(static_cast<std::size_t>(a), static_cast<std::size_t>(next[a])), a, next[a], ver[a], ver[next[a]]});
418 std::push_heap(heap.begin(), heap.end(), cmp);
419 }
420 }
421
422 // Compact the survivors back into a_particles (preserving chain order).
423 thread_local std::vector<P> survivors;
424
425 survivors.clear();
426 survivors.reserve(count);
427
428 for (std::size_t i = 0; i < n; i++) {
429 if (alive[i]) {
430 survivors.push_back(a_particles[i]);
431 }
432 }
433
434 a_particles.swap(survivors);
435}
436
437} // namespace detail
438
439template <class Packed, Real Packed::*packWeight, RealVect Packed::*packPosition, class P, class Traits>
440inline ParticleMerger<P, Traits>
441makeSfcNearestNeighborMerger(std::function<Packed(const ParticleSoA<P, Traits>&, std::size_t)> a_gather,
442 std::function<void(Packed&, const Packed&)> a_combine,
443 std::function<void(ParticleSoA<P, Traits>&, const Packed&)> a_scatter) noexcept
444{
445 return [=](ParticleSoA<P, Traits>& a_particles, const CellInfo& /*a_cellInfo*/, int a_ppc) noexcept {
446 CH_TIMERS("ParticleManagement::makeSfcNearestNeighborMerger");
447 CH_TIMER("ParticleManagement::makeSfcNearestNeighborMerger::populate", t1);
448 CH_TIMER("ParticleManagement::makeSfcNearestNeighborMerger::sort_merge", t2);
449 CH_TIMER("ParticleManagement::makeSfcNearestNeighborMerger::emit", t3);
450
451 CH_START(t1);
452 thread_local std::vector<Packed> particles;
453
454 particles.clear();
455 particles.reserve(a_particles.size());
456
457 Real W = 0.0;
458
459 for (std::size_t i = 0; i < a_particles.size(); i++) {
460 Packed p = a_gather(a_particles, i);
461 W += p.*packWeight;
462 particles.emplace_back(std::move(p));
463 }
464 CH_STOP(t1);
465
466 if (W < 2.0 || a_ppc <= 0) {
467 return;
468 }
469
470 const std::size_t target = static_cast<std::size_t>(a_ppc);
471
472 CH_START(t2);
473 if (particles.size() > target) {
474 RealVect loCorner = +std::numeric_limits<Real>::max() * RealVect::Unit;
475 RealVect hiCorner = -std::numeric_limits<Real>::max() * RealVect::Unit;
476
477 for (const Packed& p : particles) {
478 const RealVect& x = p.*packPosition;
479
480 for (int dir = 0; dir < SpaceDim; dir++) {
481 loCorner[dir] = std::min(loCorner[dir], x[dir]);
482 hiCorner[dir] = std::max(hiCorner[dir], x[dir]);
483 }
484 }
485
486 RealVect invExtent = RealVect::Zero;
487 for (int dir = 0; dir < SpaceDim; dir++) {
488 invExtent[dir] = (hiCorner[dir] > loCorner[dir]) ? 1.0 / (hiCorner[dir] - loCorner[dir]) : 0.0;
489 }
490
491 constexpr uint32_t maxCoord = (1U << 21) - 1U;
492 thread_local std::vector<std::pair<uint64_t, std::size_t>> keyed;
493
494 keyed.clear();
495 keyed.reserve(particles.size());
496
497 for (std::size_t i = 0; i < particles.size(); i++) {
498 const RealVect& x = particles[i].*packPosition;
499
500 std::array<uint32_t, SpaceDim> coords;
501
502 for (int dir = 0; dir < SpaceDim; dir++) {
503 Real t = (x[dir] - loCorner[dir]) * invExtent[dir];
504 t = std::max(0.0, std::min(1.0, t));
505 coords[dir] = static_cast<uint32_t>(t * maxCoord);
506 }
507
508 keyed.emplace_back(LoadBalancing::hilbertIndex<SpaceDim>(coords), i);
509 }
510
511 std::sort(keyed.begin(), keyed.end(), [](const auto& a, const auto& b) {
512 return a.first < b.first;
513 });
514
515 thread_local std::vector<Packed> sorted;
516
517 sorted.clear();
518 sorted.reserve(particles.size());
519
520 for (const auto& k : keyed) {
521 sorted.push_back(particles[k.second]);
522 }
523 particles.swap(sorted);
524
525 detail::mergeAdjacentNearest<Packed, packPosition>(particles, target, a_combine);
526 }
527 else if (particles.size() < target) {
528 while (particles.size() < target) {
529 std::size_t hi = 0;
530
531 for (std::size_t i = 1; i < particles.size(); i++) {
532 if (particles[i].*packWeight > particles[hi].*packWeight) {
533 hi = i;
534 }
535 }
536
537 if (particles[hi].*packWeight < 2.0) {
538 break;
539 }
540
541 // Use floor/ceil rather than *= 0.5 so that integer weights remain integers after
542 // the split. weight * 0.5 on an odd integer (e.g. 3 -> 1.5) produces a fractional
543 // weight that later breaks removePhysicalParticles, which assumes integer weights.
544 const Real hw = std::floor(particles[hi].*packWeight * 0.5);
545 Packed half = particles[hi];
546 particles[hi].*packWeight -= hw;
547 half.*packWeight = hw;
548 particles.push_back(half);
549 }
550 }
551 CH_STOP(t2);
552
553 CH_START(t3);
554 a_particles.clear();
555 for (const Packed& p : particles) {
556 a_scatter(a_particles, p);
557 }
558 CH_STOP(t3);
559 };
560}
561
562template <class Packed, Real Packed::*packWeight, RealVect Packed::*packPosition, class P, class Traits>
563inline ParticleMerger<P, Traits>
565 std::function<Packed(const ParticleSoA<P, Traits>&, std::size_t)> a_gather,
567 std::function<void(ParticleSoA<P, Traits>&, const Packed*, const Packed*, const CellInfo&)> a_scatterLeaf) noexcept
568{
569 return [=](ParticleSoA<P, Traits>& a_particles, const CellInfo& a_cellInfo, int a_ppc) noexcept {
570 CH_TIMERS("ParticleManagement::makeEqualWeightKDMerger");
571 CH_TIMER("ParticleManagement::makeEqualWeightKDMerger::populate", t1);
572 CH_TIMER("ParticleManagement::makeEqualWeightKDMerger::build_kd", t2);
573 CH_TIMER("ParticleManagement::makeEqualWeightKDMerger::scatter", t3);
574
575 CH_START(t1);
576 thread_local std::vector<Packed> particles;
577
578 particles.clear();
579 particles.reserve(a_particles.size());
580
581 Real W = 0.0;
582
583 for (std::size_t i = 0; i < a_particles.size(); i++) {
584 Packed p = a_gather(a_particles, i);
585 W += p.*packWeight;
586 particles.emplace_back(std::move(p));
587 }
588 CH_STOP(t1);
589
590 if (W < 2.0 || a_ppc <= 0) {
591 return;
592 }
593
594 CH_START(t2);
595 thread_local std::vector<std::pair<const Packed*, const Packed*>> leaves;
596
597 detail::buildEqualWeightKDLeaves<Packed, packWeight, packPosition>(particles, a_ppc, a_reconcile, leaves);
598 CH_STOP(t2);
599
600 CH_START(t3);
601 a_particles.clear();
602 for (const auto& leaf : leaves) {
603 a_scatterLeaf(a_particles, leaf.first, leaf.second, a_cellInfo);
604 }
605 CH_STOP(t3);
606 };
607}
608
609template <class Context, class P, class Traits>
610inline ParticleMerger<P, Traits>
611makeReinitializeMerger(std::function<std::pair<long long, Context>(const ParticleSoA<P, Traits>&)> a_aggregate,
612 std::function<void(ParticleSoA<P, Traits>&, const RealVect&, long long, const Context&)> a_emit,
613 std::function<RealVect()> a_probLo) noexcept
614{
615 return [=](ParticleSoA<P, Traits>& a_particles, const CellInfo& a_cellInfo, int a_ppc) noexcept {
616 CH_TIME("ParticleManagement::makeReinitializeMerger");
617
618 if (a_ppc <= 0) {
619 return;
620 }
621
622 const auto [numPhysical, context] = a_aggregate(a_particles);
623
624 if (numPhysical <= 0LL) {
625 return;
626 }
627
628 const std::vector<long long> weights = partitionParticleWeights(numPhysical, (long long)a_ppc);
629
630 const Real dx = a_cellInfo.getDx();
631 const Real kappa = a_cellInfo.getVolFrac();
632 const RealVect cellPos = a_probLo() + dx * (a_cellInfo.getGridIndex() + 0.5 * RealVect::Unit);
633 const RealVect& validLo = a_cellInfo.getValidLo();
634 const RealVect& validHi = a_cellInfo.getValidHi();
635 const RealVect& bndryCentroid = a_cellInfo.getBndryCentroid();
636 const RealVect& bndryNormal = a_cellInfo.getBndryNormal();
637
638 a_particles.clear();
639
640 for (const long long wt : weights) {
641 const RealVect x = Random::randomPosition(cellPos, validLo, validHi, bndryCentroid, bndryNormal, dx, kappa);
642 a_emit(a_particles, x, wt, context);
643 }
644 };
645}
646
647template <typename P, typename Traits, typename T, typename>
648inline void
649removePhysicalParticles(ParticleSoA<P, Traits>& a_particles, const T a_numPhysPartToRemove) noexcept
650{
651 CH_TIME("ParticleManagement::removePhysicalParticles(SoA)");
652
653 constexpr T zero = (T)0;
654
655 if (a_numPhysPartToRemove < zero) {
656 MayDay::Error("ParticleManagement::removePhysicalParticles(SoA) - 'a_numPhysPartoToRemove < 0'");
657 }
658
659 const std::size_t numComp = a_particles.size();
660
661 if (numComp > 0) {
662 T numRemoved = zero;
663
664 // 1. Compute the minimum particle weight.
665 T minWeight = std::numeric_limits<T>::max();
666 for (std::size_t i = 0; i < numComp; i++) {
667 minWeight = std::min(minWeight, (T)a_particles.weight(i));
668 }
669
670 // 2. Trim particle weights down to minWeight.
671 for (std::size_t i = 0; i < numComp; i++) {
672 const T diff1 = (T)a_particles.weight(i) - minWeight;
673 const T diff2 = a_numPhysPartToRemove - numRemoved;
674
675 CH_assert(diff1 >= zero);
676 CH_assert(diff2 >= zero);
677
678 const T r = std::max(0LL, std::min(diff1, diff2));
679
680 a_particles.weight(i) -= 1.0 * r;
681 numRemoved += r;
682 }
683
684 // 3. "Uniformly" subtract the particle weights.
685 if (a_numPhysPartToRemove - numRemoved > zero) {
686 const T numCompParticles = (T)numComp;
687 const T uniformWeight = (a_numPhysPartToRemove - numRemoved) / numCompParticles;
688 const T uniformRemainder = (a_numPhysPartToRemove - numRemoved) % numCompParticles;
689
690 if (uniformWeight > zero) {
691 // uniformWeight is constant over the loop, so the running accumulation equals
692 // uniformWeight * numComp -- hoist it out so the body is a pure elementwise update.
693 double* const w = a_particles.weightColumn();
694
695 ParticleLoops::loop(a_particles, [&](const std::size_t i) {
696 w[i] -= 1.0 * uniformWeight;
697 });
698
699 numRemoved += uniformWeight * static_cast<T>(numComp);
700 }
701
702 if (uniformRemainder > zero) {
703 T W = 0;
704
705 for (std::size_t i = 0; i < numComp; i++) {
706
707 // Never remove so that weight is negative.
708 const T w = std::min((T)a_particles.weight(i), uniformRemainder - W);
709
710 a_particles.weight(i) -= 1.0 * w;
711
712 W += w;
713 numRemoved += w;
714
715 if (W == uniformRemainder) {
716 break;
717 }
718 }
719 }
720 }
721
722 CH_assert(numRemoved == a_numPhysPartToRemove);
723 }
724}
725
726template <typename P, typename Traits>
727inline void
728deleteParticles(ParticleSoA<P, Traits>& a_particles, const Real a_weightThresh) noexcept
729{
730 CH_TIME("ParticleManagement::deleteParticles(SoA)");
731
732 // Swap-and-pop: do not advance the index after a removal (a new particle now occupies slot i).
733 std::size_t i = 0;
734 while (i < a_particles.size()) {
735 if (a_particles.weight(i) < a_weightThresh) {
736 a_particles.remove(i);
737 }
738 else {
739 i++;
740 }
741 }
742}
743
744template <typename T, typename>
745inline std::vector<T>
746partitionParticleWeights(const T a_numPhysicalParticles, const T a_maxCompParticles) noexcept
747{
748 std::vector<T> ret(0);
749
750 constexpr T zero = (T)0;
751 constexpr T one = (T)1;
752
753 if (a_maxCompParticles > zero) {
754 if (a_numPhysicalParticles <= a_maxCompParticles) {
755 ret.resize(a_numPhysicalParticles, one);
756 }
757 else {
758 const T W = a_numPhysicalParticles / a_maxCompParticles;
759 T r = a_numPhysicalParticles % a_maxCompParticles;
760
761 if (W > zero) {
762 ret.resize(a_maxCompParticles, W);
763
764 for (std::size_t i = 0; i < ret.size() && r > zero; i++) {
765 ret[i] += one;
766 r--;
767 }
768 }
769 else {
770 ret.resize(1, r);
771 }
772 }
773 }
774
775 return ret;
776}
777
778namespace detail {
779
780template <typename T, typename>
781inline T
782partitionParticles(const T a_numParticles)
783{
784#ifdef CH_MPI
785 const T quotient = a_numParticles / numProc();
786 const T remainder = a_numParticles % numProc();
787
788 Vector<T> particlesPerRank(numProc(), quotient);
789
790 for (int i = 0; i < remainder; i++) {
791 particlesPerRank[i]++;
792 }
793
794 return particlesPerRank[procID()];
795#else
796 return a_numParticles;
797#endif
798}
799
800} // namespace detail
801
802template <typename P, typename Traits, typename T, typename>
803inline void
804drawRandomParticles(ParticleSoA<P, Traits>& a_particles,
805 const T a_numParticles,
806 const std::function<RealVect()>& a_distribution)
807{
808 a_particles.clear();
809
810 // Each rank draws its own share (unit weight) into the buffer. Routing to owners happens later when
811 // the buffer is added to a ParticleContainer.
812 const T numParticles = detail::partitionParticles(a_numParticles);
813
814 for (T t = 0; t < numParticles; t++) {
815 a_particles.append(a_distribution(), 1.0);
816 }
817}
818
819template <typename P, typename Traits, typename T, typename>
820inline void
821drawGaussianParticles(ParticleSoA<P, Traits>& a_particles,
822 const T a_numParticles,
823 const RealVect& a_center,
824 const Real a_radius) noexcept
825{
826 CH_TIME("ParticleManagement::drawGaussianParticles(SoA)");
827
828 std::normal_distribution<Real> gauss(0.0, a_radius);
829
830 auto ranGauss = [&]() -> RealVect {
831 return a_center + Random::get(gauss) * Random::getDirection();
832 };
833
834 drawRandomParticles(a_particles, a_numParticles, ranGauss);
835}
836
837template <typename P, typename Traits, typename T, typename>
838inline void
839drawBoxParticles(ParticleSoA<P, Traits>& a_particles,
840 const T a_numParticles,
841 const RealVect& a_loCorner,
842 const RealVect& a_hiCorner) noexcept
843{
844 CH_TIME("ParticleManagement::drawBoxParticles(SoA)");
845
846 CH_assert(a_hiCorner >= a_loCorner);
847
848 auto ranBox = [&]() -> RealVect {
849 return RealVect(D_DECL(a_loCorner[0] + (a_hiCorner[0] - a_loCorner[0]) * Random::getUniformReal01(),
850 a_loCorner[1] + (a_hiCorner[1] - a_loCorner[1]) * Random::getUniformReal01(),
851 a_loCorner[2] + (a_hiCorner[2] - a_loCorner[2]) * Random::getUniformReal01()));
852 };
853
854 drawRandomParticles(a_particles, a_numParticles, ranBox);
855}
856
857template <typename P, typename Traits, typename T, typename>
858inline void
859drawSphereParticles(ParticleSoA<P, Traits>& a_particles,
860 const T a_numParticles,
861 const RealVect& a_center,
862 const Real a_radius) noexcept
863{
864 CH_TIME("ParticleManagement::drawSphereParticles(SoA)");
865
866 auto ranSphere = [&]() -> RealVect {
867 RealVect x = std::numeric_limits<Real>::max() * RealVect::Unit;
868
869 while (x.vectorLength() > a_radius) {
870 for (int d = 0; d < SpaceDim; d++) {
871 x[d] = a_radius * Random::getUniformReal11();
872 }
873 }
874
875 return x + a_center;
876 };
877
878 drawRandomParticles(a_particles, a_numParticles, ranSphere);
879}
880} // namespace ParticleManagement
881
882#include <CD_NamespaceFooter.H>
883
884#endif
Declaration of a static class for various load balancing operations.
Declaration of a namespace for SIMD-decorated loops over SoA particles.
Namespace containing various particle management utilities.
File containing some useful static methods related to random number generation.
Class for the cell-information that is often queried when merging particles inside a cell.
Definition CD_CellInfo.H:26
Arena-backed Struct-of-Arrays particle container for a single grid patch.
Definition CD_ParticleSoA.H:655
void append(const RealVect &a_position, const double a_weight)
Append one particle with a default-constructed payload.
Definition CD_ParticleSoA.H:955
std::size_t size() const noexcept
Number of particles currently stored.
Definition CD_ParticleSoA.H:882
void clear() noexcept
Drop all particles (keeps the arena; invalidates the cell sort).
Definition CD_ParticleSoA.H:911
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
static RealVect randomPosition(const RealVect &a_lo, const RealVect &a_hi) noexcept
Return a random position in the cube (a_lo, a_hi);.
Definition CD_RandomImplem.H:284
ALWAYS_INLINE void loop(const ParticleSoA< P, Traits > &a_soa, Functor &&a_kernel)
Launch a kernel over every particle in a ParticleSoA, decorating the loop with CD_PRAGMA_SIMD.
Definition CD_ParticleLoops.H:87
void mergeAdjacentNearest(std::vector< P > &a_particles, const std::size_t a_target, const std::function< void(P &, const P &)> &a_combine) noexcept
Merge an already spatially-ordered particle list down to a target count by repeatedly combining the n...
Definition CD_ParticleManagementImplem.H:320
void buildEqualWeightKDLeaves(const std::vector< P > &a_particles, const int a_maxLeaves, const BinaryParticleReconcile< P > &a_particleReconcile, std::vector< std::pair< const P *, const P * > > &a_leaves) noexcept
Build an equal-weight KD partition of a list of particles and return the leaf particle ranges.
Definition CD_ParticleManagementImplem.H:87
Namespace for various particle management tools.
Definition CD_KDParticleMerge.H:33
std::function< void(P &p1, P &p2, const P &p0)> BinaryParticleReconcile
Declaration of a reconciliation function when splitting particles.
Definition CD_ParticleManagement.H:94
ParticleMerger< P, Traits > makeReinitializeMerger(std::function< std::pair< long long, Context >(const ParticleSoA< P, Traits > &)> a_aggregate, std::function< void(ParticleSoA< P, Traits > &, const RealVect &, long long, const Context &)> a_emit, std::function< RealVect()> a_probLo) noexcept
Create a reinitialize super-particle merger as a reusable ParticleMerger.
Definition CD_ParticleManagementImplem.H:611
ParticleMerger< P, Traits > makeSfcNearestNeighborMerger(std::function< Packed(const ParticleSoA< P, Traits > &, std::size_t)> a_gather, std::function< void(Packed &, const Packed &)> a_combine, std::function< void(ParticleSoA< P, Traits > &, const Packed &)> a_scatter) noexcept
Create a space-filling-curve nearest-neighbor super-particle merger as a reusable ParticleMerger.
Definition CD_ParticleManagementImplem.H:441
ParticleMergeMethod
The specific super-particle merge methods.
Definition CD_ParticleManagement.H:40
@ NnSfc
Cell: makeSfcNearestNeighborMerger.
@ KdSkinNn
AMR: mergeKDInterior for the uncontested tier, nearest-neighbor pairs for the skin.
@ NnPairTree
AMR: mergeNearestNeighborsTree (whole-patch PointCloudBVH search).
@ KdPatch
AMR: mergeKDPatch (same build, patch-local – no ghosts, no contested particles).
@ NnPairOneCell
AMR: mergeNearestNeighborsOneCell (per-cell PointCloudBVH search, Chebyshev distance 1 only).
@ External
Cell: caller-supplied per-cell merger.
@ Reinitialize
Cell: makeReinitializeMerger.
@ EqualWeightKD
Cell: makeEqualWeightKDMerger.
@ KdCarve
AMR: mergeKDCarve (whole-patch kd-tree build, arbitrated patch boundaries).
@ ReinitializeBVH
Cell: makeEqualWeightKDMerger with reinitialized leaf positions.
@ NnPairHash
AMR: mergeNearestNeighborsHash (whole-patch PointCloudHashGrid search).
ParticleMergeMethod mergeMethodFromString(const std::string &a_str) noexcept
Map a merge-algorithm selector string to a ParticleMergeMethod.
Definition CD_ParticleManagementImplem.H:38
ParticleMerger< P, Traits > makeEqualWeightKDMerger(std::function< Packed(const ParticleSoA< P, Traits > &, std::size_t)> a_gather, BinaryParticleReconcile< Packed > a_reconcile, std::function< void(ParticleSoA< P, Traits > &, const Packed *, const Packed *, const CellInfo &)> a_scatterLeaf) noexcept
Create an equal-weight KD-tree super-particle merger as a reusable ParticleMerger.
Definition CD_ParticleManagementImplem.H:564