5  ALPINE Manager Hierarchy

ALPINE is IPPL’s application layer: a collection of plasma-physics mini-apps in the alpine/ directory that run full particle-in-cell (PIC) simulations end to end. They are not isolated library demos. Each mini-app wires together particles, fields, interpolation, Poisson solvers, MPI load balancing, and diagnostics the way a real simulation code would.

This chapter explains what those mini-apps are for, how they are organized, and how the manager hierarchy keeps application logic separate from reusable PIC infrastructure. We use Landau damping as the main walkthrough because it is the smallest complete example; the same pattern applies to the other ALPINE applications.

For the underlying PIC model, see Chapter 3. For gather/scatter and load balancing, see Chapter 9. For solver options, see Chapter 11 and the runnable LandauDamping example in Chapter 16.

5.1 What ALPINE mini-apps are

IPPL ships two kinds of executables that look similar from the outside but serve different goals:

Unit tests and examples (test/, examples/) ALPINE mini-apps (alpine/)
Goal Verify one subsystem (a solver, FFT layout, CIC gather/scatter, …) Run a complete PIC-style plasma simulation
Scope Often a single loop or manufactured solution Domain setup, particles, time stepping, field solve, diagnostics, scaling
Typical user Developer validating a change Application author learning how to assemble IPPL, or researcher running a benchmark
Code style Direct calls into IPPL classes Thin main() + a manager class that owns the simulation

Mini-apps therefore serve three practical purposes:

  1. Reference applications — they show how to combine IPPL building blocks into a working PIC code without reimplementing the standard loop.
  2. Correctness and performance benchmarks — standard plasma problems with known behavior are used to test solvers, preconditioners, and Kokkos/MPI portability on large machines [1].
  3. Templates for new codes — to add a new simulation, you typically subclass the existing managers and override physics-specific methods rather than writing a PIC driver from scratch.

At a high level, every ALPINE mini-app follows the same skeleton:

flowchart TB
  main["main() parses CLI"]
  pre["manager.pre_run()"]
  loop["manager.run(nt)"]
  step["advance(): push → scatter → solve → gather → push"]
  main --> pre
  pre --> loop
  loop --> step
  step --> loop

The executable stays small; the manager owns containers, solvers, and the time-stepping policy.

5.2 Applications in alpine/

CMake builds three manager-based executables from alpine/CMakeLists.txt. Each has a thin driver (*.cpp) and a problem-specific manager that subclasses AlpineManager:

Executable Manager class Physical problem
LandauDamping LandauDampingManager Electrostatic Landau damping on a periodic domain
PenningTrap PenningTrapManager Magnetized plasma in a Penning-trap geometry with imposed \(\mathbf{E}\) and \(\mathbf{B}\) fields
BumponTailInstability BumponTailInstabilityManager Bump-on-tail (or related beam–plasma) velocity instability on a periodic domain

All three share the same command-line shape:

srun ./<Executable> Nx Ny Nz Np Nt <solver> <lbthres> <stepper> [preconditioner args...] --overallocate <factor> --info <level>

Landau damping is documented in detail below because it is the smallest complete walkthrough. The other managers override the same hooks—pre_run(), initializeParticles(), advance(), and dump()—while reusing AlpineManager for deposition, gather, solver dispatch, and load balancing.

5.2.1 Landau damping in brief

Landau damping is a classic kinetic-plasma test case: a small sinusoidal perturbation in particle density launches an electrostatic wave that decays in time because of resonant particle–wave interaction (no collisions required). LandauDampingManager sets a periodic box with \(k_w=0.5\), perturbation amplitude \(\alpha=0.05\), Maxwellian velocities, and positions sampled from \(1+\alpha\cos(k_w x)\).

srun ./LandauDamping 64 64 64 1048576 20 FFT 0.1 LeapFrog --info 5

This constructs the manager, calls pre_run() to build fields and particles, then runs 20 Leapfrog steps with an FFT Poisson solve. ORB load balancing triggers when the particle imbalance exceeds lbthres (here 10%).

5.2.2 Penning trap in brief

PenningTrapManager models particles in a static trap: an external quadrupole-like electric potential plus a uniform axial magnetic field \(B_z = 5\) (in code units). Self-fields from the deposited charge are added to these external fields during the push. The domain is periodic on \([0,20]^3\) with Gaussian spatial and velocity distributions centered in the box. The time integrator is a magnetized Leapfrog variant (Boris-style \(\mathbf{v}\times\mathbf{B}\) coupling in the velocity kicks), not the purely electrostatic push used in Landau damping.

srun ./PenningTrap 128 128 128 10000 300 FFT 0.01 LeapFrog --overallocate 1.0 --info 10

