370 std::vector<KDLeaf>& a_leaves,
372 const Real a_splitWeightLeafDx,
373 const RealVect& a_dx,
374 const RealVect& a_probLo,
375 const FArrayBox& a_cellCounts)
noexcept
377 CH_TIME(
"ParticleManagement::buildKDQuotaLeaves");
381 if (a_particles.empty() || a_ppc <= 0) {
390 const auto firstUnmergeable = std::stable_partition(a_particles.begin(),
393 const IntVect cell = kdCellKeyOf(a_p.position, a_probLo, a_dx);
397 return a_cellCounts.box().contains(cell)
398 ? static_cast<int>(a_cellCounts(cell, 0)) > a_ppc
402 const std::size_t numMergeable =
static_cast<std::size_t
>(firstUnmergeable - a_particles.begin());
404 for (std::size_t idx = numMergeable; idx < a_particles.size(); idx++) {
405 RealVect boxLo, boxHi;
406 kdBBox(boxLo, boxHi, a_particles, idx, idx + 1);
408 a_leaves.push_back(
KDLeaf{idx, idx + 1, boxLo, boxHi});
411 if (numMergeable == 0) {
429 auto centroidCellOf = [&](
const std::size_t a_lo,
const std::size_t a_hi) -> std::pair<IntVect, Real> {
430 RealVect centroid = RealVect::Zero;
433 for (std::size_t idx = a_lo; idx < a_hi; idx++) {
434 centroid += a_particles[idx].weight * a_particles[idx].position;
435 totalW += a_particles[idx].weight;
442 centroid = a_particles[a_lo].position;
445 return {
kdCellKeyOf(centroid, a_probLo, a_dx), totalW};
463 const auto byWeight = [](
const KDCand& a_lhs,
const KDCand& a_rhs)
noexcept ->
bool {
464 return a_lhs.weight < a_rhs.weight;
470 FArrayBox& used = a_used;
476 std::vector<KDCand> heap;
477 std::vector<KDLeaf> finalLeaves;
479 heap.reserve(numMergeable);
480 finalLeaves.reserve(numMergeable);
483 const auto [rootCell, rootWeight] = centroidCellOf(0, numMergeable);
488 CH_assert(used.box().contains(rootCell));
491 heap.push_back(KDCand{rootWeight, numMergeable, 0, numMergeable, rootCell});
494 while (!heap.empty()) {
495 std::pop_heap(heap.begin(), heap.end(), byWeight);
497 const KDCand node = heap.back();
500 if (node.count < 2) {
501 RealVect boxLo, boxHi;
502 kdBBox(boxLo, boxHi, a_particles, node.lo, node.hi);
504 finalLeaves.push_back(
KDLeaf{node.lo, node.hi, boxLo, boxHi});
513 RealVect nodeBoxLo, nodeBoxHi;
514 kdBBox(nodeBoxLo, nodeBoxHi, a_particles, node.lo, node.hi);
516 const Real nodeSpan =
kdMaxAxisSpan(nodeBoxLo, nodeBoxHi, a_dx);
517 const int splitAxis = (nodeBoxHi - nodeBoxLo).maxDir(
true);
523 const std::size_t mid = (nodeSpan <= a_splitWeightLeafDx)
527 const auto [cellLeft, weightLeft] = centroidCellOf(node.lo, mid);
528 const auto [cellRight, weightRight] = centroidCellOf(mid, node.hi);
530 CH_assert(used.box().contains(cellLeft));
531 CH_assert(used.box().contains(cellRight));
533 used(node.cell, 0)--;
535 used(cellRight, 0)++;
541 if (nodeSpan <= s_kdMaxLeafExtent && (used(cellLeft, 0) > a_ppc || used(cellRight, 0) > a_ppc)) {
543 used(cellRight, 0)--;
544 used(node.cell, 0)++;
546 finalLeaves.push_back(
KDLeaf{node.lo, node.hi, nodeBoxLo, nodeBoxHi});
551 heap.push_back(KDCand{weightLeft, mid - node.lo, node.lo, mid, cellLeft});
552 std::push_heap(heap.begin(), heap.end(), byWeight);
554 heap.push_back(KDCand{weightRight, node.hi - mid, mid, node.hi, cellRight});
555 std::push_heap(heap.begin(), heap.end(), byWeight);
558 for (
const KDLeaf& leaf : finalLeaves) {
559 a_leaves.push_back(leaf);
566 std::size_t coveredMembers = 0;
568 for (
const KDLeaf& bl : a_leaves) {
569 CH_assert(bl.hi > bl.lo);
570 CH_assert(bl.hi <= a_particles.size());
572 coveredMembers += bl.hi - bl.lo;
575 CH_assert(coveredMembers == a_particles.size());
652 EBAMRFAB& a_cellHistogram,
653 EBAMRFAB& a_leafQuota,
656 const Real a_splitWeightLeafDx,
657 const Gather& a_gather,
658 const Combine& a_combine,
659 const Scatter& a_scatter,
660 const Allocator& a_allocateID,
661 const PosValid& a_isPositionValid)
663 using namespace detail;
676 operator<(
const KDBoxKey& a_rhs)
const noexcept
678 return (volume != a_rhs.volume) ? (volume < a_rhs.volume) : (anchor < a_rhs.anchor);
713 CH_TIMERS(
"ParticleManagement::mergeKDCarve");
714 CH_TIMER(
"ParticleManagement::mergeKDCarve::build_classify", t_build);
715 CH_TIMER(
"ParticleManagement::mergeKDCarve::carve_exchange", t_carve);
716 CH_TIMER(
"ParticleManagement::mergeKDCarve::remove_consumed", t_remove);
717 CH_TIMER(
"ParticleManagement::mergeKDCarve::place_results", t_place);
719 const std::string realm = a_particles.
getRealm();
721 const RealVect probLo = a_amr.
getProbLo();
728 EBAMRFAB& histogram = a_cellHistogram;
729 EBAMRFAB& leafQuota = a_leafQuota;
731 CH_assert(histogram[0]->nComp() == 1);
732 CH_assert(leafQuota[0]->nComp() == 1);
733 CH_assert(histogram[0]->ghostVect() >= IntVect::Unit);
734 CH_assert(leafQuota[0]->ghostVect() >= IntVect::Unit);
736 const int myRank = procID();
737 const int numRanks = numProc();
752 std::vector<KDMember> members;
772 struct SelfClaimEntry
779 std::vector<PatchWork> patchWork;
794 std::vector<MergeParticle<Packed>> gatheredParticles;
795 std::vector<std::pair<ParticleID, std::size_t>> particlesByID;
796 std::vector<RuntimeBox> myBoxes;
797 std::vector<ParticleID> consumedIDs;
798 std::vector<MergedResult> mergedResults;
809 std::vector<SelfClaimEntry> selfClaims;
814 std::size_t combinedCountUpperBound = 0;
816 for (
int lvl = 0; lvl <= finestLevel; lvl++) {
817 const DisjointBoxLayout& dbl = a_amr.
getGrids(realm)[lvl];
818 const DataIterator& dit = dbl.dataIterator();
819 const int nbox = dit.size();
821#pragma omp parallel for schedule(runtime) reduction(+ : combinedCountUpperBound)
822 for (
int mybox = 0; mybox < nbox; mybox++) {
823 combinedCountUpperBound += a_particles[lvl][dit[mybox]].size();
827 gatheredParticles.reserve(combinedCountUpperBound);
828 particlesByID.reserve(combinedCountUpperBound);
829 selfClaims.reserve(combinedCountUpperBound);
830 consumedIDs.reserve(combinedCountUpperBound);
836 std::vector<KDLeaf> leaves;
837 std::vector<KDMember> members;
846 for (
int lvl = 0; lvl <= finestLevel; lvl++) {
847 const DisjointBoxLayout& dbl = a_amr.
getGrids(realm)[lvl];
848 const DataIterator& dit = dbl.dataIterator();
850 const RealVect dx = a_amr.
getDx()[lvl] * RealVect::Unit;
852 const int nbox = dit.size();
858 for (
int mybox = 0; mybox < nbox; mybox++) {
859 const DataIndex& din = dit[mybox];
863 const BaseFab<bool>& exposureDin = (*exposure[lvl])[din];
865 std::vector<MergeParticle<Packed>> combined;
866 combined.reserve(leaf.
size());
868 for (std::size_t i = 0; i < leaf.
size(); i++) {
878 combined.push_back(p);
884 gatheredParticles.push_back(p);
885 particlesByID.emplace_back(p.
globalID, gatheredParticles.size() - 1);
888 patchWork.push_back(PatchWork{lvl, din, dx, dbl[din]});
889 const int patchIdx =
static_cast<int>(patchWork.size()) - 1;
891 if (combined.empty()) {
899 FArrayBox& cellCounts = (*histogram[lvl])[din];
900 FArrayBox& quota = (*leafQuota[lvl])[din];
902 kdFillCellHistogram(cellCounts, combined, probLo, dx);
904 buildKDQuotaLeaves(combined, quota, leaves, a_ppc, a_splitWeightLeafDx, dx, probLo, cellCounts);
906 for (
const KDLeaf& bl : leaves) {
908 members.reserve(bl.hi - bl.lo);
910 bool anyLocal =
false;
916 for (std::size_t idx = bl.lo; idx < bl.hi; idx++) {
917 members.push_back(KDMember{combined[idx].globalID, combined[idx].ownerRank});
918 anchor = std::min(anchor, combined[idx].globalID);
923 if (!combined[idx].isGhost) {
933 Real leafVolume = 1.0;
935 for (
int dir = 0; dir < SpaceDim; dir++) {
936 leafVolume *= (bl.boxHi[dir] - bl.boxLo[dir]);
943 const bool unmergeable = kdMaxAxisSpan(bl.boxLo, bl.boxHi, dx) > s_kdMaxLeafExtent;
952 std::vector<bool> exposed(bl.hi - bl.lo,
false);
953 bool anyExposed =
false;
954 bool anyGhost =
false;
956 for (std::size_t idx = bl.lo; idx < bl.hi; idx++) {
957 if (combined[idx].isGhost) {
963 const IntVect cell = kdCellKeyOf(combined[idx].position, probLo, dx);
965 CH_assert(exposureDin.box().contains(cell));
967 const bool isExposed = exposureDin(cell, 0);
969 exposed[idx - bl.lo] = isExposed;
970 anyExposed = anyExposed || isExposed;
974 for (std::size_t idx = bl.lo; idx < bl.hi; idx++) {
978 if (!combined[idx].isGhost && exposed[idx - bl.lo]) {
980 selfClaims.push_back(SelfClaimEntry{combined[idx].globalID, KDBoxKey{}, -1});
987 if (!anyExposed && !anyGhost) {
1003 if (bl.hi - bl.lo >= 2) {
1004 std::vector<Packed> payloads;
1005 std::vector<Real> weights;
1006 RealVect centroid = RealVect::Zero;
1009 payloads.reserve(bl.hi - bl.lo);
1010 weights.reserve(bl.hi - bl.lo);
1012 for (std::size_t idx = bl.lo; idx < bl.hi; idx++) {
1017 payloads.push_back(p.
payload);
1018 weights.push_back(p.
weight);
1025 kdCheckCentroid(centroid, totalW, bl.boxLo, bl.boxHi);
1027 if (a_isPositionValid(centroid)) {
1034 merged.
payload = a_combine(payloads.data(), weights.data(), payloads.size());
1036 mergedResults.push_back(MergedResult{merged, patchIdx});
1038 for (std::size_t idx = bl.lo; idx < bl.hi; idx++) {
1039 consumedIDs.push_back(combined[idx].globalID);
1048 if (members.size() >= 2) {
1049 const KDBoxKey key{leafVolume, anchor};
1051 const int boxIdx =
static_cast<int>(myBoxes.size());
1052 myBoxes.push_back(RuntimeBox{key, patchIdx, members});
1054 for (
const KDMember& m : members) {
1055 if (m.owner == myRank) {
1059 selfClaims.push_back(SelfClaimEntry{m.id, key, boxIdx});
1066 selfClaims.push_back(SelfClaimEntry{members[0].id, KDBoxKey{}, -1});
1078 std::sort(particlesByID.begin(), particlesByID.end(), [](
const auto& a_lhs,
const auto& a_rhs) {
1079 return a_lhs.first < a_rhs.first;
1082 std::size_t writeIdx = 0;
1084 for (std::size_t readIdx = 0; readIdx < particlesByID.size();) {
1085 std::size_t runEnd = readIdx + 1;
1087 while (runEnd < particlesByID.size() && particlesByID[runEnd].first == particlesByID[readIdx].first) {
1091 std::size_t chosen = readIdx;
1092 for (std::size_t k = readIdx; k < runEnd; k++) {
1093 if (!gatheredParticles[particlesByID[k].second].isGhost) {
1100 if (writeIdx != chosen) {
1101 particlesByID[writeIdx] = particlesByID[chosen];
1107 particlesByID.resize(writeIdx);
1113 const auto it = std::lower_bound(particlesByID.begin(),
1114 particlesByID.end(),
1116 [](
const std::pair<ParticleID, std::size_t>& a_entry,
const ParticleID a_key) {
1117 return a_entry.first < a_key;
1120 CH_assert(it != particlesByID.end() && it->first == a_id);
1122 return gatheredParticles[it->second];
1128 std::sort(selfClaims.begin(), selfClaims.end(), [](
const SelfClaimEntry& a_lhs,
const SelfClaimEntry& a_rhs) {
1129 return a_lhs.id < a_rhs.id;
1136 std::vector<std::vector<KDClaim>> claimSendByRank(numRanks);
1138 for (std::size_t boxIdx = 0; boxIdx < myBoxes.size(); boxIdx++) {
1139 const RuntimeBox& box = myBoxes[boxIdx];
1141 for (
const KDMember& m : box.members) {
1142 if (m.owner != myRank) {
1143 claimSendByRank[m.owner].push_back(KDClaim{m.id, box.key, myRank,
static_cast<int>(boxIdx)});
1150 std::vector<KDClaim> incomingClaims = kdExchangeByRank(claimSendByRank);
1151 std::sort(incomingClaims.begin(), incomingClaims.end(), [](
const KDClaim& a_lhs,
const KDClaim& a_rhs) {
1152 return a_lhs.memberID < a_rhs.memberID;
1155 auto claimsRange = [&incomingClaims](
const ParticleID a_id) {
1156 const auto lo = std::lower_bound(incomingClaims.begin(),
1157 incomingClaims.end(),
1159 [](
const KDClaim& a_c,
const ParticleID a_key) {
1160 return a_c.memberID < a_key;
1162 const auto hi = std::upper_bound(incomingClaims.begin(),
1163 incomingClaims.end(),
1165 [](
const ParticleID a_key,
const KDClaim& a_c) {
1166 return a_key < a_c.memberID;
1169 return std::make_pair(lo, hi);
1197 std::vector<WinnerEntry> nominalWinner;
1198 nominalWinner.reserve(selfClaims.size());
1200 for (std::size_t idx = 0; idx < selfClaims.size();) {
1203 std::size_t idxEnd = idx + 1;
1205 while (idxEnd < selfClaims.size() && selfClaims[idxEnd].id ==
id) {
1209 bool haveWinner =
false;
1211 RankID bestRank = myRank;
1212 int bestBoxIdx = -1;
1214 for (std::size_t k = idx; k < idxEnd; k++) {
1215 if (selfClaims[k].boxIdx < 0) {
1219 if (!haveWinner || selfClaims[k].key < bestKey) {
1221 bestKey = selfClaims[k].key;
1223 bestBoxIdx = selfClaims[k].boxIdx;
1227 const auto range = claimsRange(
id);
1229 for (
auto cit = range.first; cit != range.second; ++cit) {
1230 const KDClaim& c = *cit;
1232 if (!haveWinner || c.key < bestKey) {
1235 bestRank = c.proposerRank;
1241 bestBoxIdx = c.proposerBoxIdx;
1246 nominalWinner.push_back(WinnerEntry{id, Winner{bestRank, bestBoxIdx, bestKey}});
1254 auto findWinner = [&nominalWinner](
const ParticleID a_id) ->
const Winner* {
1255 const auto it = std::lower_bound(nominalWinner.begin(),
1256 nominalWinner.end(),
1258 [](
const WinnerEntry& a_entry,
const ParticleID a_key) {
1259 return a_entry.id < a_key;
1262 return (it != nominalWinner.end() && it->id == a_id) ? &it->winner :
nullptr;
1270 std::vector<std::vector<KDVerdict>> verdictSendByRank(numRanks);
1272 for (std::size_t idx = 0; idx < incomingClaims.size();) {
1273 const ParticleID id = incomingClaims[idx].memberID;
1275 std::size_t idxEnd = idx + 1;
1277 while (idxEnd < incomingClaims.size() && incomingClaims[idxEnd].memberID ==
id) {
1281 const Winner* winner = findWinner(
id);
1288 CH_assert(winner !=
nullptr);
1290 for (std::size_t k = idx; k < idxEnd; k++) {
1291 const KDClaim& c = incomingClaims[k];
1300 const bool won = (c.proposerRank == winner->rank) && (c.proposerBoxIdx == winner->boxIdx);
1302 verdictSendByRank[c.proposerRank].push_back(KDVerdict{id, c.proposerBoxIdx, won});
1308 const std::vector<KDVerdict> incomingVerdicts = kdExchangeByRank(verdictSendByRank);
1317 std::vector<std::vector<ParticleID>> wonForeignByBox(myBoxes.size());
1319 for (
const KDVerdict& v : incomingVerdicts) {
1321 wonForeignByBox[v.claimantBoxIdx].push_back(v.memberID);
1325 for (std::vector<ParticleID>& won : wonForeignByBox) {
1326 std::sort(won.begin(), won.end());
1330 std::vector<std::vector<KDCommit>> commitSendByRank(numRanks);
1332 for (std::size_t boxIdx = 0; boxIdx < myBoxes.size(); boxIdx++) {
1333 const RuntimeBox& box = myBoxes[boxIdx];
1335 std::vector<ParticleID> survivingLocal;
1336 std::vector<ParticleID> survivingForeign;
1338 for (
const KDMember& m : box.members) {
1339 if (m.owner == myRank) {
1340 const Winner* winner = findWinner(m.id);
1342 if (winner !=
nullptr && winner->rank == myRank && winner->boxIdx ==
static_cast<int>(boxIdx)) {
1343 survivingLocal.push_back(m.id);
1347 const std::vector<ParticleID>& won = wonForeignByBox[boxIdx];
1349 if (std::binary_search(won.begin(), won.end(), m.id)) {
1350 survivingForeign.push_back(m.id);
1355 const std::size_t totalSurvivors = survivingLocal.size() + survivingForeign.size();
1357 bool committed =
false;
1359 if (totalSurvivors >= 2) {
1360 std::vector<Packed> payloads;
1361 std::vector<Real> weights;
1362 RealVect centroid = RealVect::Zero;
1364 RealVect surviveBoxLo, surviveBoxHi;
1365 bool haveSurviveBox =
false;
1367 payloads.reserve(totalSurvivors);
1368 weights.reserve(totalSurvivors);
1370 auto accumulate = [&](
const ParticleID a_id) {
1373 payloads.push_back(p.
payload);
1374 weights.push_back(p.
weight);
1378 if (!haveSurviveBox) {
1381 haveSurviveBox =
true;
1384 for (
int dir = 0; dir < SpaceDim; dir++) {
1385 surviveBoxLo[dir] = std::min(surviveBoxLo[dir], p.
position[dir]);
1386 surviveBoxHi[dir] = std::max(surviveBoxHi[dir], p.
position[dir]);
1395 for (
const ParticleID id : survivingForeign) {
1401 kdCheckCentroid(centroid, totalW, surviveBoxLo, surviveBoxHi);
1403 if (a_isPositionValid(centroid)) {
1412 merged.
payload = a_combine(payloads.data(), weights.data(), payloads.size());
1414 mergedResults.push_back(MergedResult{merged, box.patchIdx});
1418 consumedIDs.push_back(
id);
1441 for (
const ParticleID id : survivingForeign) {
1442 commitSendByRank[findParticle(
id).ownerRank].push_back(KDCommit{id, committed});
1447 const std::vector<KDCommit> incomingCommits = kdExchangeByRank(commitSendByRank);
1449 for (
const KDCommit& c : incomingCommits) {
1451 consumedIDs.push_back(c.memberID);
1455 std::sort(consumedIDs.begin(), consumedIDs.end());
1458 CH_assert(std::is_sorted(consumedIDs.begin(), consumedIDs.end()));
1464 for (
const PatchWork& pw : patchWork) {
1469 while (i < leaf.
size()) {
1470 if (!leaf.
isGhost(i) && std::binary_search(consumedIDs.begin(), consumedIDs.end(), leaf.
particleID(i))) {
1485 std::vector<std::vector<MergeParticle<Packed>>> scatterByDestRank(numRanks);
1487 auto insertHere = [&](
const MergeParticle<Packed>& a_p,
const int a_level,
const DataIndex& a_din) {
1490 a_scatter(leaf, a_p);
1493 for (
const MergedResult& mr : mergedResults) {
1494 const PatchWork& pw = patchWork[mr.patchIdx];
1496 RealVect boxRealLo, boxRealHi;
1498 kdBoxRealBounds(boxRealLo, boxRealHi, pw.box, pw.dx, probLo);
1500 bool inOwnBox =
true;
1502 for (
int dir = 0; dir < SpaceDim && inOwnBox; dir++) {
1503 if (mr.particle.position[dir] < boxRealLo[dir] || mr.particle.position[dir] >= boxRealHi[dir]) {
1509 insertHere(mr.particle, pw.level, pw.din);
1517 MayDay::Error(
"ParticleManagement::mergeKDCarve -- merged particle not found in any patch");
1520 if (dst.rank == myRank) {
1521 const DataIndex din = a_amr.
getLevelTiles(realm)[dst.level]->getMyGrids().at(dst.gridIndex);
1523 insertHere(mr.particle, dst.level, din);
1529 scatterByDestRank[dst.rank].push_back(corrected);
1533 const std::vector<MergeParticle<Packed>> incomingScattered = kdExchangeByRank(scatterByDestRank);
1538 if (!dst.valid || dst.rank != myRank) {
1539 MayDay::Error(
"ParticleManagement::mergeKDCarve -- incoming scattered particle not "
1540 "found in any of this rank's own patches");
1543 const DataIndex din = a_amr.
getLevelTiles(realm)[dst.level]->getMyGrids().at(dst.gridIndex);
1548 insertHere(corrected, dst.
level, din);
1608 EBAMRFAB& a_cellHistogram,
1609 EBAMRFAB& a_leafQuota,
1612 const Real a_splitWeightLeafDx,
1613 const Gather& a_gather,
1614 const Combine& a_combine,
1615 const Scatter& a_scatter,
1616 const Allocator& a_allocateID,
1617 const PosValid& a_isPositionValid)
1619 using namespace detail;
1621 CH_TIMERS(
"ParticleManagement::mergeKDInterior");
1622 CH_TIMER(
"ParticleManagement::mergeKDInterior::build", t_build);
1623 CH_TIMER(
"ParticleManagement::mergeKDInterior::commit", t_commit);
1624 CH_TIMER(
"ParticleManagement::mergeKDInterior::remove_place", t_place);
1626 const std::string realm = a_particles.
getRealm();
1628 const RealVect probLo = a_amr.
getProbLo();
1632 CH_assert(a_interior.
getRealm() == realm);
1635 EBAMRFAB& histogram = a_cellHistogram;
1636 EBAMRFAB& leafQuota = a_leafQuota;
1638 CH_assert(histogram[0]->nComp() == 1);
1639 CH_assert(leafQuota[0]->nComp() == 1);
1640 CH_assert(histogram[0]->ghostVect() >= IntVect::Unit);
1641 CH_assert(leafQuota[0]->ghostVect() >= IntVect::Unit);
1643 const int myRank = procID();
1649 std::vector<KDLeaf> leaves;
1651 for (
int lvl = 0; lvl <= finestLevel; lvl++) {
1652 const DisjointBoxLayout& dbl = a_amr.
getGrids(realm)[lvl];
1653 const DataIterator& dit = dbl.dataIterator();
1655 const RealVect dx = a_amr.
getDx()[lvl] * RealVect::Unit;
1657 const int nbox = dit.size();
1661 for (
int mybox = 0; mybox < nbox; mybox++) {
1662 const DataIndex& din = dit[mybox];
1669 std::vector<MergeParticle<Packed>> combined;
1670 combined.reserve(leaf.
size());
1672 for (std::size_t i = 0; i < leaf.
size(); i++) {
1680 p.
payload = a_gather(leaf, i);
1682 combined.push_back(p);
1685 if (combined.empty()) {
1695 FArrayBox& cellCounts = (*histogram[lvl])[din];
1696 FArrayBox& quota = (*leafQuota[lvl])[din];
1698 kdFillCellHistogram(cellCounts, combined, probLo, dx);
1700 buildKDQuotaLeaves(combined, quota, leaves, a_ppc, a_splitWeightLeafDx, dx, probLo, cellCounts);
1705 std::vector<ParticleID> consumedIDs;
1706 std::vector<MergeParticle<Packed>> mergedResults;
1708 for (
const KDLeaf& bl : leaves) {
1709 if (bl.hi - bl.lo < 2) {
1714 if (kdMaxAxisSpan(bl.boxLo, bl.boxHi, dx) > s_kdMaxLeafExtent) {
1725 bool anyGhost =
false;
1727 for (std::size_t idx = bl.lo; idx < bl.hi && !anyGhost; idx++) {
1728 anyGhost = combined[idx].isGhost;
1735 std::vector<Packed> payloads;
1736 std::vector<Real> weights;
1737 RealVect centroid = RealVect::Zero;
1740 payloads.reserve(bl.hi - bl.lo);
1741 weights.reserve(bl.hi - bl.lo);
1743 for (std::size_t idx = bl.lo; idx < bl.hi; idx++) {
1750 payloads.push_back(p.
payload);
1751 weights.push_back(p.
weight);
1758 kdCheckCentroid(centroid, totalW, bl.boxLo, bl.boxHi);
1760 if (!a_isPositionValid(centroid)) {
1771 merged.
payload = a_combine(payloads.data(), weights.data(), payloads.size());
1773 mergedResults.push_back(merged);
1775 for (std::size_t idx = bl.lo; idx < bl.hi; idx++) {
1776 consumedIDs.push_back(combined[idx].globalID);
1782 if (mergedResults.empty()) {
1790 std::sort(consumedIDs.begin(), consumedIDs.end());
1793 CH_assert(std::is_sorted(consumedIDs.begin(), consumedIDs.end()));
1798 while (i < leaf.
size()) {
1799 if (!leaf.
isGhost(i) && std::binary_search(consumedIDs.begin(), consumedIDs.end(), leaf.
particleID(i))) {
1808 RealVect boxRealLo, boxRealHi;
1810 kdBoxRealBounds(boxRealLo, boxRealHi, dbl[din], dx, probLo);
1813 bool inOwnBox =
true;
1815 for (
int dir = 0; dir < SpaceDim && inOwnBox; dir++) {
1816 if (mr.position[dir] < boxRealLo[dir] || mr.position[dir] >= boxRealHi[dir]) {
1822 a_scatter(a_interior[lvl][din], mr);
1832 if (!dst.valid || dst.rank != myRank) {
1833 MayDay::Error(
"ParticleManagement::mergeKDInterior -- merged particle left this rank's own patches");
1836 const DataIndex dstDin = a_amr.
getLevelTiles(realm)[dst.level]->getMyGrids().at(dst.gridIndex);
1838 a_scatter(a_interior[dst.level][dstDin], mr);