13#ifndef CD_PARTICLEMANAGEMENTIMPLEM_H
14#define CD_PARTICLEMANAGEMENTIMPLEM_H
33#include <CD_NamespaceHeader.H>
40 if (a_str ==
"none") {
43 else if (a_str ==
"equal_weight_kd") {
46 else if (a_str ==
"reinitialize") {
49 else if (a_str ==
"reinitialize_bvh") {
52 else if (a_str ==
"nn_sfc") {
55 else if (a_str ==
"nn_pair_tree") {
58 else if (a_str ==
"nn_pair_onecell") {
61 else if (a_str ==
"nn_pair_hash") {
64 else if (a_str ==
"kd_carve") {
67 else if (a_str ==
"kd_patch") {
70 else if (a_str ==
"kd_skin_nn") {
73 else if (a_str ==
"external") {
77 MayDay::Abort((
"ParticleManagement::mergeMethodFromString - unknown merge method '" + a_str +
"'").c_str());
85template <
class P, Real P::*weight, RealVect P::*position>
88 const int a_maxLeaves,
90 std::vector<std::pair<const P*, const P*>>& a_leaves)
noexcept
92 CH_TIME(
"ParticleManagement::buildEqualWeightKDLeaves");
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;
113 const std::size_t numInput = a_particles.size();
114 if (numInput == 0 || a_maxLeaves <= 0) {
118 constexpr Real splitThresh = 2.0 - std::numeric_limits<Real>::min();
119 const std::size_t maxLeaves =
static_cast<std::size_t
>(a_maxLeaves);
124 s_cur.reserve(numInput + maxLeaves);
125 s_cur.insert(s_cur.end(), a_particles.begin(), a_particles.end());
129 for (
const P& p : s_cur) {
134 s_curNodes.push_back({
static_cast<std::size_t
>(0), numInput, W});
138 bool keepGoing =
true;
139 while (keepGoing && s_curNodes.size() < maxLeaves) {
148 std::size_t splitBudget = maxLeaves - s_curNodes.size();
150 for (std::size_t ni = 0; ni < s_curNodes.size(); ni++) {
151 const NodeRange node = s_curNodes[ni];
153 if (node.w > splitThresh && splitBudget > 0) {
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;
163 RealVect loCorner = +std::numeric_limits<Real>::max() * RealVect::Unit;
164 RealVect hiCorner = -std::numeric_limits<Real>::max() * RealVect::Unit;
166 for (P* p = beg; p != end; ++p) {
167 const RealVect& pos = (*p).*position;
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]);
175 const int splitDir = (hiCorner - loCorner).maxDir(
true);
177 std::sort(beg, end, [splitDir](
const P& p1,
const P& p2) ->
bool {
178 return (p1.*position)[splitDir] < (p2.*position)[splitDir];
184 Real wr = Wn - beg[0].*weight;
186 for (std::size_t i = 1; i < n; i++) {
187 const Real& w = beg[id].*weight;
191 wr = Wn - wl - beg[id].*weight;
198 const P med = beg[id];
199 const Real pw = med.*weight;
200 const Real dw = wr - wl;
202 CH_assert(wl + wr + pw == Wn);
205 bool hasExtraL =
false;
206 bool hasExtraR =
false;
210 if (pw >= splitThresh && pw >= std::abs(dw)) {
214 const Real ddw = pw - dw;
215 const long long Nsplit = (
long long)ddw;
218 const long long Nr = Nsplit / 2;
219 const long long Nl = Nsplit - Nr;
221 dwl += (ddw / Nsplit) * Nl;
222 dwr += (ddw / Nsplit) * Nr;
225 if (dwl > 0.0 && dwr > 0.0) {
230 CH_assert(dwl >= 1.0);
231 CH_assert(dwr >= 1.0);
236 extraL.*weight = dwl;
237 extraR.*weight = dwr;
239 a_particleReconcile(extraL, extraR, med);
244 else if (dwl > 0.0 && dwr == 0.0) {
246 CH_assert(dwl >= 1.0);
248 extraL.*weight = dwl;
251 else if (dwl == 0.0 && dwr > 0.0) {
253 CH_assert(dwr >= 1.0);
255 extraR.*weight = dwr;
259 MayDay::Abort(
"ParticleManagement::buildEqualWeightKDLeaves - logic bust");
276 CH_assert(std::abs(wl + wr - Wn) <= Wn * std::numeric_limits<Real>::epsilon() * 16);
277 CH_assert(std::abs(wl - wr) <= 1.0);
282 const std::size_t leftLo = s_nxt.size();
283 s_nxt.insert(s_nxt.end(), beg, beg +
id);
285 s_nxt.push_back(std::move(extraL));
287 s_nxtNodes.push_back({leftLo, s_nxt.size(), wl});
289 const std::size_t rightLo = s_nxt.size();
290 s_nxt.insert(s_nxt.end(), beg +
id + 1, end);
292 s_nxt.push_back(std::move(extraR));
294 s_nxtNodes.push_back({rightLo, s_nxt.size(), wr});
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});
307 s_curNodes.swap(s_nxtNodes);
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));
318template <
class P, RealVect P::*position>
321 const std::size_t a_target,
322 const std::function<
void(P&,
const P&)>& a_combine)
noexcept
324 CH_TIME(
"ParticleManagement::mergeAdjacentNearest");
326 const std::size_t n = a_particles.size();
327 if (a_target == 0 || n <= a_target) {
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;
337 for (
int d = 0; d < SpaceDim; d++) {
338 const Real dd = pi[d] - pj[d];
348 thread_local std::vector<int> prev, next;
349 thread_local std::vector<uint8_t> alive;
350 thread_local std::vector<uint32_t> ver;
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);
371 const auto cmp = [](
const Edge& x,
const Edge& y) {
375 thread_local std::vector<Edge> heap;
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});
383 std::make_heap(heap.begin(), heap.end(), cmp);
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();
391 const int a = edge.a;
392 const int b = edge.b;
394 if (!alive[a] || !alive[b] || ver[a] != edge.va || ver[b] != edge.vb || next[a] != b) {
399 a_combine(a_particles[a], a_particles[b]);
401 const int nb = next[b];
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);
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);
423 thread_local std::vector<P> survivors;
426 survivors.reserve(count);
428 for (std::size_t i = 0; i < n; i++) {
430 survivors.push_back(a_particles[i]);
434 a_particles.swap(survivors);
439template <
class Packed, Real Packed::*packWeight, RealVect Packed::*packPosition,
class P,
class Traits>
440inline ParticleMerger<P, Traits>
442 std::function<
void(Packed&,
const Packed&)> a_combine,
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);
452 thread_local std::vector<Packed> particles;
455 particles.reserve(a_particles.
size());
459 for (std::size_t i = 0; i < a_particles.
size(); i++) {
460 Packed p = a_gather(a_particles, i);
462 particles.emplace_back(std::move(p));
466 if (W < 2.0 || a_ppc <= 0) {
470 const std::size_t target =
static_cast<std::size_t
>(a_ppc);
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;
477 for (
const Packed& p : particles) {
478 const RealVect& x = p.*packPosition;
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]);
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;
491 constexpr uint32_t maxCoord = (1U << 21) - 1U;
492 thread_local std::vector<std::pair<uint64_t, std::size_t>> keyed;
495 keyed.reserve(particles.size());
497 for (std::size_t i = 0; i < particles.size(); i++) {
498 const RealVect& x = particles[i].*packPosition;
500 std::array<uint32_t, SpaceDim> coords;
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);
508 keyed.emplace_back(LoadBalancing::hilbertIndex<SpaceDim>(coords), i);
511 std::sort(keyed.begin(), keyed.end(), [](
const auto& a,
const auto& b) {
512 return a.first < b.first;
515 thread_local std::vector<Packed> sorted;
518 sorted.reserve(particles.size());
520 for (
const auto& k : keyed) {
521 sorted.push_back(particles[k.second]);
523 particles.swap(sorted);
525 detail::mergeAdjacentNearest<Packed, packPosition>(particles, target, a_combine);
527 else if (particles.size() < target) {
528 while (particles.size() < target) {
531 for (std::size_t i = 1; i < particles.size(); i++) {
532 if (particles[i].*packWeight > particles[hi].*packWeight) {
537 if (particles[hi].*packWeight < 2.0) {
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);
555 for (
const Packed& p : particles) {
556 a_scatter(a_particles, p);
562template <
class Packed, Real Packed::*packWeight, RealVect Packed::*packPosition,
class P,
class Traits>
563inline ParticleMerger<P, Traits>
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);
576 thread_local std::vector<Packed> particles;
579 particles.reserve(a_particles.
size());
583 for (std::size_t i = 0; i < a_particles.
size(); i++) {
584 Packed p = a_gather(a_particles, i);
586 particles.emplace_back(std::move(p));
590 if (W < 2.0 || a_ppc <= 0) {
595 thread_local std::vector<std::pair<const Packed*, const Packed*>> leaves;
597 detail::buildEqualWeightKDLeaves<Packed, packWeight, packPosition>(particles, a_ppc, a_reconcile, leaves);
602 for (
const auto& leaf : leaves) {
603 a_scatterLeaf(a_particles, leaf.first, leaf.second, a_cellInfo);
609template <
class Context,
class P,
class Traits>
610inline ParticleMerger<P, Traits>
613 std::function<RealVect()> a_probLo)
noexcept
616 CH_TIME(
"ParticleManagement::makeReinitializeMerger");
622 const auto [numPhysical, context] = a_aggregate(a_particles);
624 if (numPhysical <= 0LL) {
628 const std::vector<long long> weights = partitionParticleWeights(numPhysical, (
long long)a_ppc);
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();
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);
647template <
typename P,
typename Traits,
typename T,
typename>
651 CH_TIME(
"ParticleManagement::removePhysicalParticles(SoA)");
653 constexpr T zero = (T)0;
655 if (a_numPhysPartToRemove < zero) {
656 MayDay::Error(
"ParticleManagement::removePhysicalParticles(SoA) - 'a_numPhysPartoToRemove < 0'");
659 const std::size_t numComp = a_particles.size();
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));
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;
675 CH_assert(diff1 >= zero);
676 CH_assert(diff2 >= zero);
678 const T r = std::max(0LL, std::min(diff1, diff2));
680 a_particles.weight(i) -= 1.0 * r;
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;
690 if (uniformWeight > zero) {
693 double*
const w = a_particles.weightColumn();
696 w[i] -= 1.0 * uniformWeight;
699 numRemoved += uniformWeight *
static_cast<T
>(numComp);
702 if (uniformRemainder > zero) {
705 for (std::size_t i = 0; i < numComp; i++) {
708 const T w = std::min((T)a_particles.weight(i), uniformRemainder - W);
710 a_particles.weight(i) -= 1.0 * w;
715 if (W == uniformRemainder) {
722 CH_assert(numRemoved == a_numPhysPartToRemove);
726template <
typename P,
typename Traits>
730 CH_TIME(
"ParticleManagement::deleteParticles(SoA)");
734 while (i < a_particles.size()) {
735 if (a_particles.weight(i) < a_weightThresh) {
736 a_particles.remove(i);
744template <
typename T,
typename>
746partitionParticleWeights(
const T a_numPhysicalParticles,
const T a_maxCompParticles)
noexcept
748 std::vector<T> ret(0);
750 constexpr T zero = (T)0;
751 constexpr T one = (T)1;
753 if (a_maxCompParticles > zero) {
754 if (a_numPhysicalParticles <= a_maxCompParticles) {
755 ret.resize(a_numPhysicalParticles, one);
758 const T W = a_numPhysicalParticles / a_maxCompParticles;
759 T r = a_numPhysicalParticles % a_maxCompParticles;
762 ret.resize(a_maxCompParticles, W);
764 for (std::size_t i = 0; i < ret.size() && r > zero; i++) {
780template <
typename T,
typename>
782partitionParticles(
const T a_numParticles)
785 const T quotient = a_numParticles / numProc();
786 const T remainder = a_numParticles % numProc();
788 Vector<T> particlesPerRank(numProc(), quotient);
790 for (
int i = 0; i < remainder; i++) {
791 particlesPerRank[i]++;
794 return particlesPerRank[procID()];
796 return a_numParticles;
802template <
typename P,
typename Traits,
typename T,
typename>
805 const T a_numParticles,
806 const std::function<RealVect()>& a_distribution)
812 const T numParticles = detail::partitionParticles(a_numParticles);
814 for (T t = 0; t < numParticles; t++) {
815 a_particles.
append(a_distribution(), 1.0);
819template <
typename P,
typename Traits,
typename T,
typename>
822 const T a_numParticles,
823 const RealVect& a_center,
824 const Real a_radius)
noexcept
826 CH_TIME(
"ParticleManagement::drawGaussianParticles(SoA)");
828 std::normal_distribution<Real> gauss(0.0, a_radius);
830 auto ranGauss = [&]() -> RealVect {
834 drawRandomParticles(a_particles, a_numParticles, ranGauss);
837template <
typename P,
typename Traits,
typename T,
typename>
840 const T a_numParticles,
841 const RealVect& a_loCorner,
842 const RealVect& a_hiCorner)
noexcept
844 CH_TIME(
"ParticleManagement::drawBoxParticles(SoA)");
846 CH_assert(a_hiCorner >= a_loCorner);
848 auto ranBox = [&]() -> RealVect {
854 drawRandomParticles(a_particles, a_numParticles, ranBox);
857template <
typename P,
typename Traits,
typename T,
typename>
860 const T a_numParticles,
861 const RealVect& a_center,
862 const Real a_radius)
noexcept
864 CH_TIME(
"ParticleManagement::drawSphereParticles(SoA)");
866 auto ranSphere = [&]() -> RealVect {
867 RealVect x = std::numeric_limits<Real>::max() * RealVect::Unit;
869 while (x.vectorLength() > a_radius) {
870 for (
int d = 0; d < SpaceDim; d++) {
878 drawRandomParticles(a_particles, a_numParticles, ranSphere);
882#include <CD_NamespaceFooter.H>
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