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

AMReX SoA Named Components #382

Merged
merged 3 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 30 additions & 0 deletions src/Particle/ParticleContainer.H
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ void make_ParticleContainer_and_Iterators (py::module &m, std::string allocstr)
.def_property_readonly("num_position_components", [](const py::object&){ return AMREX_SPACEDIM; })
.def_property_readonly("byte_spread", &ParticleContainerType::ByteSpread)

// compile-time components
.def("set_soa_compile_time_names", &ParticleContainerType::SetSoACompileTimeNames)

// runtime components
.def("add_real_comp", py::overload_cast<int>(&ParticleContainerType::AddRealComp),
py::arg("communicate")=1,
Expand All @@ -224,6 +227,33 @@ void make_ParticleContainer_and_Iterators (py::module &m, std::string allocstr)
"add a new runtime component with type Int"
)

.def_property_readonly("real_soa_names",
&ParticleContainerType::GetRealSoANames,
"Get the names for the Real SoA components"
)
.def_property_readonly("int_soa_names",
&ParticleContainerType::GetIntSoANames,
"Get the names for the int SoA components"
)

.def("has_real_comp",
&ParticleContainerType::HasRealComp,
"Check if a container has an ParticleReal component"
)
.def("has_int_comp",
&ParticleContainerType::HasIntComp,
"Check if a container has an Integer component"
)

.def("get_real_comp_index",
&ParticleContainerType::GetRealCompIndex,
"Get the ParticleReal SoA index of a component"
)
.def("get_int_comp_index",
&ParticleContainerType::GetIntCompIndex,
"Get the Integer SoA index of a component"
)

.def_property_readonly("finest_level", &ParticleContainerBase::finestLevel)

// ParticleContainer ( const ParticleContainer &) = delete;
Expand Down
10 changes: 10 additions & 0 deletions src/Particle/StructOfArrays.H
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ void make_StructOfArrays(py::module &m, std::string allocstr)
py::arg("index"),
"Get access to a particle Real component Array (compile-time and runtime component)")

// names
.def_property_readonly("real_names",
&SOAType::GetRealNames,
"Names for the Real SoA components"
)
.def_property_readonly("int_names",
&SOAType::GetIntNames,
"Names for the int SoA components"
)

