chombo-discharge
Loading...
Searching...
No Matches
Classes | Typedefs | Functions
ParticleManagement::detail Namespace Reference

Internal-only implementation details of the ParticleManagement API. More...

Classes

struct  KDLeaf
 One leaf of a kd tree: a contiguous index range into the (in-place reordered) particle buffer, plus its axis-aligned bounding box. More...
 
struct  NNCellKeyHasher
 Hash functor for NNCellKey – REQUIRED whenever NNCellKey is used as a hash-map key; NNCellKey must never be used as an ordered (std::map or std::set) key. More...
 
struct  NNFallbackCandidate
 One additional ranked candidate beyond a query's primary nearest neighbor. More...
 
struct  NNMergeEdge
 One candidate merge edge: a single particle's own nearest-neighbor query result. More...
 
struct  NNMergeProposal
 A cross-patch merge proposal: one particle's own nearest-neighbor query result, bundled as a self-contained, atomic pair for shipment to whichever rank owns the candidate. More...
 
struct  NNMergeResult
 One resolved merge: the two consumed particles' ids and the newly created merged particle (not yet placed into any patch – see placeMergedParticles()). More...
 
struct  NNMergeVerdict
 The judge's reply to one proposal. More...
 
struct  NNPooledParticle
 Pooled per-round record for one particle: the particle plus its cached boundary-exposure flag. More...
 

Typedefs

using NNCellKey = std::pair< int, IntVect >
 A cell key together with the AMR level it was computed on – see cellKeyOf()'s warning.
 

Functions

template<typename Packed >
void buildKDQuotaLeaves (std::vector< MergeParticle< Packed > > &a_particles, FArrayBox &a_used, std::vector< KDLeaf > &a_leaves, const int a_ppc, const Real a_splitWeightLeafDx, const RealVect &a_dx, const RealVect &a_probLo, const FArrayBox &a_cellCounts) noexcept
 Build one whole-patch kd tree: partition a_particles by position into leaves, each of which becomes exactly one super-particle, subject to a live per-cell leaf quota of a_ppc.
 
template<typename T >
std::vector< T > kdExchangeByRank (const std::vector< std::vector< T > > &a_sendByRank)
 Generic Alltoallv-style exchange for a trivially-copyable record type: send a per-destination-rank bucket, return the flat concatenation of everything received.
 
template<typename Packed >
void kdBBox (RealVect &a_boxLo, RealVect &a_boxHi, const std::vector< MergeParticle< Packed > > &a_particles, const std::size_t a_lo, const std::size_t a_hi) noexcept
 Axis-aligned bounding box of particles[a_lo, a_hi).
 
Real kdMaxAxisSpan (const RealVect &a_boxLo, const RealVect &a_boxHi, const RealVect &a_dx) noexcept
 Largest per-axis extent of [a_boxLo,a_boxHi], expressed as a fraction of that axis's own cell width.
 
IntVect kdCellKeyOf (const RealVect &a_position, const RealVect &a_probLo, const RealVect &a_dx) noexcept
 The unclamped, position-derived cell index a physical position falls in.
 
bool kdIntVectLess (const IntVect &a_lhs, const IntVect &a_rhs) noexcept
 Strict weak ordering over IntVect, lexicographic component-wise.
 
template<typename Packed >
void kdFillCellHistogram (FArrayBox &a_counts, const std::vector< MergeParticle< Packed > > &a_particles, const RealVect &a_probLo, const RealVect &a_dx) noexcept
 Tally a per-cell particle-count histogram over one patch's gathered particles.
 
template<typename Packed >
std::size_t kdSplitCountMedian (std::vector< MergeParticle< Packed > > &a_particles, const std::size_t a_lo, const std::size_t a_hi, const int a_axis) noexcept
 Split particles[a_lo,a_hi) in place by the longest axis at the count-median – the sole split rule used by buildKDQuotaLeaves().
 
template<typename Packed >
std::size_t kdSplitWeightMedian (std::vector< MergeParticle< Packed > > &a_particles, const std::size_t a_lo, const std::size_t a_hi, const int a_axis) noexcept
 Split particles[a_lo,a_hi) in place by the longest axis at the WEIGHT median – the plane with half this node's total particle weight on either side.
 
