OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
TrackRun.cpp
Go to the documentation of this file.
1// Class TrackRun
2// The RUN command.
3//
4// Copyright (c) 200x - 2022, Paul Scherrer Institut, Villigen PSI, Switzerland
5// All rights reserved
6//
7// This file is part of OPAL.
8//
9// OPAL is free software: you can redistribute it and/or modify
10// it under the terms of the GNU General Public License as published by
11// the Free Software Foundation, either version 3 of the License, or
12// (at your option) any later version.
13//
14// You should have received a copy of the GNU General Public License
15// along with OPAL. If not, see <https://www.gnu.org/licenses/>.
16//
17#include "Track/TrackRun.h"
18
20
22
24
26
27#include "Beamlines/TBeamline.h"
28
29#include "BasicActions/Option.h"
30
32
34
36
38
40
42
44
45#include "Physics/Physics.h"
46#include "Physics/Units.h"
47
52
53#include "Track/Track.h"
54
56
58#include "Structure/Beam.h"
60#include "Structure/DataSink.h"
64
65#include "BuildInfo.h"
66#include "Utility/Inform.h"
67#include "changes.h"
68
69#include "Utilities/BiMap.h"
70
71#include <algorithm>
72#include <cmath>
73#include <cstddef>
74#include <fstream>
75#include <iomanip>
76#include <memory>
77#include <vector>
78
79#include <unistd.h>
80
81#include <filesystem>
82
83extern Inform* gmsg;
84
85namespace {
86
88 std::string h5RestartSourceForContainer(
89 const std::string& restartFile, const std::string& containerH5FileName,
90 size_t numContainers) {
91 if (numContainers <= 1) {
92 return restartFile;
93 }
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();
99 }
100 return leaf.string();
101 }
102
108 void requireUnitMacroWeight(const Beam& beam, const std::string& role) {
109 const double partsPerMacro =
110 beam.getChargePerParticle() / (beam.getCharge() * Physics::q_e);
111 if (std::abs(partsPerMacro - 1.0) > 1e-2) {
112 throw OpalException(
113 "TrackRun::execute",
114 "DECAY requires one physical particle per macroparticle, but " + role
115 + " beam \"" + beam.getOpalName()
116 + "\" has particles-per-macro = " + std::to_string(partsPerMacro)
117 + ". Set BCHARGE = NALLOC * |CHARGE| * q_e.");
118 }
119 }
120
124 std::vector<std::unique_ptr<GlobalProcess>> makeGlobalProcessesForBeam(
125 const Beam& beam, std::size_t containerIndex) {
126 std::vector<std::unique_ptr<GlobalProcess>> processes;
127 const std::vector<std::string> processNames = beam.getGlobalProcessNames();
128 processes.reserve(processNames.size());
129
130 for (const std::string& processName : processNames) {
131 if (processName == "DECAY") {
132 const std::string particleName = beam.getParticleName();
133 const ParticleType pType = ParticleProperties::getParticleType(particleName);
134 const double tau = ParticleProperties::getParticleLifetime(pType);
135 const double mass = ParticleProperties::getParticleMass(pType);
136 const double parentQ = beam.getCharge();
137 const int parentSign = (parentQ > 0.0) - (parentQ < 0.0);
138
139 requireUnitMacroWeight(beam, "parent");
140
141 switch (pType) {
143 if (!beam.hasPolarization()) {
144 throw OpalException(
145 "TrackRun::execute",
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).");
149 }
150 processes.push_back(
151 std::make_unique<MuonDecay>(tau, containerIndex, mass, parentSign));
152 break;
154 processes.push_back(
155 std::make_unique<PionDecay>(tau, containerIndex, mass, parentSign));
156 break;
157 default:
158 throw OpalException(
159 "TrackRun::execute",
160 "No decay implementation for PARTICLE=" + particleName
161 + ". Supported: MUON, PION.");
162 }
163 continue;
164 }
165
166 throw OpalException(
167 "TrackRun::execute",
168 "Unknown global process \"" + processName + "\". Supported values: DECAY.");
169 }
170
171 return processes;
172 }
173
174} // namespace
175
176namespace TRACKRUN {
177 // The attributes of class TrackRun.
178 enum {
179 METHOD, // Tracking method to use.
180 TURNS, // The number of turns to be tracked, we keep that for the moment
181 FIELDSOLVER, // The field solver attached
182 BOUNDARYGEOMETRY, // The boundary geometry
183 TRACKBACK, // In case we run the beam backwards
184 SIZE
185 };
186} // namespace TRACKRUN
187
190 bimap.insert(TrackRun::RunMethod::PARALLEL, "PARALLEL");
191 return bimap;
192}();
193
195 : Action(TRACKRUN::SIZE, "RUN",
196 "The \"RUN\" sub-command tracks the defined particles through "
197 "the given lattice."),
198 itsTracker_m(nullptr),
199 fs_m(nullptr),
200 ds_m(nullptr),
201 phaseSpaceSinks_m(),
202 isFollowupTrack_m(false),
203 method_m(RunMethod::NONE) {
205 "METHOD", "Name of tracking algorithm to use.", {"PARALLEL"});
206
208 "TURNS",
209 "Number of turns to be tracked; Number of neighboring bunches to be tracked in "
210 "cyclotron.",
211 1.0);
212
214 Attributes::makeString("FIELDSOLVER", "Field solver to be used.");
215
217 "BOUNDARYGEOMETRY", "Boundary geometry to be used NONE (default).", "NONE");
218
220 Attributes::makeBool("TRACKBACK", "Track in reverse direction, default: false.", false);
221
224}
225
226TrackRun::TrackRun(const std::string& name, TrackRun* parent)
227 : Action(name, parent),
228 itsTracker_m(nullptr),
229 fs_m(nullptr),
230 ds_m(nullptr),
231 phaseSpaceSinks_m(),
232 isFollowupTrack_m(false),
233 method_m(RunMethod::NONE) {
234 /*
235 the opal dictionary
236 */
237
239
240 const Vector_t<int, 3> nr(8);
241
242 ippl::NDIndex<3> domain;
243 for (unsigned i = 0; i < 3; i++) {
244 domain[i] = ippl::Index(nr[i]);
245 }
246
247 std::array<bool, 3> isParallel;
248
249 for (unsigned d = 0; d < 3; ++d) {
250 isParallel[d] = true;
251 }
252}
253
255
256TrackRun* TrackRun::clone(const std::string& name) { return new TrackRun(name, this); }
257
259 const int currentVersion = ((buildinfo::version_major * 100) + buildinfo::version_minor) * 100;
260
261 if (Options::version < currentVersion) {
262 unsigned int fileVersion = Options::version / 100;
263 bool newerChanges = false;
264 for (auto it = Versions::changes.begin(); it != Versions::changes.end(); ++it) {
265 if (it->first > fileVersion) {
266 newerChanges = true;
267 break;
268 }
269 }
270 if (newerChanges) {
271 Inform errorMsg("Error");
272 errorMsg << "\n******************** V E R S I O N M I S M A T C H "
273 "***********************\n"
274 << endl;
275 for (auto it = Versions::changes.begin(); it != Versions::changes.end(); ++it) {
276 if (it->first > fileVersion) {
277 errorMsg << it->second << endl;
278 }
279 }
280 errorMsg << "\n"
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************************************************************************"
286 "****\n"
287 << endl;
288 throw OpalException("TrackRun::execute", "Version mismatch");
289 }
290 }
291
292 // Follow-up behavior is still based on whether a bunch was allocated already.
293 // Emission sources are resolved from the selected BEAM later.
296 throw OpalException("TrackRun::execute", "\"FIELDSOLVER\" must be set in \"RUN\" command.");
297 }
298
299 // Field solver commands are registry-owned by OpalData; TrackRun only borrows it.
301 *gmsg << level1 << *fs_m << endl;
302 if (fs_m->hasBinningCmd()) {
303 *gmsg << level1 << *fs_m->getBinningCmd() << endl;
304 }
305
306 // Process BEAM object names
307 std::vector<std::string> beamNames = Track::block->beamNames_m;
308 if (beamNames.empty()) {
309 throw OpalException(
310 "TrackRun::execute", "No beam specified: set TRACK::BEAM or TRACK::BEAMS.");
311 }
312
313 // Create vector of BEAMs
314 std::vector<Beam*> beams;
315 beams.reserve(beamNames.size());
316 for (const auto& name : beamNames) {
317 if (name.empty()) {
318 throw OpalException("TrackRun::execute", "Empty beam name in resolved beam list.");
319 }
320 beams.push_back(Beam::find(name)); // fail fast
321 }
322 for (const auto* b : beams) {
323 if (b->isPhoton()) {
324 throw OpalException(
325 "TrackRun::execute",
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.");
329 }
330 }
331 *gmsg << level1 << "* RUN resolved beams: ";
332 for (size_t i = 0; i < beamNames.size(); ++i) {
333 *gmsg << beamNames[i] << (i + 1 < beamNames.size() ? ", " : "");
334 }
335 *gmsg << endl;
336 // Print the BEAM banner for each resolved beam.
337 for (Beam* b : beams) {
338 *gmsg << level1 << *b << endl;
339 }
340
341 // Vectors for each species
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());
350
351 // Fill macro quantities and emissionSourceList per container (beam)
352 for (size_t i = 0; i < beams.size(); ++i) {
353 Beam* b = beams[i];
354
355 const double macrocharge = b->getChargePerParticle();
356 const double macromass = b->getMassPerParticle();
357 macrocharges.push_back(macrocharge);
358 macromasses.push_back(macromass);
359
360 const double part_per_macro_ratio = macrocharge / (b->getCharge() * Physics::q_e);
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
367 << endl;
368
370 const auto& sources = esl->fetchSources();
371 if (sources.empty()) {
372 throw OpalException(
373 "TrackRun::execute", "Emission sources list for beam '" + beamNames[i]
374 + "' must contain at least one EMISSIONSOURCE.");
375 }
376 emissionSourcesLists.emplace_back(sources.begin(), sources.end());
377
378 globalProcessesLists[i] = makeGlobalProcessesForBeam(*b, i);
379 }
380
381 /*
382 Need the following units for mass and charge:
383 - Charge per macro particle in [C], this should be macrocharge_m or q_m in the bunch.
384 This will be used for the field calculations.
385 - The pusher needs consistent units: eV for mass and elementary charges for charge.
386 This will (hopefully) be handled inside the pusher routines!
387 */
388
389 initDataSink(beams.size());
390
391 // Set total particles per container (beam)
392 std::vector<size_t> totalParticlesPerBeam(beams.size());
393 for (size_t i = 0; i < beams.size(); ++i) {
394 Beam* b = beams[i];
395 totalParticlesPerBeam[i] = computeTotalAllocationForBunch(b, emissionSourcesLists[i]);
396 }
397
398 // Create PartBunch (PIC Manager) with multiple particle containers
399 bunch_m = std::make_unique<bunch_type>(
400 macrocharges, // Macro charge [C]
401 macromasses, // Macro Mass [GeV]
402 beams, // Beam objects per container
403 totalParticlesPerBeam, // Per-beam particle counts for allocation
404 1.0, // lbt
405 "LF2", // Integrator
406 fs_m, // Fieldsolver
407 ds_m); // Data sink
408
409 // Validate container setup produced by constructor
410 const auto& particleContainers = bunch_m->getParticleContainers();
411 if (particleContainers.size() != beams.size()) {
412 throw OpalException(
413 "TrackRun::execute", "Mismatch between number of beams and particle containers.");
414 }
415
416 // Global processes
417 setupGlobalProcesses(std::move(globalProcessesLists));
419
420 // BC handler
421 *gmsg << level2 << *(bunch_m->getBCHandler()) << endl;
422
424
425 // Get algorithm to use.
426 setRunMethod();
427
428 switch (method_m) {
429 case RunMethod::PARALLEL: {
430 break;
431 }
432 default: {
433 throw OpalException("TrackRun::execute", "Unknown \"METHOD\" for the \"RUN\" command");
434 }
435 }
436
437 // double deltaP = Attributes::getReal(itsAttr[Distribution::OFFSETP]);
438 // if (inputMoUnits_m == InputMomentumUnits::EVOVERC) {
439 // deltaP = Util::convertMomentumEVoverCToBetaGamma(deltaP, beam->getM());
440 // }
441
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;
445
446 // *gmsg << "omp_get_max_threads() " << omp_get_max_threads() << endl;
447
448 int world_size;
449 MPI_Comm_size(MPI_COMM_WORLD, &world_size);
450 *gmsg << level5 << "MPI_Comm_size= " << world_size << endl;
451 }
452
453 // Setup all distributions and samplers, perform initial sampling (t0 == 0),
454 // and prepare per-container emitting sampler lists for ParallelTracker.
455 // Do this for each particle container
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);
461 }
462 configureImageChargeFromSources(emissionSourcesLists);
463
464 // Reset the field solver with correct hr_m based on the distribution.
465 bunch_m->setCharge();
466 bunch_m->setMass();
467
468 // Calculate extents and update moments for each container
469 bunch_m->bunchUpdate();
470 bunch_m->print(*gmsg);
471
472 // Set ZStart, ZStop, and dT
473 if (bunch_m->getParticleContainer()->getTotalNum() > 0) {
474 double spos = Track::block->zstart;
475 auto& zstop = Track::block->zstop;
476 auto it = Track::block->dT.begin();
477
478 unsigned int i = 0;
479 while (i + 1 < zstop.size() && zstop[i + 1] < spos) {
480 ++i;
481 ++it;
482 }
483
484 bunch_m->setdT(*it);
485 } else {
486 Track::block->zstart = 0.0;
487 }
488
489 /* \todo this is also not unsed in the master.
490 This needs to come back as soon as we have RF
491
492 findPhasesForMaxEnergy();
493
494 */
495 itsTracker_m = std::make_unique<ParallelTracker>(
496 *Track::block->use->fetchLine(), *bunch_m, ds_m, false, Track::block->localTimeSteps,
497 Track::block->zstart, Track::block->zstop, Track::block->dT, emittingSamplersList);
498 itsTracker_m->execute();
499
500 /*
501 opal_m->setRestartRun(false);
502
503 opal_m->bunchIsAllocated();
504 */
505}
506
509 throw OpalException(
510 "TrackRun::setRunMethod",
511 "The attribute \"METHOD\" isn't set for the \"RUN\" command");
512 } else {
514 if (it != stringMethod_s.right.end()) {
515 method_m = it->second;
516 }
517 }
518}
519
520std::string TrackRun::getRunMethodName() const { return stringMethod_s.left.at(method_m); }
521
522void TrackRun::initDataSink(size_t numParticleContainers) {
523 phaseSpaceSinks_m.clear();
524 phaseSpaceSinks_m.reserve(numParticleContainers);
525
526 const std::string base = opal_m->getInputBasename();
527
528 for (size_t i = 0; i < numParticleContainers; ++i) {
529 const std::string stem =
530 DataSink::diagnosticStemForContainer(base, numParticleContainers, i);
531 const std::string dest = stem + std::string(".h5");
532
533 if (opal_m->inRestartRun()) {
534 const std::string src = h5RestartSourceForContainer(
535 OpalData::getInstance()->getRestartFileName(), dest, numParticleContainers);
536 phaseSpaceSinks_m.push_back(
537 std::make_unique<H5PartWrapperForPT>(
538 dest, opal_m->getRestartStep(), src, H5_O_WRONLY));
539 } else if (isFollowupTrack_m) {
540 phaseSpaceSinks_m.push_back(
541 std::make_unique<H5PartWrapperForPT>(
542 dest, -1, stem + std::string(".h5"), H5_O_WRONLY));
543 } else {
544 phaseSpaceSinks_m.push_back(std::make_unique<H5PartWrapperForPT>(dest, H5_O_WRONLY));
545 }
546 }
547
548 const std::vector<H5PartWrapper*> sinks = borrowedPhaseSpaceSinks();
549 if (!opal_m->inRestartRun()) {
551 opal_m->setDataSink(new DataSink(sinks, false, numParticleContainers));
552 } else {
553 DataSink* raw = opal_m->getDataSink();
554 raw->changeH5Wrappers(sinks);
555 }
556 } else {
557 opal_m->setDataSink(new DataSink(sinks, true, numParticleContainers));
558 }
559
560 // DataSink lifetime is managed by OpalData; TrackRun only borrows it.
562}
563
564std::vector<H5PartWrapper*> TrackRun::borrowedPhaseSpaceSinks() const {
565 std::vector<H5PartWrapper*> sinks;
566 sinks.reserve(phaseSpaceSinks_m.size());
567 for (const auto& sink : phaseSpaceSinks_m) {
568 sinks.push_back(sink.get());
569 }
570 return sinks;
571}
572
575 // Ask the dictionary if BoundaryGeometry is allocated.
576 // If it is allocated use the allocated BoundaryGeometry
577 if (!OpalData::getInstance()->hasGlobalGeometry()) {
578 const std::string geomDescriptor =
580 BoundaryGeometry* bg = BoundaryGeometry::find(geomDescriptor)->clone(geomDescriptor);
582 }
583 }
584}
585
587 std::vector<std::vector<std::unique_ptr<GlobalProcess>>> globalProcessesLists) {
588 const auto& particleContainers = bunch_m->getParticleContainers();
589 if (particleContainers.size() != globalProcessesLists.size()) {
590 throw OpalException(
591 "TrackRun::setupGlobalProcesses",
592 "Mismatch between number of particle containers and global process lists.");
593 }
594
595 for (size_t i = 0; i < particleContainers.size(); ++i) {
596 if (!particleContainers[i]) {
597 continue;
598 }
599 particleContainers[i]->setGlobalProcesses(std::move(globalProcessesLists[i]));
600 }
601}
602
603void TrackRun::wireDaughterContainers(const std::vector<Beam*>& beams) {
604 const auto& containers = bunch_m->getParticleContainers();
605 const std::vector<std::string> beamNames = Track::block->beamNames_m;
606
607 for (std::size_t i = 0; i < beams.size(); ++i) {
608 const std::string daughterName = beams[i]->getDaughterBeamName();
609 if (daughterName.empty()) {
610 continue;
611 }
612
613 // Find the container index whose beam name matches DAUGHTERBEAM.
614 auto it = std::find(beamNames.begin(), beamNames.end(), daughterName);
615 if (it == beamNames.end()) {
616 throw OpalException(
617 "TrackRun::wireDaughterContainers",
618 "DAUGHTERBEAM=\"" + daughterName + "\" on beam \"" + beamNames[i]
619 + "\" does not match any beam in the TRACK.");
620 }
621 const std::size_t daughterIdx =
622 static_cast<std::size_t>(std::distance(beamNames.begin(), it));
623
624 // Use the physical rest mass from the Beam definition (in GeV), not the
625 // macro-particle mass from the container.
626 const double daughterMass = beams[daughterIdx]->getMass();
627 for (const auto& proc : containers[i]->getGlobalProcesses()) {
628 auto* decayProc = dynamic_cast<Decay*>(proc.get());
629 if (decayProc) {
630 requireUnitMacroWeight(*beams[daughterIdx], "daughter");
631 // A muon daughter (e.g. from pion decay) receives a per-particle
632 // polarization from the decay, so its container must have spin storage —
633 // which is enabled by setting POLARIZATION on the daughter muon BEAM.
634 const ParticleType daughterType =
635 ParticleProperties::getParticleType(beams[daughterIdx]->getParticleName());
636 if (daughterType == ParticleType::MUON && !beams[daughterIdx]->hasPolarization()) {
637 throw OpalException(
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.");
642 }
643 decayProc->setDaughterContainer(containers[daughterIdx], daughterMass);
644 *gmsg << level2 << "* Wired decay on beam \"" << beamNames[i]
645 << "\" to daughter beam \"" << daughterName << "\" (container " << daughterIdx
646 << ")." << endl;
647 }
648 }
649 }
650}
651
653 Beam* beam, const std::vector<EmissionSource*>& sources) const {
654 size_t beamAllocSize = beam->getNumAlloc();
655
656 size_t totalFromDists = 0;
657 for (EmissionSource* src : sources) {
658 auto* dist = Distribution::find(src->getDistributionName());
659 totalFromDists += dist->getNumParticles();
660 }
661
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;
670 }
671 return totalFromDists;
672 }
673
674 return beamAllocSize;
675}
676
678 const std::vector<EmissionSource*>& sources, Beam* beam,
679 emittingSamplers_t& emittingSamplers, size_t index) {
680 static IpplTimings::TimerRef samplingTime = IpplTimings::getTimer("samplingTime");
681
682 IpplTimings::startTimer(samplingTime);
683
684 // Common containers / parameters used by all samplers.
685 auto pc = bunch_m->getParticleContainer(index);
686 auto fc = bunch_m->getFieldContainer();
688 const double avrgpz = beam->getMomentum() / beam->getMass();
689
690 emittingSamplers.clear();
691 distrs_m.clear();
692
693 for (EmissionSource* src : sources) {
694 // Distribution objects are registry-owned; samplers only borrow them.
695 Distribution* opalDist = Distribution::find(src->getDistributionName());
696
697 // Ensure distribution parameters and reference momentum are up to date.
698 opalDist->setDistType();
699 opalDist->setDist();
700 opalDist->setAvrgPz(avrgpz);
701
702 // File-based distributions carry absolute momenta - the BEAM's
703 // PC/ENERGY/GAMMA would be silently ignored, so forbid the combination.
704 const bool usesFileMomentum = opalDist->getType() == DistributionType::FROMFILE
706 if (usesFileMomentum) {
707 if (beam->hasExplicitEnergy()) {
708 throw OpalException(
709 "TrackRun::setupDistributionsAndSamplers()",
710 opalDist->getTypeofDistribution() + " distribution \""
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.");
715 }
716 } else {
717 if (!beam->hasExplicitEnergy()) {
718 throw OpalException(
719 "TrackRun::setupDistributionsAndSamplers()",
720 "The energy hasn't been set. "
721 "Set either \"GAMMA\", \"ENERGY\" or \"PC\" on the BEAM command.");
722 }
723 }
724
725 distrs_m.push_back(opalDist);
726
727 // Build a sampler instance for this emission source.
728 std::shared_ptr<SamplingBase> sampler;
729 switch (opalDist->getType()) {
731 sampler = std::make_shared<Gaussian>(pc, fc, opalDist);
732 break;
734 sampler = std::make_shared<MultiVariateGaussian>(pc, fc, opalDist);
735 break;
737 sampler = std::make_shared<FlatTop>(pc, fc, opalDist);
738 break;
740 sampler = std::make_shared<OpalFlatTop>(pc, fc, opalDist);
741 break;
743 sampler = std::make_shared<FromFile>(pc, fc, opalDist);
744 break;
746 sampler = std::make_shared<EmittedFromFile>(pc, fc, opalDist);
747 break;
748 default:
749 throw OpalException("Distribution::create", "Unknown \"TYPE\" of \"DISTRIBUTION\"");
750 }
751
752 // Per-source emission offsets, start time, and emission model.
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());
757
758 // Initial polarization from BEAM (ignored if container has no spin attribute).
759 const std::vector<double> pol = beam->getPolarization();
760 sampler->setInitialPolarization({pol[0], pol[1], pol[2]});
761
762 const size_t Ndist = opalDist->getNumParticles();
763 size_t Nmutable = Ndist;
764
765 // Always call generateParticles once per source; time-independent samplers
766 // will internally early-return when t0 > 0, while FlatTop will set up its
767 // emission structures irrespective of t0.
768 sampler->generateParticles(Nmutable, nr);
769
770 const double globalShift = std::max(
771 OpalData::getInstance()->getGlobalPhaseShift(), sampler->getGlobalTimeShift());
773
774 // Time-dependent (emitted) distributions (e.g. FlatTop) and delayed
775 // one-shot injectors (t0 > 0) participate in emitParticles(t, dt)
776 // during tracking.
777 if (opalDist->emitting_m || src->getT0() > 0.0
778 || opalDist->getType() == DistributionType::EMITTEDFROMFILE) {
779 emittingSamplers.push_back(sampler);
780 *gmsg << level2 << "* Configured emitting source of type "
781 << opalDist->getTypeofDistribution() << " with NPARTDIST = " << Ndist
782 << ", t0 = " << t0 << endl;
783 }
784 }
785
786 *gmsg << level2 << "* Particle sampling / sampler setup for all emission sources done." << endl;
787 IpplTimings::stopTimer(samplingTime);
788}
789
791 const std::vector<std::vector<EmissionSource*>>& emissionSourcesLists) {
792 bool enableImageCharge = false;
793 bool enableShiftedGreens = false;
794 double zPlane = 0.0;
795 int dumpFrequency = 0;
796 int maxSteps = 0;
797 size_t numZeroFaceR0Z = 0;
798 size_t numShiftedGreens = 0;
799
800 for (const auto& sourceList : emissionSourcesLists) {
801 for (const auto* src : sourceList) {
802 if (!src) {
803 continue;
804 }
805
806 const bool srcZeroFace = src->getZeroFaceR0Z();
807 const bool srcShifted = src->getShiftedGreensFunction();
808 const int sourceDumpFrequency = src->getZeroFacePlaneDumpFrequency();
809
810 // Mutual exclusion within a single EMISSIONSOURCE.
811 if (srcZeroFace && srcShifted) {
812 throw OpalException(
813 "TrackRun::configureImageChargeFromSources",
814 "ZEROFACE_R0Z and SHIFTED_GREENS_FUNCTION are mutually exclusive on "
815 "the same EMISSIONSOURCE. Enable exactly one.");
816 }
817
818 if (!srcZeroFace && !srcShifted) {
819 if (sourceDumpFrequency > 0) {
820 throw OpalException(
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 "
825 "far from R0Z.)");
826 }
827 continue;
828 }
829
830 if (srcZeroFace) {
831 ++numZeroFaceR0Z;
832 enableImageCharge = true;
833 zPlane = src->getR0()[2];
834 dumpFrequency = sourceDumpFrequency;
835 maxSteps = src->getZerofaceMaxSteps();
836 } else {
837 // srcShifted
838 ++numShiftedGreens;
839 enableShiftedGreens = true;
840 zPlane = src->getR0()[2];
841 // Dumping is unsupported for the shifted path (see comment above).
842 if (sourceDumpFrequency > 0) {
843 throw OpalException(
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).");
848 }
849 maxSteps = src->getZerofaceMaxSteps();
850 }
851 }
852 }
853
854 if (numZeroFaceR0Z > 1) {
855 throw OpalException(
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.");
859 }
860 if (numShiftedGreens > 1) {
861 throw OpalException(
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.");
865 }
866 if (enableImageCharge && enableShiftedGreens) {
867 throw OpalException(
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.");
872 }
873
874 // SHIFTED_GREENS_FUNCTION requires the OPEN field solver. We inspect the
875 // FIELDSOLVER definition via the cached FieldSolverCmd (fs_m, set earlier
876 // in execute()) — the BinnedFieldSolver type is only forward-declared via
877 // PartBunch.h here so we cannot call bunch_m->getFieldSolver()->getStype()
878 // directly without pulling in the full template definition.
879 // The runtime guard inside FieldSolver::runShiftedOpenSolver will also throw,
880 // but catching the misconfiguration here gives the user a cleaner error.
881 if (enableShiftedGreens) {
882 const std::string solverType = fs_m ? fs_m->getType() : std::string("(unknown)");
883 if (solverType != "OPEN") {
884 throw OpalException(
885 "TrackRun::configureImageChargeFromSources",
886 "SHIFTED_GREENS_FUNCTION=true requires FIELDSOLVER TYPE=OPEN (got '"
887 + solverType + "').");
888 }
889 }
890
891 bunch_m->setImageChargeConfiguration(enableImageCharge, zPlane);
892 bunch_m->setShiftedGreensConfiguration(enableShiftedGreens, zPlane);
893 bunch_m->setZeroFacePlaneDumpFrequency(enableImageCharge ? dumpFrequency : 0);
894 // Both Dirichlet paths share the ZEROFACE_MAXSTEPS step budget.
895 const bool anyDirichletActive = enableImageCharge || enableShiftedGreens;
896 bunch_m->setZerofaceMaxSteps(anyDirichletActive ? maxSteps : 0);
897}
898
899Inform& TrackRun::print(Inform& os) const {
900 os << endl;
901 os << "* ************* T R A C K R U N *************************************************** "
902 << endl;
903 if (!isFollowupTrack_m) {
904 os << "* Selected Tracking Method == " << getRunMethodName() << ", NEW TRACK" << '\n'
905 << "* "
906 "********************************************************************************** "
907 << '\n';
908 } else {
909 os << "* Selected Tracking Method == " << getRunMethodName() << ", FOLLOWUP TRACK" << '\n'
910 << "* "
911 "********************************************************************************** "
912 << '\n';
913 }
914 os << "* Phase space dump frequency = " << Options::psDumpFreq << '\n'
915 << "* Statistics dump frequency = " << Options::statDumpFreq << " w.r.t. the time step."
916 << '\n'
917 << "* DT = " << Track::block->dT.front() << " [s]\n"
918 << "* MAXSTEPS = " << Track::block->localTimeSteps.front() << '\n';
919
920 std::string primaryBeamName;
921 if (Track::block && !Track::block->beamNames_m.empty()) {
922 primaryBeamName = Track::block->beamNames_m.front();
923 }
924
925 if (!primaryBeamName.empty()) {
926 Beam* beam = Beam::find(primaryBeamName);
927 os << "* Mass of simulation particle = " << beam->getMassPerParticle() << " [GeV/c^2]"
928 << '\n'
929 << "* Charge of simulation particle = " << beam->getChargePerParticle() << " [C]"
930 << '\n';
931 } else {
932 os << "* Mass of simulation particle = <unresolved>" << '\n'
933 << "* Charge of simulation particle = <unresolved>" << '\n';
934 }
935 os << "* ********************************************************************************** ";
936 return os;
937}
Inform * gmsg
Definition changes.cpp:7
ippl::Vector< T, Dim > Vector_t
Defines an emitted file distribution with old-OPAL time-column semantics.
const int nr
Defines the FlatTop class used for sampling emitting particles.
Defines the FromFile class used for reading particle phase space from ASCII files.
@ SIZE
Definition IndexMap.cpp:179
OPAL-like flat-top emission sampler with precomputed birth times.
Visitor-based parallel tracker with time as the independent variable.
Inform * gmsg
Definition changes.cpp:7
The base class for all OPAL actions.
Definition Action.h:29
Definition Beam.h:32
std::string getParticleName() const
Return Particle's name.
Definition Beam.cpp:326
std::string getEmissionSourceListName() const
Definition Beam.cpp:254
double getChargePerParticle() const
Charge per macro particle in C.
Definition Beam.cpp:334
bool hasPolarization() const
Definition Beam.cpp:286
double getMomentum() const
Definition Beam.cpp:324
static Beam * find(const std::string &name)
Find named BEAM.
Definition Beam.cpp:290
double getCharge() const
Return the charge number in elementary charge.
Definition Beam.cpp:320
size_t getNumAlloc() const
Return the allocation size (macroparticles) for this beam.
Definition Beam.cpp:300
double getMassPerParticle() const
Mass per macro particle in GeV/c^2.
Definition Beam.cpp:338
double getMass() const
Return Particle's rest mass in GeV.
Definition Beam.cpp:322
bool hasExplicitEnergy() const
True if PC, ENERGY, or GAMMA was explicitly provided by the user.
Definition Beam.cpp:332
std::vector< double > getPolarization() const
Definition Beam.cpp:278
std::vector< std::string > getGlobalProcessNames() const
Return the configured global process names for this beam.
Definition Beam.cpp:270
Simple bidirectional map with lookup in both directions.
Definition BiMap.h:28
void insert(const Left &left, const Right &right)
Insert or overwrite a left/right association.
Definition BiMap.h:99
right_view right
Right view accessor.
Definition BiMap.h:94
left_view left
Left view accessor.
Definition BiMap.h:92
static BoundaryGeometry * find(const std::string &name)
virtual BoundaryGeometry * clone(const std::string &name)
Return a clone.
void changeH5Wrappers(const std::vector< H5PartWrapper * > &h5wrappers)
Definition DataSink.cpp:180
static std::string diagnosticStemForContainer(const std::string &inputBasename, size_t numContainers, size_t index)
Definition DataSink.cpp:43
Abstract base class for particle decay processes.
Definition Decay.h:28
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
std::string getType()
bool hasBinningCmd() const
void registerOwnership(const AttributeHandler::OwnerType &itsClass) const
Definition Object.cpp:169
const std::string & getOpalName() const
Return object name.
Definition Object.cpp:267
std::vector< Attribute > itsAttr
The object attributes.
Definition Object.h:210
DataSink * getDataSink()
Definition OpalData.cpp:321
std::string getInputBasename()
get input file name without extension
Definition OpalData.cpp:571
int getRestartStep()
get the step where to restart
Definition OpalData.cpp:283
void setDataSink(DataSink *s)
Definition OpalData.cpp:316
bool hasDataSinkAllocated()
true if we already allocated a DataSink object
Definition OpalData.cpp:314
void setGlobalPhaseShift(double shift)
units: (sec)
Definition OpalData.cpp:365
bool hasBunchAllocated()
true if we already allocated a ParticleBunch object
Definition OpalData.cpp:306
static OpalData * getInstance()
Definition OpalData.cpp:193
void setGlobalGeometry(BoundaryGeometry *bg)
Definition OpalData.cpp:375
bool inRestartRun()
true if we do a restart run
Definition OpalData.cpp:277
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)
DataSink * ds_m
Definition TrackRun.h:132
void wireDaughterContainers(const std::vector< Beam * > &beams)
Wire daughter containers to cross-container processes (e.g. muon decay -> electron).
Definition TrackRun.cpp:603
RunMethod method_m
Definition TrackRun.h:148
void setupBoundaryGeometry()
Definition TrackRun.cpp:573
size_t computeTotalAllocationForBunch(Beam *beam, const std::vector< EmissionSource * > &sources) const
Definition TrackRun.cpp:652
virtual void execute()
Execute the command.
Definition TrackRun.cpp:258
std::vector< H5PartWrapper * > borrowedPhaseSpaceSinks() const
Definition TrackRun.cpp:564
std::vector< Distribution * > distrs_m
Distributions referenced by all emission sources (non-owning raw pointers).
Definition TrackRun.h:125
void setupDistributionsAndSamplers(const std::vector< EmissionSource * > &sources, Beam *beam, emittingSamplers_t &emittingSamplers, size_t index=0)
Definition TrackRun.cpp:677
bool isFollowupTrack_m
Definition TrackRun.h:146
void setRunMethod()
Definition TrackRun.cpp:507
virtual ~TrackRun()
Definition TrackRun.cpp:254
TrackRun()
Exemplar constructor.
Definition TrackRun.cpp:194
OpalData * opal_m
Definition TrackRun.h:136
void setupGlobalProcesses(std::vector< std::vector< std::unique_ptr< GlobalProcess > > > globalProcessesLists)
Attach prebuilt global process vector to each particle container.
Definition TrackRun.cpp:586
virtual TrackRun * clone(const std::string &name)
Make clone.
Definition TrackRun.cpp:256
static const BiMap< RunMethod, std::string > stringMethod_s
Definition TrackRun.h:188
std::vector< std::shared_ptr< SamplingBase > > emittingSamplers_t
Definition TrackRun.h:46
FieldSolverCmd * fs_m
Samplers for time-dependent (emitting) sources; tracker calls emitParticles(t, dt) on each.
Definition TrackRun.h:130
std::string getRunMethodName() const
Definition TrackRun.cpp:520
std::unique_ptr< Tracker > itsTracker_m
Definition TrackRun.h:122
std::unique_ptr< bunch_type > bunch_m
Definition TrackRun.h:144
void configureImageChargeFromSources(const std::vector< std::vector< EmissionSource * > > &emissionSourcesLists)
Configure image-charge mode from all configured emission sources.
Definition TrackRun.cpp:790
std::vector< std::unique_ptr< H5PartWrapper > > phaseSpaceSinks_m
Definition TrackRun.h:134
void initDataSink(size_t numParticleContainers)
Definition TrackRun.cpp:522
Inform & print(Inform &os) const
Definition TrackRun.cpp:899
std::vector< std::string > beamNames_m
Definition Track.h:92
double zstart
The location at which the simulation starts.
Definition Track.h:79
std::vector< unsigned long long > localTimeSteps
Maximal number of timesteps.
Definition Track.h:73
std::vector< double > zstop
The location at which the simulation stops.
Definition Track.h:82
static Track * block
The block of track data.
Definition Track.h:60
std::vector< double > dT
The initial timestep.
Definition Track.h:63
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.
Definition Options.cpp:39
int version
opal version of input file
Definition Options.cpp:99
int statDumpFreq
The frequency to dump statistical values, e.e. dump data when stepstatDumpFreq==0.
Definition Options.cpp:41
constexpr double q_e
The elementary charge in As.
Definition Physics.h:84
constexpr double e
The value of.
Definition Physics.h:49
@ BOUNDARYGEOMETRY
Definition TrackRun.cpp:182
std::map< unsigned int, std::string > changes
Definition changes.cpp:10