17 Developer Guide
The developer guide documents how to extend IPPL without breaking portability, correctness, or downstream applications.
17.1 Source tree orientation
| Directory | Responsibility |
|---|---|
src/Field, src/FieldLayout |
Distributed field containers and layouts. |
src/Particle |
Particle containers, attributes, and layouts. |
src/Interpolation |
Particle-field interpolation schemes. |
src/FFT |
FFT abstraction. |
src/PoissonSolvers, src/MaxwellSolvers, src/LinearSolvers |
Solver infrastructure. |
src/FEM |
Finite-element spaces, elements, and assembly. |
src/Communicate |
MPI wrappers and serialization. |
src/Utility, src/Types |
Shared utilities and core types. |
17.2 Extension checklist
- Identify the ownership boundary: user API, layout, data container, solver, or backend.
- Add or update Doxygen comments for public interfaces.
- Preserve execution-space correctness; use Kokkos math in device code.
- Add tests across representative dimensions, precisions, and execution spaces.
- Document the user-facing behavior here and link to Doxygen for signatures.
17.3 Build-system conventions
IPPL uses a target-based CMake model. The public target is ippl, with the exported alias ippl::ippl for downstream users. Dependency discovery and target wiring should stay centralized in the CMake support files rather than being repeated in examples or tests.
| Rule | Rationale |
|---|---|
Link downstream code to ippl::ippl |
Keeps include paths, compile definitions, and dependencies attached to the target. |
| Keep optional dependencies behind CMake options | Avoids forcing all users to build every solver backend. |
Use IPPL_ENABLE_TESTS and IPPL_ENABLE_UNIT_TESTS for test selection |
Keeps CI and local verification predictable. |
| Add dependencies at target scope | Preserves transitive usage requirements for installed packages. |
Minimal downstream shape:
find_package(IPPL CONFIG REQUIRED)
add_executable(my_ippl_code main.cpp)
target_link_libraries(my_ippl_code PRIVATE ippl::ippl)17.4 Doxygen comment standard
Public headers should carry enough Doxygen for a library user to call the API and enough implementation notes for a library developer to extend it. The manual should then link to those generated pages instead of duplicating signatures.
Each public class or function should answer:
| Question | Where to document |
|---|---|
| What mathematical or runtime object does this represent? | Class brief and manual chapter. |
| Which communicator, layout, or execution space does it use? | Doxygen details. |
| Is the call rank-local or collective? | Doxygen details and manual workflow notes. |
| Which parameters are required, optional, or backend-specific? | Doxygen parameter comments. |
| Which tests demonstrate expected behavior? | Manual examples and test references. |
17.5 Adding a new backend or solver
- Add the implementation behind a narrow solver or backend interface.
- Keep backend-specific parameters in
ParameterListwhen the options are runtime choices. - Provide a minimal manufactured-solution or conservation test.
- Add a manual example that states the physical problem, required CMake options, run command, and expected diagnostic.
- Add Doxygen comments for every public constructor, parameter, and result query.
17.6 Extending ALPINE solver dispatch
ALPINE examples such as alpine/LandauDamping.cpp do not instantiate solvers directly in the driver. The driver parses the solver name and hands it to LandauDampingManager; the manager creates a FieldSolver, and FieldSolver initializes the concrete solver through the manager-level solver variant in src/Manager/datatypes.h.
When adding a new solver backend such as a Ginkgo-based Poisson solver, update both the type layer and the runtime dispatch:
- Add the solver header and alias in
src/Manager/datatypes.h. - Add the alias to the
Solver_tvariant soFieldSolverBasecan store it. - Add a solver-name branch in
alpine/FieldSolver.hpp::initSolver(). - Add an initializer that creates the
ParameterList, sets RHS/LHS or gradient output consistently with the solver contract, and callsinitSolverWithParams. - Add a
runSolver()branch that extracts the solver from the variant and callssolve(). - Extend the relevant mini-app CLI documentation and tests, preferably starting with
test/solver/TestCGSolver.cppfor manufactured-solution convergence andalpine/LandauDamping.cppfor a full PIC workflow.
For Landau damping, solver choices currently include FFT/open-boundary FFT paths, plain CG, PCG, and FEM-oriented paths. Any new solver should make its boundary-condition assumptions explicit and state whether it computes the scalar potential, the field gradient, or both.
17.7 Testing expectations
Tests should cover both the numerical contract and the distributed-memory contract. A solver test should normally include a known analytic result or conservation check, while a layout or communication test should include rank-count-sensitive behavior.
| Area | Expected checks |
|---|---|
| Fields | Boundary conditions, halo updates, expression evaluation, reductions. |
| Particles | Migration, particle-count conservation, boundary behavior, attribute integrity. |
| Particle-mesh | Charge conservation, gather/scatter consistency, dimension coverage. |
| FFT and Poisson | Forward/backward consistency, manufactured solution error, backend options. |
| FEM and Maxwell | Basis correctness, DOF mapping, residuals, convergence or error norms. |
| Communication | Blocking/nonblocking exchange, collectives, serialization, tag isolation. |