Merging algorithms
Particle merging reduces the number of particles in a cell while (approximately) preserving the distribution function and its moments. This is essential for variable-weight simulations, where the particle count in a cell can grow without bound. All merging routines operate on the particles of a single species in a single grid cell, addressed via the ParticleIndexerArray.
Merging deletes particles and therefore introduces "holes" in the particle indexing; see Particle buffers and contiguous indexing for how to restore contiguity with squash_pia! afterwards. Several of the routines come in two variants: a plain one, and one that additionally takes a Grid1DUniform and clamps any post-merge particle positions that fall outside the simulation domain back into it.
Overview
| Algorithm | Setup struct | Function |
|---|---|---|
| Velocity grid-based (N:2) | GridN2Merge | merge_grid_based! |
| Octree (N:1 and N:2) | OctreeMerge | merge_octree! |
| Roulette (+ conservative variant) | — | merge_roulette! |
| NNLS, moment-preserving | NNLSMerge | merge_nnls_based! |
| NNLS, approximate electron–neutral rate-preserving | NNLSMerge | merge_nnls_based_rate_preserving! |
| NNLS, exact electron–neutral rate-preserving | NNLSMerge | merge_nnls_based_rate_preserving! |
Grid-based merging
merge_grid_based! groups particles using a Cartesian grid in velocity space (particles outside the grid are grouped by velocity octant) and merges the particles in each cell/octant down to 2 particles, following the algorithm of Oblapenko et al. (2020) (see also Vranic et al. (2015)). The velocity-space grid is described by a GridN2Merge instance. The grid extent can either be derived from the species temperature (via a PhysProps instance) or supplied explicitly, and each of these has a Grid1DUniform overload that keeps post-merge particles inside the domain.
Octree merging
merge_octree! performs octree-based N:M merging following Martin and Cambier (2016): velocity space is recursively subdivided into octants, and the particles in each leaf bin are merged down to M particles. The merge target M is a type parameter of the OctreeMerge struct, with M = 1 (N:1) and M = 2 (N:2) supported. The construction of the octree is controlled by three enums:
Merzbild.OctreeBinSplit— how the splitting velocity is chosen (OctreeBinMidSplit,OctreeBinMeanSplit,OctreeBinMedianSplit).Merzbild.OctreeInitBin— how the bounds of the initial bin are computed (OctreeInitBinMinMaxVel,OctreeInitBinMinMaxVelSym,OctreeInitBinC).Merzbild.OctreeBinBounds— how the bounds of a split sub-octant bin are computed (OctreeBinBoundsInherit,OctreeBinBoundsRecompute).
A Grid1DUniform overload of merge_octree! is available that keeps post-merge particles inside the domain.
Roulette merging
merge_roulette! deletes random particles until the target particle count is reached and re-weights the remaining particles to conserve number density, following Watrous, Seidel et al. (2023). Passing the conservative=true keyword additionally corrects the post-merge velocities to conserve momentum and energy.
NNLS merging
The NNLS (non-negative least squares) merging routines cast moment preservation as a non-negative least squares problem, following Oblapenko and Torrilhon (2026). The set of preserved moments and the workspace are held in an NNLSMerge instance; compute_multi_index_moments is a helper for building the multi-index moment set. All variants return -1 on failure (residual too large, or no reduction in particle count achieved).
merge_nnls_based!— general moment-preserving merging.merge_nnls_based_rate_preserving!— merging of electrons that additionally preserves electron–neutral elastic scattering and electron-impact ionization rates. Two methods are provided: one preserving approximate rates, and one (taking an additional neutral collision partnerParticleVector) preserving exact rates for a specific neutral species.