Because the trap study often needs many time steps, this mini-app is a common target for iterative and preconditioned Poisson solvers (CG, PCG, and the truncated-Green TG path).

5.2.3 Bump-on-tail instability in brief

BumponTailInstabilityManager initializes a periodic electrostatic setup with a bulk Maxwellian plus a fast beam in velocity space—the classic bump-on-tail configuration that drives a kinetic instability. A sinusoidal density perturbation is applied along the last spatial dimension. The standard electrostatic Leapfrog cycle applies (no external \(\mathbf{B}\) field). Optional phase-space dumps exist behind a compile-time flag (EnablePhaseDump).

srun ./BumponTailInstability 128 128 128 10000 10 FFT 0.01 LeapFrog --overallocate 2.0 --info 10

5.3 Why a manager hierarchy?

A PIC simulation repeats the same structural steps every time step (see Chapter 3 and Chapter 9): push particles, deposit charge, solve for the field, gather forces, push again, migrate particles across MPI ranks, and optionally repartition. ALPINE factors this into layers so that only the physics-specific pieces change between mini-apps:

Layer Responsibility Changes when you add a new mini-app?
BaseManager Simulation lifecycle: pre_run(), run(nt)
PicManager Owns particle and field containers, solver, load balancer; requires par2grid() / grid2par() definitions of PIC scatter/gather
AlpineManager Contains all common ALPINE functions, such as Cloud-in-Cell scatter/gather, charge conservation checks, and default diagnostics
LandauDampingManager (or others) Domain, initial conditions, time integrator, problem-specific output - This is where the physics is

Concrete manager subclasses: LandauDampingManager, PenningTrapManager, and BumponTailInstabilityManager, all derived from AlpineManager.

Source files for this stack:

File Role
alpine/LandauDamping.cpp Landau damping driver
alpine/LandauDampingManager.h Landau-specific physics
alpine/PenningTrap.cpp Penning trap driver
alpine/PenningTrapManager.h Trap fields, magnetized push, diagnostics
alpine/BumponTailInstability.cpp Bump-on-tail driver
alpine/BumponTailInstabilityManager.h Beam–plasma ICs and instability diagnostics
alpine/AlpineManager.h Shared Particle-in-Cell functions for ALPINE, inherits from PicManager which inherits from BaseManager
alpine/FieldSolver.hpp Solver name → concrete Poisson backend
alpine/FieldContainer.hpp, alpine/ParticleContainer.hpp, alpine/LoadBalancer.hpp Containers for fields and particles used by ALPINE, as well as the load balancing scheme

5.4 Class hierarchy

classDiagram
    direction TB

    class BaseManager {
      +pre_run()
      +pre_step()
      +advance()*
      +post_step()
      +run(int nt)
    }

    class PicManager~T, Dim, pc, fc, orb~ {
      +par2grid()*
      +grid2par()*
      +setParticleContainer(pc)
      +setFieldContainer(fc)
      +setFieldSolver(FieldSolverBase)
      +setLoadBalancer(orb)
    }

    class AlpineManager~T, Dim~ {
      +grid2par()
      +par2grid()
      +gatherCIC()
      +scatterCIC()
      +gatherFEM()
      +scatterFEM()
      +post_step()
      +dump()
    }

    class LandauDampingManager~T, Dim~ {
      +pre_run()
      +initializeParticles()
      +advance()
      +LeapFrogStep()
      +dump()
    }

    class PenningTrapManager~T, Dim~ {
      +pre_run()
      +initializeParticles()
      +advance()
      +LeapFrogStep()
      +dump()
    }

    class BumponTailInstabilityManager~T, Dim~ {
      +pre_run()
      +initializeParticles()
      +advance()
      +LeapFrogStep()
      +dump()
    }

    BaseManager <|-- PicManager
    PicManager <|-- AlpineManager
    AlpineManager <|-- LandauDampingManager
    AlpineManager <|-- PenningTrapManager
    AlpineManager <|-- BumponTailInstabilityManager

  • BaseManager defines when setup, time stepping, and post-step work run. run(nt) calls pre_step(), advance(), and post_step() each step.
  • PicManager specializes that contract for PIC: it holds the particle container, field container, field solver, and load balancer, and requires particle-to-grid and grid-to-particle transfers.
  • AlpineManager implements those transfers for ALPINE (CIC for spectral/FD solvers, FEM assembly for finite-element solvers), normalizes deposited charge, and provides default timing and dump hooks.
  • Problem managers (LandauDampingManager, PenningTrapManager, BumponTailInstabilityManager, …) fill in domain setup, particle sampling, the time integrator, and diagnostics. Penning trap additionally applies external \(\mathbf{E}\) and \(\mathbf{B}\) during the push; the other two are electrostatic Leapfrog.

