13#include <Kokkos_Core.hpp>
20 std::string trim(
const std::string& input) {
21 const auto first = input.find_first_not_of(
" \t\r\n");
22 if (first == std::string::npos) {
25 const auto last = input.find_last_not_of(
" \t\r\n");
26 return input.substr(first, last - first + 1);
29 std::string lowercase(std::string value) {
30 std::transform(value.begin(), value.end(), value.begin(), [](
unsigned char c) {
31 return static_cast<char>(std::tolower(c));
36 bool parseSingleInteger(
const std::string& line,
long long& value) {
37 std::istringstream stream(line);
39 return stream && stream.eof();
42 bool looksLikeHeader(
const std::string& line) {
43 const std::string lower = lowercase(line);
44 return lower.find(
"x") != std::string::npos && lower.find(
"px") != std::string::npos
45 && lower.find(
"y") != std::string::npos && lower.find(
"py") != std::string::npos
46 && lower.find(
"t") != std::string::npos && lower.find(
"pz") != std::string::npos;
49 bool parseRecordValues(
const std::string& line, std::array<double, 6>& record) {
50 std::istringstream stream(line);
51 std::vector<double> values;
53 while (stream >> value) {
54 values.push_back(value);
56 if (values.size() < 6) {
60 for (
size_t i = 0; i < record.size(); ++i) {
61 record[i] = values[i];
68 std::shared_ptr<ParticleContainer_t> pc, std::shared_ptr<FieldContainer_t> fc,
75 "EmittedFromFile::EmittedFromFile",
76 "FNAME attribute must be set for EMITTEDFROMFILE distribution type.");
84 std::shared_ptr<ParticleContainer_t> pc, std::shared_ptr<FieldContainer_t> fc,
85 const std::string& filename)
88 throw OpalException(
"EmittedFromFile::EmittedFromFile",
"Filename must not be empty.");
95 namespace fs = std::filesystem;
101 if (!inputFile.empty()) {
102 const fs::path filePath = fs::path(inputFile).parent_path() /
filename_m;
103 if (fs::exists(filePath)) {
110 std::ifstream file(filename);
111 if (!file.is_open()) {
112 throw OpalException(
"EmittedFromFile::readFile",
"Couldn't open file '" + filename +
"'.");
116 bool sawFirstPayload =
false;
117 bool expectHeaderAfterN =
false;
118 bool hasDeclaredCount =
false;
119 size_t declaredCount = 0;
120 size_t lineNumber = 0;
123 while (std::getline(file, line)) {
125 std::string stripped = trim(line);
126 if (stripped.empty()) {
130 if (stripped[0] ==
'#') {
136 if (!sawFirstPayload) {
138 if (parseSingleInteger(stripped, count)) {
141 "EmittedFromFile::readFile",
142 "Declared particle count in '" + filename +
"' must be non-negative.");
144 declaredCount =
static_cast<size_t>(count);
145 hasDeclaredCount =
true;
146 expectHeaderAfterN =
true;
147 sawFirstPayload =
true;
150 sawFirstPayload =
true;
153 if (expectHeaderAfterN) {
154 if (!looksLikeHeader(stripped)) {
156 "EmittedFromFile::readFile",
157 "Expected a header containing x px y py t pz after the count line in '"
160 expectHeaderAfterN =
false;
164 if (looksLikeHeader(stripped)) {
168 std::array<double, 6> values;
169 if (!parseRecordValues(stripped, values)) {
171 "EmittedFromFile::readFile",
"Line " + std::to_string(lineNumber) +
" in '"
173 +
"' has fewer than six numeric columns.");
176 record.
x = values[0];
177 record.
px = values[1];
178 record.
y = values[2];
179 record.
py = values[3];
181 record.
pz = values[5];
185 if (expectHeaderAfterN) {
187 "EmittedFromFile::readFile",
188 "Missing header line after declared particle count in '" + filename +
"'.");
193 "EmittedFromFile::readFile",
194 "No valid emitted particle data found in '" + filename +
"'.");
197 if (hasDeclaredCount &&
rawRecords_m.size() != declaredCount) {
199 "EmittedFromFile::readFile",
200 "Number of data lines (" + std::to_string(
rawRecords_m.size())
201 +
") does not match declared count (" + std::to_string(declaredCount)
202 +
") in '" + filename +
"'.");
209 "EmittedFromFile::generateParticles",
211 +
"' is not supported for EMITTEDFROMFILE distributions");
214 size_t requested = numberOfParticles;
224 const size_t selected =
243 double maxPulseTime = minPulseTime;
244 for (
size_t i = 1; i < selected; ++i) {
246 minPulseTime = std::min(minPulseTime, pulseTime);
247 maxPulseTime = std::max(maxPulseTime, pulseTime);
250 const double pulseCenter = 0.5 * (minPulseTime + maxPulseTime);
258 for (
size_t i = 0; i < selected; ++i) {
269 return lhs.birthTime < rhs.birthTime;
272 const double invSelected = 1.0 /
static_cast<double>(selected);
280 if (!
pc_m || totalToEmit == 0) {
284 const int nranks = ippl::Comm->size();
285 const int rank = ippl::Comm->rank();
287 return {0, totalToEmit};
290 const size_t capacity =
pc_m->R.size();
291 const size_t localNum =
pc_m->getLocalNum();
292 const size_t spaceLeft = (localNum <= capacity) ? (capacity - localNum) : size_t(0);
294 std::vector<unsigned long> spaceLeftAll(
static_cast<size_t>(nranks), 0);
295 unsigned long mySpace =
static_cast<unsigned long>(spaceLeft);
297 &mySpace, 1, MPI_UNSIGNED_LONG, spaceLeftAll.data(), 1, MPI_UNSIGNED_LONG,
298 ippl::Comm->getCommunicator());
300 const size_t nranksU =
static_cast<size_t>(nranks);
301 const size_t base = totalToEmit / nranksU;
302 const size_t rem = totalToEmit % nranksU;
304 std::vector<size_t> nlocal(nranksU, 0);
306 for (
int r = 0; r < nranks; ++r) {
307 const size_t ideal = base + (
static_cast<size_t>(r) < rem ? 1 : 0);
308 const size_t cap =
static_cast<size_t>(spaceLeftAll[
static_cast<size_t>(r)]);
309 nlocal[
static_cast<size_t>(r)] = std::min(ideal, cap);
310 assigned += nlocal[
static_cast<size_t>(r)];
313 size_t deficit = totalToEmit - assigned;
314 while (deficit > 0) {
316 for (
int r = 0; r < nranks; ++r) {
317 const size_t cap =
static_cast<size_t>(spaceLeftAll[
static_cast<size_t>(r)]);
318 if (nlocal[
static_cast<size_t>(r)] < cap) {
325 "EmittedFromFile::computeLocalEmitRange",
326 "Particle container capacity is insufficient for EMITTEDFROMFILE emission. "
327 "Increase BEAM::NALLOC or reduce NPARTDIST.");
329 nlocal[
static_cast<size_t>(chosen)] += 1;
334 for (
int r = 0; r < rank; ++r) {
335 offset += nlocal[
static_cast<size_t>(r)];
337 return {offset, nlocal[
static_cast<size_t>(rank)]};
348 const double tEnd = t + dt;
349 auto emitEndIt = std::upper_bound(
351 [](
double value,
const Record& record) {
352 return value < record.birthTime;
354 const size_t globalEnd =
static_cast<size_t>(emitEndIt -
records_m.begin());
360 const double overdueTolerance = std::max(1.0e-18, std::abs(dt) * 1.0e-12);
363 "EmittedFromFile::emitParticles",
364 "EMITTEDFROMFILE found an overdue birth time. This means the tracker "
365 "started too late or skipped a timestep containing file-emitted particles.");
377 size_type nlocalBefore,
size_t globalBegin,
size_t nNew,
double tStart,
double dt) {
382 using HostView = Kokkos::View<double**, Kokkos::HostSpace>;
383 HostView hostData(
"EmittedFromFile_hostData", nNew, 6);
384 const double tEnd = tStart + dt;
385 const double overdueTolerance = std::max(1.0e-18, std::abs(dt) * 1.0e-12);
387 for (
size_t i = 0; i < nNew; ++i) {
389 if (record.
birthTime < tStart - overdueTolerance) {
391 "EmittedFromFile::generateLocalParticles",
392 "EMITTEDFROMFILE would need to pre-age a particle born before "
393 "the current step.");
395 const double effectiveBirthTime = record.
birthTime < tStart ? tStart : record.
birthTime;
396 const double stepDt = tEnd - effectiveBirthTime;
398 hostData(i, 0) = record.
raw.
x;
399 hostData(i, 1) = record.
raw.
y;
400 hostData(i, 2) = record.
raw.
px;
401 hostData(i, 3) = record.
raw.
py;
402 hostData(i, 4) = record.
raw.
pz;
403 hostData(i, 5) = stepDt;
407 Kokkos::create_mirror(Kokkos::DefaultExecutionSpace::memory_space(), hostData);
408 Kokkos::deep_copy(deviceData, hostData);
410 auto Rview =
pc_m->R.getView();
411 auto Pview =
pc_m->P.getView();
412 auto dtView =
pc_m->dt.getView();
418 Kokkos::parallel_for(
419 "EmittedFromFile_generateLocalParticles", nNew, KOKKOS_LAMBDA(
const size_t i) {
420 const size_t j = offset + i;
422 deviceData(i, 2) + P0[0], deviceData(i, 3) + P0[1],
423 deviceData(i, 4) + P0[2]);
424 const double stepDt = deviceData(i, 5);
426 Rview(j)[0] = deviceData(i, 0) + R0[0];
427 Rview(j)[1] = deviceData(i, 1) + R0[1];
432 const double gamma = Kokkos::sqrt(1.0 + p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
433 const double drift = 0.5 * c * stepDt / gamma;
434 Rview(j)[0] += p[0] * drift;
435 Rview(j)[1] += p[1] * drift;
436 Rview(j)[2] += p[2] * drift;
440 pc_m->markMomentsDirty();
ippl::Vector< T, Dim > Vector_t
Defines an emitted file distribution with old-OPAL time-column semantics.
std::string getFilename() const
size_t getNumParticles() const
void setTEmission(double tEmission)
size_t getEmissionSteps() const
double py
Vertical momentum offset from the file.
RawRecord raw
Original parsed file row.
EmittedFromFile(std::shared_ptr< ParticleContainer_t > pc, std::shared_ptr< FieldContainer_t > fc, Distribution_t *opalDist)
Constructor for EmittedFromFile.
double birthTime
Birth time in the OPALX tracker convention.
double fileTime
Old-OPAL pre-emission time column.
bool inventoryBuilt_m
True once records_m is ready for emission.
double x
Horizontal position from the file.
void emitParticles(double t, double dt) override
Emits all file records whose birth times fall into the current step.
double emissionTime_m
Total emission time spanned by records_m.
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.
std::pair< size_t, size_t > computeLocalEmitRange(size_t totalToEmit) const
Computes the local contiguous subrange of a global emission batch.
ippl::detail::size_type size_type
size_t nextGlobalIndex_m
First global record index not emitted yet.
void readFile(const std::string &filename)
Reads and parses an old-OPAL emitted particle file.
std::string filename_m
File path to read emitted particle data from.
Vector_t< double, 3 > initialRefP_m
Average initial reference momentum.
void resolveFilenameFromInput()
Resolves relative file paths against the active input file directory.
double pz
Longitudinal momentum offset from the file.
void buildInventory(size_t requested)
Selects records, converts file times to birth times, and sorts them.
std::vector< RawRecord > rawRecords_m
Parsed file rows before selection and sorting.
size_t emissionSteps_m
Number of steps used to derive emission dt.
double px
Horizontal momentum offset from the file.
std::vector< Record > records_m
Selected records sorted by tracker birth time.
void generateParticles(size_t &numberOfParticles, Vector_t< double, 3 > nr) override
Reads the selected file records and builds the birth-time inventory.
double y
Vertical position from the file.
Raw row parsed from an old-OPAL emitted distribution dump.
Selected file row plus its converted tracker birth time.
std::string getInputFn()
get opals input filename
static OpalData * getInstance()
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::string emissionModel_m
Distribution_t * opalDist_m
std::shared_ptr< ParticleContainer_t > pc_m
constexpr double c
The velocity of light in m/s.