13#ifndef CD_NEARESTNEIGHBORPARTICLEMERGEIMPLEM_H
14#define CD_NEARESTNEIGHBORPARTICLEMERGEIMPLEM_H
24#include <DataIterator.H>
25#include <BoxIterator.H>
30#include <CD_NamespaceHeader.H>
47 for (
int dir = 0; dir < SpaceDim; dir++) {
48 const Real delta = a_a[dir] - a_b[dir];
68 for (
int dir = 0; dir < SpaceDim; dir++) {
69 dist = std::max(dist, std::abs(a_a[dir] - a_b[dir]));
94 const RealVect& a_posB,
96 const RealVect& a_probLo,
97 const std::vector<RealVect>& a_dxByLevel,
98 const int a_maxCellDistance)
noexcept
100 const int refLevel = std::max(a_levelA, a_levelB);
101 const RealVect& refDx = a_dxByLevel[refLevel];
102 const IntVect keyA =
cellKeyOf(a_posA, a_probLo, refDx);
103 const IntVect keyB =
cellKeyOf(a_posB, a_probLo, refDx);
119 Real& a_mergedWeight,
120 const RealVect& a_posA,
121 const Real a_weightA,
122 const RealVect& a_posB,
123 const Real a_weightB)
noexcept
125 a_mergedWeight = a_weightA + a_weightB;
127 for (
int dir = 0; dir < SpaceDim; dir++) {
128 a_mergedPos[dir] = (a_posA[dir] * a_weightA + a_posB[dir] * a_weightB) / a_mergedWeight;
143 const IntVect& a_cell,
144 const RealVect& a_probLo,
145 const RealVect& a_dx)
noexcept
149 for (
int dir = 0; dir < SpaceDim; dir++) {
150 const Real lo = a_probLo[dir] + a_cell[dir] * a_dx[dir];
151 const Real hi = lo + a_dx[dir];
155 if (a_point[dir] < lo) {
156 delta = lo - a_point[dir];
158 else if (a_point[dir] > hi) {
159 delta = a_point[dir] - hi;
184 const int numRanks =
static_cast<int>(a_sendByRank.size());
188 return a_sendByRank.empty() ? std::vector<T>() : a_sendByRank[0];
191 std::vector<int> sendCounts(numRanks, 0);
193 for (
int r = 0; r < numRanks; r++) {
194 sendCounts[r] =
static_cast<int>(a_sendByRank[r].size());
197 std::vector<int> recvCounts(numRanks, 0);
198 MPI_Alltoall(sendCounts.data(), 1, MPI_INT, recvCounts.data(), 1, MPI_INT, Chombo_MPI::comm);
200 std::vector<int> sdispl(numRanks, 0);
201 std::vector<int> rdispl(numRanks, 0);
206 for (
int r = 0; r < numRanks; r++) {
207 sdispl[r] =
static_cast<int>(stot);
208 stot += sendCounts[r];
210 rdispl[r] =
static_cast<int>(rtot);
211 rtot += recvCounts[r];
214 std::vector<T> sflat(stot);
216 for (
int r = 0; r < numRanks; r++) {
217 if (sendCounts[r] > 0) {
218 std::copy(a_sendByRank[r].begin(), a_sendByRank[r].end(), sflat.begin() + sdispl[r]);
222 std::vector<T> rflat(rtot);
225 std::vector<int> sendBytes(numRanks);
226 std::vector<int> recvBytes(numRanks);
227 std::vector<int> sdisplBytes(numRanks);
228 std::vector<int> rdisplBytes(numRanks);
230 for (
int r = 0; r < numRanks; r++) {
231 sendBytes[r] = sendCounts[r] *
static_cast<int>(
sizeof(T));
232 recvBytes[r] = recvCounts[r] *
static_cast<int>(
sizeof(T));
233 sdisplBytes[r] = sdispl[r] *
static_cast<int>(
sizeof(T));
234 rdisplBytes[r] = rdispl[r] *
static_cast<int>(
sizeof(T));
237 MPI_Alltoallv(sflat.data(),
249 return a_sendByRank.empty() ? std::vector<T>() : a_sendByRank[0];
254cellKeyOf(
const RealVect& a_position,
const RealVect& a_probLo,
const RealVect& a_dx)
noexcept
258 for (
int dir = 0; dir < SpaceDim; dir++) {
259 iv[dir] =
static_cast<int>(std::floor((a_position[dir] - a_probLo[dir]) / a_dx[dir]));
279template <
typename Packed>
283 const RealVect& a_probLo,
284 const RealVect& a_dx,
287 const std::size_t total = a_localValid.size() + a_ghosts.size();
289 std::unordered_map<NNWalkCell, int, LevelTiles::TileHasher> cellCount;
290 cellCount.reserve(total);
292 for (
const auto& p : a_localValid) {
293 cellCount[
cellKeyOf(p.position, a_probLo, a_dx)]++;
296 for (
const auto& p : a_ghosts) {
297 cellCount[
cellKeyOf(p.position, a_probLo, a_dx)]++;
306 const int minBudget = a_cellBudget.minimum();
308 for (
const auto& kv : cellCount) {
309 if (kv.second > minBudget) {
317template <
typename Packed,
typename Cloud>
319buildNNSpatialIndex(NNSpatialIndex<Packed, Cloud>& a_index,
322 const RealVect& a_probLo,
323 const RealVect& a_dx,
325 const int a_kNearest)
noexcept
327 CH_TIME(
"ParticleManagement::buildNNSpatialIndex");
329 a_index.pointCloud =
nullptr;
330 a_index.pointCloudGraph.clear();
331 a_index.pointCloudNLocal = 0;
332 a_index.pointCloudK = a_kNearest;
334 const std::size_t total = a_localValid.size() + a_ghosts.size();
348 const std::size_t nLocal = a_localValid.size();
350 std::vector<EBGeometry::Vec3T<Real>> positions;
351 std::vector<ParticleID> metadata;
353 positions.reserve(total);
354 metadata.reserve(total);
356 auto pushPoint = [&](
const MergeParticle<Packed>& a_p) {
357 EBGeometry::Vec3T<Real> v(0.0, 0.0, 0.0);
359 for (
int dir = 0; dir < SpaceDim; dir++) {
360 v[dir] = a_p.position[dir];
363 positions.push_back(v);
364 metadata.push_back(a_p.globalID);
367 for (
const auto& p : a_localValid) {
371 for (
const auto& p : a_ghosts) {
375 a_index.pointCloud = std::make_shared<Cloud>(positions, metadata);
376 a_index.pointCloudNLocal = nLocal;
382 a_index.pointCloudGraph = a_index.pointCloud->allNearestNeighbors(
static_cast<std::size_t
>(a_kNearest));
385template <
typename Packed,
typename Cloud>
387findNearestNeighborCandidates(std::vector<NNMergeEdge<Packed>>& a_edges,
388 const std::vector<MergeParticle<Packed>>& a_localValid,
389 const std::vector<MergeParticle<Packed>>& a_ghosts,
390 const std::unordered_set<ParticleID>& a_consumedIDs,
391 const NNSpatialIndex<Packed, Cloud>& a_spatialIndex,
392 const std::unordered_map<NNCellKey, int, NNCellKeyHasher>& a_liveCellCount,
393 const RealVect& a_probLo,
394 const RealVect& a_dx,
395 const NNCellBudget& a_cellBudget,
396 const int a_maxFallbackCandidates,
397 const std::optional<int> a_maxCellDistance)
noexcept
399 CH_TIME(
"ParticleManagement::findNearestNeighborCandidates");
403 const std::size_t nLocal = a_localValid.size();
404 const int K = 1 + a_maxFallbackCandidates;
410 std::optional<Real> maxDistBoundSquared;
411 if (a_maxCellDistance.has_value()) {
412 Real minDx = a_dx[0];
414 for (
int dir = 1; dir < SpaceDim; dir++) {
415 minDx = std::min(minDx, a_dx[dir]);
418 const Real bound =
static_cast<Real
>(a_maxCellDistance.value()) * minDx * std::sqrt(
static_cast<Real
>(SpaceDim));
419 maxDistBoundSquared = bound * bound;
422 struct RankedCandidate
429 for (std::size_t qi = 0; qi < nLocal; qi++) {
430 const MergeParticle<Packed>& q = a_localValid[qi];
434 if (a_consumedIDs.count(q.globalID) > 0) {
440 const IntVect qCell =
cellKeyOf(q.position, a_probLo, a_dx);
442 const auto cntIt = a_liveCellCount.find(qKey);
443 const int count = (cntIt != a_liveCellCount.end()) ? cntIt->second : 0;
445 if (count <= a_cellBudget(qKey)) {
449 std::vector<RankedCandidate> best;
450 best.reserve(
static_cast<std::size_t
>(K) + 1);
452 auto considerCandidate = [&](
const Real a_d2,
const std::size_t a_idx,
const bool a_isLocal) {
453 if (
static_cast<int>(best.size()) == K && a_d2 >= best.back().d2) {
457 const auto insertPos = std::upper_bound(best.begin(),
460 [](
const Real a_val,
const RankedCandidate& a_c) {
461 return a_val < a_c.d2;
464 best.insert(insertPos, RankedCandidate{a_d2, a_idx, a_isLocal});
466 if (
static_cast<int>(best.size()) > K) {
478 if (a_spatialIndex.pointCloud) {
479 const std::size_t nLocalPC = a_spatialIndex.pointCloudNLocal;
480 const int Kgraph = a_spatialIndex.pointCloudK;
481 const std::size_t rowStart = qi *
static_cast<std::size_t
>(Kgraph);
483 for (
int h = 0; h < Kgraph; h++) {
484 const typename Cloud::Hit& hit = a_spatialIndex.pointCloudGraph[rowStart +
static_cast<std::size_t
>(h)];
487 if (hit.distanceSquared == std::numeric_limits<Real>::max()) {
490 const std::size_t
c = hit.index;
491 const bool isLocal =
c < nLocalPC;
492 const std::size_t idx = isLocal ?
c :
c - nLocalPC;
493 const Real d2 = hit.distanceSquared;
495 if (maxDistBoundSquared.has_value() && d2 > maxDistBoundSquared.value()) {
499 const ParticleID gid = isLocal ? a_localValid[idx].globalID : a_ghosts[idx].globalID;
501 if (a_consumedIDs.count(gid) > 0) {
505 considerCandidate(d2, idx, isLocal);
513 NNMergeEdge<Packed> edge;
515 edge.distanceSquared = best[0].d2;
516 edge.queryID = q.globalID;
517 edge.candidateID = best[0].isLocal ? a_localValid[best[0].idx].globalID : a_ghosts[best[0].idx].globalID;
518 edge.candidateIsLocal = best[0].isLocal;
520 for (std::size_t k = 1; k < best.size(); k++) {
521 NNFallbackCandidate fc;
523 fc.candidateID = best[k].isLocal ? a_localValid[best[k].idx].globalID : a_ghosts[best[k].idx].globalID;
524 fc.distanceSquared = best[k].d2;
525 fc.candidateIsLocal = best[k].isLocal;
527 edge.fallbackCandidates.push_back(fc);
530 a_edges.push_back(std::move(edge));
534template <
typename Packed>
536buildNNCellSpatialIndex(NNCellSpatialIndex& a_index,
537 const std::vector<MergeParticle<Packed>>& a_localValid,
538 const std::vector<MergeParticle<Packed>>& a_ghosts,
539 const RealVect& a_probLo,
540 const RealVect& a_dx,
541 const NNCellBudget& a_cellBudget)
noexcept
543 CH_TIME(
"ParticleManagement::buildNNCellSpatialIndex");
547 const std::size_t total = a_localValid.size() + a_ghosts.size();
561 std::unordered_map<NNWalkCell,
562 std::pair<std::vector<EBGeometry::Vec3T<Real>>, std::vector<NNCellMeta>>,
565 perCell.reserve(total);
567 auto pushPoint = [&](
const MergeParticle<Packed>& a_p,
const bool a_isLocal) {
568 const NNWalkCell cell =
cellKeyOf(a_p.position, a_probLo, a_dx);
570 EBGeometry::Vec3T<Real> v(0.0, 0.0, 0.0);
572 for (
int dir = 0; dir < SpaceDim; dir++) {
573 v[dir] = a_p.position[dir];
576 auto& bucket = perCell[cell];
578 bucket.first.push_back(v);
579 bucket.second.push_back(NNCellMeta{a_p.globalID, a_isLocal, a_p.level});
582 for (
const auto& p : a_localValid) {
586 for (
const auto& p : a_ghosts) {
590 a_index.reserve(perCell.size());
592 for (
auto& kv : perCell) {
593 a_index[kv.first] = std::make_shared<NNCellCloud>(kv.second.first, kv.second.second);
597template <
typename Packed>
599findNearestNeighborCandidatesOneCell(std::vector<NNMergeEdge<Packed>>& a_edges,
600 const std::vector<MergeParticle<Packed>>& a_localValid,
601 const std::unordered_set<ParticleID>& a_consumedIDs,
602 const NNCellSpatialIndex& a_cellIndex,
603 const std::unordered_map<NNCellKey, int, NNCellKeyHasher>& a_liveCellCount,
604 const RealVect& a_probLo,
605 const RealVect& a_dx,
606 const std::vector<RealVect>& a_dxByLevel,
607 const NNCellBudget& a_cellBudget,
608 const int a_maxFallbackCandidates)
noexcept
610 CH_TIME(
"ParticleManagement::findNearestNeighborCandidatesOneCell");
614 const int K = 1 + a_maxFallbackCandidates;
616 struct RankedCandidate
625 std::vector<IntVect> offsets;
628 const Box neighborhood(-IntVect::Unit, IntVect::Unit);
630 for (BoxIterator bit(neighborhood); bit.ok(); ++bit) {
631 offsets.push_back(bit());
634 using PCBVH = NNCellCloud;
636 for (
const MergeParticle<Packed>& q : a_localValid) {
638 if (a_consumedIDs.count(q.globalID) > 0) {
642 const IntVect qCell =
cellKeyOf(q.position, a_probLo, a_dx);
644 const auto cntIt = a_liveCellCount.find(qKey);
645 const int count = (cntIt != a_liveCellCount.end()) ? cntIt->second : 0;
647 if (count <= a_cellBudget(qKey)) {
651 EBGeometry::Vec3T<Real> queryVec(0.0, 0.0, 0.0);
653 for (
int dir = 0; dir < SpaceDim; dir++) {
654 queryVec[dir] = q.position[dir];
661 std::vector<std::pair<Real, IntVect>> rankedCells;
662 rankedCells.reserve(offsets.size());
664 for (
const IntVect& offset : offsets) {
665 const IntVect cell = qCell + offset;
668 rankedCells.push_back(std::make_pair(d2, cell));
671 std::sort(rankedCells.begin(),
673 [](
const std::pair<Real, IntVect>& a_a,
const std::pair<Real, IntVect>& a_b) {
674 if (a_a.first != a_b.first) {
675 return a_a.first < a_b.first;
681 for (
int dir = 0; dir < SpaceDim; dir++) {
682 if (a_a.second[dir] != a_b.second[dir]) {
683 return a_a.second[dir] < a_b.second[dir];
690 std::vector<RankedCandidate> best;
691 best.reserve(
static_cast<std::size_t
>(K) + 1);
693 auto considerCandidate = [&](
const Real a_d2,
const ParticleID a_id,
const bool a_isLocal) {
694 if (
static_cast<int>(best.size()) == K && a_d2 >= best.back().d2) {
698 const auto insertPos = std::upper_bound(best.begin(),
701 [](
const Real a_val,
const RankedCandidate& a_c) {
702 return a_val < a_c.d2;
705 best.insert(insertPos, RankedCandidate{a_d2, a_id, a_isLocal});
707 if (
static_cast<int>(best.size()) > K) {
712 std::vector<typename PCBVH::Hit> hitBuf;
714 for (
const auto& ranked : rankedCells) {
715 const Real cellDistSquared = ranked.first;
717 if (
static_cast<int>(best.size()) == K && cellDistSquared > best.back().d2) {
721 const auto it = a_cellIndex.find(ranked.second);
723 if (it == a_cellIndex.end() || it->second ==
nullptr) {
727 const PCBVH& cloud = *(it->second);
732 const bool isOwnCell = (ranked.second == qCell);
733 const std::size_t kQuery = isOwnCell ?
static_cast<std::size_t
>(K + 1) : static_cast<std::size_t>(K);
735 hitBuf.resize(kQuery);
737 const std::size_t found = cloud.closestPoints(queryVec, kQuery, hitBuf.data());
739 for (std::size_t h = 0; h < found; h++) {
740 const typename PCBVH::Hit& hit = hitBuf[h];
741 const NNCellMeta& meta = cloud.metadata(hit.index);
743 if (meta.globalID == q.globalID) {
747 if (a_consumedIDs.count(meta.globalID) > 0) {
756 if (meta.level != q.level) {
757 const EBGeometry::Vec3T<Real>& candVec = cloud.position(hit.index);
759 RealVect candPos(D_DECL(candVec[0], candVec[1], candVec[2]));
766 considerCandidate(hit.distanceSquared, meta.globalID, meta.isLocal);
774 NNMergeEdge<Packed> edge;
776 edge.distanceSquared = best[0].d2;
777 edge.queryID = q.globalID;
778 edge.candidateID = best[0].id;
779 edge.candidateIsLocal = best[0].isLocal;
781 for (std::size_t k = 1; k < best.size(); k++) {
782 NNFallbackCandidate fc;
784 fc.candidateID = best[k].id;
785 fc.distanceSquared = best[k].d2;
786 fc.candidateIsLocal = best[k].isLocal;
788 edge.fallbackCandidates.push_back(fc);
791 a_edges.push_back(std::move(edge));
795template <
typename Packed,
typename Combine,
typename PosVal
id,
typename Allocator,
typename OnMerge>
797resolveTrivialTier(std::unordered_map<NNCellKey, int, NNCellKeyHasher>& a_liveCellCount,
798 std::unordered_set<ParticleID>& a_consumedIDs,
799 std::unordered_set<ParticleID>& a_hasOutgoingCommitment,
800 std::vector<NNMergeResult<Packed>>& a_results,
801 const std::vector<NNMergeEdge<Packed>>& a_edges,
802 const std::unordered_map<
ParticleID, NNPooledParticle<Packed>>& a_particlesByID,
803 const Combine& a_combine,
804 const NNCellBudget& a_cellBudget,
805 const RealVect& a_probLo,
806 const std::vector<RealVect>& a_dxByLevel,
807 const PosValid& a_isPositionValid,
808 const Allocator& a_allocateID,
809 const int a_maxFallbackCandidates,
810 const std::optional<int> a_maxCellDistance,
811 unsigned long long* a_crossLevelMergeCount,
812 const OnMerge& a_onMergeCommitted)
noexcept
814 CH_TIME(
"ParticleManagement::resolveTrivialTier");
820 std::vector<const NNMergeEdge<Packed>*> sorted;
821 sorted.reserve(a_edges.size());
823 for (
const auto& e : a_edges) {
824 sorted.push_back(&e);
827 std::sort(sorted.begin(), sorted.end(), [](
const NNMergeEdge<Packed>* a_a,
const NNMergeEdge<Packed>* a_b) {
828 if (a_a->distanceSquared != a_b->distanceSquared) {
829 return a_a->distanceSquared < a_b->distanceSquared;
832 if (a_a->queryID != a_b->queryID) {
833 return a_a->queryID < a_b->queryID;
836 return a_a->candidateID < a_b->candidateID;
839 for (
const NNMergeEdge<Packed>* edgePtr : sorted) {
840 const NNMergeEdge<Packed>& edge = *edgePtr;
843 if (a_consumedIDs.count(edge.queryID) > 0) {
847 const auto queryDataIt = a_particlesByID.find(edge.queryID);
849 if (queryDataIt == a_particlesByID.end()) {
853 const MergeParticle<Packed>& query = queryDataIt->second.particle;
854 const bool queryExposed = queryDataIt->second.exposed;
856 bool resolved =
false;
857 bool originalStale =
false;
858 bool originalIneligible =
false;
860 const int numAttempts = a_maxFallbackCandidates + 1;
862 for (
int attempt = 0; attempt < numAttempts; attempt++) {
869 if (queryExposed && attempt > 0) {
874 bool candidateIsLocal;
877 candidateID = edge.candidateID;
878 candidateIsLocal = edge.candidateIsLocal;
881 const std::size_t fbIdx =
static_cast<std::size_t
>(attempt - 1);
883 if (fbIdx >= edge.fallbackCandidates.size()) {
887 candidateID = edge.fallbackCandidates[fbIdx].candidateID;
888 candidateIsLocal = edge.fallbackCandidates[fbIdx].candidateIsLocal;
891 const bool candidateStale = candidateIsLocal && (a_consumedIDs.count(candidateID) > 0);
892 const bool candidateBusy = candidateIsLocal && !candidateStale &&
893 (a_hasOutgoingCommitment.count(candidateID) > 0);
895 const auto candDataIt = a_particlesByID.find(candidateID);
896 const bool candidateExists = candDataIt != a_particlesByID.end();
900 const bool candidateExposed = candidateIsLocal && candidateExists && candDataIt->second.exposed;
903 bool invalidPos =
false;
905 RealVect mergedPos(D_DECL(0.0, 0.0, 0.0));
906 Real mergedWeight = 0.0;
908 if (!candidateStale && candidateExists) {
909 const MergeParticle<Packed>& cand = candDataIt->second.particle;
911 if (a_maxCellDistance.has_value()) {
918 a_maxCellDistance.value());
923 invalidPos = !a_isPositionValid(mergedPos);
927 const bool ineligible = tooFar || invalidPos;
930 originalStale = candidateStale;
931 originalIneligible = ineligible;
934 const bool trivialOk = candidateIsLocal && candidateExists && !candidateStale && !candidateBusy &&
935 !candidateExposed && !queryExposed && !ineligible;
938 const MergeParticle<Packed>& cand = candDataIt->second.particle;
943 const NNCellKey qKey(query.level,
cellKeyOf(query.position, a_probLo, a_dxByLevel[query.level]));
944 const NNCellKey cKey(cand.level,
cellKeyOf(cand.position, a_probLo, a_dxByLevel[cand.level]));
946 const auto qCountIt = a_liveCellCount.find(qKey);
947 const auto cCountIt = a_liveCellCount.find(cKey);
949 const int qCount = (qCountIt != a_liveCellCount.end()) ? qCountIt->second : 0;
950 const int cCount = (cCountIt != a_liveCellCount.end()) ? cCountIt->second : 0;
952 if (qCount > a_cellBudget(qKey) && cCount > a_cellBudget(cKey)) {
954 NNMergeResult<Packed> result;
956 result.firstID = query.globalID;
957 result.secondID = cand.globalID;
959 result.merged.position = mergedPos;
960 result.merged.weight = mergedWeight;
961 result.merged.globalID = a_allocateID();
962 result.merged.ownerRank = query.ownerRank;
966 result.merged.level = query.level;
967 result.merged.payload = a_combine(query.payload, query.weight, cand.payload, cand.weight);
969 a_results.push_back(result);
971 a_consumedIDs.insert(query.globalID);
972 a_consumedIDs.insert(cand.globalID);
974 a_liveCellCount[qKey] -= 1;
975 a_liveCellCount[cKey] -= 1;
977 if (a_crossLevelMergeCount !=
nullptr && query.level != cand.level) {
978 (*a_crossLevelMergeCount)++;
981 a_onMergeCommitted(query, cand, result.merged);
1000 if (!originalStale && !originalIneligible) {
1001 a_hasOutgoingCommitment.insert(query.globalID);
1007template <
typename Packed>
1009generateProposals(std::vector<std::vector<NNMergeProposal<Packed>>>& a_proposalsByDestRank,
1010 const std::vector<NNMergeEdge<Packed>>& a_edges,
1011 const std::unordered_set<ParticleID>& a_hasOutgoingCommitment,
1012 const std::unordered_map<
ParticleID, NNPooledParticle<Packed>>& a_particlesByID,
1013 const int a_numRanks)
noexcept
1015 CH_TIME(
"ParticleManagement::generateProposals");
1017 a_proposalsByDestRank.clear();
1018 a_proposalsByDestRank.resize(a_numRanks);
1020 for (
const auto& edge : a_edges) {
1021 if (a_hasOutgoingCommitment.count(edge.queryID) == 0) {
1025 const auto sourceIt = a_particlesByID.find(edge.queryID);
1026 const auto targetIt = a_particlesByID.find(edge.candidateID);
1028 if (sourceIt == a_particlesByID.end() || targetIt == a_particlesByID.end()) {
1032 NNMergeProposal<Packed> proposal;
1034 proposal.source = sourceIt->second.particle;
1035 proposal.targetID = edge.candidateID;
1036 proposal.distanceSquared = edge.distanceSquared;
1038 const int destRank =
static_cast<int>(targetIt->second.particle.ownerRank);
1040 CH_assert(destRank >= 0 && destRank < a_numRanks);
1042 a_proposalsByDestRank[destRank].push_back(proposal);
1046template <
typename Packed,
typename Combine,
typename PosVal
id,
typename Allocator,
typename OnMerge>
1048judgeProposals(std::unordered_map<NNCellKey, int, NNCellKeyHasher>& a_liveCellCount,
1049 std::vector<NNMergeResult<Packed>>& a_results,
1050 std::vector<std::vector<NNMergeVerdict>>& a_verdictsByDestRank,
1051 const std::vector<NNMergeProposal<Packed>>& a_incomingProposals,
1052 const std::unordered_map<
ParticleID, NNPooledParticle<Packed>>& a_particlesByID,
1053 const std::unordered_set<ParticleID>& a_consumedIDs,
1054 const std::unordered_set<ParticleID>& a_hasOutgoingCommitment,
1055 const std::map<ParticleID, ParticleID>& a_outgoingTargetOf,
1056 const Combine& a_combine,
1057 const NNCellBudget& a_cellBudget,
1058 const RealVect& a_probLo,
1059 const std::vector<RealVect>& a_dxByLevel,
1060 const PosValid& a_isPositionValid,
1061 const Allocator& a_allocateID,
1062 const std::optional<int> a_maxCellDistance,
1063 unsigned long long* a_crossLevelMergeCount,
1064 const OnMerge& a_onMergeCommitted)
noexcept
1066 CH_TIME(
"ParticleManagement::judgeProposals");
1074 std::map<ParticleID, std::vector<const NNMergeProposal<Packed>*>> byTarget;
1076 for (
const auto& p : a_incomingProposals) {
1077 byTarget[p.targetID].push_back(&p);
1080 struct TargetDecision
1083 const NNMergeProposal<Packed>* winner;
1086 std::vector<TargetDecision> decisions;
1087 decisions.reserve(byTarget.size());
1089 for (
auto& kv : byTarget) {
1091 auto& proposals = kv.second;
1093 const auto targetDataIt = a_particlesByID.find(targetID);
1095 if (targetDataIt == a_particlesByID.end() || a_consumedIDs.count(targetID) > 0) {
1097 for (
const NNMergeProposal<Packed>* p : proposals) {
1098 const int destRank =
static_cast<int>(p->source.ownerRank);
1099 a_verdictsByDestRank[destRank].push_back(NNMergeVerdict{p->source.globalID,
false});
1105 const bool targetHasOutgoing = a_hasOutgoingCommitment.count(targetID) > 0;
1107 const NNMergeProposal<Packed>* winner =
nullptr;
1109 if (!targetHasOutgoing) {
1111 for (
const NNMergeProposal<Packed>* p : proposals) {
1112 if (winner ==
nullptr || p->distanceSquared < winner->distanceSquared ||
1113 (p->distanceSquared == winner->distanceSquared && p->source.globalID < winner->source.globalID)) {
1120 const auto ownTargetIt = a_outgoingTargetOf.find(targetID);
1121 if (ownTargetIt != a_outgoingTargetOf.end()) {
1122 const ParticleID ownTarget = ownTargetIt->second;
1124 for (
const NNMergeProposal<Packed>* p : proposals) {
1125 if (p->source.globalID == ownTarget) {
1126 if (targetID < ownTarget) {
1136 if (winner !=
nullptr) {
1137 decisions.push_back(TargetDecision{targetID, winner});
1142 for (
const NNMergeProposal<Packed>* p : proposals) {
1144 const int destRank =
static_cast<int>(p->source.ownerRank);
1146 a_verdictsByDestRank[destRank].push_back(NNMergeVerdict{p->source.globalID,
false});
1152 std::sort(decisions.begin(), decisions.end(), [](
const TargetDecision& a_a,
const TargetDecision& a_b) {
1153 if (a_a.winner->distanceSquared != a_b.winner->distanceSquared) {
1154 return a_a.winner->distanceSquared < a_b.winner->distanceSquared;
1157 return a_a.targetID < a_b.targetID;
1160 for (
const TargetDecision& decision : decisions) {
1161 const NNMergeProposal<Packed>& proposal = *decision.winner;
1162 const auto targetIt = a_particlesByID.find(decision.targetID);
1164 CH_assert(targetIt != a_particlesByID.end() && a_consumedIDs.count(decision.targetID) == 0);
1166 const MergeParticle<Packed>& target = targetIt->second.particle;
1167 const MergeParticle<Packed>& source = proposal.source;
1169 const int sourceDestRank =
static_cast<int>(source.ownerRank);
1173 bool tooFar =
false;
1175 if (a_maxCellDistance.has_value()) {
1182 a_maxCellDistance.value());
1186 a_verdictsByDestRank[sourceDestRank].push_back(NNMergeVerdict{source.globalID,
false});
1190 RealVect mergedPos(D_DECL(0.0, 0.0, 0.0));
1191 Real mergedWeight = 0.0;
1193 nnMergeWeightedCentroid(mergedPos, mergedWeight, target.position, target.weight, source.position, source.weight);
1195 if (!a_isPositionValid(mergedPos)) {
1196 a_verdictsByDestRank[sourceDestRank].push_back(NNMergeVerdict{source.globalID,
false});
1208 const NNCellKey tKey(target.level,
cellKeyOf(target.position, a_probLo, a_dxByLevel[target.level]));
1209 const NNCellKey sKey(source.level,
cellKeyOf(source.position, a_probLo, a_dxByLevel[source.level]));
1211 const auto tCountIt = a_liveCellCount.find(tKey);
1212 const auto sCountIt = a_liveCellCount.find(sKey);
1214 const int tCount = (tCountIt != a_liveCellCount.end()) ? tCountIt->second : 0;
1215 const int sCount = (sCountIt != a_liveCellCount.end()) ? sCountIt->second : 0;
1217 if (tCount <= a_cellBudget(tKey) || sCount <= a_cellBudget(sKey)) {
1218 a_verdictsByDestRank[sourceDestRank].push_back(NNMergeVerdict{source.globalID,
false});
1224 NNMergeResult<Packed> result;
1226 result.firstID = target.globalID;
1227 result.secondID = source.globalID;
1229 result.merged.position = mergedPos;
1230 result.merged.weight = mergedWeight;
1231 result.merged.globalID = a_allocateID();
1232 result.merged.ownerRank = target.ownerRank;
1236 result.merged.level = target.level;
1237 result.merged.payload = a_combine(target.payload, target.weight, source.payload, source.weight);
1239 a_results.push_back(result);
1244 a_liveCellCount[tKey] -= 1;
1246 if (sCountIt != a_liveCellCount.end()) {
1247 a_liveCellCount[sKey] -= 1;
1250 if (a_crossLevelMergeCount !=
nullptr && target.level != source.level) {
1251 (*a_crossLevelMergeCount)++;
1254 a_onMergeCommitted(target, source, result.merged);
1256 a_verdictsByDestRank[sourceDestRank].push_back(NNMergeVerdict{source.globalID,
true});
1261applyVerdicts(std::unordered_set<ParticleID>& a_consumedIDs,
1262 const std::vector<NNMergeVerdict>& a_incomingVerdicts)
noexcept
1264 CH_TIME(
"ParticleManagement::applyVerdicts");
1266 for (
const auto& v : a_incomingVerdicts) {
1268 a_consumedIDs.insert(v.proposerID);
1273template <
typename Packed>
1275placeMergedParticles(std::map<std::pair<int, unsigned int>, std::vector<MergeParticle<Packed>>>& a_localByPatch,
1276 std::vector<std::vector<MergeParticle<Packed>>>& a_scatterByDestRank,
1277 const std::vector<NNMergeResult<Packed>>& a_results,
1278 const PositionLocator& a_locate,
1279 const int a_thisRank)
noexcept
1281 CH_TIME(
"ParticleManagement::placeMergedParticles");
1283 a_localByPatch.clear();
1286 for (
const auto& result : a_results) {
1295 MayDay::Error(
"ParticleManagement::placeMergedParticles -- merged particle position not "
1296 "found in any patch");
1299 if (loc.
rank == a_thisRank) {
1300 a_localByPatch[std::make_pair(loc.
level, loc.
gridIndex)].push_back(result.merged);
1303 a_scatterByDestRank[loc.
rank].push_back(result.merged);
1308template <
typename P,
typename Packed,
typename Traits,
typename Gather>
1311 std::vector<NNPatchWork>& a_patchWork,
1312 std::vector<std::vector<MergeParticle<Packed>>>& a_patchLocalValid,
1313 std::vector<std::vector<MergeParticle<Packed>>>& a_patchGhosts,
1314 std::unordered_map<
ParticleID, NNPooledParticle<Packed>>& a_particlesByID,
1315 std::unordered_map<NNCellKey, int, NNCellKeyHasher>& a_liveCellCount,
1317 const std::string& a_realm,
1318 const int a_finestLevel,
1319 const RealVect& a_probLo,
1320 const std::vector<RealVect>& a_dxByLevel,
1321 const int a_ghostWidth,
1322 const Gather& a_gather)
noexcept
1324 CH_TIME(
"ParticleManagement::gatherMergeParticles");
1326 a_patchWork.clear();
1327 a_patchLocalValid.clear();
1328 a_patchGhosts.clear();
1329 a_particlesByID.clear();
1330 a_liveCellCount.clear();
1335 const AMRMask& exposure = a_amr.getParticleGhostExposure(a_realm, a_ghostWidth);
1337 for (
int lvl = 0; lvl <= a_finestLevel; lvl++) {
1338 const RealVect& dx = a_dxByLevel[lvl];
1340 const DisjointBoxLayout& dbl = a_amr.getGrids(a_realm)[lvl];
1342 const DataIterator& dit = dbl.dataIterator();
1343 const int nbox = dit.size();
1349 for (
int mybox = 0; mybox < nbox; mybox++) {
1350 const DataIndex& din = dit[mybox];
1354 std::vector<MergeParticle<Packed>> localValid;
1355 std::vector<MergeParticle<Packed>> ghosts;
1358 ghosts.reserve(leaf.
size());
1360 a_particlesByID.reserve(a_particlesByID.size() + leaf.
size());
1362 const BaseFab<bool>& exposureDin = (*exposure[lvl])[din];
1364 for (std::size_t i = 0; i < leaf.
size(); i++) {
1365 const bool isGhost = leaf.
isGhost(i);
1367 int originLevel = lvl;
1369 switch (leaf.
ghost(i)) {
1371 originLevel = lvl - 1;
1376 originLevel = lvl + 1;
1388 MergeParticle<Packed> p;
1391 p.weight = leaf.
weight(i);
1393 p.ownerRank = leaf.
rankID(i);
1394 p.level = originLevel;
1395 p.payload = a_gather(leaf, i);
1397 const IntVect cell =
cellKeyOf(p.position, a_probLo, a_dxByLevel[originLevel]);
1400 ghosts.push_back(p);
1401 a_particlesByID[p.globalID] = NNPooledParticle<Packed>{p,
false};
1406 CH_assert(exposureDin.box().contains(cell));
1408 const bool isExposed = exposureDin(cell, 0);
1410 localValid.push_back(p);
1411 a_particlesByID[p.globalID] = NNPooledParticle<Packed>{p, isExposed};
1415 a_patchWork.push_back(NNPatchWork{lvl, din, dx});
1416 a_patchLocalValid.push_back(std::move(localValid));
1417 a_patchGhosts.push_back(std::move(ghosts));
1424 for (
const auto& kv : a_particlesByID) {
1425 const MergeParticle<Packed>& p = kv.second.particle;
1426 const IntVect cell =
cellKeyOf(p.position, a_probLo, a_dxByLevel[p.level]);
1428 a_liveCellCount[
NNCellKey(p.level, cell)] += 1;
1432template <
typename P,
1442 std::unordered_map<
ParticleID, NNPooledParticle<Packed>>& a_particlesByID,
1443 std::unordered_set<ParticleID>& a_consumedIDs,
1444 std::unordered_map<NNCellKey, int, NNCellKeyHasher>& a_liveCellCount,
1445 std::vector<NNMergeResult<Packed>>& a_allResults,
1446 unsigned long long* a_crossLevelMergeCount,
1448 const std::string& a_realm,
1449 const NNCellBudget& a_cellBudget,
1450 const Combine& a_combine,
1451 const Scatter& a_scatter,
1452 const Allocator& a_allocateID,
1453 const std::optional<int> a_maxCellDistance,
1454 const PosValid& a_isPositionValid,
1455 const RealVect& a_probLo,
1456 const std::vector<RealVect>& a_dxByLevel,
1457 const std::vector<NNPatchWork>& a_patchWork,
1458 const std::vector<NNMergeEdge<Packed>>& a_pooledEdges,
1459 const std::unordered_set<ParticleID>& a_hasOutgoingCommitment,
1460 const OnMerge& a_onMergeCommitted)
noexcept
1462 CH_TIME(
"ParticleManagement::finishMergeRound");
1464 const int myRank = procID();
1465 const int numRanks =
static_cast<int>(numProc());
1470 std::map<ParticleID, ParticleID> outgoingTargetOf;
1472 for (
const auto& edge : a_pooledEdges) {
1473 if (a_hasOutgoingCommitment.count(edge.queryID) > 0) {
1474 outgoingTargetOf[edge.queryID] = edge.candidateID;
1478 std::vector<std::vector<NNMergeProposal<Packed>>> proposalsByDestRank;
1479 generateProposals(proposalsByDestRank, a_pooledEdges, a_hasOutgoingCommitment, a_particlesByID, numRanks);
1481 const std::vector<NNMergeProposal<Packed>> incomingProposals =
nnMergeExchangeByRank(proposalsByDestRank);
1483 std::vector<NNMergeResult<Packed>> judgeResults;
1484 std::vector<std::vector<NNMergeVerdict>> verdictsByDestRank(numRanks);
1486 judgeProposals(a_liveCellCount,
1492 a_hasOutgoingCommitment,
1501 a_crossLevelMergeCount,
1502 a_onMergeCommitted);
1504 a_allResults.insert(a_allResults.end(), judgeResults.begin(), judgeResults.end());
1506 for (
const auto& r : judgeResults) {
1507 a_consumedIDs.insert(r.firstID);
1512 applyVerdicts(a_consumedIDs, incomingVerdicts);
1518 return a_particles.findDestination(a_pos);
1521 std::map<std::pair<int, unsigned int>, std::vector<MergeParticle<Packed>>> localByPatch;
1522 std::vector<std::vector<MergeParticle<Packed>>> scatterByDestRank(numRanks);
1524 placeMergedParticles(localByPatch, scatterByDestRank, a_allResults, locate, myRank);
1526 const std::vector<MergeParticle<Packed>> incomingScattered =
nnMergeExchangeByRank(scatterByDestRank);
1529 for (std::size_t pw = 0; pw < a_patchWork.size(); pw++) {
1532 for (std::size_t i = 0; i < leaf.
size();) {
1542 a_particles.clearGhostParticles();
1544 for (
const auto& kv : localByPatch) {
1545 const int level = kv.first.first;
1546 const unsigned int gridIndex = kv.first.second;
1548 const DataIndex din = a_amr.getLevelTiles(a_realm)[level]->getMyGrids().at(gridIndex);
1552 for (
const auto& merged : kv.second) {
1555 MergeParticle<Packed> corrected = merged;
1557 corrected.ownerRank = myRank;
1558 a_scatter(leaf, corrected);
1562 for (
const auto& merged : incomingScattered) {
1566 MayDay::Error(
"ParticleManagement::finishMergeRound -- incoming scattered particle not "
1567 "found in any of this rank's own patches");
1570 const DataIndex din = a_amr.getLevelTiles(a_realm)[loc.
level]->getMyGrids().at(loc.
gridIndex);
1572 MergeParticle<Packed> corrected = merged;
1573 corrected.ownerRank = myRank;
1576 a_scatter(leaf, corrected);
1580template <
typename P,
1592 unsigned long long* a_crossLevelMergeCount,
1594 const NNCellBudget& a_cellBudget,
1595 const Gather& a_gather,
1596 const Combine& a_combine,
1597 const Scatter& a_scatter,
1598 const Allocator& a_allocateID,
1599 const bool a_iterateLocalTierToConvergence,
1600 const int a_maxFallbackCandidates,
1601 const std::optional<int> a_maxCellDistance,
1602 const int a_ghostWidth,
1603 const PosValid& a_isPositionValid,
1604 const OnMerge& a_onMergeCommitted)
1606 CH_TIME(
"ParticleManagement::mergeNearestNeighborsRoundImpl");
1608 const std::string realm = a_particles.
getRealm();
1610 const RealVect probLo = a_amr.
getProbLo();
1617 std::vector<RealVect> dxByLevel(finestLevel + 1);
1619 for (
int lvl = 0; lvl <= finestLevel; lvl++) {
1620 dxByLevel[lvl] = a_amr.
getDx()[lvl] * RealVect::Unit;
1623 CH_assert(a_ghostWidth >= 1);
1624 const int ghostWidth = a_ghostWidth;
1633 std::vector<NNPatchWork> patchWork;
1634 std::vector<std::vector<MergeParticle<Packed>>> patchLocalValid;
1635 std::vector<std::vector<MergeParticle<Packed>>> patchGhosts;
1636 std::unordered_map<ParticleID, NNPooledParticle<Packed>> particlesByID;
1637 std::unordered_map<NNCellKey, int, NNCellKeyHasher> liveCellCount;
1639 gatherMergeParticles<P, Packed, Traits>(a_particles,
1657 std::vector<NNSpatialIndex<Packed, Cloud>> patchSpatialIndex;
1658 patchSpatialIndex.reserve(patchWork.size());
1660 for (std::size_t pw = 0; pw < patchWork.size(); pw++) {
1661 NNSpatialIndex<Packed, Cloud> spatialIndex;
1663 buildNNSpatialIndex(spatialIndex,
1664 patchLocalValid[pw],
1669 1 + a_maxFallbackCandidates);
1671 patchSpatialIndex.push_back(std::move(spatialIndex));
1674 std::unordered_set<ParticleID> consumedIDs;
1675 std::unordered_set<ParticleID> hasOutgoingCommitment;
1677 std::vector<NNMergeResult<Packed>> allResults;
1685 std::vector<NNMergeEdge<Packed>> lastPooledEdges;
1687 auto runLocalTierPass = [&]() ->
bool {
1688 lastPooledEdges.clear();
1690 for (std::size_t pw = 0; pw < patchWork.size(); pw++) {
1694 std::vector<NNMergeEdge<Packed>> patchEdges;
1696 findNearestNeighborCandidates(patchEdges,
1697 patchLocalValid[pw],
1700 patchSpatialIndex[pw],
1705 a_maxFallbackCandidates,
1708 lastPooledEdges.insert(lastPooledEdges.end(), patchEdges.begin(), patchEdges.end());
1711 std::vector<NNMergeResult<Packed>> passResults;
1713 resolveTrivialTier(liveCellCount,
1715 hasOutgoingCommitment,
1725 a_maxFallbackCandidates,
1727 a_crossLevelMergeCount,
1728 a_onMergeCommitted);
1730 allResults.insert(allResults.end(), passResults.begin(), passResults.end());
1732 return !passResults.empty();
1735 if (a_iterateLocalTierToConvergence) {
1736 constexpr int safetyCap = 1000;
1738 for (
int iter = 0; iter < safetyCap; iter++) {
1741 hasOutgoingCommitment.clear();
1743 if (!runLocalTierPass()) {
1754 finishMergeRound<P, Packed, Traits>(a_particles,
1759 a_crossLevelMergeCount,
1772 hasOutgoingCommitment,
1773 a_onMergeCommitted);
1778template <
typename P,
1791 const Gather& a_gather,
1792 const Combine& a_combine,
1793 const Scatter& a_scatter,
1794 const Allocator& a_allocateID,
1795 const bool a_iterateLocalTierToConvergence,
1796 const int a_maxFallbackCandidates,
1797 const std::optional<int> a_maxCellDistance,
1798 const int a_ghostWidth,
1799 const PosValid& a_isPositionValid,
1800 unsigned long long* a_crossLevelMergeCount,
1801 const OnMerge& a_onMergeCommitted)
1803 CH_TIME(
"ParticleManagement::mergeNearestNeighborsTree");
1805 detail::mergeNearestNeighborsRoundImpl<P, Packed, Traits, EBGeometry::PointCloudBVH<Real, ParticleID>>(
1807 a_crossLevelMergeCount,
1814 a_iterateLocalTierToConvergence,
1815 a_maxFallbackCandidates,
1819 a_onMergeCommitted);
1822template <
typename P,
1835 const Gather& a_gather,
1836 const Combine& a_combine,
1837 const Scatter& a_scatter,
1838 const Allocator& a_allocateID,
1839 const bool a_iterateLocalTierToConvergence,
1840 const int a_maxFallbackCandidates,
1841 const std::optional<int> a_maxCellDistance,
1842 const int a_ghostWidth,
1843 const PosValid& a_isPositionValid,
1844 unsigned long long* a_crossLevelMergeCount,
1845 const OnMerge& a_onMergeCommitted)
1847 CH_TIME(
"ParticleManagement::mergeNearestNeighborsHash");
1849 detail::mergeNearestNeighborsRoundImpl<P, Packed, Traits, EBGeometry::PointCloudHashGrid<Real, ParticleID>>(
1851 a_crossLevelMergeCount,
1858 a_iterateLocalTierToConvergence,
1859 a_maxFallbackCandidates,
1863 a_onMergeCommitted);
1866template <
typename P,
1879 const Gather& a_gather,
1880 const Combine& a_combine,
1881 const Scatter& a_scatter,
1882 const Allocator& a_allocateID,
1883 const bool a_iterateLocalTierToConvergence,
1884 const int a_maxFallbackCandidates,
1885 const PosValid& a_isPositionValid,
1886 unsigned long long* a_crossLevelMergeCount,
1887 const OnMerge& a_onMergeCommitted)
1889 CH_TIME(
"ParticleManagement::mergeNearestNeighborsOneCell");
1891 using namespace detail;
1893 const std::string realm = a_particles.
getRealm();
1895 const RealVect probLo = a_amr.
getProbLo();
1899 std::vector<RealVect> dxByLevel(finestLevel + 1);
1901 for (
int lvl = 0; lvl <= finestLevel; lvl++) {
1902 dxByLevel[lvl] = a_amr.
getDx()[lvl] * RealVect::Unit;
1908 constexpr int ghostWidth = 1;
1909 const std::optional<int> maxCellDistance = 1;
1911 std::vector<NNPatchWork> patchWork;
1912 std::vector<std::vector<MergeParticle<Packed>>> patchLocalValid;
1913 std::vector<std::vector<MergeParticle<Packed>>> patchGhosts;
1914 std::unordered_map<ParticleID, NNPooledParticle<Packed>> particlesByID;
1915 std::unordered_map<NNCellKey, int, NNCellKeyHasher> liveCellCount;
1917 gatherMergeParticles<P, Packed, Traits>(a_particles,
1933 std::vector<NNCellSpatialIndex> patchCellSpatialIndex;
1934 patchCellSpatialIndex.reserve(patchWork.size());
1936 for (std::size_t pw = 0; pw < patchWork.size(); pw++) {
1937 NNCellSpatialIndex cellIndex;
1939 buildNNCellSpatialIndex(cellIndex, patchLocalValid[pw], patchGhosts[pw], probLo, patchWork[pw].dx, a_cellBudget);
1941 patchCellSpatialIndex.push_back(std::move(cellIndex));
1944 std::unordered_set<ParticleID> consumedIDs;
1945 std::unordered_set<ParticleID> hasOutgoingCommitment;
1947 std::vector<NNMergeResult<Packed>> allResults;
1948 std::vector<NNMergeEdge<Packed>> lastPooledEdges;
1950 auto runLocalTierPass = [&]() ->
bool {
1951 lastPooledEdges.clear();
1953 for (std::size_t pw = 0; pw < patchWork.size(); pw++) {
1954 std::vector<NNMergeEdge<Packed>> patchEdges;
1956 findNearestNeighborCandidatesOneCell(patchEdges,
1957 patchLocalValid[pw],
1959 patchCellSpatialIndex[pw],
1965 a_maxFallbackCandidates);
1967 lastPooledEdges.insert(lastPooledEdges.end(), patchEdges.begin(), patchEdges.end());
1970 std::vector<NNMergeResult<Packed>> passResults;
1972 resolveTrivialTier(liveCellCount,
1974 hasOutgoingCommitment,
1984 a_maxFallbackCandidates,
1986 a_crossLevelMergeCount,
1987 a_onMergeCommitted);
1989 allResults.insert(allResults.end(), passResults.begin(), passResults.end());
1991 return !passResults.empty();
1994 if (a_iterateLocalTierToConvergence) {
1995 constexpr int safetyCap = 1000;
1997 for (
int iter = 0; iter < safetyCap; iter++) {
1998 hasOutgoingCommitment.clear();
2000 if (!runLocalTierPass()) {
2009 finishMergeRound<P, Packed, Traits>(a_particles,
2014 a_crossLevelMergeCount,
2027 hasOutgoingCommitment,
2028 a_onMergeCommitted);
2033#include <CD_NamespaceFooter.H>
Declaration of core class for handling AMR-related operations (with embedded boundaries)
Declaration of a distributed, MPI-safe nearest-neighbor particle-merge algorithm.
@ Coarse
Ghost from the next-coarser level (level l-1).
@ Fine
Ghost from the next-finer level (level l+1).
std::int64_t ParticleID
Global particle identifier type (container-owned metadata column; fixed-width for I/O).
Definition CD_ParticleSoA.H:161
Vector< RefCountedPtr< LevelData< BaseFab< bool > > > > AMRMask
Alias for cutting down on the typic of booleans defined over AMR grids.
Definition CD_Realm.H:33
Class for handling spatial operations.
Definition CD_AmrMesh.H:45
RealVect getProbLo() const
Get lower-left corner of computational domain.
Definition CD_AmrMesh.cpp:3042
const Vector< Real > & getDx() const
Get spatial resolutions.
Definition CD_AmrMesh.cpp:3215
int getFinestLevel() const
Get finest grid level.
Definition CD_AmrMesh.cpp:3064
AMR-hierarchy container of computational particles, stored per patch in Struct-of-Arrays form.
Definition CD_ParticleContainer.H:123
std::string getRealm() const
Realm label.
Definition CD_ParticleContainer.H:300
The per-cell particle count this merge drains each cell down to.
Definition CD_NearestNeighborParticleMerge.H:372
Arena-backed Struct-of-Arrays particle container for a single grid patch.
Definition CD_ParticleSoA.H:655
RankID & rankID(const std::size_t a_index) noexcept
Owning rank of particle i (container-owned metadata).
Definition CD_ParticleSoA.H:1279
bool isGhost(const std::size_t a_index) const noexcept
Whether particle i is a ghost particle (any non-Valid designation).
Definition CD_ParticleSoA.H:1331
RealVect position(const std::size_t a_index) const noexcept
Position of particle i as a RealVect (by value, assembled from the scalar columns).
Definition CD_ParticleSoA.H:1188
GhostType & ghost(const std::size_t a_index) noexcept
Ghost designation of particle i (container-owned metadata).
Definition CD_ParticleSoA.H:1305
double & weight(const std::size_t a_index) noexcept
Weight of particle i.
Definition CD_ParticleSoA.H:1222
std::size_t size() const noexcept
Number of particles currently stored.
Definition CD_ParticleSoA.H:882
ParticleID & particleID(const std::size_t a_index) noexcept
Global id of particle i (container-owned metadata).
Definition CD_ParticleSoA.H:1253
void remove(const std::size_t a_index) noexcept
Remove particle i using swap-and-pop (O(1), does NOT preserve order).
Definition CD_ParticleSoA.H:1033
void reserve(const std::size_t a_capacity)
Ensure capacity for at least a_capacity particles (reallocates + moves on growth).
Definition CD_ParticleSoAImplem.H:88
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
IntVect cellKeyOf(const RealVect &a_position, const RealVect &a_probLo, const RealVect &a_dx) noexcept
The unclamped, position-derived cell index a given physical position falls in.
Definition CD_NearestNeighborParticleMergeImplem.H:254
int nnMergeChebyshevCellDistance(const IntVect &a_a, const IntVect &a_b) noexcept
Chebyshev (max-per-axis) distance between two cell-index vectors – see CD_NearestNeighborParticleMerg...
Definition CD_NearestNeighborParticleMergeImplem.H:64
void nnMergeWeightedCentroid(RealVect &a_mergedPos, Real &a_mergedWeight, const RealVect &a_posA, const Real a_weightA, const RealVect &a_posB, const Real a_weightB) noexcept
Weighted-centroid merge of two positions/weights – shared by resolveTrivialTier() and judgeProposals(...
Definition CD_NearestNeighborParticleMergeImplem.H:118
Real nnMergePointToCellDistSquared(const RealVect &a_point, const IntVect &a_cell, const RealVect &a_probLo, const RealVect &a_dx) noexcept
Squared distance from a physical point to a cell's axis-aligned bounding box (0 if the point is insid...
Definition CD_NearestNeighborParticleMergeImplem.H:142
bool nnMergeCrossLevelTooFar(const RealVect &a_posA, const int a_levelA, const RealVect &a_posB, const int a_levelB, const RealVect &a_probLo, const std::vector< RealVect > &a_dxByLevel, const int a_maxCellDistance) noexcept
a_maxCellDistance check for two positions that may be on DIFFERENT AMR levels.
Definition CD_NearestNeighborParticleMergeImplem.H:92
bool nnMergeAnyCellCrowded(const std::vector< MergeParticle< Packed > > &a_localValid, const std::vector< MergeParticle< Packed > > &a_ghosts, const RealVect &a_probLo, const RealVect &a_dx, const NNCellBudget &a_cellBudget) noexcept
Exact per-cell crowding pre-filter shared by buildNNSpatialIndex()/buildNNCellSpatialIndex(): is any ...
Definition CD_NearestNeighborParticleMergeImplem.H:281
Real nnMergeDistSquared(const RealVect &a_a, const RealVect &a_b) noexcept
Squared Euclidean distance between two physical positions.
Definition CD_NearestNeighborParticleMergeImplem.H:43
std::vector< T > nnMergeExchangeByRank(const std::vector< std::vector< T > > &a_sendByRank)
Generic alltoallv-style exchange: send a per-destination-rank bucket of trivially- copyable records,...
Definition CD_NearestNeighborParticleMergeImplem.H:182
std::pair< int, IntVect > NNCellKey
A cell key together with the AMR level it was computed on – see cellKeyOf()'s warning.
Definition CD_NearestNeighborParticleMerge.H:323
Namespace for various particle management tools.
Definition CD_KDParticleMerge.H:33
void mergeNearestNeighborsOneCell(ParticleContainer< P, Traits > &a_particles, const AmrMesh &a_amr, const NNCellBudget &a_cellBudget, const Gather &a_gather, const Combine &a_combine, const Scatter &a_scatter, const Allocator &a_allocateID, const bool a_iterateLocalTierToConvergence=false, const int a_maxFallbackCandidates=0, const PosValid &a_isPositionValid=[](const RealVect &) { return true;}, unsigned long long *a_crossLevelMergeCount=nullptr, const OnMerge &a_onMergeCommitted=[](const MergeParticle< Packed > &, const MergeParticle< Packed > &, const MergeParticle< Packed > &) { })
Run one full round of the one-cell nearest-neighbor merge: the same distributed propose/judge/verdict...
Definition CD_NearestNeighborParticleMergeImplem.H:1876
void mergeNearestNeighborsHash(ParticleContainer< P, Traits > &a_particles, const AmrMesh &a_amr, const NNCellBudget &a_cellBudget, const Gather &a_gather, const Combine &a_combine, const Scatter &a_scatter, const Allocator &a_allocateID, const bool a_iterateLocalTierToConvergence=false, const int a_maxFallbackCandidates=0, const std::optional< int > a_maxCellDistance=std::nullopt, const int a_ghostWidth=1, const PosValid &a_isPositionValid=[](const RealVect &) { return true;}, unsigned long long *a_crossLevelMergeCount=nullptr, const OnMerge &a_onMergeCommitted=[](const MergeParticle< Packed > &, const MergeParticle< Packed > &, const MergeParticle< Packed > &) { })
Run one full round of the nearest-neighbor merge algorithm, identical in every respect to mergeNeares...
Definition CD_NearestNeighborParticleMergeImplem.H:1832
void mergeNearestNeighborsTree(ParticleContainer< P, Traits > &a_particles, const AmrMesh &a_amr, const NNCellBudget &a_cellBudget, const Gather &a_gather, const Combine &a_combine, const Scatter &a_scatter, const Allocator &a_allocateID, const bool a_iterateLocalTierToConvergence=false, const int a_maxFallbackCandidates=0, const std::optional< int > a_maxCellDistance=std::nullopt, const int a_ghostWidth=1, const PosValid &a_isPositionValid=[](const RealVect &) { return true;}, unsigned long long *a_crossLevelMergeCount=nullptr, const OnMerge &a_onMergeCommitted=[](const MergeParticle< Packed > &, const MergeParticle< Packed > &, const MergeParticle< Packed > &) { })
Run one full round (one timestep's worth) of the nearest-neighbor merge algorithm.
Definition CD_NearestNeighborParticleMergeImplem.H:1788
constexpr Real c
Speed of light.
Definition CD_Units.H:40
Result of a point->block query. See findDestination.
Definition CD_LevelTiles.H:123
unsigned int gridIndex
Global grid/box index of the covering box within that level's LevelTiles.
Definition CD_LevelTiles.H:132
int rank
MPI rank owning that box.
Definition CD_LevelTiles.H:137
int level
Owning AMR level (finest tile that contains the point), or -1 if not found.
Definition CD_LevelTiles.H:127
bool valid
True if a covering tile/box was found (false when the point is off-domain).
Definition CD_LevelTiles.H:142
Hash functor for using IntVect tiles as unordered_map keys.
Definition CD_LevelTiles.H:55
Minimal, payload-agnostic description of one particle as input to a distributed merge.
Definition CD_ParticleManagement.H:430