void kdBoxRealBounds (RealVect &a_boxLo, RealVect &a_boxHi, const Box &a_box, const RealVect &a_dx, const RealVect &a_probLo) noexcept
 Real-space bounds of a box: the half-open region [lo, hi) that its cells cover.
 
void kdCheckCentroid (const RealVect &a_centroid, const Real a_totalWeight, const RealVect &a_boxLo, const RealVect &a_boxHi) noexcept
 Debug-only sanity check on a merged particle's position: a weighted centroid of positions that all lie within [a_boxLo,a_boxHi], using strictly positive weights, is mathematically guaranteed to itself lie within that same box.
 
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.
 
Real nnMergeDistSquared (const RealVect &a_a, const RealVect &a_b) noexcept
 Squared Euclidean distance between two physical positions.
 
int nnMergeChebyshevCellDistance (const IntVect &a_a, const IntVect &a_b) noexcept
 Chebyshev (max-per-axis) distance between two cell-index vectors – see CD_NearestNeighborParticleMerge.H's a_maxCellDistance docs.
 
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.
 
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() so the formula is written exactly once.
 
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 inside).
 
template<typename T >
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, return the flat concatenation of everything received.
 
template<typename Packed >
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 cell in this patch's local+ghost particle set over its a_cellBudget?
 
template<class P , Real P::* weight, RealVect P::* position>
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.
 
template<class P , RealVect P::* position>
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 nearest adjacent pair.
 
template<typename T , typename >
partitionParticles (const T a_numParticles)
 Partition particles so that all MPI rank draw a cumulative number of particles equal to a_numParticles.
 

Detailed Description

Internal-only implementation details of the ParticleManagement API.

Internal-only implementation details of the ParticleManagement API (continued).

These helpers have no external callers; they are the algorithms sitting behind the public factory/wrapper functions (make*Merger, partitionParticleWeights) and are not part of the public interface. Public callers qualify them with detail::.

Reopens the detail namespace introduced above to house partitionParticles alongside the other internal helpers; see the first detail block for the full rationale. Not part of the public interface – public callers qualify with detail::.

Typedef Documentation

◆ NNCellKey

using ParticleManagement::detail::NNCellKey = typedef std::pair<int, IntVect>

A cell key together with the AMR level it was computed on – see cellKeyOf()'s warning.

Two NNCellKeys are equal only if BOTH the level and the cell index match; a coarse- level cell and an unrelated fine-level cell that happen to share the same bare IntVect are correctly kept distinct because their level differs. This is the key type for every live per-cell count pooled across multiple levels (see a_liveCellCount in resolveTrivialTier()/ judgeProposals()/findNearestNeighborCandidates()).

Function Documentation

◆ buildEqualWeightKDLeaves()

template<class P , Real P::* weight, RealVect P::* position>
void ParticleManagement::detail::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 
)
inlinenoexcept

Build an equal-weight KD partition of a list of particles and return the leaf particle ranges.

Partitions the input particles into at most a_maxLeaves spatial groups ("leaves") whose total weights are as equal as possible (they differ by at most one physical particle between sibling nodes). At each split the particles are partitioned along the longest bounding-box axis at the weight-median; the median particle may itself be split so the two halves balance, in which case a_particleReconcile fixes up the daughters' non-weight members. This is the partitioning primitive behind super-particle merging: the caller reduces each returned leaf range to a single merged particle.

Implementation note: the tree is built in a flat, reusable per-thread scratch arena (two ping-ponged contiguous particle buffers + a flat range list), so there is no per-node heap allocation, no pointer chasing, and the scratch is reused across calls on the same thread.

Template parameters: P -> Particle type exposing the weight/position data members below P::*weight -> Pointer to the Real data member holding the particle weight P::*position -> Pointer to the RealVect data member holding the particle position.

P must be an AoS-style particle type carrying weight and position as data members – MergeParticle is the one this library provides, and the one every in-tree caller uses. ParticleSoA payload types (ItoParticle, ...) store those two in the container rather than the payload, so pack the columns into such an intermediary AoS structure first.

Parameters
[in]a_particlesInput particles (copied into internal scratch; not modified).
[in]a_maxLeavesMaximum number of leaf nodes (the per-cell target particle count).
[in]a_particleReconcileReconciliation callback applied to the two daughters when the median is split.
[out]a_leavesFilled with one {begin,end} pointer pair per leaf.
Warning
The returned ranges alias an internal thread-local buffer that is overwritten on the next call to this function on the same thread. Consume them (reduce each leaf) before calling again.

