Skip to content

Commit 39376f5

Browse files
authored
Pandas: ImpactXParticleContainer.to_df() (#458)
* Pandas: `ImpactXParticleContainer.to_df()` Copy all particles into a `pandas.DataFrame`. Supports local and MPI-gathered results. * AMReX & pyAMReX: latest development X-ref: AMReX-Codes/pyamrex#220 * DataFrame: Add Test
1 parent 82200fe commit 39376f5

File tree

6 files changed

+182
-2
lines changed

6 files changed

+182
-2
lines changed

cmake/dependencies/ABLASTR.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ set(ImpactX_ablastr_branch "23.11"
186186
set(ImpactX_amrex_repo "https://github.com/AMReX-Codes/amrex.git"
187187
CACHE STRING
188188
"Repository URI to pull and build AMReX from if(ImpactX_amrex_internal)")
189-
set(ImpactX_amrex_branch ""
189+
set(ImpactX_amrex_branch "175b99d913dc2748e43c53192737170c770fe0e8"
190190
CACHE STRING
191191
"Repository branch for ImpactX_amrex_repo if(ImpactX_amrex_internal)")
192192

cmake/dependencies/pyAMReX.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ option(ImpactX_pyamrex_internal "Download & build pyAMReX" ON)
7979
set(ImpactX_pyamrex_repo "https://github.com/AMReX-Codes/pyamrex.git"
8080
CACHE STRING
8181
"Repository URI to pull and build pyamrex from if(ImpactX_pyamrex_internal)")
82-
set(ImpactX_pyamrex_branch "23.11"
82+
set(ImpactX_pyamrex_branch "1c24a8504fd9df0e2ad2f447a4d1567750a0e4e0"
8383
CACHE STRING
8484
"Repository branch for ImpactX_pyamrex_repo if(ImpactX_pyamrex_internal)")
8585

src/python/ImpactXParticleContainer.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,50 @@
1212
#include <AMReX_MFIter.H>
1313
#include <AMReX_ParticleContainer.H>
1414

15+
#include <algorithm>
16+
#include <string>
17+
#include <vector>
18+
1519
namespace py = pybind11;
1620
using namespace impactx;
1721

1822

1923
void init_impactxparticlecontainer(py::module& m)
2024
{
25+
py::class_<RealAoS>(m, "RealAoS")
26+
.def_property_readonly_static("names_s",
27+
[](py::object) {
28+
std::vector<std::string> real_aos_names(RealAoS::names_s.size());
29+
std::copy(RealAoS::names_s.begin(), RealAoS::names_s.end(), real_aos_names.begin());
30+
return real_aos_names;
31+
},
32+
"named labels for fixed s")
33+
.def_property_readonly_static("names_t",
34+
[](py::object) {
35+
std::vector<std::string> real_aos_names(RealAoS::names_t.size());
36+
std::copy(RealAoS::names_t.begin(), RealAoS::names_t.end(), real_aos_names.begin());
37+
return real_aos_names;
38+
},
39+
"named labels for fixed t")
40+
;
41+
42+
py::class_<RealSoA>(m, "RealSoA")
43+
.def_property_readonly_static("names_s",
44+
[](py::object) {
45+
std::vector<std::string> real_soa_names(RealSoA::names_s.size());
46+
std::copy(RealSoA::names_s.begin(), RealSoA::names_s.end(), real_soa_names.begin());
47+
return real_soa_names;
48+
},
49+
"named labels for fixed s")
50+
.def_property_readonly_static("names_t",
51+
[](py::object) {
52+
std::vector<std::string> real_soa_names(RealSoA::names_t.size());
53+
std::copy(RealSoA::names_t.begin(), RealSoA::names_t.end(), real_soa_names.begin());
54+
return real_soa_names;
55+
},
56+
"named labels for fixed t")
57+
;
58+
2159
py::class_<
2260
ParIter,
2361
amrex::ParIter<0, 0, RealSoA::nattribs, IntSoA::nattribs>
@@ -43,6 +81,16 @@ void init_impactxparticlecontainer(py::module& m)
4381
amrex::ParticleContainer<0, 0, RealSoA::nattribs, IntSoA::nattribs>
4482
>(m, "ImpactXParticleContainer")
4583
//.def(py::init<>())
84+
85+
.def_property_readonly_static("RealAoS",
86+
[](py::object /* pc */){ return py::type::of<RealAoS>(); },
87+
"RealAoS attribute name labels"
88+
)
89+
.def_property_readonly_static("RealSoA",
90+
[](py::object /* pc */){ return py::type::of<RealSoA>(); },
91+
"RealSoA attribute name labels"
92+
)
93+
4694
.def("add_n_particles",
4795
&ImpactXParticleContainer::AddNParticles,
4896
py::arg("lev"),
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
This file is part of ImpactX
3+
4+
Copyright 2023 ImpactX contributors
5+
Authors: Axel Huebl
6+
License: BSD-3-Clause-LBNL
7+
"""
8+
9+
10+
def ix_pc_to_df(self, local=True, comm=None, root_rank=0):
11+
"""
12+
Copy all particles into a pandas.DataFrame
13+
14+
Parameters
15+
----------
16+
self : amrex.ParticleContainer_*
17+
A ParticleContainer class in pyAMReX
18+
local : bool
19+
MPI-local particles
20+
comm : MPI Communicator
21+
if local is False, this defaults to mpi4py.MPI.COMM_WORLD
22+
root_rank : MPI root rank to gather to
23+
if local is False, this defaults to 0
24+
25+
Returns
26+
-------
27+
A concatenated pandas.DataFrame with particles from all levels.
28+
29+
Returns None if no particles were found.
30+
If local=False, then all ranks but the root_rank will return None.
31+
"""
32+
df = super(type(self), self).to_df(local=local, comm=comm, root_rank=root_rank)
33+
34+
# rename columns according to our attribute names
35+
if df is not None:
36+
# todo: check if currently in fixed s or fixed t and pick name accordingly
37+
38+
names = []
39+
for n in self.RealAoS.names_s:
40+
names.append(n)
41+
names.append("cpuid")
42+
for n in self.RealSoA.names_s:
43+
names.append(n)
44+
45+
df.columns.values[0 : len(names)] = names
46+
47+
# todo: also rename runtime attributes (e.g., "s_lost")
48+
# https://github.com/ECP-WarpX/impactx/pull/398
49+
50+
return df
51+
52+
53+
def register_ImpactXParticleContainer_extension(ixpc):
54+
"""ImpactXParticleContainer helper methods"""
55+
56+
# register member functions for ImpactXParticleContainer
57+
ixpc.to_df = ix_pc_to_df

src/python/impactx/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
# import core bindings to C++
1919
from . import impactx_pybind as cxx
20+
from .ImpactXParticleContainer import (
21+
register_ImpactXParticleContainer_extension,
22+
)
2023
from .impactx_pybind import * # noqa
2124
from .madx_to_impactx import read_beam, read_lattice # noqa
2225

@@ -35,3 +38,6 @@
3538

3639
# MAD-X file reader for reference particle
3740
RefPart.load_file = read_beam # noqa
41+
42+
# Pure Python extensions to ImpactX types
43+
register_ImpactXParticleContainer_extension(ImpactXParticleContainer)

tests/python/test_dataframe.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright 2022-2023 The ImpactX Community
4+
#
5+
# Authors: Axel Huebl
6+
# License: BSD-3-Clause-LBNL
7+
#
8+
# -*- coding: utf-8 -*-
9+
10+
from impactx import ImpactX, RefPart, distribution, elements
11+
12+
13+
def test_df_pandas():
14+
"""
15+
This tests using ImpactX and Pandas Dataframes
16+
"""
17+
sim = ImpactX()
18+
19+
sim.particle_shape = 2
20+
sim.slice_step_diagnostics = True
21+
sim.init_grids()
22+
23+
# init particle beam
24+
kin_energy_MeV = 2.0e3
25+
bunch_charge_C = 1.0e-9
26+
npart = 10000
27+
28+
# reference particle
29+
pc = sim.particle_container()
30+
ref = pc.ref_particle()
31+
ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(kin_energy_MeV)
32+
33+
# particle bunch
34+
distr = distribution.Waterbag(
35+
sigmaX=3.9984884770e-5,
36+
sigmaY=3.9984884770e-5,
37+
sigmaT=1.0e-3,
38+
sigmaPx=2.6623538760e-5,
39+
sigmaPy=2.6623538760e-5,
40+
sigmaPt=2.0e-3,
41+
muxpx=-0.846574929020762,
42+
muypy=0.846574929020762,
43+
mutpt=0.0,
44+
)
45+
sim.add_particles(bunch_charge_C, distr, npart)
46+
47+
assert pc.TotalNumberOfParticles() == npart
48+
49+
# init accelerator lattice
50+
fodo = [
51+
elements.Drift(0.25),
52+
elements.Quad(1.0, 1.0),
53+
elements.Drift(0.5),
54+
elements.Quad(1.0, -1.0),
55+
elements.Drift(0.25),
56+
]
57+
sim.lattice.extend(fodo)
58+
59+
# simulate
60+
sim.evolve()
61+
62+
# compare number of global particles
63+
df = pc.to_df(local=False)
64+
if df is not None:
65+
assert npart == len(df)
66+
67+
68+
if __name__ == "__main__":
69+
test_df_pandas()

0 commit comments

Comments
 (0)