Read Back Plotfiles

AMReX applications write field (mesh) and particle data as plotfiles and checkpoint files. This page shows how to read such files back into pyAMReX objects, using the same AMReX C++ reader logic that applications use for restarts.

The returned objects are regular pyAMReX containers: field and particle data can be accessed with zero-copy views from NumPy, CuPy, and other libraries and processed as shown in Compute.

Note

For quick data analysis and visualization of AMReX plotfiles, yt is a feature-rich, dedicated package. Reading plotfiles directly with pyAMReX, as shown here, is ideal when the data shall be placed 1:1 into AMReX data structures again, e.g., to initialize or restart a simulation, to couple codes, or to post-process with the exact AMReX block-structure, zero-copy math and MPI-parallelism.

Field (Mesh) Data

Use PlotFileData to open a plotfile, query its meta-data and read per-level field data as MultiFab:

plt = amr.PlotFileData(plt_filename)

# meta-data: AMR levels, domain extent and cell sizes
finest_level = plt.finestLevel()
prob_lo = plt.probLo()  # physical coordinates of the lower domain corner
prob_hi = plt.probHi()  # ... and the upper domain corner
cell_size = plt.cellSize(0)  # cell sizes (dx, dy, dz) on level 0
var_names = plt.varNames()  # stored field components, e.g., ["density"]

# read a field component on a level as a MultiFab ...
mf_density = plt.get(0, "density")

# ... and access its blocks as numpy/cupy/dpnp arrays (zero-copy views)
total = 0.0
for mfi in mf_density:
    marr_xp = mf_density.array(mfi).to_xp()
    # float() coerces the per-block reduction to a host scalar for any
    # array module (NumPy/CuPy/dpnp)
    total += float(marr_xp.sum())  # compute, plot, analyze, ...

Particle Data

Particle data in a plotfile or checkpoint is stored in a sub-directory (often called particles, per species name, or similar). Use list_particle_species() to discover which particle sub-directories a file contains:

# discover the particle species stored in a plotfile
species = amr.list_particle_species(plt_file_name)
print(species)  # e.g., ["particles"]

Use read_particles() to read a species back into a particle container - no prior knowledge of the writing container’s compile-time layout is needed, the number and names of the particle components are discovered from the file:

# read all particles from <plotfile>/particles/ into a new container;
# the component names and layout are discovered from the file
pc = amr.read_particles(plt_file_name, "particles")

# access the data per tile, e.g., as zero-copy numpy/cupy arrays ...
w_idx = pc.get_real_comp_index("w")  # runtime component from the file
for lvl in range(pc.finest_level + 1):
    for pti in pc.iterator(level=lvl):
        soa = pti.soa()
        x = soa.get_real_data(0).to_xp()  # position x
        w = soa.get_real_data(w_idx).to_xp()  # runtime component "w"

# ... or copy all (MPI rank-local) particles into a pandas DataFrame
# df = pc.to_df()

For multi-level files, particles from all levels are read. The auto-created container is single-level: all particles are placed on level 0, preserving their positions and components (only the mesh-refinement level association is flattened). To move particles on MR levels again, an explicit call to Redistribute needs to be made after reading.

To only inspect the on-disk layout, e.g., to check which components a file contains before reading it, use ParticleHeader:

# inspect the on-disk particle component layout, without reading the data
header = amr.ParticleHeader.read(plt_file_name, "particles")

print(header.real_comp_names)  # e.g., ["w"]
print(header.int_comp_names)  # e.g., ["i1", "i2"]
print(header.num_particles)  # e.g., 15
print(header.is_checkpoint)  # False for plotfiles, True for checkpoints

The header also exposes the per-level grid table that locates each grid’s binary particle data on disk - useful for tools that process the file layout directly, e.g., converters and parallel readers:

# the per-level grid table locates each grid's binary particle data:
# data file index (DATA_XXXXX), particle count and byte offset
header = amr.ParticleHeader.read(plt_file_name, "particles")

for lev, entries in enumerate(header.grids):
    for entry in entries:
        print(lev, entry.which, entry.count, entry.where)

Read Into an Existing Container

read_particles() can also fill an existing, geometry-defined container, via its container argument. Use this to control the container type, MPI decomposition and mesh-refinement levels - or when the geometry cannot be recovered from the file itself: application checkpoints store their top-level Header in an application-specific format, so their AMR geometry must be defined by the application before reading.

# define the geometry in the application, e.g., for a checkpoint restart,
# then fill the container from the file
pc_read = amr.ParticleContainer_pureSoA_3_0_polymorphic(geom, dm, ba)
pc_read.arena = amr.The_Arena()
pc_read = amr.read_particles(plt_file_name, "particles", container=pc_read)

Related low-level APIs, mirroring the AMReX C++ workflows for restarts:

  • pc.restart_checkpoint(dir, file, is_checkpoint): restore particles from a checkpoint/plotfile into a live container,

  • VisMF.Read(name) / VisMF.Write(mf, name): read/write a single MultiFab at the raw multifab-file granularity,

  • write_single_level_plotfile() / write_multi_level_plotfile() and pc.write_plotfile(dir, name, real_comp_names, int_comp_names): write fields and particles, e.g., to generate the files read back above.