Skip to content

Commit 04d8110

Browse files
JBlaschkeax3l
andauthored
Add Particle I/O: restart & restart_checkpoint (#342)
Co-authored-by: Axel Huebl <[email protected]>
1 parent 1eff1df commit 04d8110

File tree

6 files changed

+199
-5
lines changed

6 files changed

+199
-5
lines changed

.github/workflows/intel.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ jobs:
161161
-DBUILD_SHARED_LIBS=ON \
162162
-DAMReX_MPI=ON \
163163
-DAMReX_SPACEDIM="1;2;3"
164-
cmake --build build --target pip_install -j 4
164+
cmake --build build --target pip_install -j 2
165165
166166
ccache -s
167167
du -hs ~/.cache/ccache

src/Particle/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ foreach(D IN LISTS AMReX_SPACEDIM)
22
target_sources(pyAMReX_${D}d
33
PRIVATE
44
ParticleContainer.cpp
5+
ParticleContainer_FHDeX.cpp
56
ParticleContainer_HiPACE.cpp
67
ParticleContainer_ImpactX.cpp
78
ParticleContainer_WarpX.cpp

src/Particle/ParticleContainer.H

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,21 @@ void make_ParticleContainer_and_Iterators (py::module &m, std::string allocstr)
312312
// const Vector<std::string>& int_comp_names = Vector<std::string>()) const
313313
// void CheckpointPre ();
314314
// void CheckpointPost ();
315+
315316
// void Restart (const std::string& dir, const std::string& file);
317+
.def("restart",
318+
py::overload_cast<std::string const &, std::string const &>(&ParticleContainerType::Restart),
319+
py::arg("dir"),
320+
py::arg("file")
321+
)
322+
316323
// void Restart (const std::string& dir, const std::string& file, bool is_checkpoint);
324+
.def("restart_checkpoint",
325+
py::overload_cast<std::string const &, std::string const &, bool>(&ParticleContainerType::Restart),
326+
py::arg("dir"),
327+
py::arg("file"),
328+
py::arg("is_checkpoint")
329+
)
317330

318331
.def("write_plotfile",
319332
//py::overload_cast<std::string const &, std::string const &>(&ParticleContainerType::WritePlotFile, py::const_),
@@ -436,10 +449,12 @@ void make_ParticleContainer_and_Iterators (py::module &m)
436449
T_ParticleType::NReal,
437450
T_ParticleType::NInt
438451
>(m);
439-
make_Particle< // superparticle
440-
T_ParticleType::NReal + T_NArrayReal,
441-
T_ParticleType::NInt + T_NArrayInt
442-
>(m);
452+
if (T_NArrayReal > 0 || T_NArrayInt > 0) {
453+
make_Particle< // superparticle
454+
T_ParticleType::NReal + T_NArrayReal,
455+
T_ParticleType::NInt + T_NArrayInt
456+
>(m);
457+
}
443458

444459
make_ArrayOfStructs<T_ParticleType::NReal, T_ParticleType::NInt> (m);
445460
make_StructOfArrays<T_NArrayReal, T_NArrayInt> (m);

src/Particle/ParticleContainer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace
3333
}
3434

3535
// forward declarations
36+
void init_ParticleContainer_FHDeX(py::module& m);
3637
void init_ParticleContainer_HiPACE(py::module& m);
3738
void init_ParticleContainer_ImpactX(py::module& m);
3839
void init_ParticleContainer_WarpX(py::module& m);
@@ -56,6 +57,7 @@ void init_ParticleContainer(py::module& m) {
5657
make_ParticleContainer_and_Iterators<Particle<2, 1>, 3, 1>(m);
5758

5859
// application codes
60+
init_ParticleContainer_FHDeX(m);
5961
init_ParticleContainer_HiPACE(m);
6062
init_ParticleContainer_ImpactX(m);
6163
init_ParticleContainer_WarpX(m);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* Copyright 2024 The AMReX Community
2+
*
3+
* Authors: Johannes Blaschke
4+
* License: BSD-3-Clause-LBNL
5+
*/
6+
#include "ParticleContainer.H"
7+
8+
#include <AMReX_Particle.H>
9+
#include <AMReX_ParticleTile.H>
10+
11+
12+
void init_ParticleContainer_FHDeX(py::module& m) {
13+
using namespace amrex;
14+
15+
make_ParticleContainer_and_Iterators<Particle<16, 4>, 0, 0>(m);
16+
}

tests/test_plotfileparticledata.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import random
4+
import shutil
5+
from dataclasses import dataclass
6+
7+
import numpy as np
8+
import pytest
9+
10+
import amrex.space3d as amr
11+
12+
# Particle container constructor -- depending on if gpus are available use the
13+
# GPU-enabled version. This uses the FHDeX Particle Container for the test
14+
if amr.Config.have_gpu:
15+
PCC = amr.ParticleContainer_16_4_0_0_managed
16+
else:
17+
PCC = amr.ParticleContainer_16_4_0_0_default
18+
19+
20+
@dataclass
21+
class Particle:
22+
"""
23+
Helper class to work with particle data
24+
"""
25+
26+
x: float
27+
y: float
28+
z: float
29+
30+
idx: int
31+
32+
def to_soa(self, aos_numpy):
33+
"""Fill amr particle SoA with x, y, z, idx data"""
34+
aos_numpy["x"] = self.x
35+
aos_numpy["y"] = self.y
36+
aos_numpy["z"] = self.z
37+
aos_numpy["idata_0"] = self.idx
38+
39+
40+
def generate_test_particles(n_part):
41+
"""
42+
Returns a list of test particles scattered throught the domain
43+
"""
44+
particles = list()
45+
46+
def generator():
47+
return 1 - 2 * random.random()
48+
49+
for i in range(n_part):
50+
particles.append(Particle(x=generator(), y=generator(), z=generator(), idx=i))
51+
52+
return particles
53+
54+
55+
def particle_container(Rpart, std_geometry, distmap, boxarr, std_real_box):
56+
"""
57+
Generate a fresh particle container, containing copies from Rpart
58+
"""
59+
pc = PCC(std_geometry, distmap, boxarr)
60+
61+
iseed = 1
62+
myt = amr.ParticleInitType_16_4_0_0()
63+
pc.init_random(len(Rpart), iseed, myt, False, std_real_box)
64+
65+
particles_tile_ct = 0
66+
# assign some values to runtime components
67+
for lvl in range(pc.finest_level + 1):
68+
for pti in pc.iterator(pc, level=lvl):
69+
aos = pti.aos()
70+
aos_numpy = aos.to_numpy(copy=False)
71+
for i, p in enumerate(aos_numpy):
72+
Rpart[i + particles_tile_ct].to_soa(p)
73+
particles_tile_ct += len(aos_numpy)
74+
75+
pc.redistribute()
76+
return pc
77+
78+
79+
def check_particles_container(pc, reference_particles):
80+
"""
81+
Checks the contents of `pc` against `reference_particles`
82+
"""
83+
for lvl in range(pc.finest_level + 1):
84+
for i, pti in enumerate(pc.iterator(pc, level=lvl)):
85+
aos = pti.aos()
86+
for p in aos.to_numpy(copy=True):
87+
ref = reference_particles[p["idata_0"]]
88+
assert Particle(x=p["x"], y=p["y"], z=p["z"], idx=p["idata_0"]) == ref
89+
90+
91+
def write_test_plotfile(filename, reference_part):
92+
"""
93+
Write single-level plotfile (in order to read it back in).
94+
"""
95+
domain_box = amr.Box([0, 0, 0], [31, 31, 31])
96+
real_box = amr.RealBox([-0.5, -0.5, -0.5], [0.5, 0.5, 0.5])
97+
geom = amr.Geometry(domain_box, real_box, amr.CoordSys.cartesian, [0, 0, 0])
98+
99+
ba = amr.BoxArray(domain_box)
100+
dm = amr.DistributionMapping(ba, 1)
101+
mf = amr.MultiFab(ba, dm, 1, 0)
102+
mf.set_val(np.pi)
103+
104+
time = 1.0
105+
level_step = 200
106+
var_names = amr.Vector_string(["density"])
107+
amr.write_single_level_plotfile(filename, mf, var_names, geom, time, level_step)
108+
109+
pc = particle_container(reference_part, geom, dm, ba, real_box)
110+
pc.write_plotfile(filename, "particles")
111+
112+
113+
def load_test_plotfile_particle_container(plot_file_name):
114+
"""
115+
Load signle-level plotfile and return particle container
116+
"""
117+
plt = amr.PlotFileData(plot_file_name)
118+
119+
probDomain = plt.probDomain(0)
120+
probLo = plt.probLo()
121+
probHi = plt.probHi()
122+
domain_box = amr.Box(probDomain.small_end, probDomain.big_end)
123+
real_box = amr.RealBox(probLo, probHi)
124+
std_geometry = amr.Geometry(domain_box, real_box, plt.coordSys(), [0, 0, 0])
125+
126+
pc = amr.ParticleContainer_16_4_0_0_default(
127+
std_geometry,
128+
plt.DistributionMap(plt.finestLevel()),
129+
plt.boxArray(plt.finestLevel()),
130+
)
131+
pc.restart(plot_file_name, "particles")
132+
133+
return pc
134+
135+
136+
@pytest.mark.skipif(amr.Config.spacedim != 3, reason="Requires AMREX_SPACEDIM = 3")
137+
def test_plotfile_particle_data_read():
138+
"""
139+
Generate and then read a plot file containing particle data. Checks that
140+
the particle data matches the original particle list.
141+
"""
142+
# seed RNG to make test reproducible -- comment out this line to generate new
143+
# random particle positions every time.
144+
random.seed(1)
145+
146+
plt_file_name = "plt_test"
147+
# Reference particle lists
148+
n_part = 15
149+
reference_part = generate_test_particles(n_part)
150+
# Write a test plotfile containing the reference particles in a paritcle
151+
# container
152+
write_test_plotfile(plt_file_name, reference_part)
153+
# Load the particle container from the test plot file
154+
pc = load_test_plotfile_particle_container(plt_file_name)
155+
# Check that the particles in the loaded particle container match the
156+
# original particle list
157+
check_particles_container(pc, reference_part)
158+
159+
# clean up after yourself
160+
shutil.rmtree(plt_file_name)

0 commit comments

Comments
 (0)