chombo-discharge
Loading...
Searching...
No Matches
CD_ParticleOpsImplem.H
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021-2026 SINTEF Energy Research
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
13#ifndef CD_PARTICLEOPSIMPLEM_H
14#define CD_PARTICLEOPSIMPLEM_H
15
16// Std includes
17#include <cstdint>
18
19// Chombo includes
20#include <CH_Timer.H>
21#include <PolyGeom.H>
22
23// Our includes
24#include <CD_ParticleOps.H>
25#include <CD_ParticleLoops.H>
26#include <CD_ParallelOps.H>
27#include <CD_PolyUtils.H>
28#include <CD_Random.H>
29#include <CD_NamespaceHeader.H>
30
31inline IntVect
32ParticleOps::getParticleCellIndex(const RealVect& a_particlePosition,
33 const RealVect& a_probLo,
34 const Real& a_dx) noexcept
35{
36 return IntVect(D_DECL(std::floor((a_particlePosition[0] - a_probLo[0]) / a_dx),
37 std::floor((a_particlePosition[1] - a_probLo[1]) / a_dx),
38 std::floor((a_particlePosition[2] - a_probLo[2]) / a_dx)));
39}
40
41inline IntVect
42ParticleOps::getParticleCellIndex(const RealVect& a_particlePosition,
43 const RealVect& a_probLo,
44 const RealVect& a_dx) noexcept
45{
46 return IntVect(D_DECL(std::floor((a_particlePosition[0] - a_probLo[0]) / a_dx[0]),
47 std::floor((a_particlePosition[1] - a_probLo[1]) / a_dx[1]),
48 std::floor((a_particlePosition[2] - a_probLo[2]) / a_dx[2])));
49}
50
51template <typename P, typename Traits>
52inline void
54{
55 CH_TIME("ParticleOps::getPhysicalParticlesPerCell(SoA)");
56
57 const RealVect probLo = a_src.getProbLo();
58
59 for (int lvl = 0; lvl <= a_src.getFinestLevel(); lvl++) {
60 const DisjointBoxLayout& dbl = a_src.getGrids()[lvl];
61 const DataIterator& dit = dbl.dataIterator();
62 const RealVect dx = a_src.getDx()[lvl];
63
64 const int nbox = dit.size();
65
66#pragma omp parallel for schedule(runtime)
67 for (int mybox = 0; mybox < nbox; mybox++) {
68 const DataIndex& din = dit[mybox];
69 const ParticleSoA<P, Traits>& leaf = a_src[lvl][din];
70
71 FArrayBox& ppc = (*a_ppc[lvl])[din].getFArrayBox();
72 ppc.setVal(0.0);
73
74 for (std::size_t i = 0; i < leaf.size(); i++) {
75 const IntVect iv = ParticleOps::getParticleGridCell(leaf.position(i), probLo, dx);
76
77 ppc(iv, 0) += leaf.weight(i);
78 }
79 }
80 }
81}
82
83template <typename P, typename Traits>
84inline void
86{
87 CH_TIME("ParticleOps::getComputationalParticlesPerCell(SoA)");
88
89 const RealVect probLo = a_src.getProbLo();
90
91 for (int lvl = 0; lvl <= a_src.getFinestLevel(); lvl++) {
92 const DisjointBoxLayout& dbl = a_src.getGrids()[lvl];
93 const DataIterator& dit = dbl.dataIterator();
94 const RealVect dx = a_src.getDx()[lvl];
95
96 const int nbox = dit.size();
97
98#pragma omp parallel for schedule(runtime)
99 for (int mybox = 0; mybox < nbox; mybox++) {
100 const DataIndex& din = dit[mybox];
101 const ParticleSoA<P, Traits>& leaf = a_src[lvl][din];
102
103 FArrayBox& ppc = (*a_ppc[lvl])[din].getFArrayBox();
104 ppc.setVal(0.0);
105
106 for (std::size_t i = 0; i < leaf.size(); i++) {
107 const IntVect iv = ParticleOps::getParticleGridCell(leaf.position(i), probLo, dx);
108
109 ppc(iv, 0) += 1.0;
110 }
111 }
112 }
113}
114
115inline IntVect
116ParticleOps::getParticleGridCell(const RealVect& a_particlePosition,
117 const RealVect& a_probLo,
118 const RealVect& a_dx) noexcept
119{
120 return IntVect(D_DECL(std::floor((a_particlePosition[0] - a_probLo[0]) / a_dx[0]),
121 std::floor((a_particlePosition[1] - a_probLo[1]) / a_dx[1]),
122 std::floor((a_particlePosition[2] - a_probLo[2]) / a_dx[2])));
123}
124
125inline bool
126ParticleOps::domainIntersection(const RealVect& a_oldPos,
127 const RealVect& a_newPos,
128 const RealVect& a_probLo,
129 const RealVect& a_probHi,
130 Real& a_s)
131{
132
133 // clang-format off
134 // TLDR: This code does a boundary intersection test and returns where on the interval [oldPos, newPos] the intersection
135 // happened. We do this by checking if the particle moves towards a particular domain side and ends up outside of it.
136 // clang-format on
137
138 a_s = std::numeric_limits<Real>::max();
139
140 bool crossedDomainBoundary = false;
141
142 const RealVect path = a_newPos - a_oldPos;
143
144 for (int dir = 0; dir < SpaceDim; dir++) {
145 for (SideIterator sit; sit.ok(); ++sit) {
146 // Normal vector pointing OUT of the domain on side sit and direction dir.
147 const Side::LoHiSide side = sit();
148 const RealVect wallPoint = (side == Side::Lo) ? a_probLo : a_probHi;
149 const RealVect n0 = sign(side) * RealVect(BASISV(dir));
150 const Real normPath = PolyGeom::dot(n0, path);
151
152 // If normPath > 0 then the particle trajectory points towards the domain edge/face and we can have an
153 // intersection.
154 if (normPath > 0.0) {
155
156 // s determines the intersection point between the particle path and the plane corresponding to the domain
157 // edge/face. Note that we consider the edge/face to be an infinite plane and we just compute the intersection
158 // point between each edge/face and select the closest intersection point.
159 const Real s = PolyGeom::dot(wallPoint - a_oldPos, n0) / normPath;
160 if (s >= 0.0 && s <= 1.0) {
161 crossedDomainBoundary = true;
162
163 if (s < a_s) {
164 a_s = s;
165 }
166 }
167 }
168 }
169 }
170
171 return crossedDomainBoundary;
172}
173
174inline bool
175ParticleOps::ebIntersectionBisect(const RefCountedPtr<BaseIF>& a_impFunc,
176 const RealVect& a_oldPos,
177 const RealVect& a_newPos,
178 const Real& a_bisectStep,
179 Real& a_s)
180{
181
182 // clang-format off
183 // TLDR: We compute the intersection point using a bisection algorithm. We divide the full path into intervals and check if an interval
184 // has a root. If it does, we compute it using Brent's algorithm.
185 // clang-format on
186
187 a_s = std::numeric_limits<Real>::max();
188
189 bool crossedEB = false;
190
191 const Real pathLen = (a_newPos - a_oldPos).vectorLength(); // Total path len
192 const int nsteps = ceil(pathLen / a_bisectStep); // Number of bisection intervals
193 const RealVect dxStep = (a_newPos - a_oldPos) / nsteps; // Physical length of each bisection interval
194
195 // Check each interval
196 RealVect curPos = a_oldPos;
197 for (int istep = 0; istep < nsteps; istep++) {
198 // fa: Value of the implicit function at the start of the bisection interval
199 // fb: Value of the implicit function at the end of the bisection interval
200 const Real fa = a_impFunc->value(curPos);
201 const Real fb = a_impFunc->value(curPos + dxStep);
202
203 if (fa * fb <= 0.0) {
204
205 // If this triggered we happen to know that f(pos+dxStep) > 0.0 and f(pos) < 0.0 and so we must have a root on the
206 // interval. We now compute the precise location where the particle crossed the EB. For that we use a Brent root
207 // finder on the interval [pos, pos+dxStep]. This is a 1D problem.
208 const RealVect intersectionPos = PolyUtils::brentRootFinder(a_impFunc, curPos, curPos + dxStep);
209
210 a_s = (intersectionPos - a_oldPos).vectorLength() / pathLen;
211 crossedEB = true;
212
213 break;
214 }
215 else { // Move to next interval
216 curPos += dxStep;
217 }
218 }
219
220 return crossedEB;
221}
222
223inline bool
224ParticleOps::ebIntersectionRaycast(const RefCountedPtr<BaseIF>& a_impFunc,
225 const RealVect& a_oldPos,
226 const RealVect& a_newPos,
227 const Real& a_tolerance,
228 Real& a_s)
229{
230
231 a_s = std::numeric_limits<Real>::max();
232
233 bool ret = false;
234
235 // Absolute distance to EB.
236 auto dist = [&](const RealVect& x) -> Real {
237 return std::abs(a_impFunc->value(x));
238 };
239
240 const Real D = (a_newPos - a_oldPos).vectorLength(); // Total particle path length
241 const Real D0 = dist(a_oldPos); // Distance to EB from starting position
242
243 // If the distance to the EB from the starting position is smaller than the total path length, we need to check for
244 // intersections.
245 if (D > D0) {
246
247 const RealVect t = (a_newPos - a_oldPos) / D; // Particle trajectory.
248
249 // Move a_oldPos along +t. If we end up too close to the boundary the particle has intersected the BC. Note that
250 // this does NOT check for whether or not the particle moves tangential to the EB surface. The length of each step
251 // is the distance to the EB, so if the particle is close to the EB but moves tangentially to it, this routine will
252 // be EXTREMELY slow.
253 RealVect xa = a_oldPos;
254 Real r = D;
255 Real d = dist(xa);
256
257 while (d < r) {
258
259 if (d < a_tolerance) { // We collided.
260 a_s = (xa - a_oldPos).vectorLength() / D;
261 ret = true;
262
263 break;
264 }
265 else { // We did not collide.
266 xa += t * d;
267 r -= d;
268 d = dist(xa);
269 }
270 }
271 }
272
273 return ret;
274}
275
276template <typename P, typename Traits>
277inline void
279{
280 CH_TIME("ParticleOps::copyDestructive(ParticleContainer<P, Traits> x2)");
281
282 CH_assert(a_dst.getRealm() == a_src.getRealm());
283
284 for (int lvl = 0; lvl <= a_dst.getFinestLevel(); lvl++) {
285 const DisjointBoxLayout& dbl = a_dst.getGrids()[lvl];
286 const DataIterator& dit = dbl.dataIterator();
287
288 const int nbox = dit.size();
289
290#pragma omp parallel for schedule(runtime)
291 for (int mybox = 0; mybox < nbox; mybox++) {
292 const DataIndex& din = dit[mybox];
293
294 a_dst[lvl][din].clear(); // drop the destination leaf's particles
295 a_dst[lvl][din].catenate(a_src[lvl][din]); // move the source leaf in (O(1) swap; src emptied)
296 }
297 }
298}
299
300template <typename P, typename Traits>
301inline Real
303{
304 CH_TIME("ParticleOps::sum(ParticleContainer<P>)");
305
306 Real particleSum = 0.0;
307
308 for (int lvl = 0; lvl <= a_particles.getFinestLevel(); lvl++) {
309 const DisjointBoxLayout& dbl = a_particles.getGrids()[lvl];
310 const DataIterator& dit = dbl.dataIterator();
311
312 const int nbox = dit.size();
313
314#pragma omp parallel for schedule(runtime) reduction(+ : particleSum)
315 for (int mybox = 0; mybox < nbox; mybox++) {
316 const DataIndex& din = dit[mybox];
317 const ParticleSoA<P, Traits>& leaf = a_particles[lvl][din];
318
319 const double* w = leaf.weightColumn(); // weight column captured once, OUTSIDE the loop
320
321 // Seed the fold with the running (thread-private) accumulator rather than 0.0 so the
322 // summation order is identical to a plain 'particleSum += w[i]' loop across this thread's boxes.
323 particleSum = ParticleLoops::reduce(leaf, particleSum, [&](Real acc, std::size_t i) {
324 return acc + w[i];
325 });
326 }
327 }
328
329 return ParallelOps::sum(particleSum);
330}
331
332template <typename P, typename Traits>
333inline void
335 const std::function<void(ParticleSoA<P, Traits>&, std::size_t)>& a_functor) noexcept
336{
337 CH_TIME("ParticleOps::setData(ParticleContainer<P>, std::function<void(ParticleSoA<P>&, std::size_t)>)");
338
339 for (int lvl = 0; lvl <= a_particles.getFinestLevel(); lvl++) {
340 const DisjointBoxLayout& dbl = a_particles.getGrids()[lvl];
341 const DataIterator& dit = dbl.dataIterator();
342
343 const int nbox = dit.size();
344
345#pragma omp parallel for schedule(runtime)
346 for (int mybox = 0; mybox < nbox; mybox++) {
347 const DataIndex& din = dit[mybox];
348
349 ParticleSoA<P, Traits>& leaf = a_particles[lvl][din];
350
351 for (std::size_t i = 0; i < leaf.size(); i++) {
352 a_functor(leaf, i);
353 }
354 }
355 }
356}
357
358#include <CD_NamespaceFooter.H>
359
360#endif
Agglomeration of basic MPI reductions.
Declaration of a namespace for SIMD-decorated loops over SoA particles.
Declaration of a static class containing some common useful particle routines that would otherwise be...
Agglomeration of some useful algebraic/polynomial routines.
File containing some useful static methods related to random number generation.
AMR-hierarchy container of computational particles, stored per patch in Struct-of-Arrays form.
Definition CD_ParticleContainer.H:123
int getFinestLevel() const
Finest AMR level index.
Definition CD_ParticleContainer.H:290
const Vector< DisjointBoxLayout > & getGrids() const
Per-level AMR grids.
Definition CD_ParticleContainer.H:260
static Real sum(const ParticleContainer< P, Traits > &a_particles) noexcept
Global sum of the container-owned weight column (SoA overload).
Definition CD_ParticleOpsImplem.H:302
static bool ebIntersectionRaycast(const RefCountedPtr< BaseIF > &a_impFunc, const RealVect &a_oldPos, const RealVect &a_newPos, const Real &a_tolerance, Real &a_s)
Compute the intersection point between a particle path and an implicit function using a ray-casting a...
Definition CD_ParticleOpsImplem.H:224
static bool ebIntersectionBisect(const RefCountedPtr< BaseIF > &a_impFunc, const RealVect &a_oldPos, const RealVect &a_newPos, const Real &a_bisectStep, Real &a_s)
Compute the intersection point between a particle path and an implicit function using a bisection alg...
Definition CD_ParticleOpsImplem.H:175
static void copyDestructive(ParticleContainer< P, Traits > &a_dst, ParticleContainer< P, Traits > &a_src) noexcept
Move all particles from a_src into a_dst (per leaf), emptying a_src. SoA overload.
Definition CD_ParticleOpsImplem.H:278
static void getComputationalParticlesPerCell(EBAMRCellData &a_ppc, const ParticleContainer< P, Traits > &a_src) noexcept
Get the number of computational particles per cell (SoA overload).
Definition CD_ParticleOpsImplem.H:85
static IntVect getParticleGridCell(const RealVect &a_particlePosition, const RealVect &a_probLo, const RealVect &a_dx) noexcept
Get the grid cell where the particle lives.
Definition CD_ParticleOpsImplem.H:116
static void setData(ParticleContainer< P, Traits > &a_particles, const std::function< void(ParticleSoA< P, Traits > &, std::size_t)> &a_functor) noexcept
Set value function for SoA containers. Lets the user set particle parameters via a (leaf,...
Definition CD_ParticleOpsImplem.H:334
static IntVect getParticleCellIndex(const RealVect &a_particlePosition, const RealVect &a_probLo, const Real &a_dx) noexcept
Get the cell index corresponding to the particle position.
Definition CD_ParticleOpsImplem.H:32
static void getPhysicalParticlesPerCell(EBAMRCellData &a_ppc, const ParticleContainer< P, Traits > &a_src) noexcept
Get the number of physical particles per cell (SoA overload).
Definition CD_ParticleOpsImplem.H:53
static bool domainIntersection(const RealVect &a_oldPos, const RealVect &a_newPos, const RealVect &a_probLo, const RealVect &a_probHi, Real &a_s)
Compute the intersection point between a particle path and a domain side.
Definition CD_ParticleOpsImplem.H:126
Arena-backed Struct-of-Arrays particle container for a single grid patch.
Definition CD_ParticleSoA.H:655
double * weightColumn() noexcept
Raw weight column (double*).
Definition CD_ParticleSoA.H:1160
RealVect position(const std::size_t a_index) const noexcept
Position of particle i as a RealVect (by value, assembled from the scalar columns).
Definition CD_ParticleSoA.H:1188
double & weight(const std::size_t a_index) noexcept
Weight of particle i.
Definition CD_ParticleSoA.H:1222
std::size_t size() const noexcept
Number of particles currently stored.
Definition CD_ParticleSoA.H:882
Real sum(const Real &a_value) noexcept
Compute the sum across all MPI ranks.
Definition CD_ParallelOpsImplem.H:354
ALWAYS_INLINE T reduce(const ParticleSoA< P, Traits > &a_soa, T a_initial, Functor &&a_kernel)
Fold a kernel over every particle in a ParticleSoA, accumulating a reduction value.
Definition CD_ParticleLoops.H:122
RealVect brentRootFinder(const RefCountedPtr< BaseIF > &a_impFunc, const RealVect &a_point1, const RealVect &a_point2)
Compute the root of a function between two points. This is a 1D problem along the line.
Definition CD_PolyUtils.cpp:25