Together, this stack connects the abstract PIC loop in Chapter 9 to the performance-portable execution model in Chapter 15.

5.5 Object ownership

classDiagram
    direction LR

    class LandauDampingManager
    class FieldContainer {
      E
      rho
      phi
      mesh
      FieldLayout
    }
    class ParticleContainer {
      R
      P
      E
      q
      ParticleSpatialLayout
    }
    class FieldSolver {
      FFT
      CG
      PCG
      FEM
      FEM_PRECON
    }
    class LoadBalancer {
      ORB repartition
      balance threshold
    }

    LandauDampingManager *-- FieldContainer
    LandauDampingManager *-- ParticleContainer
    LandauDampingManager *-- FieldSolver
    LandauDampingManager *-- LoadBalancer
    ParticleContainer --> FieldContainer : layout from mesh + field layout
    FieldSolver --> FieldContainer : solves rho -> phi,E
    LoadBalancer --> FieldContainer
    LoadBalancer --> ParticleContainer
    LoadBalancer --> FieldSolver

PicManager stores shared pointers to these objects. LandauDampingManager::pre_run() constructs them in dependency order:

Step Object or operation Role
1 Domain, spacing, time step, total charge Defines the periodic Landau problem and grid resolution
2 FieldContainer Mesh, FieldLayout, electric field E, charge density rho, and potential phi when the solver needs it
3 ParticleContainer Particle layout aligned with the field mesh; registers position R, velocity P, charge q, and gathered field E
4 FieldSolver Dispatches to FFT, CG, PCG, FEM, or preconditioned FEM via FieldSolverBase (see accepted types for each application)
5 LoadBalancer ORB repartitioning of domain - tied to layout of fields and particles

5.6 Runtime lifecycle

sequenceDiagram
    participant main as LandauDamping.cpp
    participant mgr as LandauDampingManager
    participant alpine as AlpineManager
    participant pic as PicManager/BaseManager
    participant fs as FieldSolver
    participant lb as LoadBalancer

    main->>mgr: construct(totalP, nt, nr, lbt, solver, step)
    main->>mgr: pre_run()
    mgr->>mgr: create containers and initialize particles
    mgr->>fs: warm-up solve
    mgr->>alpine: par2grid()
    mgr->>fs: runSolver()
    mgr->>alpine: grid2par()
    mgr->>mgr: dump()
    main->>pic: run(nt)
    loop each time step
      pic->>alpine: pre_step()
      pic->>mgr: advance()
      mgr->>mgr: push velocity half-step
      mgr->>mgr: push position
      mgr->>mgr: particle update/migration
      mgr->>lb: balance() and repartition() if needed
      mgr->>alpine: par2grid()
      mgr->>fs: runSolver()
      mgr->>alpine: grid2par()
      mgr->>mgr: push velocity half-step
      pic->>alpine: post_step()
    end

main() never calls scatter, gather, or migration directly. This lets new mini-apps swap physics inside advance() while reusing the same outer loop.

5.7 Landau-specific physics and time stepping

LandauDampingManager encodes the benchmark setup:

  • Periodic domain with (r_), (r_/k_w), perturbation amplitude (), and (k_w=0.5).
  • Positions sampled from (1+(k_w x)) via inverse transform; velocities from a normal (Maxwellian) distribution.
  • Total charge split uniformly across macro-particles.
  • Time step (t = (0.05,, 0.5 (x_d))) from the grid spacing.
  • Only the LeapFrog integrator is accepted in this mini-app.

Each Leapfrog step is the standard electrostatic PIC cycle:

  1. Half-step velocity kick from gathered ().
  2. Full-step position push.
  3. Particle migration (ParticleBase::update()).
  4. ORB repartition if load imbalance exceeds the threshold.
  5. Charge deposition (par2grid()).
  6. Field solve (runSolver()).
  7. Field gather (grid2par()).
  8. Second half-step velocity kick.

Diagnostics (dump(), dumpLandau()) report field and particle quantities used to verify damping behavior.

5.8 Building a new ALPINE-style mini-app

Use Landau damping as the template. Override only what your physics requires:

If you need to change… Start in… Typical change
Problem geometry and BCs YourManager::pre_run() Domain extents, solver parameters, external fields
Initial particles initializeParticles() Position/velocity distributions, particle species
Time integration advance() / stepper method New integrator
Deposition and gather AlpineManager::par2grid() / grid2par() If you need to change the CIC interpolation
Output dump() and helpers Diagnostics and output