◆ buildKDQuotaLeaves()

template<typename Packed >
void ParticleManagement::detail::buildKDQuotaLeaves ( std::vector< MergeParticle< Packed > > &  a_particles,
FArrayBox &  a_used,
std::vector< KDLeaf > &  a_leaves,
const int  a_ppc,
const Real  a_splitWeightLeafDx,
const RealVect &  a_dx,
const RealVect &  a_probLo,
const FArrayBox &  a_cellCounts 
)
inlinenoexcept

Build one whole-patch kd tree: partition a_particles by position into leaves, each of which becomes exactly one super-particle, subject to a live per-cell leaf quota of a_ppc.

a_particles is reordered IN PLACE, so each returned leaf's [lo,hi) range is contiguous in the reordered buffer; no particle is ever copied or split synthetically.

A leaf collapses to exactly ONE super-particle at its own weighted centroid, so the number of leaves whose centroid falls in a cell is that cell's post-merge population. That count is tracked live in a per-cell map while splitting, and a split that would push a cell past a_ppc is refused. Splitting takes the heaviest candidate leaf first and stops when nothing may legally be split.

Splits are along the longest real-space axis and are deliberately UNSNAPPED to the grid, so a leaf may straddle a cell face and draw members from either side. The split plane is the count median while the node is wider than a_splitWeightLeafDx cell widths, and the weight median once it is narrower: above that scale a split apportions leaves between cells, which is a question of counts; below it the weight median drives the resulting super-particles toward equal weight.

The quota constrains only leaves that can actually merge. mergeKDCarve() and mergeKDPatch() veto any leaf wider than s_kdMaxLeafExtent and leaves its members behind individually, so such a leaf costs its cell its full member count rather than one particle; the quota therefore does not block splitting it further. As a result a cell can finish slightly above a_ppc when a leaf is both too wide to merge and in a cell already at quota, since both available choices overshoot.

Particles in cells at or below a_ppc are excluded from the partition and emitted as singleton leaves. A singleton never merges (the merge threshold is two members), so an uncrowded cell cannot be drained by a neighbour's merge.

Template Parameters
PackedSee MergeParticle.
Parameters
[in,out]a_particlesParticles to partition; reordered in place.
[in,out]a_usedLive per-cell leaf quota for this patch, zeroed on entry. Its box must cover every cell a leaf centroid can fall in, i.e. it needs at least one ghost cell.
[out]a_leavesEvery resulting leaf, in no particular order.
[in]a_ppcTarget particles per cell, enforced here as a per-cell ceiling on leaf count.
[in]a_splitWeightLeafDxLeaf size, in cell widths, at or below which the split plane switches from the count median to the weight median. 0 disables the weight median.
[in]a_dxThis patch's own cell spacing.
[in]a_probLoPhysical position of the domain's low corner, for computing cell indices.
[in]a_cellCountsPer-cell TRUE particle-count histogram (see kdFillCellHistogram()). A cell is crowded, and its particles eligible to merge, iff its count exceeds a_ppc. A cell outside this box reads as 0 (empty): unlike a_used, whose box coverage is a hard precondition, this lookup guards rather than asserts, since a particle's own cell is always in-box but the guard costs nothing and documents the intent.

◆ cellKeyOf()

IntVect ParticleManagement::detail::cellKeyOf ( const RealVect &  a_position,
const RealVect &  a_probLo,
const RealVect &  a_dx 
)
inlinenoexcept

The unclamped, position-derived cell index a given physical position falls in.

The cell-key convention used throughout for the crowding trigger and a_maxCellDistance. Not clamped to any patch's box, so a ghost buckets into its true origin cell and particles in different patches' local frames stay directly comparable.

Warning
a_dx MUST be the cell spacing of the AMR level the position's particle lives on (see MergeParticle::level), never a single fixed dx shared across levels. The returned IntVect is only meaningful together with that level; NNCellKey pairs the two so they cannot be conflated (pooling bare cell keys from different levels aliases coarse- and fine-level cell counts).
Parameters
[in]a_positionPhysical position.
[in]a_probLoPhysical position of the domain's low corner.
[in]a_dxCell spacing (per direction) of the position's OWN level.
Returns
floor((a_position - a_probLo) / a_dx), component-wise.

