Skip to content

Commit 038727f

Browse files
authored
Pandas: ParticleContainer_*.to_df() (#220)
* Pandas: `ParticleContainer_*.to_df()` Copy all particles into a `pandas.DataFrame`. Supports local and MPI-gathered results. * Update Stubs
1 parent a47db85 commit 038727f

File tree

16 files changed

+1474
-7
lines changed

16 files changed

+1474
-7
lines changed

.github/workflows/ubuntu.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
export CXX=$(which g++-10)
8383
python3 -m pip install -U pip setuptools wheel
8484
python3 -m pip install -U cmake
85-
python3 -m pip install -U pytest mpi4py
85+
python3 -m pip install -U pandas pytest mpi4py
8686
8787
cmake -S . -B build \
8888
-DCMAKE_BUILD_TYPE=Debug \
@@ -174,7 +174,8 @@ jobs:
174174
export CCACHE_MAXSIZE=300M
175175
ccache -z
176176
177-
python3 -m pip install -U pip pytest
177+
python3 -m pip install -U pip
178+
python3 -m pip install -U pandas pytest
178179
python3 -m pip install -v .
179180
python3 -c "import amrex.space1d as amr; print(amr.__version__)"
180181
python3 -c "import amrex.space2d as amr; print(amr.__version__)"

.github/workflows/windows.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ jobs:
1818
python-version: '3.x'
1919
- name: Build & Install
2020
run: |
21-
python3 -m pip install -U pip pytest
21+
python3 -m pip install -U pip
22+
python3 -m pip install -U pandas pytest
2223
python3 -m pip install -v .
2324
if(!$?) { Exit $LASTEXITCODE }
2425

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Optional dependencies include:
6666
- [mpi4py](https://mpi4py.readthedocs.io) 2.1+: for multi-node and/or multi-GPU execution
6767
- [CCache](https://ccache.dev): to speed up rebuilds (for CUDA support, needs 3.7.9+ and 4.2+ is recommended)
6868
- further [optional dependencies of AMReX](https://github.com/AMReX-Codes/amrex/)
69+
- [pandas](https://pandas.pydata.org/) 2+: for DataFrame support
6970
- [pytest](https://docs.pytest.org/en/stable/) 6.2+: for running unit tests
7071

7172
Optional CUDA-capable dependencies for tests include:
@@ -105,6 +106,10 @@ If you wish to run unit tests, then please install `pytest`
105106
python3 -m pip install -U pytest
106107
```
107108

109+
Some of our tests depend on optional third-party modules (e.g., `pandas`, `cupy`, `numba`, and/or `pytorch`).
110+
If these are not installed then their tests will be skipped.
111+
112+
108113
### Configure your compiler
109114

110115
For example, using the Clang compiler:

docs/source/install/dependencies.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ Optional dependencies include:
2828
- further `optional dependencies of AMReX <https://github.com/AMReX-Codes/amrex/>`__
2929
- `Python dependencies <https://www.python.org>`__
3030

31-
- `mpi4py <https://mpi4py.readthedocs.io>`__
32-
- `cupy <https://github.com/cupy/cupy#installation>`__ 11.2+
33-
- `numba <https://numba.readthedocs.io/en/stable/user/installing.html>`__ 0.56+
34-
- `torch <https://pytorch.org/get-started/locally/>`__ 1.12+
31+
- `mpi4py 2.1+ <https://mpi4py.readthedocs.io>`__: for multi-node and/or multi-GPU execution
32+
- `cupy 11.2+ <https://github.com/cupy/cupy#installation>`__
33+
- `numba 0.56+ <https://numba.readthedocs.io/en/stable/user/installing.html>`__
34+
- `pandas 2+ <https://pandas.pydata.org>`__: for DataFrame support
35+
- `torch 1.12+ <https://pytorch.org/get-started/locally/>`__
3536

3637
For all other systems, we recommend to use a **package dependency manager**:
3738
Pick *one* of the installation methods below to install all dependencies for pyAMReX development in a consistent manner.

src/Particle/ParticleContainer.H

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ void make_Base_Iterators (py::module &m, std::string allocstr)
6868
py::return_value_policy::reference_internal)
6969

7070
.def_property_readonly_static("is_soa_particle", [](const py::object&){ return ParticleType::is_soa_particle;})
71+
.def_property_readonly("size", &iterator_base::numParticles,
72+
"the number of particles on this tile")
7173
.def_property_readonly("num_particles", &iterator_base::numParticles)
7274
.def_property_readonly("num_real_particles", &iterator_base::numRealParticles)
7375
.def_property_readonly("num_neighbor_particles", &iterator_base::numNeighborParticles)
@@ -382,6 +384,14 @@ void make_ParticleContainer_and_Iterators (py::module &m, std::string allocstr)
382384
make_Iterators< false, iterator, Allocator >(m, allocstr);
383385
using const_iterator = amrex::ParConstIter_impl<ParticleType, T_NArrayReal, T_NArrayInt, Allocator>;
384386
make_Iterators< true, const_iterator, Allocator >(m, allocstr);
387+
388+
// simpler particle iterator loops: return types of this particle box
389+
py_pc
390+
.def_property_readonly_static("iterator", [](py::object /* pc */){ return py::type::of<iterator>(); },
391+
"amrex iterator for particle boxes")
392+
.def_property_readonly_static("const_iterator", [](py::object /* pc */){ return py::type::of<const_iterator>(); },
393+
"amrex constant iterator for particle boxes (read-only)")
394+
;
385395
}
386396

387397
/** Create ParticleContainers and Iterators

src/amrex/ParticleContainer.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""
2+
This file is part of pyAMReX
3+
4+
Copyright 2023 AMReX community
5+
Authors: Axel Huebl
6+
License: BSD-3-Clause-LBNL
7+
"""
8+
9+
10+
def 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+
import pandas as pd
33+
34+
# create a DataFrame per particle box and append it to the list of
35+
# local DataFrame(s)
36+
dfs_local = []
37+
for lvl in range(self.finest_level + 1):
38+
for pti in self.const_iterator(self, level=lvl):
39+
if pti.size == 0:
40+
continue
41+
42+
if self.is_soa_particle:
43+
next_df = pd.DataFrame()
44+
else:
45+
# AoS
46+
aos_np = pti.aos().to_numpy(copy=True)
47+
next_df = pd.DataFrame(aos_np)
48+
next_df.set_index("cpuid")
49+
next_df.index.name = "cpuid"
50+
51+
# SoA
52+
soa_view = pti.soa().to_numpy(copy=True)
53+
soa_np_real = soa_view.real
54+
soa_np_int = soa_view.int
55+
56+
for idx, array in enumerate(soa_np_real):
57+
next_df[f"SoA_real_{idx}"] = array
58+
for idx, array in enumerate(soa_np_int):
59+
next_df[f"SoA_int_{idx}"] = array
60+
61+
dfs_local.append(next_df)
62+
63+
# MPI Gather to root rank if requested
64+
if local:
65+
if len(dfs_local) == 0:
66+
df = None
67+
else:
68+
df = pd.concat(dfs_local)
69+
else:
70+
from mpi4py import MPI
71+
72+
if comm is None:
73+
comm = MPI.COMM_WORLD
74+
rank = comm.Get_rank()
75+
76+
# a list for each rank's list of DataFrame(s)
77+
df_list_list = comm.gather(dfs_local, root=root_rank)
78+
79+
if rank == root_rank:
80+
flattened_list = [df for sublist in df_list_list for df in sublist]
81+
82+
if len(flattened_list) == 0:
83+
df = pd.DataFrame()
84+
else:
85+
df = pd.concat(flattened_list, ignore_index=True)
86+
else:
87+
df = None
88+
89+
return df
90+
91+
92+
def register_ParticleContainer_extension(amr):
93+
"""ParticleContainer helper methods"""
94+
import inspect
95+
import sys
96+
97+
# register member functions for every ParticleContainer_* type
98+
for _, ParticleContainer_type in inspect.getmembers(
99+
sys.modules[amr.__name__],
100+
lambda member: inspect.isclass(member)
101+
and member.__module__ == amr.__name__
102+
and member.__name__.startswith("ParticleContainer_"),
103+
):
104+
ParticleContainer_type.to_df = pc_to_df

src/amrex/space1d/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ def Print(*args, **kwargs):
4848
from ..ArrayOfStructs import register_AoS_extension
4949
from ..MultiFab import register_MultiFab_extension
5050
from ..PODVector import register_PODVector_extension
51+
from ..ParticleContainer import register_ParticleContainer_extension
5152
from ..StructOfArrays import register_SoA_extension
5253

5354
register_Array4_extension(amrex_1d_pybind)
5455
register_MultiFab_extension(amrex_1d_pybind)
5556
register_PODVector_extension(amrex_1d_pybind)
5657
register_SoA_extension(amrex_1d_pybind)
5758
register_AoS_extension(amrex_1d_pybind)
59+
register_ParticleContainer_extension(amrex_1d_pybind)

src/amrex/space1d/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ from amrex.Array4 import register_Array4_extension
4040
from amrex.ArrayOfStructs import register_AoS_extension
4141
from amrex.MultiFab import register_MultiFab_extension
4242
from amrex.PODVector import register_PODVector_extension
43+
from amrex.ParticleContainer import register_ParticleContainer_extension
4344
from amrex.StructOfArrays import register_SoA_extension
4445
from amrex.space1d.amrex_1d_pybind import (
4546
AlmostEqual,
@@ -472,6 +473,7 @@ __all__ = [
472473
"register_Array4_extension",
473474
"register_MultiFab_extension",
474475
"register_PODVector_extension",
476+
"register_ParticleContainer_extension",
475477
"register_SoA_extension",
476478
"size",
477479
"ubound",

0 commit comments

Comments
 (0)