Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Particle I/O: restart & restart_checkpoint #342

Merged
merged 23 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Particle/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ foreach(D IN LISTS AMReX_SPACEDIM)
target_sources(pyAMReX_${D}d
PRIVATE
ParticleContainer.cpp
ParticleContainer_FHDeX.cpp
ParticleContainer_HiPACE.cpp
ParticleContainer_ImpactX.cpp
ParticleContainer_WarpX.cpp
Expand Down
23 changes: 19 additions & 4 deletions src/Particle/ParticleContainer.H
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,21 @@ void make_ParticleContainer_and_Iterators (py::module &m, std::string allocstr)
// const Vector<std::string>& int_comp_names = Vector<std::string>()) const
// void CheckpointPre ();
// void CheckpointPost ();

// void Restart (const std::string& dir, const std::string& file);
.def("restart",
py::overload_cast<std::string const &, std::string const &>(&ParticleContainerType::Restart),
py::arg("dir"),
py::arg("file")
)

// void Restart (const std::string& dir, const std::string& file, bool is_checkpoint);
.def("restart_checkpoint",
py::overload_cast<std::string const &, std::string const &, bool>(&ParticleContainerType::Restart),
py::arg("dir"),
py::arg("file"),
py::arg("is_checkpoint")
)

.def("write_plotfile",
//py::overload_cast<std::string const &, std::string const &>(&ParticleContainerType::WritePlotFile, py::const_),
Expand Down Expand Up @@ -436,10 +449,12 @@ void make_ParticleContainer_and_Iterators (py::module &m)
T_ParticleType::NReal,
T_ParticleType::NInt
>(m);
make_Particle< // superparticle
T_ParticleType::NReal + T_NArrayReal,
T_ParticleType::NInt + T_NArrayInt
>(m);
if (T_NArrayReal > 0 || T_NArrayInt > 0) {
make_Particle< // superparticle
T_ParticleType::NReal + T_NArrayReal,
T_ParticleType::NInt + T_NArrayInt
>(m);
}

make_ArrayOfStructs<T_ParticleType::NReal, T_ParticleType::NInt> (m);
make_StructOfArrays<T_NArrayReal, T_NArrayInt> (m);
Expand Down
2 changes: 2 additions & 0 deletions src/Particle/ParticleContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace
}

// forward declarations
void init_ParticleContainer_FHDeX(py::module& m);
void init_ParticleContainer_HiPACE(py::module& m);
void init_ParticleContainer_ImpactX(py::module& m);
void init_ParticleContainer_WarpX(py::module& m);
Expand All @@ -56,6 +57,7 @@ void init_ParticleContainer(py::module& m) {
make_ParticleContainer_and_Iterators<Particle<2, 1>, 3, 1>(m);

// application codes
init_ParticleContainer_FHDeX(m);
init_ParticleContainer_HiPACE(m);
init_ParticleContainer_ImpactX(m);
init_ParticleContainer_WarpX(m);
Expand Down
16 changes: 16 additions & 0 deletions src/Particle/ParticleContainer_FHDeX.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* Copyright 2024 The AMReX Community
*
* Authors: Johannes Blaschke
* License: BSD-3-Clause-LBNL
*/
#include "ParticleContainer.H"

#include <AMReX_Particle.H>
#include <AMReX_ParticleTile.H>


void init_ParticleContainer_FHDeX(py::module& m) {
using namespace amrex;

make_ParticleContainer_and_Iterators<Particle<16, 4>, 0, 0>(m);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JBlaschke do you need this for 1D, 2D and 3D AMReX builds or only for 3D?

}
160 changes: 160 additions & 0 deletions tests/test_plotfileparticledata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-

import random
import shutil
from dataclasses import dataclass

import numpy as np
import pytest

import amrex.space3d as amr

# Particle container constructor -- depending on if gpus are available use the
# GPU-enabled version. This uses the FHDeX Particle Container for the test
if amr.Config.have_gpu:
PCC = amr.ParticleContainer_16_4_0_0_managed
else:
PCC = amr.ParticleContainer_16_4_0_0_default


@dataclass
class Particle:
"""
Helper class to work with particle data
"""

x: float
y: float
z: float

idx: int

def to_soa(self, aos_numpy):
"""Fill amr particle SoA with x, y, z, idx data"""
aos_numpy["x"] = self.x
aos_numpy["y"] = self.y
aos_numpy["z"] = self.z
aos_numpy["idata_0"] = self.idx


def generate_test_particles(n_part):
"""
Returns a list of test particles scattered throught the domain
"""
particles = list()

def generator():
return 1 - 2 * random.random()

for i in range(n_part):
particles.append(Particle(x=generator(), y=generator(), z=generator(), idx=i))

return particles


def particle_container(Rpart, std_geometry, distmap, boxarr, std_real_box):
"""
Generate a fresh particle container, containing copies from Rpart
"""
pc = PCC(std_geometry, distmap, boxarr)

iseed = 1
myt = amr.ParticleInitType_16_4_0_0()
pc.init_random(len(Rpart), iseed, myt, False, std_real_box)

particles_tile_ct = 0
# assign some values to runtime components
for lvl in range(pc.finest_level + 1):
for pti in pc.iterator(pc, level=lvl):
aos = pti.aos()
aos_numpy = aos.to_numpy(copy=False)
for i, p in enumerate(aos_numpy):
Rpart[i + particles_tile_ct].to_soa(p)
particles_tile_ct += len(aos_numpy)
ax3l marked this conversation as resolved.
Show resolved Hide resolved

pc.redistribute()
Copy link
Member

@ax3l ax3l Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JBlaschke This fixed the pc.OK() issue that you saw when running AMReX in Debug mode. Please double check (maybe with print debugging) if this does what you expect to do or if you geometry needs adjustment, if the pc.redistribute removes particles.

return pc


def check_particles_container(pc, reference_particles):
"""
Checks the contents of `pc` against `reference_particles`
"""
for lvl in range(pc.finest_level + 1):
for i, pti in enumerate(pc.iterator(pc, level=lvl)):
aos = pti.aos()
for p in aos.to_numpy(copy=True):
ref = reference_particles[p["idata_0"]]
assert Particle(x=p["x"], y=p["y"], z=p["z"], idx=p["idata_0"]) == ref


def write_test_plotfile(filename, reference_part):
"""
Write single-level plotfile (in order to read it back in).
"""
domain_box = amr.Box([0, 0, 0], [31, 31, 31])
real_box = amr.RealBox([-0.5, -0.5, -0.5], [0.5, 0.5, 0.5])
geom = amr.Geometry(domain_box, real_box, amr.CoordSys.cartesian, [0, 0, 0])

ba = amr.BoxArray(domain_box)
dm = amr.DistributionMapping(ba, 1)
mf = amr.MultiFab(ba, dm, 1, 0)
mf.set_val(np.pi)

time = 1.0
level_step = 200
var_names = amr.Vector_string(["density"])
amr.write_single_level_plotfile(filename, mf, var_names, geom, time, level_step)

pc = particle_container(reference_part, geom, dm, ba, real_box)
pc.write_plotfile(filename, "particles")


def load_test_plotfile_particle_container(plot_file_name):
"""
Load signle-level plotfile and return particle container
"""
plt = amr.PlotFileData(plot_file_name)

probDomain = plt.probDomain(0)
probLo = plt.probLo()
probHi = plt.probHi()
domain_box = amr.Box(probDomain.small_end, probDomain.big_end)
real_box = amr.RealBox(probLo, probHi)
std_geometry = amr.Geometry(domain_box, real_box, plt.coordSys(), [0, 0, 0])

pc = amr.ParticleContainer_16_4_0_0_default(
std_geometry,
plt.DistributionMap(plt.finestLevel()),
plt.boxArray(plt.finestLevel()),
)
pc.restart(plot_file_name, "particles")

return pc


@pytest.mark.skipif(amr.Config.spacedim != 3, reason="Requires AMREX_SPACEDIM = 3")
def test_plotfile_particle_data_read():
"""
Generate and then read a plot file containing particle data. Checks that
the particle data matches the original particle list.
"""
# seed RNG to make test reproducible -- comment out this line to generate new
# random particle positions every time.
random.seed(1)

plt_file_name = "plt_test"
# Reference particle lists
n_part = 15
reference_part = generate_test_particles(n_part)
# Write a test plotfile containing the reference particles in a paritcle
# container
write_test_plotfile(plt_file_name, reference_part)
# Load the particle container from the test plot file
pc = load_test_plotfile_particle_container(plt_file_name)
# Check that the particles in the loaded particle container match the
# original particle list
check_particles_container(pc, reference_part)

# clean up after yourself
shutil.rmtree(plt_file_name)
Loading