◆ kdBBox()

template<typename Packed >
void ParticleManagement::detail::kdBBox ( RealVect &  a_boxLo,
RealVect &  a_boxHi,
const std::vector< MergeParticle< Packed > > &  a_particles,
const std::size_t  a_lo,
const std::size_t  a_hi 
)
inlinenoexcept

Axis-aligned bounding box of particles[a_lo, a_hi).

Parameters
[out]a_boxLoLow corner.
[out]a_boxHiHigh corner.
[in]a_particlesParticle buffer.
[in]a_loStart of the range (inclusive).
[in]a_hiEnd of the range (exclusive).

◆ kdBoxRealBounds()

void ParticleManagement::detail::kdBoxRealBounds ( RealVect &  a_boxLo,
RealVect &  a_boxHi,
const Box &  a_box,
const RealVect &  a_dx,
const RealVect &  a_probLo 
)
inlinenoexcept

Real-space bounds of a box: the half-open region [lo, hi) that its cells cover.

Half-open on purpose – the high corner is the OUTER face of the last cell, so testing pos >= lo && pos < hi per direction partitions space between abutting boxes with no gap and no double-cover, which is exactly the "is this particle still in its own patch" test both callers make.

Parameters
[out]a_boxLoLow corner (inclusive).
[out]a_boxHiHigh corner (exclusive).
[in]a_boxBox whose bounds are wanted.
[in]a_dxGrid spacing at the box's level.
[in]a_probLoLower-left corner of the physical domain.

◆ kdCellKeyOf()

IntVect ParticleManagement::detail::kdCellKeyOf ( const RealVect &  a_position,
const RealVect &  a_probLo,
const RealVect &  a_dx 
)
inlinenoexcept

The unclamped, position-derived cell index a physical position falls in.

Same convention as the nearest-neighbor merge's cellKeyOf() – not clamped to any patch's box, so positions in different patches' local frames stay directly comparable.

Parameters
[in]a_positionPhysical position.
[in]a_probLoPhysical position of the domain's low corner.
[in]a_dxCell spacing.
Returns
floor((a_position - a_probLo) / a_dx), component-wise.

◆ kdCheckCentroid()

void ParticleManagement::detail::kdCheckCentroid ( const RealVect &  a_centroid,
const Real  a_totalWeight,
const RealVect &  a_boxLo,
const RealVect &  a_boxHi 
)
inlinenoexcept

Debug-only sanity check on a merged particle's position: a weighted centroid of positions that all lie within [a_boxLo,a_boxHi], using strictly positive weights, is mathematically guaranteed to itself lie within that same box.

CH_assert this explicitly (rather than trust the arithmetic silently) so that a stray sign error, a mis-set/negative weight, or a wrong id feeding the wrong position into the accumulation surfaces immediately as an assertion in a DEBUG build, instead of silently producing a merged superparticle placed somewhere non-physical. A relative tolerance accounts for floating- point round-off in the summation, not for any real violation.

Parameters
[in]a_centroidComputed weighted centroid.
[in]a_totalWeightSum of weights used to compute a_centroid.
[in]a_boxLoLow corner of the bounding box every contributing position lies within.
[in]a_boxHiHigh corner of the bounding box every contributing position lies within.

◆ kdExchangeByRank()

template<typename T >
std::vector< T > ParticleManagement::detail::kdExchangeByRank ( const std::vector< std::vector< T > > &  a_sendByRank)
inline

Generic Alltoallv-style exchange for a trivially-copyable record type: send a per-destination-rank bucket, return the flat concatenation of everything received.

A deliberate, self-contained duplicate of the nearest-neighbor merge's nnMergeExchangeByRank() (same MPI_Alltoall/Alltoallv shape) rather than a shared dependency – that function lives in a header that transitively pulls in EBGeometry, which this file must not depend on (see this file's own

). In a serial (non-MPI) build, or with a single rank, returns this rank's own bucket unchanged.

