2#include <Kokkos_Core.hpp>
13#include "Utility/Inform.h"
16 std::shared_ptr<ParticleContainer_t> pc, std::shared_ptr<FieldContainer_t> fc,
25 "FNAME attribute must be set for FROMFILE distribution type.");
29 namespace fs = std::filesystem;
33 if (!inputDir.empty()) {
34 fs::path inputPath(inputDir);
35 fs::path filePath = inputPath.parent_path() /
filename_m;
36 if (fs::exists(filePath)) {
47 std::shared_ptr<ParticleContainer_t> pc, std::shared_ptr<FieldContainer_t> fc,
48 const std::string& filename)
53 throw OpalException(
"FromFile::FromFile",
"Filename must not be empty.");
61 std::ifstream file(filename);
62 if (!file.is_open()) {
63 throw OpalException(
"FromFile::readFile",
"Couldn't open file '" + filename +
"'.");
67 auto nextLine = [&file](std::string& out) ->
bool {
69 while (std::getline(file, line)) {
70 auto first = line.find_first_not_of(
" \t\r\n");
71 if (first == std::string::npos)
continue;
72 auto last = line.find_last_not_of(
" \t\r\n");
73 line = line.substr(first, last - first + 1);
74 if (line.empty() || line[0] ==
'#')
continue;
83 if (!nextLine(line1)) {
85 "File '" + filename +
"' is empty or has no valid lines. "
86 "Expected format: first line = number of particles, second line = column names.");
90 std::istringstream iss1(line1);
92 if (!(iss1 >> npart) || npart < 0 || !iss1.eof()) {
94 "First non-empty line in '" + filename +
"' must be the number of particles (single integer). "
95 "Got: '" + line1 +
"'. Expected format: N\\n column_names\\n data...");
97 size_t expectedNumParticles =
static_cast<size_t>(npart);
99 std::string headerLine;
100 if (!nextLine(headerLine)) {
102 "Missing header line in '" + filename +
"'. "
103 "Expected format: N\\n column_names\\n data... (e.g. x px y py z pz).");
110 size_t lineNumber = 3;
113 while (std::getline(file, line)) {
114 auto first = line.find_first_not_of(
" \t\r\n");
115 if (first == std::string::npos) {
119 auto last = line.find_last_not_of(
" \t\r\n");
120 line = line.substr(first, last - first + 1);
121 if (line.empty() || line[0] ==
'#') {
126 std::istringstream lineStream(line);
127 std::vector<double> values;
129 while (lineStream >> value)
130 values.push_back(value);
133 if (values.size() <= maxColIdx) {
135 "FromFile::readFile",
136 "Line " + std::to_string(lineNumber) +
" in '" + filename
137 +
"' has fewer columns (" + std::to_string(values.size())
138 +
") than required (index " + std::to_string(maxColIdx) +
").");
141 std::vector<double> phaseSpace(6);
157 "FromFile::readFile",
"No valid particle data found in '" + filename +
"'.");
162 "FromFile::readFile",
"Number of data lines (" + std::to_string(
numParticles_m)
163 +
") does not match declared count ("
164 + std::to_string(expectedNumParticles) +
") in '"
170 std::istringstream headerStream(headerLine);
171 std::vector<std::string> columnNames;
174 while (headerStream >> token) {
175 columnNames.push_back(token);
179 std::vector<size_t> indices(6, SIZE_MAX);
181 for (
size_t i = 0; i < columnNames.size(); ++i) {
184 if (normalized ==
"x" && indices[0] == SIZE_MAX) {
186 }
else if (normalized ==
"y" && indices[1] == SIZE_MAX) {
188 }
else if (normalized ==
"z" && indices[2] == SIZE_MAX) {
190 }
else if ((normalized ==
"px" || normalized ==
"px/momentumx") && indices[3] == SIZE_MAX) {
192 }
else if ((normalized ==
"py" || normalized ==
"py/momentumy") && indices[4] == SIZE_MAX) {
194 }
else if ((normalized ==
"pz" || normalized ==
"pz/momentumz") && indices[5] == SIZE_MAX) {
200 for (
size_t i = 0; i < 6; ++i) {
201 if (indices[i] == SIZE_MAX) {
202 const char* names[] = {
"x",
"y",
"z",
"px",
"py",
"pz"};
204 "FromFile::parseHeader",
205 "Header must contain all column names (x, y, z, px, py, pz). "
206 "Missing or unrecognized: '"
207 + std::string(names[i]) +
"'.");
215 std::string normalized = name;
218 std::transform(normalized.begin(), normalized.end(), normalized.begin(), [](
unsigned char c) {
219 return std::tolower(c);
225 normalized.begin(), normalized.end(),
226 [](
unsigned char c) {
227 return std::isspace(c);
237 "FromFile::generateParticles",
239 +
"' is not supported for FROMFILE distributions");
247 Inform m(
"FromFile::generateParticles");
254 m <<
"Warning: File contains " <<
numParticles_m <<
" particles, but " << numberOfParticles
255 <<
" were requested." << endl;
256 m <<
"Using " <<
numParticles_m <<
" particles from file." << endl;
262 totalParticles = numberOfParticles;
265 numberOfParticles = totalParticles;
268 const int rank = ippl::Comm->rank();
269 const int nranks = std::max(1, ippl::Comm->size());
270 const size_t nranks_u =
static_cast<size_t>(nranks);
272 : (totalParticles / nranks_u
273 + (
static_cast<size_t>(rank) < (totalParticles % nranks_u) ? 1 : 0));
276 if (
pc_m && ippl::Comm->size() > 0) {
277 unsigned long nlocalUL =
static_cast<unsigned long>(nlocal);
278 unsigned long startIdxUL = 0;
280 &nlocalUL, &startIdxUL, 1, MPI_UNSIGNED_LONG, MPI_SUM,
281 ippl::Comm->getCommunicator());
283 startIdx =
static_cast<size_t>(startIdxUL);
288 const size_t nlocalCurrent =
pc_m->getLocalNum();
289 pc_m->createParticles(nlocal);
293 auto Rview = Kokkos::subview(RviewFull, std::make_pair(nlocalCurrent, nlocalCurrent + nlocal));
295 auto Pview = Kokkos::subview(PviewFull, std::make_pair(nlocalCurrent, nlocalCurrent + nlocal));
298 using HostView = Kokkos::View<double**, Kokkos::HostSpace>;
299 HostView hostParticleData(
"hostParticleData", totalParticles, 6);
302 for (
size_t i = 0; i < totalParticles && i <
particleData_m.size(); ++i) {
303 for (
int j = 0; j < 6; ++j) {
310 auto deviceParticleData =
311 Kokkos::create_mirror(Kokkos::DefaultExecutionSpace::memory_space(), hostParticleData);
312 Kokkos::deep_copy(deviceParticleData, hostParticleData);
315 Kokkos::parallel_for(
316 "FromFile::generateParticles", nlocal, KOKKOS_LAMBDA(
const size_t k) {
317 const size_t dataIdx = startIdx + k;
318 Rview(k)[0] = deviceParticleData(dataIdx, 0);
319 Rview(k)[1] = deviceParticleData(dataIdx, 1);
320 Rview(k)[2] = deviceParticleData(dataIdx, 2);
321 Pview(k)[0] = deviceParticleData(dataIdx, 3);
322 Pview(k)[1] = deviceParticleData(dataIdx, 4);
323 Pview(k)[2] = deviceParticleData(dataIdx, 5);
332 Kokkos::parallel_for(
333 nlocal, KOKKOS_LAMBDA(
const size_t k) {
338 Inform mALL(
"FromFile::generateParticles", INFORM_ALL_NODES);
339 mALL <<
"FromFile: Loaded " << totalParticles <<
" particles from file '" <<
filename_m <<
"'"
341 ippl::Comm->barrier();
342 mALL <<
"Rank " << rank <<
": " << nlocal <<
" local particles" << endl;
343 ippl::Comm->barrier();
347 pc_m->markMomentsDirty();
352 const double tStart = t;
353 const double tEnd = t + dt;
364 if (!(tStart <=
t0_m &&
t0_m < tEnd)) {
374 if (requested == 0) {
379 const size_t nlocalBefore =
pc_m->getLocalNum();
390 const size_t nlocalAfter =
pc_m->getLocalNum();
391 const size_t nNew = nlocalAfter - nlocalBefore;
393 const double fracDt = tEnd -
t0_m;
394 auto dtview =
pc_m->dt.getView();
395 const size_t offset = nlocalBefore;
396 Kokkos::parallel_for(
397 "FromFile_setDt", nNew,
398 KOKKOS_LAMBDA(
const size_t j) { dtview(offset + j) = fracDt; });
ippl::Vector< T, Dim > Vector_t
typename ippl::detail::ViewType< ippl::Vector< double, Dim >, 1 >::view_type view_type
Defines the FromFile class used for reading particle phase space from ASCII files.
std::string getFilename() const
size_t getNumParticles() const
std::vector< size_t > parseHeader(const std::string &headerLine)
Parses the header line to get column indices. Header must contain all of x, y, z, px,...
size_t numParticles_m
Number of particles in the file.
void readFile(const std::string &filename)
Reads and parses the particle file. Format must be: N (particle count), then header line (column name...
std::string normalizeColumnName(const std::string &name)
Normalizes column name for comparison (case-insensitive, whitespace handling).
FromFile(std::shared_ptr< ParticleContainer_t > pc, std::shared_ptr< FieldContainer_t > fc, Distribution_t *opalDist)
Constructor for FromFile.
void generateParticles(size_t &numberOfParticles, Vector_t< double, 3 > nr) override
Generates particles by reading from file.
std::string filename_m
File path to read particle data from.
std::vector< size_t > columnIndices_m
Column indices mapping: [x_idx, y_idx, z_idx, px_idx, py_idx, pz_idx].
std::vector< std::vector< double > > particleData_m
Stored particle data: each element is a 6D phase space vector [x, y, z, px, py, pz].
void emitParticles(double t, double dt) override
Time-stepped emission hook for one-shot delayed file-based injection.
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).
bool hasEmittedOnce_m
For one-shot emitters (e.g. Gaussian at delayed t0): guard to avoid double sampling.
size_t computeLocalEmitCount(size_t totalToSample) const
Computes the number of particles this rank should emit so that the global total equals totalToSample ...
std::string emissionModel_m
Distribution_t * opalDist_m
void fillPolarization(size_t startIdx, size_t count)
std::shared_ptr< ParticleContainer_t > pc_m