OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
OpalFlatTop.cpp
Go to the documentation of this file.
1#include "OpalFlatTop.h"
2
3#include <mpi.h>
4
5#include <algorithm>
6#include <cmath>
7
8#include "Distribution.h"
9#include "Physics/Physics.h"
10#include "SamplingBase.hpp"
12
14 std::shared_ptr<ParticleContainer_t> pc, std::shared_ptr<FieldContainer_t> fc,
15 Distribution_t* opalDist)
16 : SamplingBase(pc, fc, opalDist),
17 rand_pool_m(determineRandInit()),
18 host_rng_m(determineHostSeed()) {
19 setParameters(opalDist);
20}
21
23 std::shared_ptr<ParticleContainer_t> pc, std::shared_ptr<FieldContainer_t> fc,
24 bool emitting, double sigmaTFall, double sigmaTRise, Vector_t<double, 3> cutoff,
25 double tPulseLengthFWHM, Vector_t<double, 3> sigmaR, double ftOscAmplitude,
26 double ftOscPeriods)
27 : SamplingBase(pc, fc), rand_pool_m(determineRandInit()), host_rng_m(determineHostSeed()) {
29 emitting, sigmaTFall, sigmaTRise, cutoff, tPulseLengthFWHM, sigmaR, ftOscAmplitude,
30 ftOscPeriods);
31}
32
34 extern Inform* gmsg;
35 size_t randInit;
36 if (Options::seed == -1) {
37 randInit = 1234567;
38 *gmsg << "* Seed = " << randInit << " on all ranks" << endl;
39 } else {
40 randInit = static_cast<size_t>(Options::seed + 100 * ippl::Comm->rank());
41 }
42 return randInit;
43}
44
46 if (Options::seed == -1) {
47 return 1234567;
48 }
49 return static_cast<size_t>(Options::seed);
50}
51
54 opalDist->emitting_m, opalDist->getSigmaTFall(), opalDist->getSigmaTRise(),
55 opalDist->getCutoffR(), opalDist->getTPulseLengthFWHM(), opalDist->getSigmaR(),
56 opalDist->getFTOSCAmplitude(), opalDist->getFTOSCPeriods());
57
60
61 if (fc_m) {
62 fc_m->setDecomp({false, false, true});
63 }
64}
65
67 bool emitting, double sigmaTFall, double sigmaTRise, Vector_t<double, 3> cutoff,
68 double tPulseLengthFWHM, Vector_t<double, 3> sigmaR, double ftOscAmplitude,
69 double ftOscPeriods) {
70 emitting_m = emitting;
71 sigmaTFall_m = sigmaTFall;
72 sigmaTRise_m = sigmaTRise;
73 cutoffR_m = cutoff;
74 sigmaR_m = sigmaR;
75 ftOscAmplitude_m = ftOscAmplitude;
76 ftOscPeriods_m = ftOscPeriods;
77 withDomainDecomp_m = false;
78
81 tPulseLengthFWHM - std::sqrt(2.0 * std::log(2.0)) * (sigmaTRise_m + sigmaTFall_m);
82 if (flattopTime_m < 0.0) {
83 flattopTime_m = 0.0;
84 }
87
89 0.5 * std::sqrt(Physics::two_pi) * std::erf(cutoffR_m[2] / std::sqrt(2.0));
91}
92
93void OpalFlatTop::generateParticles(size_t& numberOfParticles, Vector_t<double, 3> nr) {
94 nr_m = nr;
95
96 if (!emitting_m) {
97 throw OpalException(
98 "OpalFlatTop::generateParticles",
99 "OPALFLATTOP only supports emitted beams. Set EMITTED = true.");
100 }
101 if (numberOfParticles == 0) {
102 throw OpalException(
103 "OpalFlatTop::generateParticles",
104 "OPALFLATTOP requires a positive NPARTDIST value.");
105 }
106
107 totalN_m = numberOfParticles;
108 buildBirthTimeInventory(numberOfParticles);
109}
110
111void OpalFlatTop::buildBirthTimeInventory(size_t numberOfParticles) {
112 if (distArea_m <= 0.0) {
113 throw OpalException(
114 "OpalFlatTop::buildBirthTimeInventory",
115 "The flat-top distribution area is zero. Check TPULSEFWHM, TRISE, TFALL, and "
116 "CUTOFFLONG.");
117 }
118
119 birthTimes_m.clear();
120 birthTimes_m.reserve(numberOfParticles);
122 inventoryBuilt_m = false;
123
124 const size_t numRise = static_cast<size_t>(
125 static_cast<double>(numberOfParticles) * sigmaTRise_m * normalizedFlankArea_m
126 / distArea_m);
127 const size_t numFall = static_cast<size_t>(
128 static_cast<double>(numberOfParticles) * sigmaTFall_m * normalizedFlankArea_m
129 / distArea_m);
130 const size_t numFlat =
131 (numRise + numFall <= numberOfParticles) ? numberOfParticles - numRise - numFall : 0;
132
133 const double fallLimit = sigmaTFall_m * cutoffR_m[2];
134 for (size_t i = 0; i < numFall; ++i) {
135 const double tailTime = sampleTruncatedHalfGaussian(sigmaTFall_m, fallLimit);
136 const double opalPulseTime = -tailTime + fallTime_m;
137 birthTimes_m.push_back(toBirthTime(opalPulseTime));
138 }
139
140 std::uniform_real_distribution<double> unit(0.0, 1.0);
141 const double modulationAmp = std::min(1.0, std::abs(ftOscAmplitude_m) / 100.0);
142 const double numPeriods = std::abs(ftOscPeriods_m);
143 const double modulationPeriod =
144 (numPeriods > 0.0 && flattopTime_m > 0.0) ? flattopTime_m / numPeriods : 0.0;
145
146 for (size_t i = 0; i < numFlat; ++i) {
147 double tFlat = 0.0;
148 if (flattopTime_m > 0.0) {
149 if (modulationAmp == 0.0 || modulationPeriod == 0.0) {
150 tFlat = flattopTime_m * unit(host_rng_m);
151 } else {
152 bool allow = false;
153 while (!allow) {
154 tFlat = flattopTime_m * unit(host_rng_m);
155 const double randFuncValue = unit(host_rng_m);
156 const double funcValue =
157 (1.0
158 + modulationAmp * std::sin(Physics::two_pi * tFlat / modulationPeriod))
159 / (1.0 + modulationAmp);
160 allow = randFuncValue <= funcValue;
161 }
162 }
163 }
164 birthTimes_m.push_back(toBirthTime(fallTime_m + tFlat));
165 }
166
167 const double riseLimit = sigmaTRise_m * cutoffR_m[2];
168 for (size_t i = 0; i < numRise; ++i) {
169 const double tailTime = sampleTruncatedHalfGaussian(sigmaTRise_m, riseLimit);
170 const double opalPulseTime = tailTime + fallTime_m + flattopTime_m;
171 birthTimes_m.push_back(toBirthTime(opalPulseTime));
172 }
173
174 while (birthTimes_m.size() < numberOfParticles) {
176 }
177
178 std::sort(birthTimes_m.begin(), birthTimes_m.end());
179 inventoryBuilt_m = true;
180}
181
182double OpalFlatTop::sampleTruncatedHalfGaussian(double sigma, double limit) {
183 if (sigma <= 0.0 || limit <= 0.0) {
184 return 0.0;
185 }
186
187 std::normal_distribution<double> normal(0.0, sigma);
188 for (;;) {
189 const double value = std::abs(normal(host_rng_m));
190 if (value <= limit) {
191 return value;
192 }
193 }
194}
195
196double OpalFlatTop::toBirthTime(double opalPulseTime) const {
197 return t0_m + 0.5 * emissionTime_m - opalPulseTime;
198}
199
201 if (emissionModel_m == "ASTRA") {
202 return Vector_t<double, 3>({0.0, 0.0, 0.5 * euclidean_norm(P0_m)});
203 }
204 return P0_m;
205}
206
207std::pair<size_t, size_t> OpalFlatTop::computeLocalEmitRange(size_t totalToEmit) const {
208 if (!pc_m || totalToEmit == 0) {
209 return {0, 0};
210 }
211
212 const int nranks = ippl::Comm->size();
213 const int rank = ippl::Comm->rank();
214 if (nranks <= 0) {
215 return {0, totalToEmit};
216 }
217
218 const size_t capacity = pc_m->R.size();
219 const size_t localNum = pc_m->getLocalNum();
220 const size_t spaceLeft = (localNum <= capacity) ? (capacity - localNum) : size_t(0);
221
222 std::vector<unsigned long> spaceLeftAll(static_cast<size_t>(nranks), 0);
223 unsigned long mySpace = static_cast<unsigned long>(spaceLeft);
224 MPI_Allgather(
225 &mySpace, 1, MPI_UNSIGNED_LONG, spaceLeftAll.data(), 1, MPI_UNSIGNED_LONG,
226 ippl::Comm->getCommunicator());
227
228 const size_t nranksU = static_cast<size_t>(nranks);
229 const size_t base = totalToEmit / nranksU;
230 const size_t rem = totalToEmit % nranksU;
231
232 std::vector<size_t> nlocal(nranksU, 0);
233 size_t assigned = 0;
234 for (int r = 0; r < nranks; ++r) {
235 const size_t ideal = base + (static_cast<size_t>(r) < rem ? 1 : 0);
236 const size_t cap = static_cast<size_t>(spaceLeftAll[static_cast<size_t>(r)]);
237 nlocal[static_cast<size_t>(r)] = std::min(ideal, cap);
238 assigned += nlocal[static_cast<size_t>(r)];
239 }
240
241 size_t deficit = totalToEmit - assigned;
242 while (deficit > 0) {
243 int chosen = -1;
244 for (int r = 0; r < nranks; ++r) {
245 const size_t cap = static_cast<size_t>(spaceLeftAll[static_cast<size_t>(r)]);
246 if (nlocal[static_cast<size_t>(r)] < cap) {
247 chosen = r;
248 break;
249 }
250 }
251 if (chosen < 0) {
252 throw OpalException(
253 "OpalFlatTop::computeLocalEmitRange",
254 "Particle container capacity is insufficient for OPALFLATTOP emission. "
255 "Increase BEAM::NALLOC or reduce NPARTDIST.");
256 }
257 nlocal[static_cast<size_t>(chosen)] += 1;
258 --deficit;
259 }
260
261 size_t offset = 0;
262 for (int r = 0; r < rank; ++r) {
263 offset += nlocal[static_cast<size_t>(r)];
264 }
265 return {offset, nlocal[static_cast<size_t>(rank)]};
266}
267
268void OpalFlatTop::emitParticles(double t, double dt) {
269 if (!inventoryBuilt_m || birthTimes_m.empty()) {
270 return;
271 }
272 if (nextGlobalIndex_m >= birthTimes_m.size()) {
273 return;
274 }
275
276 const double tEnd = t + dt;
277 auto emitEndIt =
278 std::upper_bound(birthTimes_m.begin() + nextGlobalIndex_m, birthTimes_m.end(), tEnd);
279 const size_t globalEnd = static_cast<size_t>(emitEndIt - birthTimes_m.begin());
280 const size_t totalNew = globalEnd - nextGlobalIndex_m;
281
282 if (totalNew == 0) {
283 return;
284 }
285
286 const double overdueTolerance = std::max(1.0e-18, std::abs(dt) * 1.0e-12);
287 if (birthTimes_m[nextGlobalIndex_m] < t - overdueTolerance) {
288 throw OpalException(
289 "OpalFlatTop::emitParticles",
290 "OPALFLATTOP found an overdue birth time. This means the tracker "
291 "time shift or emission dt skipped particles that should have been "
292 "emitted in an earlier step.");
293 }
294
295 const auto [localOffset, nNew] = computeLocalEmitRange(totalNew);
296 const size_type nlocalBefore = pc_m->getLocalNum();
297 pc_m->createParticles(static_cast<size_type>(nNew));
298
299 generateLocalParticles(nlocalBefore, nextGlobalIndex_m + localOffset, nNew, t, dt);
300 nextGlobalIndex_m = globalEnd;
301}
302
304 size_type nlocalBefore, size_t globalBegin, size_t nNew, double tStart, double dt) {
305 if (nNew == 0) {
306 return;
307 }
308
309 Kokkos::View<double*> stepDt("OpalFlatTop_stepDt", nNew);
310 auto hStepDt = Kokkos::create_mirror_view(stepDt);
311 const double tEnd = tStart + dt;
312 const double overdueTolerance = std::max(1.0e-18, std::abs(dt) * 1.0e-12);
313 for (size_t i = 0; i < nNew; ++i) {
314 const double birthTime = birthTimes_m[globalBegin + i];
315 if (birthTime < tStart - overdueTolerance) {
316 throw OpalException(
317 "OpalFlatTop::generateLocalParticles",
318 "OPALFLATTOP would need to pre-age a particle born before the "
319 "current step. Old OPAL emits these particles in earlier tracker "
320 "steps, so this indicates an inconsistent tracker time shift.");
321 }
322 const double effectiveBirthTime = birthTime < tStart ? tStart : birthTime;
323 hStepDt(i) = tEnd - effectiveBirthTime;
324 }
325 Kokkos::deep_copy(stepDt, hStepDt);
326
327 auto randPool = rand_pool_m;
328 auto Rview = pc_m->R.getView();
329 auto Pview = pc_m->P.getView();
330 auto dtView = pc_m->dt.getView();
331
332 const Vector_t<double, 3> sigmaR = sigmaR_m;
333 const Vector_t<double, 3> R0 = R0_m;
334 const Vector_t<double, 3> P0 = P0_m;
335 const double pTot = euclidean_norm(P0);
336 const bool useAstra = emissionModel_m == "ASTRA";
337 const bool useNone = emissionModel_m == "NONE";
338 if (!useAstra && !useNone) {
339 throw OpalException(
340 "OpalFlatTop::generateLocalParticles",
341 "EMISSIONMODEL '" + emissionModel_m
342 + "' is not supported for OPALFLATTOP distributions");
343 }
344
345 const size_type offset = nlocalBefore;
346 const double twoPi = Physics::two_pi;
347 const double c = Physics::c;
348 Kokkos::parallel_for(
349 "OpalFlatTop_generateLocalParticles", nNew, KOKKOS_LAMBDA(const size_t i) {
350 auto generator = randPool.get_state();
351 const double r = Kokkos::sqrt(generator.drand(0.0, 1.0));
352 const double theta = twoPi * generator.drand(0.0, 1.0);
353
354 Vector_t<double, 3> p = P0;
355 if (useAstra) {
356 const double rand1 = generator.drand(0.0, 1.0);
357 const double rand2 = generator.drand(0.0, 1.0);
358 const double phi = 2.0 * Kokkos::acos(Kokkos::sqrt(rand1));
359 const double angle = twoPi * rand2;
360 p[0] = pTot * Kokkos::sin(phi) * Kokkos::cos(angle);
361 p[1] = pTot * Kokkos::sin(phi) * Kokkos::sin(angle);
362 p[2] = pTot * Kokkos::fabs(Kokkos::cos(phi));
363 }
364 randPool.free_state(generator);
365
366 const size_t j = offset + i;
367 Rview(j)[0] = r * Kokkos::cos(theta) * sigmaR[0] + R0[0];
368 Rview(j)[1] = r * Kokkos::sin(theta) * sigmaR[1] + R0[1];
369 Rview(j)[2] = R0[2];
370 Pview(j) = p;
371 dtView(j) = stepDt(i);
372
373 const double gamma = Kokkos::sqrt(1.0 + p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
374 const double drift = 0.5 * c * stepDt(i) / gamma;
375 Rview(j)[0] += p[0] * drift;
376 Rview(j)[1] += p[1] * drift;
377 Rview(j)[2] += p[2] * drift;
378 });
379 Kokkos::fence();
380 pc_m->markMomentsDirty();
381}
382
383void OpalFlatTop::initDomainDecomp(double BoxIncr) {
384 if (!fc_m) {
385 return;
386 }
387
388 auto* mesh = &fc_m->getMesh();
389 auto* FL = &fc_m->getFL();
390 ippl::Vector<double, 3> o;
391 ippl::Vector<double, 3> e;
392 const double tol = 1e-15;
393 o[0] = -sigmaR_m[0] - tol;
394 e[0] = sigmaR_m[0] + tol;
395 o[1] = -sigmaR_m[1] - tol;
396 e[1] = sigmaR_m[1] + tol;
397 o[2] = 0.0 - tol;
398 e[2] = Physics::c * emissionTime_m + tol;
399
400 const ippl::Vector<double, 3> l = e - o;
401 hr_m = (1.0 + BoxIncr / 100.0) * (l / nr_m);
402 mesh->setMeshSpacing(hr_m);
403 mesh->setOrigin(o - 0.5 * hr_m * BoxIncr / 100.0);
404 pc_m->getLayout().updateLayout(*FL, *mesh);
405}
406
407void OpalFlatTop::setWithDomainDecomp(bool withDomainDecomp) {
408 withDomainDecomp_m = withDomainDecomp;
409}
410
411void OpalFlatTop::setBirthTimesForTest(std::vector<double> birthTimes) {
412 birthTimes_m = std::move(birthTimes);
413 std::sort(birthTimes_m.begin(), birthTimes_m.end());
414 totalN_m = birthTimes_m.size();
416 inventoryBuilt_m = true;
417}
Inform * gmsg
Definition changes.cpp:7
ippl::Vector< T, Dim > Vector_t
const int nr
OPAL-like flat-top emission sampler with precomputed birth times.
KOKKOS_INLINE_FUNCTION double euclidean_norm(const Vector_t< T, D > &v)
Definition VectorMath.h:15
double getSigmaTFall() const
double getTPulseLengthFWHM() const
double getFTOSCAmplitude() const
void setTEmission(double tEmission)
ippl::Vector< double, 3 > getSigmaR() const
double getFTOSCPeriods() const
double getSigmaTRise() const
size_t getEmissionSteps() const
ippl::Vector< double, 3 > getCutoffR() const
double fallTime_m
Duration represented by the falling flank.
std::pair< size_t, size_t > computeLocalEmitRange(size_t totalToEmit) const
Computes the local contiguous subrange of a global emission batch.
double riseTime_m
Duration represented by the rising flank.
void initDomainDecomp(double BoxIncr) override
Initializes a domain decomposition large enough for the emitted bunch.
double sigmaTRise_m
Standard deviation of the rising flank.
double sampleTruncatedHalfGaussian(double sigma, double limit)
Samples an absolute value from a Gaussian truncated at a limit.
OpalFlatTopGeneratorPool rand_pool_m
Device random number generator pool.
void setBirthTimesForTest(std::vector< double > birthTimes)
Replaces the birth-time inventory for tests.
static size_t determineHostSeed()
Determines the host random seed initialization.
bool emitting_m
Flag for particle emission status.
void setWithDomainDecomp(bool withDomainDecomp) override
Sets whether to use domain decomposition during emission.
std::mt19937_64 host_rng_m
Host generator used to sample birth times.
size_t nextGlobalIndex_m
First global birth-time index not emitted yet.
double sigmaTFall_m
Standard deviation of the falling flank.
std::vector< double > birthTimes_m
Sorted global particle birth times.
double normalizedFlankArea_m
Area of one normalized truncated Gaussian flank.
double flattopTime_m
Time duration of the flat profile section.
OpalFlatTop(std::shared_ptr< ParticleContainer_t > pc, std::shared_ptr< FieldContainer_t > fc, Distribution_t *opalDist)
Constructor for OpalFlatTop.
Vector_t< double, 3 > hr_m
Grid spacing.
Vector_t< double, 3 > nr_m
Number of grid points per direction.
size_t emissionSteps_m
Number of steps used to derive emission dt.
double ftOscAmplitude_m
Flat-top oscillation amplitude in percent.
void generateParticles(size_t &numberOfParticles, Vector_t< double, 3 > nr) override
Builds the global birth-time inventory for the requested particles.
bool withDomainDecomp_m
Flag for domain decomposition.
double toBirthTime(double opalPulseTime) const
Converts old-OPAL pulse time to tracker birth time.
bool inventoryBuilt_m
True once birthTimes_m is ready for emission.
void emitParticles(double t, double dt) override
Emits all particles whose birth times fall into the current step.
void setParameters(Distribution_t *opalDist)
Reads distribution parameters from the OPALX Distribution object.
void generateLocalParticles(size_type nlocalBefore, size_t globalBegin, size_t nNew, double tStart, double dt)
Generates the local particles assigned to this rank for one emission step.
void buildBirthTimeInventory(size_t numberOfParticles)
Builds and sorts the global particle birth-time inventory.
Vector_t< double, 3 > cutoffR_m
Cutoff multipliers for distribution support.
double emissionTime_m
Total emission time.
Vector_t< double, 3 > getInitialReferenceMomentum() const override
Returns the initial reference momentum used by the tracker.
void setInternalVariables(bool emitting, double sigmaTFall, double sigmaTRise, Vector_t< double, 3 > cutoff, double tPulseLengthFWHM, Vector_t< double, 3 > sigmaR, double ftOscAmplitude, double ftOscPeriods)
Sets derived flat-top profile and emission parameters.
static size_t determineRandInit()
Determines the device random seed initialization.
size_t totalN_m
Total number of particles in the inventory.
double distArea_m
Total area of the flat-top distribution.
ippl::detail::size_type size_type
Vector_t< double, 3 > sigmaR_m
Semi-axis lengths of the transverse disk.
double ftOscPeriods_m
Number of oscillation periods across the flat top.
Vector_t< double, 3 > P0_m
Vector_t< double, 3 > R0_m
Emission source offset: position R0, momentum P0, start time t0 (applied in sample step).
std::shared_ptr< FieldContainer_t > fc_m
std::string emissionModel_m
std::shared_ptr< ParticleContainer_t > pc_m
int seed
The current random seed.
Definition Options.cpp:37
constexpr double two_pi
The value of.
Definition Physics.h:40
constexpr double c
The velocity of light in m/s.
Definition Physics.h:60