14 std::shared_ptr<ParticleContainer_t> pc, std::shared_ptr<FieldContainer_t> fc,
17 rand_pool_m(determineRandInit()),
18 host_rng_m(determineHostSeed()) {
23 std::shared_ptr<ParticleContainer_t> pc, std::shared_ptr<FieldContainer_t> fc,
27 :
SamplingBase(pc, fc), rand_pool_m(determineRandInit()), host_rng_m(determineHostSeed()) {
29 emitting, sigmaTFall, sigmaTRise, cutoff, tPulseLengthFWHM, sigmaR, ftOscAmplitude,
38 *
gmsg <<
"* Seed = " << randInit <<
" on all ranks" << endl;
40 randInit =
static_cast<size_t>(
Options::seed + 100 * ippl::Comm->rank());
62 fc_m->setDecomp({
false,
false,
true});
69 double ftOscPeriods) {
98 "OpalFlatTop::generateParticles",
99 "OPALFLATTOP only supports emitted beams. Set EMITTED = true.");
101 if (numberOfParticles == 0) {
103 "OpalFlatTop::generateParticles",
104 "OPALFLATTOP requires a positive NPARTDIST value.");
114 "OpalFlatTop::buildBirthTimeInventory",
115 "The flat-top distribution area is zero. Check TPULSEFWHM, TRISE, TFALL, and "
124 const size_t numRise =
static_cast<size_t>(
127 const size_t numFall =
static_cast<size_t>(
130 const size_t numFlat =
131 (numRise + numFall <= numberOfParticles) ? numberOfParticles - numRise - numFall : 0;
134 for (
size_t i = 0; i < numFall; ++i) {
136 const double opalPulseTime = -tailTime +
fallTime_m;
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);
143 const double modulationPeriod =
146 for (
size_t i = 0; i < numFlat; ++i) {
149 if (modulationAmp == 0.0 || modulationPeriod == 0.0) {
155 const double randFuncValue = unit(
host_rng_m);
156 const double funcValue =
158 + modulationAmp * std::sin(
Physics::two_pi * tFlat / modulationPeriod))
159 / (1.0 + modulationAmp);
160 allow = randFuncValue <= funcValue;
168 for (
size_t i = 0; i < numRise; ++i) {
183 if (sigma <= 0.0 || limit <= 0.0) {
187 std::normal_distribution<double> normal(0.0, sigma);
189 const double value = std::abs(normal(
host_rng_m));
190 if (value <= limit) {
208 if (!
pc_m || totalToEmit == 0) {
212 const int nranks = ippl::Comm->size();
213 const int rank = ippl::Comm->rank();
215 return {0, totalToEmit};
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);
222 std::vector<unsigned long> spaceLeftAll(
static_cast<size_t>(nranks), 0);
223 unsigned long mySpace =
static_cast<unsigned long>(spaceLeft);
225 &mySpace, 1, MPI_UNSIGNED_LONG, spaceLeftAll.data(), 1, MPI_UNSIGNED_LONG,
226 ippl::Comm->getCommunicator());
228 const size_t nranksU =
static_cast<size_t>(nranks);
229 const size_t base = totalToEmit / nranksU;
230 const size_t rem = totalToEmit % nranksU;
232 std::vector<size_t> nlocal(nranksU, 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)];
241 size_t deficit = totalToEmit - assigned;
242 while (deficit > 0) {
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) {
253 "OpalFlatTop::computeLocalEmitRange",
254 "Particle container capacity is insufficient for OPALFLATTOP emission. "
255 "Increase BEAM::NALLOC or reduce NPARTDIST.");
257 nlocal[
static_cast<size_t>(chosen)] += 1;
262 for (
int r = 0; r < rank; ++r) {
263 offset += nlocal[
static_cast<size_t>(r)];
265 return {offset, nlocal[
static_cast<size_t>(rank)]};
276 const double tEnd = t + dt;
279 const size_t globalEnd =
static_cast<size_t>(emitEndIt -
birthTimes_m.begin());
286 const double overdueTolerance = std::max(1.0e-18, std::abs(dt) * 1.0e-12);
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.");
304 size_type nlocalBefore,
size_t globalBegin,
size_t nNew,
double tStart,
double dt) {
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) {
315 if (birthTime < tStart - overdueTolerance) {
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.");
322 const double effectiveBirthTime = birthTime < tStart ? tStart : birthTime;
323 hStepDt(i) = tEnd - effectiveBirthTime;
325 Kokkos::deep_copy(stepDt, hStepDt);
328 auto Rview =
pc_m->R.getView();
329 auto Pview =
pc_m->P.getView();
330 auto dtView =
pc_m->dt.getView();
338 if (!useAstra && !useNone) {
340 "OpalFlatTop::generateLocalParticles",
342 +
"' is not supported for OPALFLATTOP distributions");
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);
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));
364 randPool.free_state(generator);
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];
371 dtView(j) = stepDt(i);
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;
380 pc_m->markMomentsDirty();
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;
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);
ippl::Vector< T, Dim > Vector_t
OPAL-like flat-top emission sampler with precomputed birth times.
KOKKOS_INLINE_FUNCTION double euclidean_norm(const Vector_t< T, D > &v)
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.
constexpr double two_pi
The value of.
constexpr double c
The velocity of light in m/s.