Template Parameters
TTrivially-copyable record type (KDClaim, KDVerdict, KDCommit, or MergeParticle<Packed> with Packed itself trivially copyable).
Parameters
[in]a_sendByRankOne bucket per destination rank (size == number of ranks).
Returns
Flat vector of every record received from every rank (including this rank's own bucket addressed to itself).

◆ kdFillCellHistogram()

template<typename Packed >
void ParticleManagement::detail::kdFillCellHistogram ( FArrayBox &  a_counts,
const std::vector< MergeParticle< Packed > > &  a_particles,
const RealVect &  a_probLo,
const RealVect &  a_dx 
)
inlinenoexcept

Tally a per-cell particle-count histogram over one patch's gathered particles.

Built from every particle's own position, so it is unaffected by how buildKDQuotaLeaves() later partitions the space – a node-local count is NOT a safe substitute, see that function's own docs for why. Shared by mergeKDCarve()/mergeKDPatch()/mergeKDInterior(): all three need the same ground truth, and the invariant that it comes from true positions rather than from the partition is easy to break if each keeps its own copy.

a_counts must include at least one ghost cell, since a gathered ghost sits up to one cell outside the patch's own box.

The count is stored in a_counts' Real elements but is always a non-negative integer. A double represents every integer exactly up to 2^53, so consumers – e.g. the crowding test in buildKDQuotaLeaves() – may static_cast<int> it losslessly for any realistic per-cell count.

Template Parameters
PackedSee MergeParticle.
Parameters
[out]a_countsPer-cell counts, zeroed here before tallying.
[in]a_particlesGathered particles for this patch.
[in]a_probLoPhysical position of the domain's low corner.
[in]a_dxThis patch's own cell spacing.

◆ kdIntVectLess()

bool ParticleManagement::detail::kdIntVectLess ( const IntVect &  a_lhs,
const IntVect &  a_rhs 
)
inlinenoexcept

Strict weak ordering over IntVect, lexicographic component-wise.

Used to sort/binary-search the per-patch, per-cell TRUE particle-count histogram (see buildKDQuotaLeaves()'s own docs) – IntVect has no built-in total order.

Parameters
[in]a_lhsLeft-hand cell index.
[in]a_rhsRight-hand cell index.
Returns
True iff a_lhs sorts strictly before a_rhs.

◆ kdMaxAxisSpan()

Real ParticleManagement::detail::kdMaxAxisSpan ( const RealVect &  a_boxLo,
const RealVect &  a_boxHi,
const RealVect &  a_dx 
)
inlinenoexcept

Largest per-axis extent of [a_boxLo,a_boxHi], expressed as a fraction of that axis's own cell width.

The correctness backstop buildKDQuotaLeaves(), mergeKDCarve() and mergeKDPatch() rely on to decide whether a leaf is compact enough to merge: unlike an aggregate VOLUME ratio (a product across axes), this cannot be fooled by an anisotropic (long-and-thin) bounding box – e.g. a box 5 cells long in one axis and 0.2 cells wide in another has volume ratio 1.0 (deceptively "one cell's worth") but a maxAxisSpan of 5.0 (correctly flagged as still spanning several cells). Particles hugging a curved EB surface, or funneled along a narrow L-shaped coarse-fine strip, produce exactly this kind of elongated cluster.

Parameters
[in]a_boxLoLow corner.
[in]a_boxHiHigh corner.
[in]a_dxCell spacing.
Returns
max_dir (a_boxHi[dir] - a_boxLo[dir]) / a_dx[dir].

◆ kdSplitCountMedian()

template<typename Packed >
std::size_t ParticleManagement::detail::kdSplitCountMedian ( std::vector< MergeParticle< Packed > > &  a_particles,
const std::size_t  a_lo,
const std::size_t  a_hi,
const int  a_axis 
)
inlinenoexcept

Split particles[a_lo,a_hi) in place by the longest axis at the count-median – the sole split rule used by buildKDQuotaLeaves().

The split point is the position-sorted median particle (via nth_element), balancing COUNT as evenly as possible – mirrors equal_weight_kd's own single-cell logic, but by count, never by weight, and with NO synthetic particle splitting (a particle is never divided across the two children). nth_element divides the range in half by count however the positions are distributed, so a tightly clustered node still splits evenly.

Parameters
[in,out]a_particlesParticle buffer; reordered in [a_lo,a_hi).
[in]a_loStart of the range (inclusive).
[in]a_hiEnd of the range (exclusive); a_hi - a_lo must be >= 2.
[in]a_axisSplit axis – the node's longest box axis, precomputed once by the caller (which already needs the box for the span test) rather than re-derived here from a second bounding box.
Returns
The split index: [a_lo,mid) and [mid,a_hi) are the two children, each non-empty.

◆ kdSplitWeightMedian()

template<typename Packed >
std::size_t ParticleManagement::detail::kdSplitWeightMedian ( std::vector< MergeParticle< Packed > > &  a_particles,
const std::size_t  a_lo,
const std::size_t  a_hi,
const int  a_axis 
)
inlinenoexcept

Split particles[a_lo,a_hi) in place by the longest axis at the WEIGHT median – the plane with half this node's total particle weight on either side.

Used instead of the count median so that the resulting leaves carry roughly equal total weight, and therefore so do the super-particles they collapse into. Uniform super-particle weight is what keeps statistical noise down, and is the defining property of the equal_weight_kd merge.

Unlike equal_weight_kd this NEVER divides a particle synthetically, so exact equality is not attainable: once a single particle's weight exceeds a leaf's target weight, that particle alone sets a floor no partition can get under. The bound equal_weight_kd guarantees (max/min <= 2) is bought precisely by that synthetic division and is therefore out of reach here by construction.

A weight median can land very unevenly BY COUNT – one dominant particle carrying half the node's weight forces a 1-vs-(n-1) split. That is harmless here only because buildKDQuotaLeaves() enforces a hard per-cell leaf quota independently of how the split plane behaves: an uneven split can cost merge quality but cannot leave a cell above target. Under any of the earlier, quota-less formulations the same degeneracy was fatal.

Parameters
[in,out]a_particlesParticle buffer; reordered in [a_lo,a_hi).
[in]a_loStart of the range (inclusive).
[in]a_hiEnd of the range (exclusive); a_hi - a_lo must be >= 2.
[in]a_axisSplit axis – the node's longest box axis, precomputed once by the caller (which already needs the box for the span test) rather than re-derived here from a second bounding box.
Returns
The split index: [a_lo,mid) and [mid,a_hi) are the two children, each non-empty.

◆ mergeAdjacentNearest()

template<class P , RealVect P::* position>
void ParticleManagement::detail::mergeAdjacentNearest ( std::vector< P > &  a_particles,
const std::size_t  a_target,
const std::function< void(P &, const P &)> &  a_combine 
)
inlinenoexcept

Merge an already spatially-ordered particle list down to a target count by repeatedly combining the nearest adjacent pair.

The particles are assumed to be in a spatially coherent order (e.g. sorted along a space-filling curve by the caller), so adjacency in the list approximates spatial proximity. The closest adjacent pair (by position) is combined repeatedly until a_target particles remain. Combining the second particle into the first is delegated to a_combine (the caller decides the payload semantics, e.g. a weight-weighted centroid and energy). This does NOT split, so it cannot increase the count – it is a no-op when the list already has a_target or fewer particles (the caller handles the split direction). Operates in place on per-thread scratch (allocation-free after warmup); on return a_particles holds the surviving merged particles.

Parameters
[in,out]a_particlesSpatially-ordered particle list; overwritten with the merged result.
[in]a_targetDesired number of particles.
[in]a_combineCallback that combines the second particle into the first.

◆ nnMergeAnyCellCrowded()

template<typename Packed >
bool ParticleManagement::detail::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 
)
inlinenoexcept

Exact per-cell crowding pre-filter shared by buildNNSpatialIndex()/buildNNCellSpatialIndex(): is any cell in this patch's local+ghost particle set over its a_cellBudget?

A count-only tally keyed as the crowding trigger (cellKeyOf(pos, probLo, dx), local and ghost alike); checks the true per-cell maximum, not an average density (a low-average patch can still hold one over-threshold cell).

Template Parameters
PackedSee MergeParticle.
Parameters
[in]a_localValidThis patch's own full valid-particle set.
[in]a_ghostsEvery ghost shipped to this patch this round.
[in]a_probLoPhysical position of the domain's low corner.
[in]a_dxCell spacing of THIS PATCH's own level.
[in]a_cellBudgetThe count each cell is drained down to – see NNCellBudget.
Returns
True iff some cell holds strictly more particles than its own budget allows.

◆ nnMergeChebyshevCellDistance()

int ParticleManagement::detail::nnMergeChebyshevCellDistance ( const IntVect &  a_a,
const IntVect &  a_b 
)
inlinenoexcept

Chebyshev (max-per-axis) distance between two cell-index vectors – see CD_NearestNeighborParticleMerge.H's a_maxCellDistance docs.

Parameters
[in]a_aFirst cell index.
[in]a_bSecond cell index.
Returns
Max-per-axis distance between a_a and a_b.

◆ nnMergeCrossLevelTooFar()

bool ParticleManagement::detail::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 
)
inlinenoexcept

