OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
ParallelTracker.cpp
Go to the documentation of this file.
1
21
22#include <algorithm>
23#include <array>
24#include <cfloat>
25#include <cmath>
26#include <fstream>
27#include <functional>
28#include <iomanip>
29#include <iostream>
30#include <limits>
31#include <sstream>
32#include <string>
33#include <vector>
34
35#include "Algorithms/Matrix.h"
37
41#include "BasicActions/Option.h"
42
43#include "Beamlines/Beamline.h"
47#include "Physics/Units.h"
48
50
55#include "Utilities/Options.h"
56#include "Utilities/Timer.h"
57#include "Utilities/Util.h"
59
62
63extern Inform* gmsg;
64
65// --- Constructors ---
66
70ParallelTracker::ParallelTracker(const Beamline& beamline, bool revBeam)
71 : Tracker(beamline, revBeam, false),
72 itsDataSink_m(),
73 itsOpalBeamline_m(beamline.getOrigin3D(), beamline.getInitialDirection()),
74 globalEOL_m(false),
75 zstart_m(0.0),
76 dtCurrentTrack_m(0.0),
77 repartFreq_m(0),
78 timeIntegrationTimer1_m(IpplTimings::getTimer("TIntegration1")),
79 timeIntegrationTimer2_m(IpplTimings::getTimer("TIntegration2")),
80 fieldEvaluationTimer_m(IpplTimings::getTimer("External field eval")),
81 PluginElemTimer_m(IpplTimings::getTimer("PluginElements")),
82 BinRepartTimer_m(IpplTimings::getTimer("Binaryrepart")),
83 OrbThreader_m(IpplTimings::getTimer("OrbThreader")) {}
84
89 const Beamline& beamline, PartBunch_t& bunch, DataSink* ds, bool revBeam,
90 const std::vector<unsigned long long>& maxSteps, double zstart,
91 const std::vector<double>& zstop, const std::vector<double>& dt,
92 const std::vector<std::vector<std::shared_ptr<SamplingBase>>>& emittingSamplers)
93 : Tracker(beamline, bunch, revBeam, false),
94 itsDataSink_m(ds),
95 itsOpalBeamline_m(beamline.getOrigin3D(), beamline.getInitialDirection()),
96 globalEOL_m(false),
97 zstart_m(zstart),
98 dtCurrentTrack_m(0.0),
99 repartFreq_m(0),
100 emittingSamplers_m(emittingSamplers),
101 timeIntegrationTimer1_m(IpplTimings::getTimer("TIntegration1")),
102 timeIntegrationTimer2_m(IpplTimings::getTimer("TIntegration2")),
103 fieldEvaluationTimer_m(IpplTimings::getTimer("External field eval")),
104 BinRepartTimer_m(IpplTimings::getTimer("Binaryrepart")),
105 OrbThreader_m(IpplTimings::getTimer("OrbThreader")) {
106 for (unsigned int i = 0; i < zstop.size(); ++i) {
107 stepSizes_m.push_back(dt[i], zstop[i], maxSteps[i]);
108 }
109
112}
113
118// --- Visit functions ---
119
124 if (comp.getType() == ElementType::LASER) {
125 throw LogicalError(
126 "ParallelTracker::visitComponent()",
127 "Tracking of the \"LASER\" element is not implemented yet.");
128 }
130}
131
133 throw LogicalError(
134 "ParallelTracker::visitLaser()",
135 "Tracking of the \"LASER\" element is not implemented yet.");
136}
137
143 const FlaggedBeamline* fbl = static_cast<const FlaggedBeamline*>(&bl);
144 /*
145 if (fbl->getRelativeFlag()) {
146 OpalBeamline stash(fbl->getOrigin3D(), fbl->getInitialDirection());
147 stash.swap(itsOpalBeamline_m);
148 fbl->iterate(*this, false);
149 itsOpalBeamline_m.prepareSections();
150 itsOpalBeamline_m.compute3DLattice();
151 stash.merge(itsOpalBeamline_m);
152 stash.swap(itsOpalBeamline_m);
153 } else {
154 fbl->iterate(*this, false);
155 }
156 */
157 fbl->iterate(*this, false);
158}
159
160// --- execute() ---
161
166 Inform m("ParallelTracker::execute");
167
168 // PartBunch::resetPcActive() ran in the constructor while containers were still empty
169 // (allocate-then-destroy for capacity). Initial particles are loaded later in TrackRun
170 // without refreshing these flags, so reference updates must not skip all containers.
173
174 // Initialize the Boris particle pusher
175 BorisPusher pusher;
176 m << level3 << "Initialized Boris pusher." << endl;
177
178 // Ensure the time step is positive for the setup phase
179 itsBunch_m->setdT(std::abs(itsBunch_m->getdT()));
180
181 // Populate the OpalBeamline and calculate coordinate transformations
183
184 // Select the minimal time step from the configuration
185 double minTimeStep = stepSizes_m.getMinTimeStep();
186 m << level3 << "Selected minimum time step from configuration: " << minTimeStep << endl;
187
188 // Activate all beamline elements (sets Component::online_m = true)
190 m << level3 << "Activated all beamline elements." << endl;
191
192 // Calculate the coordinate transformation from the beamline origin to the lab frame
194
195 // Per-container: lab transform and reference orbit state (each beam's PartData for P0
196 // fallback).
197 const auto& particleContainers = itsBunch_m->getParticleContainers();
198 for (size_t ci = 0; ci < particleContainers.size(); ++ci) {
199 const auto& pc = particleContainers[ci];
200 if (!pc) {
201 continue;
202 }
203 if (!pc->getReference()) {
204 throw OpalException(
205 "ParallelTracker::execute",
206 "Particle container has null PartData reference during lab-frame init.");
207 }
208 pc->setToLabTrafo(beamlineToLab);
209 pc->getRefPartR() = beamlineToLab.transformTo(Vector_t<double, 3>(0, 0, 0));
210 if (pc->getTotalNum() > 0) {
211 pc->getRefPartP() = beamlineToLab.rotateTo(pc->getMeanP());
212 } else {
213 bool useSamplerReference = false;
214 Vector_t<double, 3> samplerRefP = 0.0;
215 if (ci < emittingSamplers_m.size()) {
216 for (const auto& sampler : emittingSamplers_m[ci]) {
217 if (sampler && sampler->hasInitialReferenceMomentum()) {
218 samplerRefP = sampler->getInitialReferenceMomentum();
219 useSamplerReference = true;
220 break;
221 }
222 }
223 }
224
225 if (useSamplerReference) {
226 if (dot(samplerRefP, samplerRefP) <= 0.0) {
227 throw OpalException(
228 "ParallelTracker::execute",
229 "Sampler-provided initial reference momentum is zero.");
230 }
231 pc->getRefPartP() = beamlineToLab.rotateTo(samplerRefP);
232 } else {
233 const PartData& pref = *pc->getReference();
234 const double P0 = pref.getP() / pref.getM(); // beta*gamma from BEAM pc
235 pc->getRefPartP() = beamlineToLab.rotateTo(Vector_t<double, 3>(0.0, 0.0, P0));
236 }
237 }
238 }
239
240 m << level4
241 << "Transformed reference particle position and momentum to lab frame (all containers)."
242 << endl;
243
244 // Integrate reference orbits forward until all container path lengths reach zstart_m (when
245 // needed).
246 findStartPositions(pusher);
247
248 // Advance through the time step configurations, skipping any that end
249 // before the start position (zstart_m)
251
252 // Global spatial bounds: union over all containers
253 Vector_t<double, 3> rmin(0.0), rmax(0.0);
255 computeInitialBounds(rmin, rmax);
256 m << level4 << "Initial bunch bounds:\n"
257 << " rmin = " << rmin << "\n"
258 << " rmax = " << rmax << endl;
259 }
260
261 // Print reference particle information for all containers
263
264 // Start timing for the OrbitThreader section
265 IpplTimings::startTimer(OrbThreader_m);
266
267 // Flags to control phase space and statistics dumping for the initial step
268 bool const psDump0 = 0;
269 bool const statDump0 = 0;
270
271 // Write initial phase space and statistics
272 writePhaseSpace(0, psDump0, statDump0);
273 m << level2 << "Dump initial phase space done." << endl;
274
275 // Create an OrbitThreader object to handle orbit threading and element queries
276 OrbitThreader oth(
277 *itsBunch_m->getParticleContainer(0)->getReference(),
278 itsBunch_m->getParticleContainer(0)->getRefPartR(),
279 itsBunch_m->getParticleContainer(0)->getRefPartP(),
280 itsBunch_m->getParticleContainer(0)->get_sPos(),
281 -rmin(2), // Negative minimum z bound
282 itsBunch_m->getT(), // Current bunch time
283 minTimeStep,
284 stepSizes_m, // Step size configuration
285 itsOpalBeamline_m); // OpalBeamline object
286
287 // Execute the orbit threading (queries elements and updates state)
288 oth.execute();
289 m << level4 << "Orbit threader execution done." << endl;
290
291 // Stop timing for the OrbitThreader section
292 IpplTimings::stopTimer(OrbThreader_m);
293
294 // Get bounding box
295 BoundingBox globalBoundingBox = oth.getBoundingBox();
296
297 // Set the time view of the particle bunch
298 setTime();
299 m << level4 << "Set time view of particle bunch." << endl;
300
301 // Legacy OPAL emission starts the tracker before the RF reference time for centered
302 // flat-top pulses, so the early half is accelerated before statistics reach t = 0.
303 const double globalTimeShift = OpalData::getInstance()->getGlobalPhaseShift();
304 double time = itsBunch_m->getT() - globalTimeShift;
305 itsBunch_m->setT(time);
306 m << level4 << "Reset bunch time to " << time << "." << endl;
307
308 // Get the current global tracking step
309 unsigned long long step = itsBunch_m->getGlobalTrackStep();
310 OPALTimer::Timer myt1;
311 *gmsg << level1 << "* Track start at: " << myt1.time() << ", t= " << Util::getTimeString(time)
312 << "; "
313 << "zstart at: " << Util::getLengthString(itsBunch_m->getParticleContainer(0)->get_sPos())
314 << endl
315 << "* Initial dt = " << Util::getTimeString(itsBunch_m->getdT()) << endl
316 << "* Max integration steps = " << stepSizes_m.getMaxSteps() << ", next step = " << step
317 << endl;
318
320
321 globalEOL_m = false;
322 // wakeStatus_m = false;
323
325
326 // Before the tracker loop: bunch sanity checks.
328
329 // Handle any dump field requests
331
332 // Main tracking loop over step size configurations
333 m << level5 << ">>>>>>>>>>>>>>>>>> Starting Tracking Loop >>>>>>>>>>>>>>>>>>" << endl;
334 while (!stepSizes_m.reachedEnd()) {
335 // Set the number of steps for the current track
336 unsigned long long trackSteps = stepSizes_m.getNumSteps() + step;
338
339 // Select global dt from dtCurrentTrack_m and copy to all container dt views.
340 changeDT();
343
344 // Inner loop over the number of steps for the current configuration
345 m << level2 << "Starting track with dt = " << Util::getTimeString(dtCurrentTrack_m)
346 << ", track steps = " << step << " to " << trackSteps << "." << endl;
347 for (; step < trackSteps; ++step) {
348 if (!itsBunch_m->anyPcActive()) {
349 m << level4 << "No active particle containers; ending inner track segment." << endl;
350 break;
351 }
352
353 // Particle R and mesh are in REFERENCE frame for the whole step except inside
354 // computeSpaceChargeFields (beam frame only during computeSelfFields).
355
356 // Reset EOL flag each step: transient OutOfBounds (e.g. from invalid mesh
357 // bounds immediately after first emission) must not persist across steps.
358 globalEOL_m = false;
359
360 // Get the bunch spatial bounds across all containers.
361 Vector_t<double, 3> rmin(0.0), rmax(0.0);
363 computeInitialBounds(rmin, rmax);
364 }
365
366 // First half of the time integration
367 timeIntegration1(pusher);
368 m << level4 << "timeIntegration1 done at step " << step << "." << endl;
369 const size_t nSourceMarkedAfterPush = markBackwardParticlesAtSourcePlane();
370 if (nSourceMarkedAfterPush > 0) {
371 deleteInvalidParticles(true, m, "backward source-plane particles after first push");
372 }
374 m << level5 << "Bunch updated after timeIntegration1." << endl;
375
376 // Reset E and B fields
377 resetFields();
378 m << level4 << "E and B fields reset at step " << step << "." << endl;
379
380 // std::cout << "local num: " << itsBunch_m->getLocalNum() << std::endl;
381
382 // Space charge field computation
383 // if (itsBunch_m->getLocalNum() > 1) {
384 // Otherwise no interaction, can skip (and for some reason seg-fault...)
386 m << level4 << "Space charge field computation done at step " << step << "." << endl;
387 //}
388
389 // Emission is placed BETWEEN space-charge and external-field evaluation
390 // to match the legacy OPAL ordering (ParallelTTracker): newly emitted
391 // particles experience external fields in their creation step.
392 selectDT();
393
394 // Reset per-particle dt for all existing particles BEFORE emission, so that
395 // newly emitted particles retain their fractional dt (sampled in generateUniformDisk).
396 setTime();
397 m << level5 << "Set time view of particle bunch to dt = "
398 << Util::getTimeString(itsBunch_m->getdT()) << "." << endl;
399
400 // Emit particles from time-dependent (emitting) sources (R set in REFERENCE frame).
401 // New particles receive a fractional per-particle dt ∈ (0, dt) from the sampler.
403 m << level4 << "Emit particles from emission sources done at step " << step << "."
404 << endl;
405 const size_t nSourceMarkedAfterEmission = markBackwardParticlesAtSourcePlane();
406 if (nSourceMarkedAfterEmission > 0) {
407 deleteInvalidParticles(true, m, "backward source-plane particles after emission");
408 }
410 m << level5 << "Bunch updated after emission." << endl;
411
412 // External field computation
414 m << level4 << "External field computation done at step " << step << "." << endl;
415
416 // Thomas-BMT spin precession using the lab-frame E, B at the particle.
417 // No-op for containers that have not registered the Pol attribute.
419 m << level5 << "Spin T-BMT update done at step " << step << "." << endl;
420
421 // Second half of the time integration
422 timeIntegration2(pusher);
423 m << level4 << "timeIntegration2 done at step " << step << "." << endl;
424 const size_t nSourceMarkedAfterStep = markBackwardParticlesAtSourcePlane();
425 if (nSourceMarkedAfterStep > 0) {
426 deleteInvalidParticles(true, m, "backward source-plane particles");
427 }
429 m << level5 << "Bunch updated after timeIntegration2." << endl;
430
431 // Apply global processes (e.g. decay) and mark afftected particles for deletion
432 const size_t nProcessMarked = applyGlobalProcesses(itsBunch_m->getdT());
433 m << level5 << "Applied global processes at step " << step << "." << endl;
434 if (nProcessMarked > 0) {
435 deleteInvalidParticles(false, m, "global processes");
436 }
437
438 // Small sanity check
440 m << level5 << "WARNING: No particles in the bunch at step " << step << " on rank "
441 << ippl::Comm->rank() << ". This has no effect on the simulation." << endl;
442 }
443
444 // Update the bunch time
446 m << level5 << "Incremented bunch time to " << Util::getTimeString(itsBunch_m->getT())
447 << "." << endl;
448
449 // Update reference particle
450 if (itsBunch_m->getT() > 0.0) {
451 updateReference(pusher);
452 m << level4 << "Updated reference particle at step " << step << "." << endl;
453 }
454
455 // Mark particles outside boundpdestroy invalid for deletion for every container.
456 double sigmas = static_cast<double>(Options::boundpDestroy);
457 const auto& particleContainersStep = itsBunch_m->getParticleContainers();
458 size_t nBoundpMarked = 0;
459 for (size_t i = 0; i < particleContainersStep.size(); ++i) {
460 const auto& pc = particleContainersStep[i];
461 size_t nMarked = 0;
462 if (!pc || !itsBunch_m->isPcActive(i)) {
463 continue;
464 }
465 nMarked = pc->markParticlesOutside(sigmas);
466 nBoundpMarked += nMarked;
467 if (nMarked > 0) {
468 m << level2 << "Marked " << nMarked << " particles outside " << sigmas
469 << "-sigma boundary for deletion (container " << i << ")." << endl;
470 }
471 }
472
473 // Then immediately delete all marked particles before they can interact with the fields
474 // or be counted in diagnostics.
475 if (nBoundpMarked > 0) {
476 deleteInvalidParticles(true, m, std::to_string(sigmas) + "-sigma boundary");
477 }
478
479 for (size_t i = 0; i < particleContainersStep.size(); ++i) {
480 const auto& pc = particleContainersStep[i];
481 if (!pc || !itsBunch_m->isPcActive(i)) {
482 continue;
483 }
484 m << level4 << "Current path length (container " << i << ") is " << pc->get_sPos()
485 << "." << endl;
486 }
487
488 // if (hasEndOfLineReached(globalBoundingBox)) break;
489
490 // Dump phase space and statistics at configured intervals
491 bool const psDump =
494 bool const statDump =
497 dumpStats(step, psDump, statDump);
498
499 // Increment the global track step counter at the end of the step
501 m << level5 << "Track steps incremented." << endl;
502
503 // Check if active containers have reached the end of the current step size
504 // configuration (zstop), and if so prepare to switch to the next configuration on the
505 // next iteration.
506 for (size_t i = 0; i < particleContainers.size(); ++i) {
507 const auto& pc = particleContainers[i];
508 if (!pc || !itsBunch_m->isPcActive(i)) {
509 continue;
510 }
511 ippl::Vector<double, 3> pdivg =
512 pc->getRefPartP() / Util::getGamma(pc->getRefPartP());
513 double beta = euclidean_norm(pdivg);
514 double driftPerTimeStep = std::abs(itsBunch_m->getdT()) * Physics::c * beta;
515 m << level4 << "Calculated drift per time step (container " << i
516 << "): " << Util::getLengthString(driftPerTimeStep) << "." << endl;
517
518 if (std::abs(stepSizes_m.getZStop() - pc->get_sPos()) < 0.5 * driftPerTimeStep) {
519 m << level2
520 << "Approaching end of current step size configuration for container " << i
521 << " (zstop = " << Util::getLengthString(stepSizes_m.getZStop())
522 << ", path length = " << Util::getLengthString(pc->get_sPos()) << ")."
523 << endl;
525 }
526 }
527 if (!itsBunch_m->anyPcActive()) {
528 m << level2
529 << "All active containers reached current zstop. Preparing to switch to "
530 << "next configuration." << endl;
531 break;
532 }
533 }
534
535 // globalEOL_m is reset at the start of each step, so if it is still true
536 // here the last step genuinely ended with an out-of-bounds or reference
537 // particle hitting an element. Synchronize across ranks before deciding.
538 ippl::Comm->allreduce(globalEOL_m, 1, std::logical_and<bool>());
539
540 if (globalEOL_m) break;
541 ++stepSizes_m;
542 }
543 bool const psDump =
546 bool const statDump =
549
550 writePhaseSpace((step + 1), psDump, statDump);
551
552 *gmsg << level2 << "* Dump phase space of last step" << endl;
553
555
556 // Ensure all Kokkos operations are complete
557 Kokkos::fence();
558
559 if (itsBunch_m->hasFieldSolver()) {
560 m << level2 << "Total FieldSolver calls: " << itsBunch_m->getFieldSolver()->getCallCounter()
561 << endl;
562 }
563
564 OPALTimer::Timer myt3;
565 *gmsg << level1 << endl
566 << "* Done executing ParallelTracker at " << myt3.time() << endl
567 << endl;
568}
569
570// --- PIC integration and fields ---
571
576 Inform m("ParallelTracker::timeIntegration1");
577 IpplTimings::startTimer(timeIntegrationTimer1_m);
578 const size_t n = itsBunch_m->getNumParticleContainers();
579 for (size_t i = 0; i < n; ++i) {
580 if (!itsBunch_m->isPcActive(i)) {
581 continue;
582 }
583 auto pc = itsBunch_m->getParticleContainer(i);
584 if (!pc) {
585 continue;
586 }
587 pushParticles(pusher, *pc);
588 }
589 IpplTimings::stopTimer(timeIntegrationTimer1_m);
590 m << level4 << "Push particles done for all containers." << endl;
591}
592
597 // Legacy note: cathode transport/emission was sequenced after space charge so that
598 // the first step of newborn particles omits self-fields; multi-container emission
599 // is handled separately in execute().
600 Inform m("ParallelTracker::timeIntegration2");
601
602 IpplTimings::startTimer(timeIntegrationTimer2_m);
603 const size_t n = itsBunch_m->getNumParticleContainers();
604 for (size_t i = 0; i < n; ++i) {
605 if (!itsBunch_m->isPcActive(i)) {
606 continue;
607 }
608 auto pc = itsBunch_m->getParticleContainer(i);
609 if (!pc) {
610 continue;
611 }
612 kickParticles(pusher, *pc);
613 pushParticles(pusher, *pc);
614 pc->dt = itsBunch_m->getdT();
615 }
616 m << level4 << "Kick/push particles done for all containers." << endl;
617
618 // Put the update directly into pushParticles instead!...
619 // double newdT = itsBunch_m->getdT();
620 // itsBunch_m->getParticleContainer()->dt = newdT;
621 // m << "Update particle time step done." << endl;
622
623 IpplTimings::stopTimer(timeIntegrationTimer2_m);
624}
625
636void ParallelTracker::computeSpaceChargeFields(unsigned long long step) {
637 Inform m("ParallelTracker::computeSpaceChargeFields");
638 // Current limitation: space-charge transform/scatter/gather is applied via the primary
639 // container path only. Keep this behavior until the dedicated multi-container SC refactor.
640 if (!itsBunch_m->hasFieldSolver()) {
641 /*
642 This should not happen, so when we do not have a field solve, we can
643 throw an exception. If we have "no solver" and want to run it, we would
644 choose the null solver.
645 */
646 *gmsg << level1 << "no solver available!" << endl;
647 throw OpalException(
648 "ParallelTracker::computeSpaceChargeFields",
649 "Bunch has no field solver assigned! If you want to run without "
650 "space charge effects, please use TYPE=NONE for the field solver.");
651 }
652
654 m << level4 << "Calculate beam parameters done." << endl;
655
656 // Use mean momentum for beam-frame alignment; with 0 or 1 particle get_pmean() can be
657 // zero or negligible (e.g. rank with no particles), which would make getQuaternion throw.
658 const double pmean_tol = 1e-12;
659 Vector_t<double, 3> pmean = itsBunch_m->getParticleContainer()->getMeanP();
660 double pmean_len2 = dot(pmean, pmean);
661 if (pmean_len2 < pmean_tol * pmean_tol) {
662 pmean = itsBunch_m->getParticleContainer()->getRefPartP();
663 pmean_len2 = dot(pmean, pmean);
664 }
665 if (pmean_len2 < pmean_tol * pmean_tol) {
666 pmean = Vector_t<double, 3>(0, 0, 1);
667 }
668 Quaternion alignment = getQuaternion(pmean, Vector_t<double, 3>(0, 0, 1));
669
670 CoordinateSystemTrafo beamToReferenceCSTrafo(
671 Vector_t<double, 3>(0, 0, itsBunch_m->getParticleContainer()->get_sPos()),
672 alignment.conjugate());
673
674 CoordinateSystemTrafo referenceToBeamCSTrafo = beamToReferenceCSTrafo.inverted();
675
676 // Transform particle positions to the beam frame.
677 referenceToBeamCSTrafo.transformBunchTo(
678 itsBunch_m->getParticleContainer()->R.getView(),
679 itsBunch_m->getParticleContainer()->getLocalNum());
680 m << level4 << "Transform particle positions to beam coordinate system done." << endl;
682 m << level5 << "Bunch updated for positions in beam coordinate system." << endl;
683
684 // TODO: itsBunch_m->boundp() not implemented yet.
685 // itsBunch_m->boundp();
686
687 if (repartFreq_m > 0 && step % repartFreq_m + 1 == repartFreq_m) {
689 m << level4 << "Binary repartition done." << endl;
690 }
691
692 // itsBunch_m->setGlobalMeanR(itsBunch_m->get_centroid());
693
695 m << level3 << "Compute self fields done." << endl;
696
697 // Transform positions back to the reference frame.
698 const size_t nLocRef = itsBunch_m->getParticleContainer()->getLocalNum();
699 beamToReferenceCSTrafo.transformBunchTo(
700 itsBunch_m->getParticleContainer()->R.getView(), nLocRef);
701 m << level5 << "Transform particle positions back to reference coordinate system done." << endl;
702 // Rotate E and B back to the reference frame.
703 beamToReferenceCSTrafo.rotateBunchTo(itsBunch_m->getParticleContainer()->E.getView(), nLocRef);
704 m << level5 << "Rotate E fields back to reference coordinate system done." << endl;
705 beamToReferenceCSTrafo.rotateBunchTo(itsBunch_m->getParticleContainer()->B.getView(), nLocRef);
706 m << level5
707 << "Rotate B fields back to reference coordinate system done. ComputeSelfFields done."
708 << endl;
709 // Rebuild mesh from reference-frame R (computeSelfFields used beam-frame bunchUpdate).
711 m << level5 << "Bunch updated for positions in reference coordinate system." << endl;
712}
713
718 IpplTimings::startTimer(fieldEvaluationTimer_m);
719 Inform msg("ParallelTracker ", *gmsg);
720
721 const size_t nContainers = itsBunch_m->getNumParticleContainers();
722 for (size_t ci = 0; ci < nContainers; ++ci) {
723 if (!itsBunch_m->isPcActive(ci)) {
724 continue;
725 }
726 auto pc = itsBunch_m->getParticleContainer(ci);
727 if (!pc) {
728 continue;
729 }
730
731 // Bunch bounds for this container.
732 Vector_t<double, 3> rmin(0.0), rmax(0.0);
733 if (pc->getTotalNum() > 0) {
734 pc->computeMinMaxR();
735 rmin = pc->getMinR();
736 rmax = pc->getMaxR();
737
738 // get_bounds returns cached mesh extents (rmin_m/rmax_m). These are
739 // sentinel values (DBL_MAX / DBL_MIN) when the mesh hasn't been
740 // computed yet — e.g. immediately after the first particles are emitted
741 // before calcBeamParameters() has run. Fall back to zero so the query
742 // uses pathLength_m as the centre with zero half-width.
743 if (!std::isfinite(rmin(2)) || !std::isfinite(rmax(2))) {
744 rmin = rmax = 0.0;
745 }
746 }
747
748 // Get elements at bunch position.
750 try {
751 elements = oth.query(pc->get_sPos() + 0.5 * (rmax(2) + rmin(2)), rmax(2) - rmin(2));
752 } catch (IndexMap::OutOfBounds& e) {
753 globalEOL_m = true;
754 continue;
755 }
756
757 // Iterate over all elements.
758 IndexMap::value_t::const_iterator it = elements.begin();
759 const IndexMap::value_t::const_iterator end = elements.end();
760 for (; it != end; ++it) {
761 CoordinateSystemTrafo refToLocalCSTrafo =
763 * (itsOpalBeamline_m.getCSTrafoLab2Local((*it)) * pc->getToLabTrafo()));
764
765 CoordinateSystemTrafo localToRefCSTrafo = refToLocalCSTrafo.inverted();
766
767 (*it)->setCurrentSCoordinate(pc->get_sPos() + rmin(2));
768
769 pc->transformBunch(refToLocalCSTrafo);
770
771 // Apply element to this iteration's particle container.
772 (*it)->apply(pc);
773
774 pc->transformBunch(localToRefCSTrafo);
775 }
776 }
777
778 IpplTimings::stopTimer(fieldEvaluationTimer_m);
779}
780
785 const auto& containers = itsBunch_m->getParticleContainers();
786 for (size_t ci = 0; ci < containers.size(); ++ci) {
787 const auto& pc = containers[ci];
788 if (!pc) {
789 continue;
790 }
791 if (itsBunch_m->pcAtZStop(ci)) {
792 continue;
793 }
794
795 // Record the extent of the position array and the current local particle count
796 // before emission. If an internal resize (Kokkos::realloc) happens during
797 // emission, the extent of R will change and we can flag this as an error.
798 const size_t extentBeforeEmission = pc->R.size();
799
800 CoordinateSystemTrafo refToGun =
801 itsOpalBeamline_m.getCSTrafoLab2Local() * pc->getToLabTrafo();
802 pc->transformBunch(refToGun);
803 if (ci < emittingSamplers_m.size()) {
804 for (const auto& sampler : emittingSamplers_m[ci]) {
805 if (sampler) {
806 sampler->emitParticles(t, dt);
807 }
808 }
809 }
810 pc->transformBunch(refToGun.inverted());
811
812 pc->setM(pc->getMassPerParticle());
813 pc->setQ(pc->getChargePerParticle());
814 // itsBunch_m->updateNumTotal(); // handled internally by ippl
815 // itsBunch_m->bunchUpdate();
816
817 // Sanity guard: the total number of macroparticles in the bunch must
818 // never exceed the globally configured BEAM::NALLOC value. Overshooting
819 // this limit would trigger internal reallocations in the particle
820 // container and silently drop already-tracked particles/delete their data in the particle
821 // attributes. This is only a check for the number of local particles.
822 const size_t extentAfterEmission = pc->R.size();
823 if (extentAfterEmission != extentBeforeEmission) {
824 throw OpalException(
825 "ParallelTracker::emitFromEmissionSources",
826 "Local particle storage was resized during emission (likely due to "
827 "over-emission causing a Kokkos::realloc). This leads to loss of "
828 "previously tracked particles. Please increase the total number of "
829 "macroparticles or adjust NPARTDIST / the emission profile. If you are "
830 "using emission sources, please check the emission profile and adjust "
831 "the number of particles emitted.");
832 }
833 }
835}
836
838 const size_t nContainers = itsBunch_m->getNumParticleContainers();
839 const long long globalTrackStep = itsBunch_m->getGlobalTrackStep();
840 size_t nMarked = 0;
841
842 for (size_t ci = 0; ci < nContainers; ++ci) {
843 auto pc = itsBunch_m->getParticleContainer(ci);
844 if (!pc) {
845 continue;
846 }
847 const auto& processes = pc->getGlobalProcesses();
848 if (processes.empty()) {
849 continue;
850 }
851 for (const auto& process : processes) {
852 if (process) {
853 nMarked += process->apply(*pc, dt, globalTrackStep, ci);
854 }
855 }
856 }
857 return nMarked;
858}
859
861 bool activeOnly, Inform& m, const std::string& reason) {
862 const size_t nContainers = itsBunch_m->getNumParticleContainers();
863 size_t nDeleted = 0;
864
865 for (size_t ci = 0; ci < nContainers; ++ci) {
866 if (activeOnly && !itsBunch_m->isPcActive(ci)) {
867 continue;
868 }
869 auto pc = itsBunch_m->getParticleContainer(ci);
870 if (!pc) {
871 continue;
872 }
873
874 const size_t nContainerDeleted = pc->deleteInvalidParticles();
875 nDeleted += nContainerDeleted;
876 if (nContainerDeleted > 0) {
877 m << level2 << "Deleted " << nContainerDeleted << " particles marked by " << reason
878 << ", " << pc->getTotalNum() << " remaining (container " << ci << ")." << endl;
879 }
880 }
881
882 return nDeleted;
883}
884
888 auto* bsolver = itsBunch_m->getFieldSolver();
889 if (!bsolver) {
890 return 0;
891 }
892
893 const bool imageChargeConfigured = bsolver->isImageChargeEnabled();
894 const bool shiftedGreensConfigured = bsolver->isShiftedGreensEnabled();
895 if (!imageChargeConfigured && !shiftedGreensConfigured) {
896 return 0;
897 }
898
899 const double sourcePlaneZ = imageChargeConfigured ? bsolver->getImageChargePlaneZ()
900 : bsolver->getShiftedGreensPlaneZ();
901
902 size_type localTotalMarked = 0;
903 const size_t nContainers = itsBunch_m->getNumParticleContainers();
904 for (size_t ci = 0; ci < nContainers; ++ci) {
905 if (!itsBunch_m->isPcActive(ci)) {
906 continue;
907 }
908
909 auto pc = itsBunch_m->getParticleContainer(ci);
910 if (!pc || pc->getLocalNum() == 0) {
911 continue;
912 }
913
914 const CoordinateSystemTrafo refToSource =
915 itsOpalBeamline_m.getCSTrafoLab2Local() * pc->getToLabTrafo();
916 const matrix3x3_t rotation = refToSource.getRotationMatrix();
917 const Vector_t<double, 3> origin = refToSource.getOrigin();
918
919 auto Rview = pc->R.getView();
920 auto Pview = pc->P.getView();
921 auto invalid = pc->InvalidMask.getView();
922
923 size_type localMarked = 0;
924 const size_type nLocal = static_cast<size_type>(pc->getLocalNum());
925 Kokkos::parallel_reduce(
926 "ParallelTracker::markBackwardParticlesAtSourcePlane", nLocal,
927 KOKKOS_LAMBDA(const size_type i, size_type& count) {
928 Vector_t<double, 3> delta(0.0);
929 for (unsigned d = 0; d < 3; ++d) {
930 delta[d] = Rview(i)[d] - origin[d];
931 }
932 const Vector_t<double, 3> localR = prod_vector(rotation, delta);
933 const Vector_t<double, 3> localP = prod_vector(rotation, Pview(i));
934 const bool backwards = localR[2] < sourcePlaneZ && localP[2] < 0.0;
935 const bool newlyMarked = backwards && !invalid(i);
936 invalid(i) = invalid(i) || backwards;
937 count += newlyMarked ? 1 : 0;
938 },
939 localMarked);
940 Kokkos::fence();
941
942 localTotalMarked += localMarked;
943 }
944
945 size_type globalTotalMarked = 0;
946 ippl::Comm->allreduce(localTotalMarked, globalTotalMarked, 1, std::plus<size_type>());
947 return static_cast<size_t>(globalTotalMarked);
948}
949
954 const size_t n = itsBunch_m->getNumParticleContainers();
955 for (size_t i = 0; i < n; ++i) {
956 if (!itsBunch_m->isPcActive(i)) {
957 continue;
958 }
959 auto pc = itsBunch_m->getParticleContainer(i);
960 if (!pc) {
961 continue;
962 }
963 pc->E = 0;
964 pc->B = 0;
965 }
966}
967
975 // Per-particle dt is used so that newly emitted particles with fractional dt
976 // (sampled during emission) are pushed proportionally to their sub-timestep fraction.
978
979 auto Rview = pc.R.getView();
980 auto Pview = pc.P.getView();
981 // auto dtview = pc.dt.getView();
982
983 Kokkos::parallel_for(
984 "pushParticles", pc.getLocalNum(), KOKKOS_LAMBDA(const size_t i) {
985 // Half drift: x_{n+1/2} = x_n + (dt/2) * v; unitless form via pusher.push(..., 0).
986 // TODO: verify sign convention for half-step push.
987 Vector_t<double, 3> x = Rview(i);
988 pusher.push(
989 x, Pview(i),
990 0); // this 0 is "dt" that is not used with unitless positions!
991 Rview(i) = x;
992 });
993
995 // TODO: pc->update() changes results on single rank; keep disabled until investigated.
996 // itsBunch_m->getParticleContainer()->update();
997 Kokkos::fence();
998 ippl::Comm->barrier();
999 pc.markMomentsDirty();
1000 // itsBunch_m->bunchUpdate();
1001}
1002
1009 const BorisPusher& pusher, PartBunch_t::ParticleContainer_t& pc) {
1010 Inform m("ParallelTracker::kickParticles");
1011
1012 // auto Rview = pc.R.getView();
1013 auto Pview = pc.P.getView();
1014 auto dtview = pc.dt.getView();
1015 auto Efview = pc.E.getView();
1016 auto Bfview = pc.B.getView();
1017 m << level5 << "Got particle views for kick operation." << endl;
1018 // Mass (eV) and charge (proton charges) from this container's reference particle,
1019 // passed explicitly into BorisPusher::kick for GPU-safe kernels.
1020
1021 const PartData& ref = *pc.getReference();
1022 const double mass = ref.getM();
1023 const double charge = ref.getQ();
1024 Kokkos::parallel_for(
1025 "kickParticles", pc.getLocalNum(), KOKKOS_LAMBDA(const size_t i) {
1026 // Boris kick: Birdsall & Langdon (1985), ch. 4-4 (non-relativistic) and ch. 15-4
1027 // (relativistic); scaled P = (v/c)*gamma, mass in rest energy units.
1028 Vector_t<double, 3> p = Pview(i);
1029 // TODO: consider dropping unused R/dt arguments from kick when API allows.
1030 pusher.kick(0, p, Efview(i), Bfview(i), dtview(i), mass, charge);
1031 Pview(i) = p;
1032 });
1033 /*
1034 Wait until everyone completed the kick operation before proceeding. For now,
1035 this is just a precaution and could be removed for a small performance gain
1036 (technically, these shouldn't be necessary!).
1037 */
1038 Kokkos::fence();
1039 ippl::Comm->barrier();
1040 pc.markMomentsDirty();
1041
1042 m << level5 << "Completed parallel kick operation." << endl;
1043}
1044
1046 const size_t n = itsBunch_m->getNumParticleContainers();
1047 for (size_t i = 0; i < n; ++i) {
1048 if (!itsBunch_m->isPcActive(i)) {
1049 continue;
1050 }
1051 auto pc = itsBunch_m->getParticleContainer(i);
1052 if (!pc || !pc->hasSpin()) {
1053 continue;
1054 }
1055
1056 const PartData& ref = *pc->getReference();
1057 const double mass = ref.getM();
1058 const double charge = ref.getQ();
1059 const double anom = ref.getAnomaly();
1060
1061 auto Polview = pc->Pol.getView();
1062 auto Pview = pc->P.getView();
1063 auto Efview = pc->E.getView();
1064 auto Bfview = pc->B.getView();
1065 auto dtview = pc->dt.getView();
1066
1067 SpinTBMTPusher spinPusher;
1068 Kokkos::parallel_for(
1069 "evolveSpinTBMT", pc->getLocalNum(), KOKKOS_LAMBDA(const size_t j) {
1070 spinPusher.evolve(
1071 Polview(j), Pview(j), Efview(j), Bfview(j), dtview(j), mass, charge,
1072 anom);
1073 });
1074 Kokkos::fence();
1075 }
1076}
1077
1078// --- Helpers (beamline, dt, bounds, I/O) ---
1079
1084 // Calls ParallelTracker::visitBeamline() -> TBeamline::iterate() ->
1085 // For each element:
1086 // FlaggedElemPtr::accept() -> DefaultVisitor::visitFlaggedElmPtr() ->
1087 // ElementBase::accept() -> ParallelTracker::visit[ElemName]() ->
1088 // OpalBeamline::visit([ElemName], bunch):
1089 // This initialises the FieldList elements_m object of OpalBeamline
1090 // with clones of all the elements
1091 itsBeamline_m.accept(*this);
1092
1093 // Sorts the elements in OpalBeamline::elements_m by starting position
1095
1096 // Computes the coordinate transformations for non-straight sections
1098
1099 // Write 3D Lattice
1102}
1103
1108 double selectedDt = dtCurrentTrack_m;
1109 double emissionDt = std::numeric_limits<double>::max();
1110 bool hasEmissionDt = false;
1111 const double currentTime = itsBunch_m->getT();
1112
1113 for (const auto& samplers : emittingSamplers_m) {
1114 for (const auto& sampler : samplers) {
1115 if (!sampler || sampler->isEmissionDone(currentTime)) {
1116 continue;
1117 }
1118 const double samplerDt = sampler->getEmissionTimeStep();
1119 if (samplerDt > 0.0) {
1120 emissionDt = std::min(emissionDt, samplerDt);
1121 hasEmissionDt = true;
1122 }
1123 }
1124 }
1125
1126 if (hasEmissionDt) {
1127 selectedDt = emissionDt;
1128 }
1129 itsBunch_m->setdT(selectedDt);
1130}
1131
1136 Inform m("ParallelTracker::changeDT");
1137 selectDT();
1138 double newdT = itsBunch_m->getdT();
1139 for (const auto& pc : itsBunch_m->getParticleContainers()) {
1140 if (pc) {
1141 pc->dt = newdT;
1142 }
1143 }
1144 m << level5 << "Changed particle container time step to " << newdT << "." << endl;
1145}
1146
1151 for (size_t i = 0; i < emittingSamplers_m.size(); ++i) {
1152 for (const auto& sampler : emittingSamplers_m[i]) {
1153 if (sampler && !sampler->isEmissionDone(t)) {
1155 break;
1156 }
1157 }
1158 }
1159}
1160
1165 Inform m("ParallelTracker::doBinaryRepartition");
1166 m << level2
1167 << "Binary load-balancer repartition is disabled while the ORB path is "
1168 "not wired for the current multi-container bunch state; skipping."
1169 << endl;
1170}
1171
1176 const auto& particleContainers = itsBunch_m->getParticleContainers();
1177 bool hasNonEmpty = false;
1178 ippl::Vector<double, 3> rminLoc(0.0), rmaxLoc(0.0);
1179
1180 for (const auto& pc : particleContainers) {
1181 if (!pc || pc->getTotalNum() == 0) {
1182 continue;
1183 }
1184 pc->computeMinMaxR();
1185 const ippl::Vector<double, 3> mn = pc->getMinR();
1186 const ippl::Vector<double, 3> mx = pc->getMaxR();
1187 if (!hasNonEmpty) {
1188 rminLoc = mn;
1189 rmaxLoc = mx;
1190 hasNonEmpty = true;
1191 } else {
1192 for (int i = 0; i < 3; ++i) {
1193 rminLoc[i] = std::min(rminLoc[i], mn[i]);
1194 rmaxLoc[i] = std::max(rmaxLoc[i], mx[i]);
1195 }
1196 }
1197 }
1198
1199 if (!hasNonEmpty) {
1200 if (particleContainers.empty() || !particleContainers[0]) {
1201 throw OpalException(
1202 "ParallelTracker::computeInitialBounds",
1203 "No valid particle container for initial bounds.");
1204 }
1205 particleContainers[0]->computeMinMaxR();
1206 rminLoc = particleContainers[0]->getMinR();
1207 rmaxLoc = particleContainers[0]->getMaxR();
1208 }
1209
1210 rmax = rmaxLoc;
1211 rmin = rminLoc;
1212 ippl::Comm->allreduce(rmax, 1, std::greater<ippl::Vector<double, 3>>());
1213 ippl::Comm->allreduce(rmin, 1, std::less<ippl::Vector<double, 3>>());
1214 ippl::Comm->barrier();
1215}
1216
1221 const auto& particleContainers = itsBunch_m->getParticleContainers();
1222 for (size_t i = 0; i < particleContainers.size(); ++i) {
1223 const auto& pc = particleContainers[i];
1224 if (!pc) {
1225 m << level3 << "ParallelTrack: container " << i << " is null." << endl;
1226 continue;
1227 }
1228 if (pc->getTotalNum() > 0) {
1229 m << level3 << "ParallelTrack (container " << i
1230 << "): momentum z = " << pc->getMeanP()(2) << "\n"
1231 << "RefPartR (container " << i << ") = " << pc->getRefPartR() << "\n"
1232 << "RefPartP (container " << i << ") = " << pc->getRefPartP() << endl;
1233 } else {
1234 m << level3 << "ParallelTrack: container " << i
1235 << " empty; total particles = " << itsBunch_m->getTotalNumAllContainers() << "\n"
1236 << "RefPartR (container " << i << ") = " << pc->getRefPartR() << "\n"
1237 << "RefPartP (container " << i << ") = " << pc->getRefPartP() << endl;
1238 }
1239 }
1240}
1241
1246 Inform m("ParallelTracker::updateReference");
1249 m << level5 << "Updated reference particles." << endl;
1250}
1251
1256 const double dt = std::min(itsBunch_m->getT(), itsBunch_m->getdT());
1257 const double scaleFactor = Physics::c * dt;
1258
1259 const size_t n = itsBunch_m->getNumParticleContainers();
1260 for (size_t i = 0; i < n; ++i) {
1261 if (!itsBunch_m->isPcActive(i)) {
1262 continue;
1263 }
1264 auto pcPtr = itsBunch_m->getParticleContainer(i);
1265 if (!pcPtr) {
1266 continue;
1267 }
1268 auto& pc = *pcPtr;
1269 const PartData& refKick = *pc.getReference();
1270 Vector_t<double, 3> Ef(0.0), Bf(0.0);
1271
1272 pc.getRefPartR() /= scaleFactor;
1273 pusher.push(pc.getRefPartR(), pc.getRefPartP(), dt);
1274 pc.getRefPartR() *= scaleFactor;
1275
1277 IndexMap::value_t::const_iterator it = elements.begin();
1278 const IndexMap::value_t::const_iterator end = elements.end();
1279
1280 for (; it != end; ++it) {
1281 const CoordinateSystemTrafo& refToLocalCSTrafo =
1283
1284 Vector_t<double, 3> localR = refToLocalCSTrafo.transformTo(pc.getRefPartR());
1285 Vector_t<double, 3> localP = refToLocalCSTrafo.rotateTo(pc.getRefPartP());
1286 Vector_t<double, 3> localE(0.0), localB(0.0);
1287
1288 if ((*it)->applyToReferenceParticle(
1289 localR, localP, itsBunch_m->getT() - 0.5 * dt, localE, localB)) {
1290 *gmsg << level1 << "The reference particle hit an element" << endl;
1291 globalEOL_m = true;
1292 }
1293
1294 Ef += refToLocalCSTrafo.rotateFrom(localE);
1295 Bf += refToLocalCSTrafo.rotateFrom(localB);
1296 }
1297
1298 pusher.kick(pc.getRefPartR(), pc.getRefPartP(), Ef, Bf, dt, refKick.getM(), refKick.getQ());
1299
1300 pc.getRefPartR() /= scaleFactor;
1301 pusher.push(pc.getRefPartR(), pc.getRefPartP(), dt);
1302 pc.getRefPartR() *= scaleFactor;
1303 }
1304}
1305
1310 // Transform reference position to lab, but only rotate the momentum vector.
1311 // Momentum is a direction/axis and must not be translated.
1312 const double bunchDT = itsBunch_m->getdT();
1313 const size_t n = itsBunch_m->getNumParticleContainers();
1314 for (size_t i = 0; i < n; ++i) {
1315 if (!itsBunch_m->isPcActive(i)) {
1316 continue;
1317 }
1318 auto pc = itsBunch_m->getParticleContainer(i);
1319 if (pc) {
1320 pc->updateRefToLabCSTrafo(bunchDT);
1321 }
1322 }
1323}
1324
1329 // Primary container (index 0) drives segment advances.
1330 auto primary = itsBunch_m->getParticleContainer(0);
1331
1332 if (zstart_m <= primary->get_sPos()) {
1333 return;
1334 }
1335
1336 StepSizeConfig stepSizesCopy(stepSizes_m);
1337
1338 double t = 0.0;
1339 itsBunch_m->setT(t);
1340
1341 dtCurrentTrack_m = stepSizesCopy.getdT();
1342 selectDT();
1343
1344 const auto& containers = itsBunch_m->getParticleContainers();
1345
1346 while (true) {
1347 autophaseCavities(pusher);
1348
1349 t += itsBunch_m->getdT();
1350 itsBunch_m->setT(t);
1351
1352 std::vector<Vector_t<double, 3>> oldRs(containers.size());
1353 for (size_t i = 0; i < containers.size(); ++i) {
1354 if (containers[i]) {
1355 oldRs[i] = containers[i]->getRefPartR();
1356 }
1357 }
1358
1360
1361 for (size_t i = 0; i < containers.size(); ++i) {
1362 if (!containers[i]) {
1363 continue;
1364 }
1365 Vector_t<double, 3> dR = containers[i]->getRefPartR() - oldRs[i];
1366 containers[i]->set_sPos(containers[i]->get_sPos() + euclidean_norm(dR));
1367 }
1368
1370 primary->getRefPartP() * Physics::c / Util::getGamma(primary->getRefPartP());
1371 double speed = euclidean_norm(tmp);
1372
1373 if (primary->get_sPos() > stepSizesCopy.getZStop()) {
1374 ++stepSizesCopy;
1375
1376 if (stepSizesCopy.reachedEnd()) {
1377 --stepSizesCopy;
1378 const double zTarget = stepSizesCopy.getZStop();
1379 const double tauPrimary = (zTarget - primary->get_sPos()) / speed;
1380 itsBunch_m->setT(itsBunch_m->getT() + tauPrimary);
1381 for (const auto& pc : containers) {
1382 if (!pc) {
1383 continue;
1384 }
1386 pc->getRefPartP() * Physics::c / Util::getGamma(pc->getRefPartP());
1387 double speed_i = euclidean_norm(pv);
1388 double tau_i = (zTarget - pc->get_sPos()) / speed_i;
1389 pc->applyFractionalStep(pusher, tau_i, zstart_m);
1390 }
1391
1392 break;
1393 }
1394
1395 dtCurrentTrack_m = stepSizesCopy.getdT();
1396 selectDT();
1397 }
1398
1399 if (std::abs(primary->get_sPos() - zstart_m) <= 0.5 * itsBunch_m->getdT() * speed) {
1400 const double zTarget = zstart_m;
1401 const double tauPrimary = (zTarget - primary->get_sPos()) / speed;
1402 itsBunch_m->setT(itsBunch_m->getT() + tauPrimary);
1403 for (const auto& pc : containers) {
1404 if (!pc) {
1405 continue;
1406 }
1408 pc->getRefPartP() * Physics::c / Util::getGamma(pc->getRefPartP());
1409 double speed_i = euclidean_norm(pv);
1410 double tau_i = (zTarget - pc->get_sPos()) / speed_i;
1411 pc->applyFractionalStep(pusher, tau_i, zstart_m);
1412 }
1413
1414 break;
1415 }
1416 }
1417
1418 changeDT();
1419}
1420
1424void ParallelTracker::dumpStats(long long step, bool psDump, bool statDump) {
1425 OPALTimer::Timer myt2;
1426 const size_t totalAll = itsBunch_m->getTotalNumAllContainers();
1427 const long long globalStep = itsBunch_m->getGlobalTrackStep();
1428 const bool printStepInfo = Options::stepInfoFreq > 0 && globalStep % Options::stepInfoFreq == 0;
1429
1430 if (totalAll == 0 && printStepInfo) {
1431 *gmsg << level1 << "* " << myt2.time() << " "
1432 << "Step " << std::setw(6) << globalStep << "; "
1433 << " -- no emission yet -- "
1434 << "t= " << Util::getTimeString(itsBunch_m->getT()) << endl;
1435 }
1436
1437 bool anyLogged = false;
1438 const auto& containers = itsBunch_m->getParticleContainers();
1439 for (size_t ci = 0; ci < containers.size(); ++ci) {
1440 const auto& pc = containers[ci];
1441 if (!pc || pc->getTotalNum() == 0) {
1442 continue;
1443 }
1444 pc->updateMoments();
1445 const double sPos = pc->get_sPos();
1446 if (std::isnan(sPos) || std::isinf(sPos)) {
1447 throw OpalException(
1448 "ParallelTracker::dumpStats()",
1449 "invalid path length s for particle container " + std::to_string(ci));
1450 }
1451 if (printStepInfo) {
1452 *gmsg << level1 << "* " << myt2.time() << " "
1453 << "Step " << std::setw(6) << globalStep << " "
1454 << "container[" << ci << "] "
1455 << "at " << Util::getLengthString(sPos) << ", "
1456 << "t= " << Util::getTimeString(itsBunch_m->getT()) << ", "
1457 << "E=" << Util::getEnergyString(pc->getMeanKineticEnergy()) << endl;
1458 }
1459 anyLogged = true;
1460 }
1461
1462 if (anyLogged || statDump) {
1463 writePhaseSpace(step, psDump, statDump);
1464 }
1465}
1466
1471 /*
1472 minStepforReBin_m = Options::minStepForRebin;
1473 RealVariable* br =
1474 dynamic_cast<RealVariable*>(OpalData::getInstance()->find("MINSTEPFORREBIN"));
1475 if (br)
1476 minStepforReBin_m = static_cast<int>(br->getReal());
1477 msg << level2 << "MINSTEPFORREBIN " << minStepforReBin_m << endl;
1478 */
1479 Inform m("ParallelTracker::setOptionalVariables");
1480
1481 repartFreq_m = 0;
1482
1483 // ORB repartitioning is a per-container operation, but PartBunch currently
1484 // only passes the primary container to LoadBalancer. Until the load
1485 // balancer handles every particle container, keep REPARTFREQ accepted for
1486 // input compatibility but do not schedule the disabled repartition operation.
1487 if (ippl::Comm->size() == 1) {
1488 m << level3 << "Binary load-balancer repartition disabled on one rank." << endl;
1489 } else {
1490 long long requestedRepartFreq = static_cast<long long>(Options::repartFreq) * 100;
1491 RealVariable* rep =
1492 dynamic_cast<RealVariable*>(OpalData::getInstance()->find("REPARTFREQ"));
1493 if (rep) {
1494 requestedRepartFreq = static_cast<long long>(rep->getReal());
1495 }
1496 if (requestedRepartFreq > 0) {
1497 m << level2 << "REPARTFREQ = " << requestedRepartFreq
1498 << " requested, but binary load-balancer repartition is disabled." << endl;
1499 } else {
1500 m << level3 << "Binary load-balancer repartition disabled." << endl;
1501 }
1502 }
1503}
1504
1509 // Old IPPL used OpBitwiseAndAssign() via reduce(); new IPPL needs allreduce
1510 // so that all ranks receive the result (reduce sends only to root).
1511 // In-place allreduce avoids the aliased-buffer error of MPI_Reduce.
1512 ippl::Comm->allreduce(globalEOL_m, 1, std::logical_and<bool>());
1514 || globalBoundingBox.isOutside(itsBunch_m->getParticleContainer()->getRefPartR());
1515 return globalEOL_m;
1516}
1517
1522 double newdT = itsBunch_m->getdT();
1523 for (const auto& pc : itsBunch_m->getParticleContainers()) {
1524 if (pc) {
1525 pc->dt = newdT;
1526 }
1527 }
1528}
1529
1533void ParallelTracker::writePhaseSpace(const long long /*step*/, bool psDump, bool statDump) {
1534 Inform m("ParallelTracker::writePhaseSpace");
1535 Vector_t<double, 3> externalE, externalB;
1536
1537 Vector_t<double, 3> rmin, rmax;
1538 itsBunch_m->get_bounds(rmin, rmax);
1539 m << level5 << "Bunch bounds in REFERENCE frame: rmin = " << rmin << ", rmax = " << rmax
1540 << endl;
1541
1542 const size_t nContainers = itsBunch_m->getNumParticleContainers();
1543 std::vector<std::array<Vector_t<double, 3>, 2>> fdByContainer(nContainers);
1544
1545 if (psDump || statDump) {
1546 for (size_t i = 0; i < nContainers; ++i) {
1547 auto pc = itsBunch_m->getParticleContainer(i);
1548 if (!pc || pc->getTotalNum() == 0) {
1549 fdByContainer[i][0] = Vector_t<double, 3>(0.0);
1550 fdByContainer[i][1] = Vector_t<double, 3>(0.0);
1551 continue;
1552 }
1553 externalB = Vector_t<double, 3>(0.0);
1554 externalE = Vector_t<double, 3>(0.0);
1556 pc->getRefPartR(), pc->getRefPartP(),
1557 itsBunch_m->getT() - 0.5 * itsBunch_m->getdT(), externalE, externalB);
1558 fdByContainer[i][0] = externalB;
1559 fdByContainer[i][1] = externalE * Units::Vpm2MVpm;
1560 m << level5 << "External fields (container " << i << "): externalE = " << externalE
1561 << ", externalB = " << externalB << endl;
1562 }
1563 }
1564
1565 if (statDump) {
1566 itsDataSink_m->dumpSDDS(*itsBunch_m, fdByContainer, -1.0);
1567 *gmsg << level3 << "* Wrote beam statistics." << endl;
1568 }
1569
1570 if (psDump && itsBunch_m->getTotalNumAllContainers() > 0) {
1571 itsDataSink_m->dumpH5(*itsBunch_m, fdByContainer);
1572
1573 /*
1574 // Write fields to .h5 file.
1575 const size_t localNum = itsBunch_m->getLocalNum();
1576 double distToLastStop = stepSizes_m.getFinalZStop() -
1577 itsBunch_m->getParticleContainer()->get_sPos(); Vector_t<double, 3> beta =
1578 itsBunch_m->RefPartP_m / Util::getGamma(itsBunch_m->RefPartP_m); Vector_t<double, 3>
1579 driftPerTimeStep = itsBunch_m->getdT()
1580 * Physics::c; // \todo * itsBunch_m->toLabTrafo_m.rotateFrom(beta);
1581 bool driftToCorrectPosition =
1582 std::abs(distToLastStop) < 0.5 * euclidean_norm(driftPerTimeStep);
1583 // \todo Ppos_t stashedR;
1584 Vector_t<double, 3> stashedR;
1585 Vector_t<double, 3> stashedRefPartR;
1586
1587 if (driftToCorrectPosition) {
1588 const double tau =
1589 distToLastStop / euclidean_norm(driftPerTimeStep) * itsBunch_m->getdT();
1590
1591 if (localNum > 0) {
1592
1593 stashedR.create(localNum);
1594 stashedR = itsBunch_m->R;
1595 stashedRefPartR = itsBunch_m->RefPartR_m;
1596
1597 for (size_t i = 0; i < localNum; ++i) {
1598 itsBunch_m->R[i] +=
1599 tau
1600 * (Physics::c * itsBunch_m->P[i] / Util::getGamma(itsBunch_m->P[i])
1601 - driftPerTimeStep / itsBunch_m->getdT());
1602 }
1603
1604 }
1605
1606 driftPerTimeStep = itsBunch_m->toLabTrafo_m.rotateTo(driftPerTimeStep);
1607 itsBunch_m->RefPartR_m =
1608 itsBunch_m->RefPartR_m + tau * driftPerTimeStep / itsBunch_m->getdT();
1609 CoordinateSystemTrafo update(
1610 tau * driftPerTimeStep / itsBunch_m->getdT(), Quaternion(1.0, 0.0, 0.0, 0.0));
1611 itsBunch_m->toLabTrafo_m = itsBunch_m->toLabTrafo_m * update.inverted();
1612
1613 itsBunch_m->set_sPos(stepSizes_m.getFinalZStop());
1614
1615 itsBunch_m->calcBeamParameters();
1616 }
1617 if (!statDump && !driftToCorrectPosition)
1618 itsBunch_m->calcBeamParameters();
1619
1620 msg << *itsBunch_m << endl;
1621
1622 itsDataSink_m->dumpH5(*itsBunch_m, FDext);
1623 */
1624
1625 /*
1626
1627 if (driftToCorrectPosition) {
1628 if (localNum > 0) {
1629 itsBunch_m->R = stashedR;
1630 }
1631
1632 itsBunch_m->RefPartR_m = stashedRefPartR;
1633 itsBunch_m->getParticleContainer()->set_sPos(
1634 itsBunch_m->getParticleContainer()->get_sPos());
1635
1636 itsBunch_m->calcBeamParameters();
1637 }
1638 */
1639
1640 *gmsg << level3 << "* Wrote beam phase space." << endl;
1641 }
1642}
1643
1644// --- Autophasing (RF and traveling-wave cavities) ---
1645
1649void ParallelTracker::updateRFElement(std::string elName, double maxPhase) {
1650 Inform m("ParallelTracker::updateRFElement");
1653 cavities.insert(cavities.end(), travelingwaves.begin(), travelingwaves.end());
1654 m << level5 << "Got cavities and traveling waves." << endl;
1655
1656 for (FieldList::iterator fit = cavities.begin(); fit != cavities.end(); ++fit) {
1657 if ((*fit).getElement()->getName() == elName) {
1658 RFCavity* element = static_cast<RFCavity*>((*fit).getElement().get());
1659
1660 element->setPhasem(maxPhase);
1661 element->setAutophaseVeto();
1662
1663 m << level3 << "Restored cavity phase from the h5 file. Name: " << element->getName()
1664 << ", phase: " << maxPhase << " rad" << endl;
1665 return;
1666 }
1667 }
1668}
1669
1674
1679 typedef std::vector<MaxPhasesT>::iterator iterator_t;
1680
1681 if (OpalData::getInstance()->hasPriorTrack() || OpalData::getInstance()->inRestartRun()) {
1682 iterator_t it = OpalData::getInstance()->getFirstMaxPhases();
1683 iterator_t end = OpalData::getInstance()->getLastMaxPhases();
1684 for (; it < end; ++it) {
1685 updateRFElement((*it).first, (*it).second);
1686 }
1687 }
1688}
1689
1694 const PartData& ref = *itsBunch_m->getParticleContainer()->getReference();
1695 double t = itsBunch_m->getT();
1696 Vector_t<double, 3> nextR =
1697 itsBunch_m->getParticleContainer()->getRefPartR() / (Physics::c * itsBunch_m->getdT());
1698 pusher.push(nextR, itsBunch_m->getParticleContainer()->getRefPartP(), itsBunch_m->getdT());
1699 nextR *= Physics::c * itsBunch_m->getdT();
1700
1701 auto elementSet = itsOpalBeamline_m.getElements(nextR);
1702 for (auto element : elementSet) {
1703 if (element->getType() == ElementType::TRAVELINGWAVE) {
1704 const TravelingWave* TWelement = static_cast<const TravelingWave*>(element.get());
1705 if (!TWelement->getAutophaseVeto()) {
1706 CavityAutophaser ap(ref, element);
1709 element, itsBunch_m->getParticleContainer()->getRefPartR()),
1711 element, itsBunch_m->getParticleContainer()->getRefPartP()),
1712 t, itsBunch_m->getdT());
1713 }
1714
1715 } else if (element->getType() == ElementType::RFCAVITY) {
1716 const RFCavity* RFelement = static_cast<const RFCavity*>(element.get());
1717 if (!RFelement->getAutophaseVeto()) {
1718 CavityAutophaser ap(ref, element);
1721 element, itsBunch_m->getParticleContainer()->getRefPartR()),
1723 element, itsBunch_m->getParticleContainer()->getRefPartP()),
1724 t, itsBunch_m->getdT());
1725 }
1726 }
1727 }
1728}
1729
1730// --- Commented-out legacy / not-in-use implementations (reference only) ---
1731/*
1732void ParallelTracker::visitScalingFFAMagnet(const ScalingFFAMagnet& //bend) {
1733 *gmsg << level4 << "Adding ScalingFFAMagnet" << endl;
1734 *gmsg << level4 << "passed ScalingFFAMagnet argument not used in
1735ParallelTracker::visitScalingFFAMagnet" << endl;
1736}
1737
1738void ParallelTracker::visitRing(const Ring& ring) {
1739 *gmsg << level4 << "* ----------------------------- Ring -------------------------------------
1740*" << endl;
1741
1742 delete opalRing_m;
1743 opalRing_m = dynamic_cast<Ring*>(ring.clone());
1744 myElements.push_back(opalRing_m);
1745 opalRing_m->initialise(itsBunch_m);
1746
1747 referenceR = opalRing_m->getBeamRInit();
1748 referencePr = opalRing_m->getBeamPRInit();
1749 referenceTheta = opalRing_m->getBeamPhiInit();
1750
1751 if (referenceTheta <= -180.0 || referenceTheta > 180.0) {
1752 throw OpalException(
1753 "Error in ParallelTracker::visitRing", "PHIINIT is out of [-180, 180)!");
1754 }
1755
1756 referenceZ = 0.0;
1757 referencePz = 0.0;
1758
1759 referencePtot = itsReference.getGamma() * itsReference.getBeta();
1760 referencePt = std::sqrt(referencePtot * referencePtot - referencePr * referencePr);
1761
1762 if (referencePtot < 0.0)
1763 referencePt *= -1.0;
1764
1765 sinRefTheta_m = std::sin(referenceTheta * Units::deg2rad);
1766 cosRefTheta_m = std::cos(referenceTheta * Units::deg2rad);
1767
1768 double BcParameter[8] = {};
1769 buildupFieldList(BcParameter, ElementType::RING, opalRing_m);
1770}
1771
1772void ParallelTracker::visitVerticalFFAMagnet(const VerticalFFAMagnet& mag) {
1773 *gmsg << level4 << "Adding Vertical FFA Magnet" << endl;
1774 if (opalRing_m != nullptr)
1775 opalRing_m->appendElement(mag);
1776 else
1777 throw OpalException(
1778 "ParallelCyclotronTracker::visitVerticalFFAMagnet",
1779 "Need to define a RINGDEFINITION to use VerticalFFAMagnet element");
1780}
1781
1782void ParallelTracker::buildupFieldList(
1783 double BcParameter[], ElementType elementType, Component* elptr) {
1784 beamline_list::iterator sindex;
1785 type_pair* localpair = new type_pair();
1786 localpair->first = elementType;
1787
1788 for (int i = 0; i < 8; i++)
1789 *(((localpair->second).first) + i) = *(BcParameter + i);
1790
1791 (localpair->second).second = elptr;
1792 if (elementType == ElementType::RING) {
1793 sindex = FieldDimensions.begin();
1794 } else {
1795 sindex = FieldDimensions.end();
1796 }
1797 FieldDimensions.insert(sindex, localpair);
1798}
1799
1800bool ParallelTracker::applyPluginElements(const double dt) {
1801 IpplTimings::startTimer(PluginElemTimer_m);
1802
1803 bool flag = false;
1804 for (PluginElement* element : pluginElements_m) {
1805 bool tmp = element->check(itsBunch_m, turnnumber_m, itsBunch_m->getT(), dt);
1806 flag |= tmp;
1807 }
1808
1809 IpplTimings::stopTimer(PluginElemTimer_m);
1810 return flag;
1811}
1812*/
std::list< BeamlineFieldElement > FieldList
Inform * gmsg
Definition changes.cpp:7
ippl::Vector< T, Dim > Vector_t
elements
Definition IndexMap.cpp:168
KOKKOS_INLINE_FUNCTION T prod_vector(const matrix_t< Rows, Cols > &rotation, const T &vect)
Definition Matrix.h:106
Inform * gmsg
Definition changes.cpp:7
Visitor-based parallel tracker with time as the independent variable.
ippl::detail::size_type size_type
Quaternion getQuaternion(ippl::Vector< double, 3 > u, ippl::Vector< double, 3 > ref)
Return a unit quaternion that rotates vec onto reference.
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
Definition Vector3D.cpp:95
KOKKOS_INLINE_FUNCTION double euclidean_norm(const Vector_t< T, D > &v)
Definition VectorMath.h:15
An abstract sequence of beam line components.
Definition Beamline.h:34
bool isImageChargeEnabled() const
KOKKOS_INLINE_FUNCTION void kick(const Vector_t< double, 3 > &R, Vector_t< double, 3 > &P, const Vector_t< double, 3 > &Ef, const Vector_t< double, 3 > &Bf, const double &dt, const double &mass, const double &charge) const
Definition BorisPusher.h:45
KOKKOS_INLINE_FUNCTION void push(Vector_t< double, 3 > &R, const Vector_t< double, 3 > &P, const double &dt) const
bool isOutside(const Vector_t< double, 3 > &position) const
Definition BoundingBox.h:59
double getPhaseAtMaxEnergy(const Vector_t< double, 3 > &R, const Vector_t< double, 3 > &P, double t, double dt)
virtual ElementType getType() const
Get element type std::string.
Definition Component.cpp:49
Rigid spatial transform between a parent frame and a local frame.
ippl::Vector< double, 3 > getOrigin() const
ippl::Vector< double, 3 > rotateFrom(const ippl::Vector< double, 3 > &r) const
Rotate a vector from the local frame back into the parent frame.
matrix3x3_t getRotationMatrix() const
ippl::Vector< double, 3 > rotateTo(const ippl::Vector< double, 3 > &r) const
Rotate a vector from the parent frame into the local frame.
void rotateBunchTo(ViewType Pview, size_t nLocal) const
Apply rotateTo() to a Kokkos view of vectors such as momenta.
void transformBunchTo(ViewType Rview, size_t nLocal) const
Apply transformTo() to a Kokkos view of particle positions.
ippl::Vector< double, 3 > transformTo(const ippl::Vector< double, 3 > &r) const
Map a point from the parent frame to the local frame.
CoordinateSystemTrafo inverted() const
Return the inverse transform.
void dumpH5(PartBunch_t &beam, Vector_t< double, 3 > FDext[]) const
Write H5 phase-space data for all particle containers.
Definition DataSink.cpp:71
void storeCavityInformation()
Write cavity information from H5 file.
Definition DataSink.cpp:172
void dumpSDDS(PartBunch_t &beam, const std::vector< std::array< Vector_t< double, 3 >, 2 > > &fdextPerContainer, const double &azimuth) const
Write SDDS statistics with per-container external fields.
Definition DataSink.cpp:122
static void writeFields(const std::set< std::shared_ptr< Component > > &elements)
virtual const std::string & getName() const
Get element name.
virtual void accept(BeamlineVisitor &visitor) const =0
Apply visitor.
size_t getCallCounter()
std::set< std::shared_ptr< Component > > value_t
Definition IndexMap.h:46
Passive OPALX laser element.
Definition Laser.h:25
Logical error exception.
std::string time() const
Return time.
Definition Timer.cpp:36
FieldList getElementByType(ElementType)
Vector_t< double, 3 > rotateToLocalCS(const std::shared_ptr< Component > &comp, const Vector_t< double, 3 > &r) const
CoordinateSystemTrafo getMisalignment(const std::shared_ptr< Component > &comp) const
unsigned long getFieldAt(const unsigned int &, const Vector_t< double, 3 > &, const long &, const double &, Vector_t< double, 3 > &, Vector_t< double, 3 > &)
void prepareSections()
void compute3DLattice()
CoordinateSystemTrafo getCSTrafoLab2Local(const std::shared_ptr< Component > &comp) const
Return the nominal rigid placement transform .
Vector_t< double, 3 > transformToLocalCS(const std::shared_ptr< Component > &comp, const Vector_t< double, 3 > &r) const
std::set< std::shared_ptr< Component > > getElements(const Vector_t< double, 3 > &x)
void switchElementsOff()
void activateElements()
std::vector< MaxPhasesT >::iterator getLastMaxPhases()
Definition OpalData.cpp:329
double getGlobalPhaseShift()
units: (sec)
Definition OpalData.cpp:370
std::vector< MaxPhasesT >::iterator getFirstMaxPhases()
Definition OpalData.cpp:327
Object * find(const std::string &name)
Find entry.
Definition OpalData.cpp:477
static OpalData * getInstance()
Definition OpalData.cpp:193
IndexMap::value_t query(IndexMap::key_t::first_type step, IndexMap::key_t::second_type length)
BoundingBox getBoundingBox() const
void activateEmittingContainers(double t)
Force-activate containers whose emitting samplers have not yet finished.
void saveCavityPhases()
Persist cavity phases to the data sink.
void updateRFElement(std::string elName, double maxPhi)
Set stored RF phase on the named cavity or traveling-wave element.
double dtCurrentTrack_m
Global for the current track segment.
void printInitialContainerRefs(Inform &m) const
Log reference state for each container at track start.
void restoreCavityPhases()
Restore cavity phases from a prior track or restart.
void evolveSpinTBMT()
Thomas-BMT spin precession across all active containers that store Pol. Must be called after external...
size_t deleteInvalidParticles(bool activeOnly, Inform &m, const std::string &reason)
Delete particles marked invalid by the central per-container mask.
void prepareSections()
Accept beamline visitor, prepare sections, compute and save 3D lattice.
DataSink * itsDataSink_m
Borrowed beam statistics and phase-space output sink.
void resetFields()
Zero E and B on all active particle containers.
void doBinaryRepartition()
Trigger binary repartition for the field solver if configured.
void updateRefToLabCSTrafo()
Refresh each container's reference-to-lab transform from current state.
void kickParticles(const BorisPusher &pusher, PartBunch_t::ParticleContainer_t &pc)
Boris half-kick using E, B and per-particle dt on one container.
void autophaseCavities(const BorisPusher &pusher)
Autophase TRAVELINGWAVE and RFCAVITY elements along the reference orbit.
IpplTimings::TimerRef OrbThreader_m
void emitFromEmissionSources(double t, double dt)
Emit macroparticles from configured samplers per container.
void setTime()
Reset per-particle dt views to the current global bunch dt.
virtual void execute()
Run the main tracking loop until all step-size segments complete.
std::vector< std::vector< std::shared_ptr< SamplingBase > > > emittingSamplers_m
Per-container emitters.
void setOptionalVariables()
Load REPARTFREQ and related options from input.
unsigned long long repartFreq_m
Space-charge repartition period (steps); 0 disables it.
void updateReferenceParticles(const BorisPusher &pusher)
Advance reference positions/momenta through the beamline for one step.
void writePhaseSpace(const long long step, bool psDump, bool statDump)
Write phase space and/or statistics when flags request it.
ParallelTracker(const Beamline &bl, bool revBeam)
Construct tracker with beamline only (no bunch attached here).
void timeIntegration2(BorisPusher &pusher)
Second half: kick then push all active containers.
size_t applyGlobalProcesses(double dt)
Apply global processes and return the global number of particles marked invalid.
virtual void visitBeamline(const Beamline &)
Visit the full beamline (iterates elements into OpalBeamline). Overrides DefaultVisitor.
virtual void visitLaser(const Laser &)
Reject laser tracking until dedicated laser tracking is implemented.
void selectDT()
Set global bunch dt to dtCurrentTrack_m.
double zstart_m
Path-length start position for the track (m).
void changeDT()
Set bunch dt from StepSizeConfig and copy to all container dt views.
size_t markBackwardParticlesAtSourcePlane()
Mark particles moving backward behind an active source/cathode plane.
void updateReference(const BorisPusher &pusher)
Update reference trajectories and lab/reference coordinate transforms.
IpplTimings::TimerRef timeIntegrationTimer2_m
void computeExternalFields(OrbitThreader &oth)
Apply external fields from elements intersecting each active container.
void computeSpaceChargeFields(unsigned long long step)
Self-fields in beam frame (primary container); optional binary repartition.
bool hasEndOfLineReached(const BoundingBox &globalBoundingBox)
Whether tracking should stop at end-of-line (global reduction).
void findStartPositions(const BorisPusher &pusher)
Integrate references in time until path length reaches zstart_m.
bool globalEOL_m
End-of-line flag (e.g. orbit threader out of bounds).
void dumpStats(long long step, bool psDump, bool statDump)
Log per-container stats and trigger dumps according to dump flags.
virtual void visitComponent(const Component &)
Visit a generic component using the base tracker behavior.
IpplTimings::TimerRef timeIntegrationTimer1_m
void computeInitialBounds(Vector_t< double, 3 > &rmin, Vector_t< double, 3 > &rmax)
Union of per-container spatial bounds over MPI.
IpplTimings::TimerRef fieldEvaluationTimer_m
void pushParticles(const BorisPusher &pusher, PartBunch_t::ParticleContainer_t &pc)
Boris position push (unitless positions) on one container.
void timeIntegration1(BorisPusher &pusher)
First half of the leapfrog step: push all active containers.
StepSizeConfig stepSizes_m
virtual ~ParallelTracker()
Destructor; releases tracker resources.
OpalBeamline itsOpalBeamline_m
Cloned field elements and coordinate transforms.
Vector_t< double, Dim > R(size_t)
Do not use; throws (access positions via ParticleContainer::R).
Definition PartBunch.h:611
void setdT(double dt)
Set the global time step.
Definition PartBunch.h:639
void updateMoments()
Definition PartBunch.h:338
void performBunchSanityChecks() const
Validate BC handler, solver wiring, field pointers, and layout extents.
void computeSelfFields()
Compute the bunch self-fields (binned when available).
void incTrackSteps()
Increment globalTrackStep_m by one.
Definition PartBunch.h:676
double getdT() const
Get the global time step.
Definition PartBunch.h:642
void setT(double t)
Set the current simulation time.
Definition PartBunch.h:645
bool pcAtZStop(size_t i) const
Definition PartBunch.h:267
void calcBeamParameters()
Update moments and set rmin_m / rmax_m from the primary (first) container.
bool hasFieldSolver() const
Definition PartBunch.h:554
double getT() const
Get the current simulation time.
Definition PartBunch.h:651
void incrementT()
Advance time by one global time step.
Definition PartBunch.h:648
void setPcAtZStop(size_t i)
Deactivate container i until the next step-size segment (z-stop reached).
const PartData * getReference() const
Definition PartBunch.h:494
void bunchUpdate()
Refresh mesh from particle extents, update layouts, and recompute moments.
size_t getTotalNumAllContainers() const
Sum of getTotalNum() over all particle containers.
Definition PartBunch.h:226
bool anyPcActive() const
Definition PartBunch.h:276
void refreshPcActiveAfterEmit()
After emission: reactivate non-empty containers not marked at z-stop.
long long getGlobalTrackStep() const
Current global tracking step.
Definition PartBunch.h:673
BinnedFieldSolver_t * getFieldSolver()
Non-const pointer to the concrete BinnedFieldSolver.
bool isPcActive(size_t i) const
Definition PartBunch.h:256
void get_bounds(Vector_t< double, Dim > &rmin, Vector_t< double, Dim > &rmax)
Copy cached bunch extent (rmin_m, rmax_m) from calcBeamParameters.
Definition PartBunch.h:633
void resetPcActive()
At segment start: active if container is non-empty; inactive if empty.
void setPcActive(size_t i)
Force container i active (e.g. for containers with pending emission).
Definition PartBunch.h:259
Particle reference data.
Definition PartData.h:37
double getAnomaly() const
Definition PartData.h:92
double getP() const
The constant reference momentum per particle.
Definition PartData.h:111
KOKKOS_INLINE_FUNCTION double getM() const
The constant mass per particle.
Definition PartData.h:109
KOKKOS_INLINE_FUNCTION double getQ() const
The constant charge per particle.
Definition PartData.h:107
Container for all per-particle (and per-simulation) fields tracked during OPALX tracking.
ippl::ParticleAttrib< double > dt
timestep in [s]
Base::particle_position_type B
electric field for gun simulation with bins
void switchOffUnitlessPositions()
Restore physical positions from unitless form using each particle's dt[i].
const PartData * getReference() const
Get reference particle data.
Base::particle_position_type E
electric field at particle position
Base::particle_position_type P
particle momenta [\beta\gamma]
void switchToUnitlessPositions()
Transform positions to unitless coordinates using each particle's dt[i].
Quaternion storage and rotation algebra used by OPALX geometry code.
Quaternion conjugate() const
Return the quaternion conjugate .
Interface for standing wave cavities.
Definition RFCavity.h:34
virtual bool getAutophaseVeto() const
Definition RFCavity.h:361
virtual void setPhasem(double phase)
Definition RFCavity.h:343
virtual void setAutophaseVeto(bool veto=true)
Definition RFCavity.h:359
virtual double getReal() const
Return value.
KOKKOS_INLINE_FUNCTION void evolve(SpinVec &Pol, const Vector_t< double, 3 > &P, const Vector_t< double, 3 > &Ef, const Vector_t< double, 3 > &Bf, const double &dt, const double &mass, const double &charge, const double &anom) const
double getMinTimeStep() const
StepSizeConfig & advanceToPos(double spos)
double getdT() const
void printDirect(Inform &out) const
double getZStop() const
void push_back(double dt, double zstop, unsigned long numSteps)
bool reachedEnd() const
unsigned long getNumSteps() const
unsigned long long getMaxSteps() const
virtual void iterate(BeamlineVisitor &, bool r2l) const
Apply visitor to all elements of the line.
Definition TBeamline.h:193
const Beamline & itsBeamline_m
Definition Tracker.h:115
virtual void visitComponent(const Component &)
Store the bunch.
Definition Tracker.cpp:101
PartBunch_t * itsBunch_m
The bunch of particles to be tracked. Borrowed; lifetime is managed by TrackRun.
Definition Tracker.h:119
Interface for traveling wave cavities.
int stepInfoFreq
The frequency to print per-step tracking status lines; 0 disables them.
Definition Options.cpp:43
int psDumpFreq
The frequency to dump the phase space, i.e.dump data when steppsDumpFreq==0.
Definition Options.cpp:39
int repartFreq
The frequency to do particles repartition for better load balance between nodes.
Definition Options.cpp:51
double boundpDestroy
Governs how many sigmas away particles are deleted.
Definition Options.cpp:89
int statDumpFreq
The frequency to dump statistical values, e.e. dump data when stepstatDumpFreq==0.
Definition Options.cpp:41
constexpr double c
The velocity of light in m/s.
Definition Physics.h:60
constexpr double Vpm2MVpm
Definition Units.h:125
double getGamma(ippl::Vector< double, 3 > p)
Definition Util.h:44
std::string getEnergyString(double energyInMeV, unsigned int precision=3)
Definition Util.h:135
std::string getTimeString(double time, unsigned int precision=3)
Definition Util.h:65
std::string getLengthString(double spos, unsigned int precision=3)
Definition Util.h:88