66#include "Utility/Inform.h"
88 std::string h5RestartSourceForContainer(
89 const std::string& restartFile,
const std::string& containerH5FileName,
90 size_t numContainers) {
91 if (numContainers <= 1) {
94 namespace fs = std::filesystem;
95 fs::path rf(restartFile);
96 fs::path leaf = fs::path(containerH5FileName).filename();
97 if (rf.has_parent_path()) {
98 return (rf.parent_path() / leaf).string();
100 return leaf.string();
108 void requireUnitMacroWeight(
const Beam& beam,
const std::string& role) {
109 const double partsPerMacro =
111 if (std::abs(partsPerMacro - 1.0) > 1
e-2) {
114 "DECAY requires one physical particle per macroparticle, but " + role
116 +
"\" has particles-per-macro = " + std::to_string(partsPerMacro)
117 +
". Set BCHARGE = NALLOC * |CHARGE| * q_e.");
124 std::vector<std::unique_ptr<GlobalProcess>> makeGlobalProcessesForBeam(
125 const Beam& beam, std::size_t containerIndex) {
126 std::vector<std::unique_ptr<GlobalProcess>> processes;
128 processes.reserve(processNames.size());
130 for (
const std::string& processName : processNames) {
131 if (processName ==
"DECAY") {
137 const int parentSign = (parentQ > 0.0) - (parentQ < 0.0);
139 requireUnitMacroWeight(beam,
"parent");
146 "Muon decay requires spin tracking: the differential decay "
147 "rate is polarization-dependent. Set POLARIZATION = "
148 "{Px, Py, Pz} on the muon BEAM (this enables spin tracking).");
151 std::make_unique<MuonDecay>(tau, containerIndex, mass, parentSign));
155 std::make_unique<PionDecay>(tau, containerIndex, mass, parentSign));
160 "No decay implementation for PARTICLE=" + particleName
161 +
". Supported: MUON, PION.");
168 "Unknown global process \"" + processName +
"\". Supported values: DECAY.");
196 "The \"RUN\" sub-command tracks the defined particles through "
197 "the given lattice."),
198 itsTracker_m(nullptr),
202 isFollowupTrack_m(false),
205 "METHOD",
"Name of tracking algorithm to use.", {
"PARALLEL"});
209 "Number of turns to be tracked; Number of neighboring bunches to be tracked in "
217 "BOUNDARYGEOMETRY",
"Boundary geometry to be used NONE (default).",
"NONE");
228 itsTracker_m(nullptr),
232 isFollowupTrack_m(false),
242 ippl::NDIndex<3> domain;
243 for (
unsigned i = 0; i < 3; i++) {
244 domain[i] = ippl::Index(
nr[i]);
247 std::array<bool, 3> isParallel;
249 for (
unsigned d = 0; d < 3; ++d) {
250 isParallel[d] =
true;
259 const int currentVersion = ((buildinfo::version_major * 100) + buildinfo::version_minor) * 100;
263 bool newerChanges =
false;
265 if (it->first > fileVersion) {
271 Inform errorMsg(
"Error");
272 errorMsg <<
"\n******************** V E R S I O N M I S M A T C H "
273 "***********************\n"
276 if (it->first > fileVersion) {
277 errorMsg << it->second << endl;
281 <<
"* Make sure you do understand these changes and adjust your input file \n"
282 <<
"* accordingly. Then add\n"
283 <<
"* OPTION, VERSION = " << currentVersion <<
";\n"
284 <<
"* to your input file. " << endl;
285 errorMsg <<
"\n************************************************************************"
288 throw OpalException(
"TrackRun::execute",
"Version mismatch");
296 throw OpalException(
"TrackRun::execute",
"\"FIELDSOLVER\" must be set in \"RUN\" command.");
308 if (beamNames.empty()) {
310 "TrackRun::execute",
"No beam specified: set TRACK::BEAM or TRACK::BEAMS.");
314 std::vector<Beam*> beams;
315 beams.reserve(beamNames.size());
316 for (
const auto& name : beamNames) {
318 throw OpalException(
"TrackRun::execute",
"Empty beam name in resolved beam list.");
322 for (
const auto* b : beams) {
326 "TRACK does not support BEAM, PARTICLE=PHOTON yet. "
327 "Photon beams may be defined for future OPALX features, but they are currently "
328 "rejected during tracking.");
331 *
gmsg << level1 <<
"* RUN resolved beams: ";
332 for (
size_t i = 0; i < beamNames.size(); ++i) {
333 *
gmsg << beamNames[i] << (i + 1 < beamNames.size() ?
", " :
"");
337 for (
Beam* b : beams) {
338 *
gmsg << level1 << *b << endl;
342 std::vector<double> macrocharges;
343 std::vector<double> macromasses;
344 std::vector<std::vector<EmissionSource*>> emissionSourcesLists;
345 std::vector<std::vector<std::unique_ptr<GlobalProcess>>> globalProcessesLists;
346 macrocharges.reserve(beams.size());
347 macromasses.reserve(beams.size());
348 emissionSourcesLists.reserve(beams.size());
349 globalProcessesLists.resize(beams.size());
352 for (
size_t i = 0; i < beams.size(); ++i) {
357 macrocharges.push_back(macrocharge);
358 macromasses.push_back(macromass);
361 *
gmsg << level2 <<
"* Beam[" << i <<
"] " << beamNames[i]
362 <<
" macro charge per particle [C]: " << macrocharge << endl;
363 *
gmsg << level2 <<
"* Beam[" << i <<
"] " << beamNames[i]
364 <<
" macro mass per particle [GeV/c^2]: " << macromass << endl;
365 *
gmsg << level2 <<
"* Beam[" << i <<
"] " << beamNames[i]
366 <<
" particles per macro particle: " << part_per_macro_ratio << endl
371 if (sources.empty()) {
373 "TrackRun::execute",
"Emission sources list for beam '" + beamNames[i]
374 +
"' must contain at least one EMISSIONSOURCE.");
376 emissionSourcesLists.emplace_back(sources.begin(), sources.end());
378 globalProcessesLists[i] = makeGlobalProcessesForBeam(*b, i);
392 std::vector<size_t> totalParticlesPerBeam(beams.size());
393 for (
size_t i = 0; i < beams.size(); ++i) {
399 bunch_m = std::make_unique<bunch_type>(
403 totalParticlesPerBeam,
410 const auto& particleContainers =
bunch_m->getParticleContainers();
411 if (particleContainers.size() != beams.size()) {
413 "TrackRun::execute",
"Mismatch between number of beams and particle containers.");
421 *
gmsg << level2 << *(
bunch_m->getBCHandler()) << endl;
433 throw OpalException(
"TrackRun::execute",
"Unknown \"METHOD\" for the \"RUN\" command");
442 if (ippl::Comm->rank() == 0) {
443 long number_of_processors = sysconf(_SC_NPROCESSORS_ONLN);
444 *
gmsg << level5 <<
"sysconf(_SC_NPROCESSORS_ONLN)= " << number_of_processors << endl;
449 MPI_Comm_size(MPI_COMM_WORLD, &world_size);
450 *
gmsg << level5 <<
"MPI_Comm_size= " << world_size << endl;
457 std::vector<emittingSamplers_t> emittingSamplersList(particleContainers.size());
458 for (
size_t i = 0; i < particleContainers.size(); ++i) {
460 emissionSourcesLists[i], beams[i], emittingSamplersList[i], i);
473 if (
bunch_m->getParticleContainer()->getTotalNum() > 0) {
479 while (i + 1 < zstop.size() && zstop[i + 1] < spos) {
510 "TrackRun::setRunMethod",
511 "The attribute \"METHOD\" isn't set for the \"RUN\" command");
528 for (
size_t i = 0; i < numParticleContainers; ++i) {
529 const std::string stem =
531 const std::string dest = stem + std::string(
".h5");
534 const std::string src = h5RestartSourceForContainer(
537 std::make_unique<H5PartWrapperForPT>(
541 std::make_unique<H5PartWrapperForPT>(
542 dest, -1, stem + std::string(
".h5"), H5_O_WRONLY));
544 phaseSpaceSinks_m.push_back(std::make_unique<H5PartWrapperForPT>(dest, H5_O_WRONLY));
565 std::vector<H5PartWrapper*> sinks;
568 sinks.push_back(sink.get());
578 const std::string geomDescriptor =
587 std::vector<std::vector<std::unique_ptr<GlobalProcess>>> globalProcessesLists) {
588 const auto& particleContainers =
bunch_m->getParticleContainers();
589 if (particleContainers.size() != globalProcessesLists.size()) {
591 "TrackRun::setupGlobalProcesses",
592 "Mismatch between number of particle containers and global process lists.");
595 for (
size_t i = 0; i < particleContainers.size(); ++i) {
596 if (!particleContainers[i]) {
599 particleContainers[i]->setGlobalProcesses(std::move(globalProcessesLists[i]));
604 const auto& containers =
bunch_m->getParticleContainers();
607 for (std::size_t i = 0; i < beams.size(); ++i) {
608 const std::string daughterName = beams[i]->getDaughterBeamName();
609 if (daughterName.empty()) {
614 auto it = std::find(beamNames.begin(), beamNames.end(), daughterName);
615 if (it == beamNames.end()) {
617 "TrackRun::wireDaughterContainers",
618 "DAUGHTERBEAM=\"" + daughterName +
"\" on beam \"" + beamNames[i]
619 +
"\" does not match any beam in the TRACK.");
621 const std::size_t daughterIdx =
622 static_cast<std::size_t
>(std::distance(beamNames.begin(), it));
626 const double daughterMass = beams[daughterIdx]->getMass();
627 for (
const auto& proc : containers[i]->getGlobalProcesses()) {
628 auto* decayProc =
dynamic_cast<Decay*
>(proc.get());
630 requireUnitMacroWeight(*beams[daughterIdx],
"daughter");
638 "TrackRun::wireDaughterContainers",
639 "Decay produces muons in daughter beam \"" + daughterName
640 +
"\", whose polarization must be tracked. Set POLARIZATION "
641 "= {Px, Py, Pz} on that BEAM to enable spin tracking.");
643 decayProc->setDaughterContainer(containers[daughterIdx], daughterMass);
644 *
gmsg << level2 <<
"* Wired decay on beam \"" << beamNames[i]
645 <<
"\" to daughter beam \"" << daughterName <<
"\" (container " << daughterIdx
653 Beam* beam,
const std::vector<EmissionSource*>& sources)
const {
656 size_t totalFromDists = 0;
659 totalFromDists += dist->getNumParticles();
662 if (totalFromDists > 0) {
663 *
gmsg << level3 <<
"* Sum of per-distribution NPARTDIST over all emission sources = "
664 << totalFromDists <<
", BEAM::NALLOC = " << beamAllocSize << endl;
665 if (totalFromDists > beamAllocSize) {
666 *
gmsg << level1 <<
"* WARNING: Sum of NPARTDIST over all distributions ("
667 << totalFromDists <<
") exceeds BEAM::NALLOC (" << beamAllocSize
668 <<
"). Allocation baseline may be insufficient; "
669 <<
"macro-charge per particle is still derived from BEAM::NALLOC." << endl;
671 return totalFromDists;
674 return beamAllocSize;
678 const std::vector<EmissionSource*>& sources,
Beam* beam,
680 static IpplTimings::TimerRef samplingTime = IpplTimings::getTimer(
"samplingTime");
682 IpplTimings::startTimer(samplingTime);
685 auto pc =
bunch_m->getParticleContainer(index);
686 auto fc =
bunch_m->getFieldContainer();
690 emittingSamplers.clear();
706 if (usesFileMomentum) {
709 "TrackRun::setupDistributionsAndSamplers()",
711 + src->getDistributionName()
712 +
"\" cannot be combined with PC/ENERGY/GAMMA on the BEAM. "
713 "Remove the energy attribute from the BEAM command - "
714 "particle momenta are read from the file.");
719 "TrackRun::setupDistributionsAndSamplers()",
720 "The energy hasn't been set. "
721 "Set either \"GAMMA\", \"ENERGY\" or \"PC\" on the BEAM command.");
728 std::shared_ptr<SamplingBase> sampler;
731 sampler = std::make_shared<Gaussian>(pc, fc, opalDist);
734 sampler = std::make_shared<MultiVariateGaussian>(pc, fc, opalDist);
737 sampler = std::make_shared<FlatTop>(pc, fc, opalDist);
740 sampler = std::make_shared<OpalFlatTop>(pc, fc, opalDist);
743 sampler = std::make_shared<FromFile>(pc, fc, opalDist);
746 sampler = std::make_shared<EmittedFromFile>(pc, fc, opalDist);
749 throw OpalException(
"Distribution::create",
"Unknown \"TYPE\" of \"DISTRIBUTION\"");
753 const auto R0 = src->getR0();
754 const auto P0 = src->getP0();
755 const double t0 = src->getT0();
756 sampler->setEmissionOffsets(R0, P0, t0, src->getEmissionModel());
760 sampler->setInitialPolarization({pol[0], pol[1], pol[2]});
763 size_t Nmutable = Ndist;
768 sampler->generateParticles(Nmutable,
nr);
770 const double globalShift = std::max(
777 if (opalDist->
emitting_m || src->getT0() > 0.0
779 emittingSamplers.push_back(sampler);
780 *
gmsg << level2 <<
"* Configured emitting source of type "
782 <<
", t0 = " << t0 << endl;
786 *
gmsg << level2 <<
"* Particle sampling / sampler setup for all emission sources done." << endl;
787 IpplTimings::stopTimer(samplingTime);
791 const std::vector<std::vector<EmissionSource*>>& emissionSourcesLists) {
792 bool enableImageCharge =
false;
793 bool enableShiftedGreens =
false;
795 int dumpFrequency = 0;
797 size_t numZeroFaceR0Z = 0;
798 size_t numShiftedGreens = 0;
800 for (
const auto& sourceList : emissionSourcesLists) {
801 for (
const auto* src : sourceList) {
806 const bool srcZeroFace = src->getZeroFaceR0Z();
807 const bool srcShifted = src->getShiftedGreensFunction();
808 const int sourceDumpFrequency = src->getZeroFacePlaneDumpFrequency();
811 if (srcZeroFace && srcShifted) {
813 "TrackRun::configureImageChargeFromSources",
814 "ZEROFACE_R0Z and SHIFTED_GREENS_FUNCTION are mutually exclusive on "
815 "the same EMISSIONSOURCE. Enable exactly one.");
818 if (!srcZeroFace && !srcShifted) {
819 if (sourceDumpFrequency > 0) {
821 "TrackRun::configureImageChargeFromSources",
822 "ZEROFACEPLANEDUMP > 0 requires ZEROFACE_R0Z=true on the same "
823 "EMISSIONSOURCE. (Dumping is not supported for "
824 "SHIFTED_GREENS_FUNCTION since the computational domain may be "
832 enableImageCharge =
true;
833 zPlane = src->getR0()[2];
834 dumpFrequency = sourceDumpFrequency;
835 maxSteps = src->getZerofaceMaxSteps();
839 enableShiftedGreens =
true;
840 zPlane = src->getR0()[2];
842 if (sourceDumpFrequency > 0) {
844 "TrackRun::configureImageChargeFromSources",
845 "ZEROFACEPLANEDUMP > 0 is not supported with "
846 "SHIFTED_GREENS_FUNCTION=true (the computational domain may be "
847 "far from R0Z, making the interpolated plane dump meaningless).");
849 maxSteps = src->getZerofaceMaxSteps();
854 if (numZeroFaceR0Z > 1) {
856 "TrackRun::configureImageChargeFromSources",
857 "Cannot have more than one emission source with ZEROFACE_R0Z=true, since image "
858 "charge computation is only implemented for one plane.");
860 if (numShiftedGreens > 1) {
862 "TrackRun::configureImageChargeFromSources",
863 "Cannot have more than one emission source with SHIFTED_GREENS_FUNCTION=true, "
864 "since the shifted Green's function correction is only implemented for one plane.");
866 if (enableImageCharge && enableShiftedGreens) {
868 "TrackRun::configureImageChargeFromSources",
869 "Cannot have ZEROFACE_R0Z=true on one EMISSIONSOURCE and "
870 "SHIFTED_GREENS_FUNCTION=true on another; the two Dirichlet-correction paths "
871 "are mutually exclusive at the run level.");
881 if (enableShiftedGreens) {
882 const std::string solverType =
fs_m ?
fs_m->
getType() : std::string(
"(unknown)");
883 if (solverType !=
"OPEN") {
885 "TrackRun::configureImageChargeFromSources",
886 "SHIFTED_GREENS_FUNCTION=true requires FIELDSOLVER TYPE=OPEN (got '"
887 + solverType +
"').");
891 bunch_m->setImageChargeConfiguration(enableImageCharge, zPlane);
892 bunch_m->setShiftedGreensConfiguration(enableShiftedGreens, zPlane);
893 bunch_m->setZeroFacePlaneDumpFrequency(enableImageCharge ? dumpFrequency : 0);
895 const bool anyDirichletActive = enableImageCharge || enableShiftedGreens;
896 bunch_m->setZerofaceMaxSteps(anyDirichletActive ? maxSteps : 0);
901 os <<
"* ************* T R A C K R U N *************************************************** "
904 os <<
"* Selected Tracking Method == " <<
getRunMethodName() <<
", NEW TRACK" <<
'\n'
906 "********************************************************************************** "
909 os <<
"* Selected Tracking Method == " <<
getRunMethodName() <<
", FOLLOWUP TRACK" <<
'\n'
911 "********************************************************************************** "
920 std::string primaryBeamName;
925 if (!primaryBeamName.empty()) {
927 os <<
"* Mass of simulation particle = " << beam->
getMassPerParticle() <<
" [GeV/c^2]"
932 os <<
"* Mass of simulation particle = <unresolved>" <<
'\n'
933 <<
"* Charge of simulation particle = <unresolved>" <<
'\n';
935 os <<
"* ********************************************************************************** ";
ippl::Vector< T, Dim > Vector_t
Defines an emitted file distribution with old-OPAL time-column semantics.
Defines the FlatTop class used for sampling emitting particles.
Defines the FromFile class used for reading particle phase space from ASCII files.
OPAL-like flat-top emission sampler with precomputed birth times.
Visitor-based parallel tracker with time as the independent variable.
The base class for all OPAL actions.
std::string getParticleName() const
Return Particle's name.
std::string getEmissionSourceListName() const
double getChargePerParticle() const
Charge per macro particle in C.
bool hasPolarization() const
double getMomentum() const
static Beam * find(const std::string &name)
Find named BEAM.
double getCharge() const
Return the charge number in elementary charge.
size_t getNumAlloc() const
Return the allocation size (macroparticles) for this beam.
double getMassPerParticle() const
Mass per macro particle in GeV/c^2.
double getMass() const
Return Particle's rest mass in GeV.
bool hasExplicitEnergy() const
True if PC, ENERGY, or GAMMA was explicitly provided by the user.
std::vector< double > getPolarization() const
std::vector< std::string > getGlobalProcessNames() const
Return the configured global process names for this beam.
Simple bidirectional map with lookup in both directions.
void insert(const Left &left, const Right &right)
Insert or overwrite a left/right association.
right_view right
Right view accessor.
left_view left
Left view accessor.
static BoundaryGeometry * find(const std::string &name)
virtual BoundaryGeometry * clone(const std::string &name)
Return a clone.
void changeH5Wrappers(const std::vector< H5PartWrapper * > &h5wrappers)
static std::string diagnosticStemForContainer(const std::string &inputBasename, size_t numContainers, size_t index)
Abstract base class for particle decay processes.
static Distribution * find(const std::string &name)
DistributionType getType() const
std::string getTypeofDistribution()
size_t getNumParticles() const
void setAvrgPz(double avrgpz)
static EmissionSourceList * find(const std::string &name)
const std::vector< EmissionSource * > & fetchSources() const
Return the list of EmissionSource pointers. Valid after parse/execute.
static FieldSolverCmd * find(const std::string &name)
Find named FieldSolverCmd.
BinningCmd * getBinningCmd() const
bool hasBinningCmd() const
void registerOwnership(const AttributeHandler::OwnerType &itsClass) const
const std::string & getOpalName() const
Return object name.
std::vector< Attribute > itsAttr
The object attributes.
std::string getInputBasename()
get input file name without extension
int getRestartStep()
get the step where to restart
void setDataSink(DataSink *s)
bool hasDataSinkAllocated()
true if we already allocated a DataSink object
void setGlobalPhaseShift(double shift)
units: (sec)
bool hasBunchAllocated()
true if we already allocated a ParticleBunch object
static OpalData * getInstance()
void setGlobalGeometry(BoundaryGeometry *bg)
bool inRestartRun()
true if we do a restart run
static double getParticleMass(const ParticleType &type)
static double getParticleLifetime(const ParticleType &type)
Return the mean rest-frame lifetime [s]. Throws if the particle is stable.
static ParticleType getParticleType(const std::string &str)
void wireDaughterContainers(const std::vector< Beam * > &beams)
Wire daughter containers to cross-container processes (e.g. muon decay -> electron).
void setupBoundaryGeometry()
size_t computeTotalAllocationForBunch(Beam *beam, const std::vector< EmissionSource * > &sources) const
virtual void execute()
Execute the command.
std::vector< H5PartWrapper * > borrowedPhaseSpaceSinks() const
std::vector< Distribution * > distrs_m
Distributions referenced by all emission sources (non-owning raw pointers).
void setupDistributionsAndSamplers(const std::vector< EmissionSource * > &sources, Beam *beam, emittingSamplers_t &emittingSamplers, size_t index=0)
TrackRun()
Exemplar constructor.
void setupGlobalProcesses(std::vector< std::vector< std::unique_ptr< GlobalProcess > > > globalProcessesLists)
Attach prebuilt global process vector to each particle container.
virtual TrackRun * clone(const std::string &name)
Make clone.
static const BiMap< RunMethod, std::string > stringMethod_s
std::vector< std::shared_ptr< SamplingBase > > emittingSamplers_t
FieldSolverCmd * fs_m
Samplers for time-dependent (emitting) sources; tracker calls emitParticles(t, dt) on each.
std::string getRunMethodName() const
std::unique_ptr< Tracker > itsTracker_m
std::unique_ptr< bunch_type > bunch_m
void configureImageChargeFromSources(const std::vector< std::vector< EmissionSource * > > &emissionSourcesLists)
Configure image-charge mode from all configured emission sources.
std::vector< std::unique_ptr< H5PartWrapper > > phaseSpaceSinks_m
void initDataSink(size_t numParticleContainers)
Inform & print(Inform &os) const
std::vector< std::string > beamNames_m
double zstart
The location at which the simulation starts.
std::vector< unsigned long long > localTimeSteps
Maximal number of timesteps.
std::vector< double > zstop
The location at which the simulation stops.
static Track * block
The block of track data.
std::vector< double > dT
The initial timestep.
Attribute makeBool(const std::string &name, const std::string &help)
Make logical attribute.
Attribute makePredefinedString(const std::string &name, const std::string &help, const std::initializer_list< std::string > &predefinedStrings)
Make predefined string attribute.
Attribute makeReal(const std::string &name, const std::string &help)
Make real attribute.
std::string getString(const Attribute &attr)
Get string value.
Attribute makeString(const std::string &name, const std::string &help)
Make string attribute.
int psDumpFreq
The frequency to dump the phase space, i.e.dump data when steppsDumpFreq==0.
int version
opal version of input file
int statDumpFreq
The frequency to dump statistical values, e.e. dump data when stepstatDumpFreq==0.
constexpr double q_e
The elementary charge in As.
constexpr double e
The value of.
std::map< unsigned int, std::string > changes