a_maxCellDistance check for two positions that may be on DIFFERENT AMR levels.

Raw cell indices computed with two different dx values are not directly comparable, so both positions are re-expressed in the finer of the two levels' cell-size units (higher AMR level = finer) before taking the Chebyshev distance. Reduces to the same-level case when a_levelA == a_levelB.

Parameters
[in]a_posAFirst participant's position.
[in]a_levelAFirst participant's AMR level.
[in]a_posBSecond participant's position.
[in]a_levelBSecond participant's AMR level.
[in]a_probLoPhysical position of the domain's low corner.
[in]a_dxByLevelCell spacing per AMR level.
[in]a_maxCellDistanceThe cap (see CD_NearestNeighborParticleMerge.H's a_maxCellDistance docs).
Returns
True iff the pair exceeds a_maxCellDistance.

◆ nnMergeDistSquared()

Real ParticleManagement::detail::nnMergeDistSquared ( const RealVect &  a_a,
const RealVect &  a_b 
)
inlinenoexcept

Squared Euclidean distance between two physical positions.

Parameters
[in]a_aFirst position.
[in]a_bSecond position.
Returns
Squared distance between a_a and a_b.

◆ nnMergeExchangeByRank()

template<typename T >
std::vector< T > ParticleManagement::detail::nnMergeExchangeByRank ( const std::vector< std::vector< T > > &  a_sendByRank)
inline

Generic alltoallv-style exchange: send a per-destination-rank bucket of trivially- copyable records, return the flat concatenation of everything received.

Packs one bucket per rank and exchanges via MPI_Alltoall (byte counts) + MPI_Alltoallv (payload). In a serial (non-MPI) build, or with a single rank, this returns this rank's own bucket unchanged.

Template Parameters
TTrivially-copyable record type (NNMergeProposal<Packed>, NNMergeVerdict, or MergeParticle<Packed> – all satisfy this given Packed itself is required to).
Parameters
[in]a_sendByRankOne bucket per destination rank (size == number of ranks).
Returns
Flat vector of every record received from every rank (including this rank's own bucket addressed to itself).

◆ nnMergePointToCellDistSquared()

Real ParticleManagement::detail::nnMergePointToCellDistSquared ( const RealVect &  a_point,
const IntVect &  a_cell,
const RealVect &  a_probLo,
const RealVect &  a_dx 
)
inlinenoexcept

Squared distance from a physical point to a cell's axis-aligned bounding box (0 if the point is inside).

Parameters
[in]a_pointPhysical position.
[in]a_cellCell index.
[in]a_probLoPhysical position of the domain's low corner.
[in]a_dxCell spacing.
Returns
Squared distance from a_point to [a_probLo + a_cell*a_dx, a_probLo + (a_cell + 1)*a_dx].

◆ nnMergeWeightedCentroid()

void ParticleManagement::detail::nnMergeWeightedCentroid ( RealVect &  a_mergedPos,
Real &  a_mergedWeight,
const RealVect &  a_posA,
const Real  a_weightA,
const RealVect &  a_posB,
const Real  a_weightB 
)
inlinenoexcept

Weighted-centroid merge of two positions/weights – shared by resolveTrivialTier() and judgeProposals() so the formula is written exactly once.

Parameters
[out]a_mergedPosWeighted-centroid position.
[out]a_mergedWeightSummed weight.
[in]a_posAFirst particle's position.
[in]a_weightAFirst particle's weight.
[in]a_posBSecond particle's position.
[in]a_weightBSecond particle's weight.

◆ partitionParticles()

template<typename T , typename >
T ParticleManagement::detail::partitionParticles ( const T  a_numParticles)
inline

Partition particles so that all MPI rank draw a cumulative number of particles equal to a_numParticles.

Parameters
[in]a_numParticlesTotal number of particles to be drawn.
Returns
Returns the number of particles drawn by each rank.