.def("__len__", &SOAType::size,
"Get the number of particles")
.def_property_readonly("size", &SOAType::size,
Expand Down
7 changes: 0 additions & 7 deletions src/amrex/extensions/ParticleComponentNames.py

This file was deleted.

95 changes: 12 additions & 83 deletions src/amrex/extensions/StructOfArrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,71 +9,6 @@
from collections import namedtuple


def soa_real_comps(self, num_comps, spacedim=3, rotate=True):
"""
Name the ParticleReal components in SoA.

Parameters
----------
self : SoA Type
maybe unused, depending on implementation
num_comps : int
number of components to generate names for.
spacedim : int
AMReX dimensionality
rotate : bool = True
start with "x", "y", "z", "a", "b", ...

Returns
-------
A list of length num_comps with values
rotate=True (for pure SoA layout):
- 3D: "x", "y", "z", "a", "b", ... "w", "r0", "r1", ...
- 2D: "x", "y", "a", "b", ... "w", "r0", "r1", ...
- 1D: "x", "a", "b", ... "w", "r0", "r1", ...
rotate=False (for legacy layout):
- 1D-3D: "a", "b", ... "w", "r0", "r1", ...
"""
import string

# x, y, z, a, b, ...
comp_names = list(string.ascii_lowercase)
if rotate:
# rotate x, y, z to be beginning (positions)
comp_names = comp_names[-3:] + comp_names[:-3]
else:
# cut off x, y, z to avoid confusion
comp_names = comp_names[:-3]

num_named = len(comp_names)
if num_comps < num_named:
comp_names = list(comp_names)[0:num_comps]
elif num_comps > num_named:
comp_names.extend(["r" + str(i) for i in range(num_comps - num_named)])

return comp_names


def soa_int_comps(self, num_comps):
"""
Name the int components in SoA.

Parameters
----------
self : SoA Type
maybe unused, depending on implementation
num_comps : int
number of components to generate names for.

Returns
-------
A list of length num_comps with values "i1", "i2", "i3", ...
"""
comp_names = ["i" + str(i) for i in range(num_comps)]

return comp_names


def soa_to_numpy(self, copy=False):
"""
Provide NumPy views into a StructOfArrays.
Expand Down Expand Up @@ -107,18 +42,17 @@ def soa_to_numpy(self, copy=False):
else:
soa_view = SoA_np({}, {}, None)

# for the legacy data layout, do not start with x, y, z but with a, b, c, ...
if self.has_idcpu:
real_comp_names = self.soa_real_comps(self.num_real_comps)
else:
real_comp_names = self.soa_real_comps(self.num_real_comps, rotate=False)

real_comp_names = self.real_names
if len(real_comp_names) != self.num_real_comps:
raise ValueError("Missing names for SoA Real components.")
for idx_real in range(self.num_real_comps):
soa_view.real[real_comp_names[idx_real]] = self.get_real_data(
idx_real
).to_numpy(copy=copy)

int_comp_names = self.soa_int_comps(self.num_int_comps)
int_comp_names = self.int_names
if len(int_comp_names) != self.num_int_comps:
raise ValueError("Missing names for SoA int components.")
for idx_int in range(self.num_int_comps):
soa_view.int[int_comp_names[idx_int]] = self.get_int_data(idx_int).to_numpy(
copy=copy
Expand Down Expand Up @@ -165,18 +99,17 @@ def soa_to_cupy(self, copy=False):
else:
soa_view = SoA_cp({}, {}, None)

# for the legacy data layout, do not start with x, y, z but with a, b, c, ...
if self.has_idcpu:
real_comp_names = self.soa_real_comps(self.num_real_comps)
else:
real_comp_names = self.soa_real_comps(self.num_real_comps, rotate=False)

real_comp_names = self.real_names
if len(real_comp_names) != self.num_real_comps:
raise ValueError("Missing names for SoA Real components.")
for idx_real in range(self.num_real_comps):
soa_view.real[real_comp_names[idx_real]] = self.get_real_data(idx_real).to_cupy(
copy=copy
)

int_comp_names = self.soa_int_comps(self.num_int_comps)
int_comp_names = self.int_names
if len(int_comp_names) != self.num_int_comps:
raise ValueError("Missing names for SoA int components.")
for idx_int in range(self.num_int_comps):
soa_view.int[int_comp_names[idx_int]] = self.get_int_data(idx_int).to_cupy(
copy=copy
Expand Down Expand Up @@ -226,10 +159,6 @@ def register_SoA_extension(amr):
and member.__module__ == amr.__name__
and member.__name__.startswith("StructOfArrays_"),
):
# name providers
SoA_type.soa_real_comps = soa_real_comps
SoA_type.soa_int_comps = soa_int_comps

# converters
SoA_type.to_numpy = soa_to_numpy
SoA_type.to_cupy = soa_to_cupy
Expand Down
20 changes: 14 additions & 6 deletions tests/test_particleContainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ def particle_container(Npart, std_geometry, distmap, boxarr, std_real_box):
pc.init_random(Npart, iseed, myt, False, std_real_box)

# add runtime components: 1 real 2 int
pc.add_real_comp(True)
pc.add_int_comp(True)
pc.add_int_comp(True)
pc.add_real_comp("b", True)
pc.add_int_comp("i1", True)
pc.add_int_comp("i2", True)

# assign some values to runtime components
for lvl in range(pc.finest_level + 1):
Expand All @@ -79,13 +79,21 @@ def soa_particle_container(Npart, std_geometry, distmap, boxarr, std_real_box):
myt.real_array_data = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
myt.int_array_data = []

with pytest.raises(Exception):
pc.set_soa_compile_time_names(
["x", "y", "z", "z", "b", "c", "d", "e"], []
) # error: z added twice
pc.set_soa_compile_time_names(["x", "y", "z", "a", "b", "c", "d", "e"], [])

iseed = 1
pc.init_random(Npart, iseed, myt, False, std_real_box)

# add runtime components: 1 real 2 int
pc.add_real_comp(True)
pc.add_int_comp(True)
pc.add_int_comp(True)
with pytest.raises(Exception):
pc.add_real_comp("a", True) # already used as a compile-time component
pc.add_real_comp("f", True)
pc.add_int_comp("i1", True)
pc.add_int_comp("i2", True)

# assign some values to runtime components
for lvl in range(pc.finest_level + 1):
Expand Down
Loading