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

AlgorithmSetup structFunction
Velocity grid-based (N:2)GridN2Mergemerge_grid_based!
Octree (N:1 and N:2)OctreeMergemerge_octree!
Roulette (+ conservative variant)merge_roulette!
NNLS, moment-preservingNNLSMergemerge_nnls_based!
NNLS, approximate electron–neutral rate-preservingNNLSMergemerge_nnls_based_rate_preserving!
NNLS, exact electron–neutral rate-preservingNNLSMergemerge_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:

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 partner ParticleVector) preserving exact rates for a specific neutral species.