Python
These tutorials use pyAMReX, the
Python bindings of AMReX, with zero-copy NumPy (CPU), CuPy (CUDA/HIP) or
dpnp (SYCL) views of AMReX data. They are Python scripts written for 3D
(amrex.space3d) and need pyAMReX 26.09 or newer (import amrex),
plus CuPy for CUDA/HIP or dpnp for SYCL GPU builds of pyAMReX; see the
pyAMReX documentation for installation. As with the C++ programs,
arguments after the script name are passed to AMReX, so
python3 main.py inputs n_cell=64 reads the inputs file and overrides
n_cell. The scripts are not compiled; with -DAMReXTutorials_PYTHON=ON
(and -DPython3_EXECUTABLE pointing to a Python that has pyAMReX), CTest runs
them as the tests Python_<Name>. See Getting Started. The
MPMD tutorial also has a pyAMReX program.
MultiFab
- Level:
★
- Source:
- Key APIs:
amr.initialize,amr.Box,amr.BoxArray,amr.DistributionMapping,amr.Geometry,amr.MultiFab,amr.xp,mf.array(mfi).to_xp(),mf.to_xp(),mf.fill_boundary,mf.min,mf.max,mf.sum,amr.write_single_level_plotfile,amr.ParmParse
The Python version of MultiFab: build a MultiFab, fill it
through zero-copy views, fill ghost cells, compute reductions, and write a
plotfile.
for mfi in mf: loops over the boxes owned by this MPI rank, and
mf.array(mfi).to_xp(copy=False) returns a NumPy, CuPy or dpnp array
that shares memory with the box’s data. The view includes the ghost cells, is
indexed [x, y, z, component] with indices starting at 0, so the valid
cells are [ng:ng+nx, ng:ng+ny, ng:ng+nz] for a box of nx*ny*nz
cells. mf.to_xp() returns the views of all local boxes as a list. Pass
order="C" for [component, z, y, x] indexing, which is usually faster
on GPUs.
# Loop over the boxes owned by this rank. mf.array(mfi).to_xp() is a
# zero-copy view of the box's data including ghost cells, indexed
# [x, y, z, component] with local indices starting at 0.
for mfi in mf:
bx = mfi.validbox()
phi = mf.array(mfi).to_xp(copy=False)
x = (xp.arange(bx.small_end[0], bx.big_end[0] + 1) + 0.5) * dx[0]
y = (xp.arange(bx.small_end[1], bx.big_end[1] + 1) + 0.5) * dx[1]
z = (xp.arange(bx.small_end[2], bx.big_end[2] + 1) + 0.5) * dx[2]
r2 = (
(x[:, None, None] - 0.5) ** 2
+ (y[None, :, None] - 0.5) ** 2
+ (z[None, None, :] - 0.5) ** 2
)
# Valid cells only: skip ng ghost cells on each side
valid = phi[ng : ng + x.size, ng : ng + y.size, ng : ng + z.size, 0]
valid[...] = 1.0 + xp.exp(-r2 / 0.01)
# All local boxes at once: a list of views, one per box
for phi in mf.to_xp():
phi[..., 1] = 2.0 * phi[..., 0]
Command-line arguments, such as an inputs file, are passed to AMReX, and
all AMReX objects must be destroyed before amr.finalize:
if __name__ == "__main__":
if not hasattr(amr, "xp"):
sys.exit("This tutorial needs pyAMReX 26.09 or newer.")
# Command line arguments, e.g., an inputs file, are passed to AMReX.
amr.initialize(sys.argv[1:])
status = main() # AMReX objects must be gone before finalize
amr.Print("PASSED" if status == 0 else "FAILED")
amr.finalize()
sys.exit(status)
Run:
cd Python/MultiFab
python3 main.py inputs
mpiexec -n 2 python3 main.py inputs n_cell=64
min including ghost cells before FillBoundary: 0.0
min including ghost cells after FillBoundary: 1.0
max(phi) = 1.9293757195853387
integral(phi) = 1.0055683279968117 (exact: 1.0055683279968317)
max(2 phi) = 3.8587514391706774
Wrote plt00000
PASSED
HeatEquation
- Level:
★
- Source:
- Key APIs:
amr.MultiFab,amr.xp,mf.array(mfi).to_xp(),mf.fill_boundary,geom.periodicity(),mf.max,amr.ParmParse.to_dict,amr.write_single_level_plotfile
The Python version of HeatEquation: the heat equation \(\partial\phi/\partial t = D\nabla^2\phi\) with explicit time steps on a periodic unit cube. The initial condition is \(1 + \sin(2\pi x)\sin(2\pi y)\sin(2\pi z)\); its sine mode decays as \(\exp(-12\pi^2 D t)\), and the final error against this exact solution is checked.
The stencil is written with array slicing on the zero-copy view of each
box. The loop over boxes is in Python, but the work on each box is done by
NumPy, CuPy or dpnp, so large boxes (max_grid_size) keep the Python
overhead small.
# Fill ghost cells, here all from periodic images and neighbors.
phi_old.fill_boundary(geom.periodicity())
# phi_new = phi_old + dt * D * Laplacian(phi_old), with array slicing
fac = dt_step * diffusion_coef
for mfi in phi_new:
po = phi_old.array(mfi).to_xp(copy=False)[..., 0]
pn = phi_new.array(mfi).to_xp(copy=False)[..., 0]
c = po[v]
pn[v] = c + fac * (
(po[2:, 1:-1, 1:-1] - 2.0 * c + po[:-2, 1:-1, 1:-1]) / dx[0] ** 2
+ (po[1:-1, 2:, 1:-1] - 2.0 * c + po[1:-1, :-2, 1:-1]) / dx[1] ** 2
+ (po[1:-1, 1:-1, 2:] - 2.0 * c + po[1:-1, 1:-1, :-2]) / dx[2] ** 2
)
Run:
cd Python/HeatEquation
python3 main.py inputs
mpiexec -n 2 python3 main.py inputs n_cell=64 plot_int=20
Step 10, time = 0.0014648437499999998
...
Finished 69 steps at time 0.01, mode amplitude = 0.3059442057
Max error = 0.001952850866, tolerance = 0.008614567486
PASSED