Skip to content

Commit e39b821

Browse files
committed
Added particle splitting
1 parent 607b091 commit e39b821

File tree

5 files changed

+387
-0
lines changed

5 files changed

+387
-0
lines changed

Source/Particles/Resampling/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ foreach(D IN LISTS WarpX_DIMS)
66
ResamplingTrigger.cpp
77
LevelingThinning.cpp
88
VelocityCoincidenceThinning.cpp
9+
ParticleSplitting.cpp
910
)
1011
endforeach()

Source/Particles/Resampling/Make.package

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ CEXE_sources += Resampling.cpp
22
CEXE_sources += ResamplingTrigger.cpp
33
CEXE_sources += LevelingThinning.cpp
44
CEXE_sources += VelocityCoincidenceThinning.cpp
5+
CEXE_sources += ParticleSplitting.cpp
56

67
VPATH_LOCATIONS += $(WARPX_HOME)/Source/Particles/Resampling/
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* Copyright 2024 The WarpX Community
2+
*
3+
* This file is part of WarpX.
4+
*
5+
* License: BSD-3-Clause-LBNL
6+
*/
7+
#ifndef WARPX_PARTICLE_SPLITTING_H_
8+
#define WARPX_PARTICLE_SPLITTING_H_
9+
10+
#include "Particles/Algorithms/KineticEnergy.H"
11+
#include "Resampling.H"
12+
#include "Utils/Parser/ParserUtils.H"
13+
#include "Utils/ParticleUtils.H"
14+
15+
#include <AMReX_Algorithm.H>
16+
#include <AMReX_Geometry.H>
17+
#include <AMReX_REAL.H>
18+
#include <string>
19+
20+
21+
/**
22+
* \brief This class implements a particle splitting scheme.
23+
*/
24+
class ParticleSplitting: public ResamplingAlgorithm {
25+
public:
26+
27+
/**
28+
* \brief Default constructor of the ParticleSplitting class.
29+
*/
30+
ParticleSplitting () = default;
31+
32+
/**
33+
* \brief Constructor of the ParticleSplitting class
34+
*
35+
* @param[in] species_name the name of the resampled species
36+
*/
37+
ParticleSplitting (const std::string& species_name);
38+
39+
/**
40+
* \brief A method that performs merging for the considered species.
41+
*
42+
* @param[in] geom_lev the geometry of the current refinement level
43+
* @param[in] pti WarpX particle iterator of the particles to resample.
44+
* @param[in] lev the index of the refinement level.
45+
* @param[in] pc a pointer to the particle container.
46+
*/
47+
void operator() (
48+
const amrex::Geometry& geom_lev, WarpXParIter& pti,
49+
int lev, WarpXParticleContainer* pc) const final;
50+
51+
private:
52+
53+
int m_min_ppc = 1;
54+
bool m_resampling_random_splitting_angle = true;
55+
amrex::ParticleReal m_splitting_angle = 0.0_rt;
56+
std::string m_splitting_type = "position_axes_aligned_split";
57+
};
58+
#endif // WARPX_PARTICLE_SPLITTING_H_
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
/* Copyright 2024 The WarpX Community
2+
*
3+
* This file is part of WarpX.
4+
*
5+
* License: BSD-3-Clause-LBNL
6+
*/
7+
8+
#include "ParticleSplitting.H"
9+
#include "Particles/PhysicalParticleContainer.H"
10+
#include "Utils/Parser/ParserUtils.H"
11+
#include "WarpX.H"
12+
#include <AMReX_REAL.H>
13+
#include <AMReX_Gpu.H>
14+
#include <AMReX_Random.H>
15+
#include <AMReX_GpuQualifiers.H>
16+
#include <AMReX_ParmParse.H>
17+
18+
using namespace amrex;
19+
using warpx::fields::FieldType;
20+
21+
ParticleSplitting::ParticleSplitting (const std::string& species_name)
22+
{
23+
using namespace amrex::literals;
24+
25+
const amrex::ParmParse pp_species_name(species_name);
26+
27+
utils::parser::queryWithParser(
28+
pp_species_name, "resampling_min_ppc", m_min_ppc
29+
);
30+
WARPX_ALWAYS_ASSERT_WITH_MESSAGE(
31+
m_min_ppc >= 1,
32+
"Resampling min_ppc should be greater than or equal to 1"
33+
);
34+
35+
utils::parser::queryWithParser(
36+
pp_species_name, "resampling_random_splitting_angle", m_resampling_random_splitting_angle);
37+
38+
pp_species_name.query("resampling_splitting_type", m_splitting_type);
39+
40+
WARPX_ALWAYS_ASSERT_WITH_MESSAGE(
41+
m_splitting_type == "position_axes_aligned_split" || m_splitting_type == "position_velocity_aligned_split",
42+
"Invalid resampling_splitting_type specified.\n"
43+
"Valid options are:\n"
44+
" - position_axes_aligned_split\n"
45+
" - position_velocity_aligned_split.\n");
46+
47+
if (!m_resampling_random_splitting_angle) {
48+
utils::parser::queryWithParser(
49+
pp_species_name, "resampling_splitting_angle", m_splitting_angle);
50+
}
51+
}
52+
void ParticleSplitting::operator() (
53+
const amrex::Geometry& geom_lev, WarpXParIter& pti,
54+
const int lev, WarpXParticleContainer * const pc) const
55+
{
56+
using namespace amrex::literals;
57+
58+
auto& ptile = pc->ParticlesAt(lev, pti);
59+
const auto num_particles_tile = ptile.numParticles();
60+
61+
if (num_particles_tile == 0) return;
62+
// Bin particles by cell
63+
auto bins = ParticleUtils::findParticlesInEachCell(geom_lev, pti, ptile);
64+
const auto n_cells = static_cast<int>(bins.numBins());
65+
auto *const indices = bins.permutationPtr();
66+
auto *const cell_offsets = bins.offsetsPtr();
67+
68+
const std::array<amrex::Real,3>& dx = WarpX::CellSize(lev);
69+
70+
int np_split_per_parent = 2;
71+
72+
if (m_splitting_type == "position_axes_aligned_split") {
73+
#if defined(WARPX_DIM_1D_Z)
74+
np_split_per_parent = 2;
75+
#endif
76+
#if defined(WARPX_DIM_3D)
77+
np_split_per_parent = 6;
78+
#endif
79+
#if defined(WARPX_DIM_XZ)
80+
np_split_per_parent = 4;
81+
#endif
82+
}
83+
else if (m_splitting_type == "position_velocity_aligned_split") {
84+
np_split_per_parent = 2;
85+
}
86+
87+
const auto min_ppc = m_min_ppc;
88+
89+
amrex::Gpu::DeviceVector<int> n_new_children_per_cell(n_cells);
90+
int* num_new_children_ptr = n_new_children_per_cell.data();
91+
92+
amrex::ParallelFor(n_cells,
93+
[=] AMREX_GPU_DEVICE (int i_cell) noexcept
94+
{
95+
const auto cell_start = static_cast<int>(cell_offsets[i_cell]);
96+
const auto cell_stop = static_cast<int>(cell_offsets[i_cell+1]);
97+
const auto cell_numparts = cell_stop - cell_start;
98+
99+
// Skip cells with enough particles or empty cells
100+
if (cell_numparts == 0 || cell_numparts >= min_ppc) {
101+
num_new_children_ptr[i_cell] = 0;
102+
return;
103+
}
104+
105+
// Calculate how many particles need to be split per cell
106+
const int deficit = min_ppc - cell_numparts;
107+
const int particles_to_split = amrex::min(
108+
cell_numparts,
109+
static_cast<int>(amrex::Math::ceil(static_cast<amrex::Real>(deficit) / (np_split_per_parent - 1.0_prt)))
110+
);
111+
112+
// Each parent that splits creates np_split_per_parent children per cell, or particles_to_split * np_split_per_parent per tile
113+
num_new_children_ptr[i_cell] = particles_to_split * np_split_per_parent;
114+
}
115+
);
116+
117+
amrex::Gpu::DeviceVector<int> offsets(n_cells);
118+
int* offset_ptr = offsets.data();
119+
120+
int num_new_children_tile = amrex::Scan::ExclusiveSum(n_cells, num_new_children_ptr, offset_ptr);
121+
122+
if (num_new_children_tile == 0) return;
123+
124+
ptile.resize(num_particles_tile + num_new_children_tile);
125+
126+
auto& soa = ptile.GetStructOfArrays();
127+
128+
#if !defined(WARPX_DIM_1D_Z)
129+
auto * const AMREX_RESTRICT x = soa.GetRealData(PIdx::x).data();
130+
#endif
131+
#if defined(WARPX_DIM_3D)
132+
auto * const AMREX_RESTRICT y = soa.GetRealData(PIdx::y).data();
133+
#endif
134+
#if defined(WARPX_ZINDEX)
135+
auto * const AMREX_RESTRICT z = soa.GetRealData(PIdx::z).data();
136+
#endif
137+
138+
auto * const AMREX_RESTRICT ux = soa.GetRealData(PIdx::ux).data();
139+
auto * const AMREX_RESTRICT uy = soa.GetRealData(PIdx::uy).data();
140+
auto * const AMREX_RESTRICT uz = soa.GetRealData(PIdx::uz).data();
141+
auto * const AMREX_RESTRICT w = soa.GetRealData(PIdx::w).data();
142+
auto * const AMREX_RESTRICT idcpu = soa.GetIdCPUData().data();
143+
144+
amrex::ParallelForRNG(n_cells,
145+
[=] AMREX_GPU_DEVICE (int i_cell, amrex::RandomEngine const& engine) noexcept
146+
{
147+
const auto cell_start = static_cast<int>(cell_offsets[i_cell]);
148+
const auto cell_stop = static_cast<int>(cell_offsets[i_cell+1]);
149+
const auto cell_numparts = cell_stop - cell_start;
150+
151+
// Skip cells with enough particles or empty cells
152+
if (cell_numparts == 0 || cell_numparts >= min_ppc) return;
153+
154+
// Calculate how many particles need to be split
155+
const int deficit = min_ppc - cell_numparts;
156+
const int particles_to_split = amrex::min(
157+
cell_numparts,
158+
static_cast<int>(amrex::Math::ceil(static_cast<amrex::Real>(deficit) / (np_split_per_parent - 1.0_prt)))
159+
);
160+
161+
// Calculate cell-dependent position offset
162+
const amrex::Real inv_split = 1.0_prt / (2.0_prt * np_split_per_parent * particles_to_split);
163+
#if !defined(WARPX_DIM_1D_Z)
164+
amrex::ParticleReal offset_x = dx[0] * inv_split;
165+
#endif
166+
#if defined(WARPX_DIM_3D)
167+
amrex::ParticleReal offset_y = dx[1] * inv_split;
168+
#endif
169+
#if defined(WARPX_ZINDEX)
170+
amrex::ParticleReal offset_z = dx[2] * inv_split;
171+
#endif
172+
173+
const auto resampling_random_splitting_angle = m_resampling_random_splitting_angle;
174+
const amrex::Real splitting_angle = resampling_random_splitting_angle ?
175+
amrex::Random(engine) * 2.0_rt * MathConst::pi : m_splitting_angle;
176+
177+
// Starting index for new children particles for i_cell
178+
const int new_particle_start = num_particles_tile + offset_ptr[i_cell];
179+
180+
int split_count = 0;
181+
for (int i = cell_start; i < cell_stop && split_count < particles_to_split; ++i) {
182+
const int parent_idx = indices[i];
183+
184+
#if !defined(WARPX_DIM_1D_Z)
185+
amrex::ParticleReal xp = x[parent_idx];
186+
#endif
187+
#if defined(WARPX_DIM_3D)
188+
amrex::ParticleReal yp = y[parent_idx];
189+
#endif
190+
#if defined(WARPX_ZINDEX)
191+
amrex::ParticleReal zp = z[parent_idx];
192+
#endif
193+
// Get parent particle properties
194+
const amrex::Real parent_weight = w[parent_idx];
195+
const amrex::Real child_weight = parent_weight / static_cast<amrex::Real>(np_split_per_parent);
196+
const int child_base = new_particle_start + split_count * np_split_per_parent;
197+
198+
if (m_splitting_type == "position_axes_aligned_split") {
199+
#if defined(WARPX_DIM_1D_Z)
200+
amrex::Print() << "Splitting particle along z axis (WARPX_DIM_1D_Z)" << std::endl;
201+
// Split particle in 2 along z axis
202+
for (int k = 0; k < 2; ++k) {
203+
const int sign_offset = (k == 0) ? -1 : 1;
204+
const int idx = child_base + k;
205+
z[idx] = zp + sign_offset * offset_z;
206+
ux[idx] = ux[parent_idx];
207+
uy[idx] = uy[parent_idx];
208+
uz[idx] = uz[parent_idx];
209+
w[idx] = child_weight;
210+
idcpu[idx] = amrex::SetParticleIDandCPU(
211+
amrex::LongParticleIds::NoSplitParticleID,
212+
amrex::ParallelDescriptor::MyProc()
213+
);
214+
}
215+
#elif defined(WARPX_DIM_XZ)
216+
// Split particle in 4 particles: split in the x–z plane with a rotation by splitting_angle around the z-axis.
217+
for (int k = 0; k < 4; ++k) {
218+
const int idx = child_base + k;
219+
const int sign_offset = (k % 2 == 0) ? -1 : 1;
220+
if (k < 2) {
221+
// Split 2 of 4 particles
222+
x[idx] = xp + std::cos(splitting_angle) * sign_offset * offset_x;
223+
z[idx] = zp - std::sin(splitting_angle) * sign_offset * offset_z;
224+
} else {
225+
// Split other 2 of 4 particles
226+
x[idx] = xp - std::sin(splitting_angle) * sign_offset * offset_x;
227+
z[idx] = zp + std::cos(splitting_angle) * sign_offset * offset_z;
228+
}
229+
ux[idx] = ux[parent_idx];
230+
uy[idx] = uy[parent_idx];
231+
uz[idx] = uz[parent_idx];
232+
w[idx] = child_weight;
233+
idcpu[idx] = amrex::SetParticleIDandCPU(
234+
amrex::LongParticleIds::NoSplitParticleID,
235+
amrex::ParallelDescriptor::MyProc()
236+
);
237+
}
238+
#elif defined(WARPX_DIM_3D) || defined(WARPX_DIM_RZ)
239+
// Split parent particle in 6 particles
240+
for (int k = 0; k < 6; ++k) {
241+
const int idx = child_base + k;
242+
const int sign_offset = (k % 2 == 0) ? -1 : 1;
243+
244+
if (k < 4) {
245+
// Split 4 of 6 particles in the x–y plane with a rotation by splitting_angle around the z-axis.
246+
if (k < 2) {
247+
x[idx] = xp + std::cos(splitting_angle) * sign_offset * offset_x;
248+
y[idx] = yp + std::sin(splitting_angle) * sign_offset * offset_y;
249+
} else {
250+
x[idx] = xp - std::sin(splitting_angle) * sign_offset * offset_x;
251+
y[idx] = yp + std::cos(splitting_angle) * sign_offset * offset_y;
252+
}
253+
z[idx] = zp;
254+
} else {
255+
// Split 2 of 6 particles along z-axis
256+
x[idx] = xp;
257+
y[idx] = yp;
258+
z[idx] = zp + sign_offset * offset_z;
259+
}
260+
ux[idx] = ux[parent_idx];
261+
uy[idx] = uy[parent_idx];
262+
uz[idx] = uz[parent_idx];
263+
w[idx] = child_weight;
264+
idcpu[idx] = amrex::SetParticleIDandCPU(
265+
amrex::LongParticleIds::NoSplitParticleID,
266+
amrex::ParallelDescriptor::MyProc()
267+
);
268+
}
269+
#elif defined(WARPX_DIM_RCYLINDER) || defined(WARPX_DIM_RSPHERE)
270+
// Split particle in 2 along x axis
271+
for (int k = 0; k < 2; ++k) {
272+
const int sign_offset = (k == 0) ? -1 : 1;
273+
const int idx = child_base + k;
274+
x[idx] = xp + sign_offset * offset_x;
275+
ux[idx] = ux[parent_idx];
276+
uy[idx] = uy[parent_idx];
277+
uz[idx] = uz[parent_idx];
278+
w[idx] = child_weight;
279+
idcpu[idx] = amrex::SetParticleIDandCPU(
280+
amrex::LongParticleIds::NoSplitParticleID,
281+
amrex::ParallelDescriptor::MyProc()
282+
);
283+
}
284+
#endif
285+
}
286+
else if (m_splitting_type == "position_velocity_aligned_split") {
287+
// Split particle in 2 along the velocity direction
288+
const amrex::Real u2 = ux[parent_idx] * ux[parent_idx] +
289+
uy[parent_idx] * uy[parent_idx] +
290+
uz[parent_idx] * uz[parent_idx];
291+
const amrex::Real u_norm = (std::sqrt(u2) > 0.0_rt) ? std::sqrt(u2) : 1.0_rt;
292+
293+
for (int k = 0; k < 2; ++k) {
294+
const int sign_offset = (k == 0) ? -1 : 1;
295+
const int idx = child_base + k;
296+
#if !defined(WARPX_DIM_1D_Z)
297+
x[idx] = xp + sign_offset * offset_x * ux[parent_idx] / u_norm;
298+
#endif
299+
#if defined(WARPX_DIM_3D)
300+
y[idx] = yp + sign_offset * offset_y * uy[parent_idx] / u_norm;
301+
#endif
302+
#if defined(WARPX_ZINDEX)
303+
z[idx] = zp + sign_offset * offset_z * uz[parent_idx] / u_norm;
304+
#endif
305+
ux[idx] = ux[parent_idx];
306+
uy[idx] = uy[parent_idx];
307+
uz[idx] = uz[parent_idx];
308+
w[idx] = child_weight;
309+
idcpu[idx] = amrex::SetParticleIDandCPU(
310+
amrex::LongParticleIds::NoSplitParticleID,
311+
amrex::ParallelDescriptor::MyProc()
312+
);
313+
}
314+
}
315+
316+
// Mark parent particles as invalid
317+
idcpu[parent_idx] = amrex::ParticleIdCpus::Invalid;
318+
++split_count;
319+
}
320+
}
321+
);
322+
}

0 commit comments

Comments
 (0)