Run with MPI¶
This guide builds adc_cpp with the distributed comm seam and runs the tests across
several MPI ranks. It assumes you can already build the reference Kokkos Serial backend
and that MPI and a Kokkos install are available on your machine. For the full backend
model, the validation status of each configuration and the coverage matrix, see the
parallel backends page.
MPI distributes subdomains across ranks through the comm.hpp seam; the local compute
stays on the Kokkos Serial execution space, one process per rank. Enable it with the
POPS_USE_MPI CMake option.
Build with MPI¶
This uses the same Kokkos Serial install as the reference build, plus the MPI seam.
Configure the build with both
POPS_USE_KOKKOSandPOPS_USE_MPIturned on. ReplaceKOKKOS_PREFIXwith the path to your Kokkos Serial install.cmake -S . -B build-mpi -DCMAKE_BUILD_TYPE=Release -DPOPS_USE_KOKKOS=ON -DPOPS_USE_MPI=ON -DKokkos_ROOT=KOKKOS_PREFIX
Compile.
cmake --build build-mpi -j
Run the distributed tests¶
The MPI test targets each replay the run at np=1, np=2 and np=4 under mpirun, and
check that the observables (parity, AMR, Krylov, mass) are bit-identical across the
number of ranks.
Run the test suite. On a small machine, allow oversubscribe so that np=4 fits on fewer than four physical cores.
OMPI_MCA_rmaps_base_oversubscribe=true ctest --test-dir build-mpi --output-on-failure
The non-MPI tests run at np=1 in this build (MPI-linked, single process); the MPI block checks rank invariance at np=1, np=2 and np=4.
Run on multiple CPU threads per rank¶
To use all cores of every rank, build the MPI plus Kokkos OpenMP hybrid against a
Kokkos OpenMP install. Replace KOKKOS_OPENMP_PREFIX with the path to that install.
cmake -S . -B build-mpi-omp -DCMAKE_BUILD_TYPE=Release -DPOPS_USE_MPI=ON -DPOPS_USE_KOKKOS=ON -DKokkos_ROOT=KOKKOS_OPENMP_PREFIX
Bound the thread count per rank with OMP_NUM_THREADS when you run the tests.
OMP_NUM_THREADS=4 OMPI_MCA_rmaps_base_oversubscribe=true ctest --test-dir build-mpi-omp --output-on-failure
Multi-rank from Python¶
The pops Python module is MPI-aware when _pops is built with POPS_USE_MPI=ON
(the mpi CMake preset, which writes to build-mpi and relies on POPS_USE_KOKKOS
defaulting to ON). Run a script across ranks the same way as a C++ binary, by
launching the interpreter under mpirun.
cmake --preset mpi && cmake --build --preset mpi # or the ad-hoc cmake above, conda-free
mpirun -np 4 python your_script.py
pops.my_rank() and pops.n_ranks() report the rank and the rank count. The global
accessors (sim.state_global(name), sim.density_global(name),
sim.potential_global()) and sim.write(...) are collective: they all_reduce/gather
across ranks, so every rank must call them or the run deadlocks. sim.write gathers
the field collectively and then writes ONE file on rank 0 (the Cartesian System is
mono-box); the other ranks return the path without I/O.
import numpy as np
import pops
from pops.physics import Model
from pops.math import laplacian, grad, div, ddt
from pops.mesh.cartesian import CartesianMesh
from pops.mesh.layouts import Uniform
from pops.time import Program
from pops.lib.time import ssprk2
from pops.fields import PoissonProblem
from pops.fields.bcs import Periodic
from pops.fields.rhs import ChargeDensity
from pops.solvers.elliptic import GeometricMG
from pops.codegen import Production
# Built and stepped identically on every rank. The Production backend is the MPI-capable path.
m = Model("diocotron")
U = m.state("U", components=["ne"], roles={"ne": "density"})
(ne,) = U
phi = m.field("phi")
m.solve_field("fields_from_state", equation=(-laplacian(phi) == ne),
outputs={"phi": phi, "grad_x": grad(phi).x, "grad_y": grad(phi).y},
solver=GeometricMG())
E = m.vector_field("E", x=-grad(phi).x, y=-grad(phi).y)
flux = m.flux("F", on=U, x=[ne * E.y], y=[ne * (-E.x)], waves={"x": [E.y], "y": [-E.x]})
m.rate("explicit_rate", ddt(U) == -div(flux))
m.check()
poisson = PoissonProblem(name="phi", unknown="phi",
equation=(-laplacian("phi") == ChargeDensity.from_blocks("ne")),
bcs=(Periodic(),), solver=GeometricMG())
program = Program("ssprk2")
ssprk2(program, "ne")
case = (pops.Case(layout=Uniform(CartesianMesh(n=96, L=1.0, periodic=True)))
.block("ne", physics=m.lower()).field(poisson).time(program))
compiled = pops.compile(case, backend=Production())
sim = pops.bind(compiled, state={"ne": np.ascontiguousarray(np.ones((96, 96)))})
sim.run(0.05, cfl=0.4)
# Collective: every rank calls it; rank 0 writes the single .vti file.
sim.write("out/state", format="vtk")
if pops.my_rank() == 0:
print("ranks =", pops.n_ranks(), "t =", sim.time())
For an advanced multi-box output, sim.write(format="hdf5", parallel=True) writes
per-rank hyperslabs into one file (needs h5py built with MPI plus mpi4py); on the
mono-box Cartesian System it falls back to a rank-0 write.
Plot the result¶
Open the .vti output in ParaView following
visualize with ParaView.
Next steps¶
To run multi-GPU on ROMEO, see the parallel backends page.
A multi-thread (Kokkos OpenMP) Python module is available via the
python-parallelCMake preset, with the thread count set bypops.set_threads(n)right afterimport pops; there is no nvcc/CUDA Python module (GPU is C++-only). See the installation page.The multi-rank Python API above is real and works under
mpirun. What is missing is Python MPI test coverage: no Python test runs under MPI in CI, so the distributed Python surface is exercised by hand, while the distributed numerics are validated through the C++/ctest path. See BACKEND_COVERAGE.md.