OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
PartBunch.cpp
Go to the documentation of this file.
1
7#include <algorithm>
8#include "Algorithms/Matrix.h"
10#include "Particle/ParticleAttrib.h"
12#include "Structure/Beam.h"
13#include "Structure/DataSink.h"
14#include "Utilities/Util.h"
15
16#undef doDEBUG
17
21template <typename T, unsigned Dim>
23 std::vector<double> qi, std::vector<double> mi, const std::vector<Beam*>& beams,
24 std::vector<size_t> totalParticlesPerBeam, double lbt, std::string integration_method,
25 FieldSolverCmd* OPALFieldSolver, DataSink* dataSink)
26 : ippl::PicManager<
28 dt_m(0),
29 it_m(0),
30 integration_method_m(integration_method),
31 solver_m(""),
32 lbt_m(lbt),
33 isFirstRepartition_m(true),
34 OPALFieldSolver_m(OPALFieldSolver),
35 dataSink_m(dataSink),
36 globalTrackStep_m(0),
37 rmsDensity_m(0.0) {
38 qi_m = qi;
39 mi_m = mi;
40 bunchState_m = std::make_shared<BunchStateHandler>();
41
42 Inform m("PartBunch::PartBunch");
43 m << level4 << "PartBunch Constructor" << endl;
44
45 const size_t num_containers = beams.size();
46 if (num_containers == 0) {
47 throw OpalException("PartBunch::PartBunch", "num_containers must be > 0.");
48 }
49 if (OPALFieldSolver_m == nullptr) {
50 throw OpalException("PartBunch::PartBunch", "OPALFieldSolver must not be null.");
51 }
52 if (dataSink_m == nullptr) {
53 throw OpalException("PartBunch::PartBunch", "dataSink must not be null.");
54 }
55 if (qi.size() != num_containers) {
56 throw OpalException("PartBunch::PartBunch", "qi size must match num_containers.");
57 }
58 if (mi.size() != num_containers) {
59 throw OpalException("PartBunch::PartBunch", "mi size must match num_containers.");
60 }
61 if (totalParticlesPerBeam.size() != num_containers) {
62 throw OpalException(
63 "PartBunch::PartBunch", "totalParticlesPerBeam size must match num_containers.");
64 }
65 for (size_t i = 0; i < num_containers; ++i) {
66 if (beams[i] == nullptr) {
67 throw OpalException("PartBunch::PartBunch", "beams must not contain null pointers.");
68 }
69 }
70
71 // get the needed information from OPAL FieldSolver command
72
75 nrZBase_m = nr_m[Dim - 1];
76
77 const Vector_t<bool, 3> domainDecomposition = OPALFieldSolver_m->getDomDec();
78
79 for (unsigned i = 0; i < Dim; i++) {
80 this->domain_m[i] = ippl::Index(nr_m[i]);
81 this->decomp_m[i] = domainDecomposition[i];
82 }
83
84 this->setBCHandler(std::make_shared<BCHandler_t>(OPALFieldSolver_m->constructBCHandler()));
85
86 // TODO: support mixed periodic/open per axis; currently all periodic or all open.
87 bool isAllPeriodic = this->getBCHandler()->isAll(BCHandler_t::PERIODIC);
88 m << level5 << "* FieldContainer set to isAllPeriodic = " << isAllPeriodic << endl;
89
90 // set stuff for pre_run i.e. warmup
91 // this will be reset when the correct computational
92 // domain is set
93
94 Vector_t<double, Dim> length(6.0);
95 this->hr_m = length / this->nr_m;
96 this->origin_m = -3.0;
97 this->dt_m = 0.5 / this->nr_m[2];
98
100 rmax_m = origin_m + length;
101
102 this->setFieldContainer(
103 std::make_shared<FieldContainer_t>(
104 hr_m, rmin_m, rmax_m, decomp_m, domain_m, origin_m, isAllPeriodic));
105
106 this->setParticleContainer(
107 std::make_shared<ParticleContainer_t>(
108 this->fcontainer_m->getMesh(), this->fcontainer_m->getFL(),
109 beams[0]->hasPolarization()));
110 this->pcontainer_m->setBunchStateHandler(bunchState_m);
113 for (size_t i = 1; i < num_containers; ++i) {
114 auto pc = std::make_shared<ParticleContainer_t>(
115 this->fcontainer_m->getMesh(), this->fcontainer_m->getFL(),
116 beams[i]->hasPolarization());
117 pc->setBunchStateHandler(bunchState_m);
118 this->addParticleContainer(pc);
119 }
120 const auto& containers = this->getParticleContainers();
121 particleNames_m.resize(containers.size());
122 for (size_t i = 0; i < containers.size(); ++i) {
123 containers[i]->setQ(qi[i]);
124 containers[i]->setM(mi[i]);
125 containers[i]->setReference(&beams[i]->getReference());
126 particleNames_m[i] = beams[i]->getParticleName();
127 containers[i]->Sp = static_cast<short>(
129 }
130
131 // Pre-allocate per-rank capacity without bumping localNum_m. Subsequent emission /
132 // distribution loaders call createParticles() to fill the buffer non-destructively.
133 const double nRanks = static_cast<double>(ippl::Comm->size());
134 for (size_t i = 0; i < num_containers; ++i) {
135 const size_t maxLocalNum =
136 static_cast<size_t>(totalParticlesPerBeam[i] / nRanks + 2 * nRanks + 1);
137 containers[i]->allocateParticles(maxLocalNum);
138 *gmsg << level3 << "* Container " << i << ": capacity for " << maxLocalNum
139 << " particles allocated." << endl;
140 }
141
142 setSolver();
143
144 // Build temporary accumulation fields after solver/field initialization so they
145 // match the current mesh/layout and backing storage configuration.
146 this->setTempEField(std::make_shared<VField_t<T, Dim>>());
147 this->getTempEField()->initialize(this->fcontainer_m->getMesh(), this->fcontainer_m->getFL());
148 this->setTempBField(std::make_shared<VField_t<T, Dim>>());
149 this->getTempBField()->initialize(this->fcontainer_m->getMesh(), this->fcontainer_m->getFL());
150 // -----------------------------------------------
151
152 pre_run();
153 this->setT(0.0);
154
155 globalPartPerNode_m = std::make_unique<size_t[]>(ippl::Comm->size());
156
158
159 m << level5 << "* PartBunch constructor done." << endl;
160}
161
165template <typename T, unsigned Dim>
167 const auto& containers = this->getParticleContainers();
168 const size_t n = containers.size();
169 pcActive_m.resize(n);
170 pcAtZStop_m.resize(n);
171 for (size_t i = 0; i < n; ++i) {
172 pcAtZStop_m[i] = false;
173 const auto& pc = containers[i];
174 if (!pc || pc->getTotalNum() == 0) {
175 pcActive_m[i] = false;
176 } else {
177 pcActive_m[i] = true;
178 }
179 }
180}
181
185template <typename T, unsigned Dim>
187 if (i >= pcActive_m.size()) {
188 return;
189 }
190 pcActive_m[i] = false;
191 pcAtZStop_m[i] = true;
192}
193
197template <typename T, unsigned Dim>
199 const auto& containers = this->getParticleContainers();
200 const size_t n = containers.size();
201 if (pcActive_m.size() != n) {
202 return;
203 }
204 for (size_t i = 0; i < n; ++i) {
205 if (pcAtZStop_m[i]) {
206 continue;
207 }
208 const auto& pc = containers[i];
209 if (pc && pc->getTotalNum() > 0) {
210 pcActive_m[i] = true;
211 }
212 }
213}
214
218template <typename T, unsigned Dim>
220 Inform m("PartBunch::do_binaryRepart");
221 m << level2
222 << "Binary load-balancer repartition is disabled while the ORB path is "
223 "not wired for the current multi-container bunch state; skipping."
224 << endl;
225}
226
230template <typename T, unsigned Dim>
232 std::fill_n(globalPartPerNode_m.get(), ippl::Comm->size(), 0); // Fill the array with zeros
233 globalPartPerNode_m[ippl::Comm->rank()] = this->getParticleContainer()->getLocalNum();
234 ippl::Comm->allreduce(globalPartPerNode_m.get(), ippl::Comm->size(), std::plus<size_t>());
235}
236
240template <typename T, unsigned Dim>
242 Inform m("PartBunch::setSolver");
243 m << level2 << "Initializing solver: " << OPALFieldSolver_m->getType() << endl;
244 if (this->solver_m != "")
245 m << level1 << "Warning solver already initiated but overwrite ..." << endl;
246
247 this->solver_m = OPALFieldSolver_m->getType();
248
249 this->fcontainer_m->initializeFields(this->solver_m);
250
251 // Needs to happen before setting the field solver, since the field solver needs the bins.
252 setBins();
253
254 BinningCmd* binningCmd = OPALFieldSolver_m->getBinningCmd();
255 auto binnedSolver = std::make_shared<BinnedFieldSolver<T, Dim>>(
256 this->solver_m, &this->fcontainer_m->getRho(), &this->fcontainer_m->getE(),
257 &this->fcontainer_m->getPhi(), this->getBCHandler(),
258 binningCmd ? binningCmd->getTablePrintFrequency() : 0,
259 binningCmd ? binningCmd->getAdaptiveBinning() : true);
260 this->setFieldSolver(binnedSolver);
261 m << level4 << "Binned field solver set (binned or legacy at runtime)." << endl;
262
263 this->fsolver_m->initSolver();
264 m << level4 << "Field solver initialized." << endl;
265
266 // TODO: allow constructing a load balancer when no field solver is present.
267 this->setLoadBalancer(
268 std::make_shared<LoadBalancer_t>(
269 this->lbt_m, this->fcontainer_m, this->pcontainer_m, this->fsolver_m));
270 m << level3 << "Solver and Load Balancer set." << endl;
271}
272
276template <typename T, unsigned Dim>
278 return static_cast<BinnedFieldSolver_t*>(this->fsolver_m.get());
279}
280
281template <typename T, unsigned Dim>
283 return static_cast<const BinnedFieldSolver_t*>(this->fsolver_m.get());
284}
285
286template <typename T, unsigned Dim>
288 return this->getFieldSolver()->getStype();
289}
290
291template <typename T, unsigned Dim>
293 Inform m("PartBunch::setBins");
294
295 BinningCmd* binningCmd = OPALFieldSolver_m->getBinningCmd();
296
297 if (!OPALFieldSolver_m->hasBinningCmd()) {
298 m << level2 << "Solver " << OPALFieldSolver_m->getOpalName()
299 << " has no binning command attached, not using binning." << endl;
300 return;
301 }
302
303 m << level4 << "Using binning command: " << binningCmd->getOpalName() << endl;
304
305 switch (binningCmd->getParameterType()) {
307 this->setBins(
308 std::make_shared<
310 *this->getParticleContainer(), CoordinateSelector_t(2),
311 binningCmd->getMaxBins(), binningCmd->getBinningAlpha(),
312 binningCmd->getBinningBeta(), binningCmd->getDesiredWidth(),
313 binningCmd->getOpalName()));
314 break;
316 this->setBins(
317 std::make_shared<
319 *this->getParticleContainer(), GammaSelector_t(2),
320 binningCmd->getMaxBins(), binningCmd->getBinningAlpha(),
321 binningCmd->getBinningBeta(), binningCmd->getDesiredWidth(),
322 binningCmd->getOpalName()));
323 break;
324 default:
325 throw OpalException(
326 "PartBunch::setBins",
327 "Binning parameter " + binningCmd->getParameter()
328 + " not supported yet! Only VELOCITYZ and GAMMAZ.");
329 }
330 m << level3 << "Bins set." << endl;
331 this->getBins()->debug();
332}
333
337template <typename T, unsigned Dim>
339 Inform m("PartBunch::calcBeamParameters");
340 std::shared_ptr<ParticleContainer_t> pc = this->pcontainer_m;
341
342 using view_type = ippl::ParticleAttrib<Vector_t<double, 3>>::view_type;
343 view_type Rview = pc->R.getView();
344 view_type Pview = pc->P.getView();
345 this->getParticleContainer()->updateMoments();
346 m << level5 << "Moments updated." << endl;
347
351
352 using MomentsVec = ippl::Vector<double, 2 * Dim>;
353 using MomentsMat = ippl::Vector<MomentsVec, 2 * Dim>;
354
355 MomentsVec loc_centroid(0.0);
356 MomentsMat loc_moment(MomentsVec(0.0));
357
358 MomentsVec centroid(0.0);
359 MomentsMat moment(MomentsVec(0.0));
360
361 for (unsigned i = 0; i < 2 * Dim; ++i) {
362 const size_t nLocal = pc->getLocalNum();
363 Kokkos::parallel_reduce(
364 "calc moments of particle distr.", nLocal,
365 KOKKOS_LAMBDA(
366 const size_t k, double& cent, double& mom0, double& mom1, double& mom2,
367 double& mom3, double& mom4, double& mom5) {
368 double part[2 * Dim];
369 part[0] = Rview(k)[0];
370 part[1] = Pview(k)[0];
371 part[2] = Rview(k)[1];
372 part[3] = Pview(k)[1];
373 part[4] = Rview(k)[2];
374 part[5] = Pview(k)[2];
375
376 cent += part[i];
377 mom0 += part[i] * part[0];
378 mom1 += part[i] * part[1];
379 mom2 += part[i] * part[2];
380 mom3 += part[i] * part[3];
381 mom4 += part[i] * part[4];
382 mom5 += part[i] * part[5];
383 },
384 Kokkos::Sum<T>(loc_centroid[i]), Kokkos::Sum<T>(loc_moment[i][0]),
385 Kokkos::Sum<T>(loc_moment[i][1]), Kokkos::Sum<T>(loc_moment[i][2]),
386 Kokkos::Sum<T>(loc_moment[i][3]), Kokkos::Sum<T>(loc_moment[i][4]),
387 Kokkos::Sum<T>(loc_moment[i][5]));
388 Kokkos::fence();
389 }
390 m << level5 << "Local moments calculated." << endl;
391
392 moment = loc_moment;
393 centroid = loc_centroid;
394 ippl::Comm->allreduce(moment, 1, std::plus<MomentsMat>());
395 ippl::Comm->allreduce(centroid, 1, std::plus<MomentsVec>());
396 ippl::Comm->barrier();
397 m << level5 << "Global moments calculated." << endl;
398
399 ippl::Vector<double, Dim> rmax_loc(0.0);
400 ippl::Vector<double, Dim> rmin_loc(0.0);
401 ippl::Vector<double, Dim> rmax(0.0);
402 ippl::Vector<double, Dim> rmin(0.0);
403
404 // TODO: fuse min/max reductions with ippl::Vector reductions.
405 for (unsigned d = 0; d < Dim; ++d) {
406 Kokkos::parallel_reduce(
407 "rel max", pc->getLocalNum(),
408 KOKKOS_LAMBDA(const int i, double& mm) {
409 double tmp_vel = Rview(i)[d];
410 mm = tmp_vel > mm ? tmp_vel : mm;
411 },
412 Kokkos::Max<T>(rmax_loc[d]));
413
414 Kokkos::parallel_reduce(
415 "rel min", pc->getLocalNum(),
416 KOKKOS_LAMBDA(const int i, double& mm) {
417 double tmp_vel = Rview(i)[d];
418 mm = tmp_vel < mm ? tmp_vel : mm;
419 },
420 Kokkos::Min<T>(rmin_loc[d]));
421 }
422 m << level5 << "Local min/max calculated." << endl;
423 Kokkos::fence();
424 rmax = rmax_loc;
425 rmin = rmin_loc;
426 ippl::Comm->allreduce(rmax, 1, std::greater<ippl::Vector<double, Dim>>());
427 ippl::Comm->allreduce(rmin, 1, std::less<ippl::Vector<double, Dim>>());
428 ippl::Comm->barrier();
429 m << level5 << "Global min/max calculated." << endl;
430
431 rmax_m = rmax;
432 rmin_m = rmin;
433}
434
438template <typename T, unsigned Dim>
440 Inform m("PartBunch::pre_run");
441 m << level2 << "Starting pre_run..." << endl;
442 auto rhoView = this->fcontainer_m->getRho().getView();
443 Kokkos::deep_copy(rhoView, 0.0);
444 m << level4 << "Rho initialized to zero." << endl;
445
446 /*
447 * Skip full field dumps during warmup: runSolver(true) is implemented on the
448 * concrete solver type, not on the IPPL base class.
449 */
450 this->getFieldSolver()->runSolver(true);
451 m << level4 << "Field solver ran during pre_run." << endl;
452 this->getFieldSolver()->resetCallCounter();
453 m << level4 << "Call counter reset. pre_run done." << endl;
454}
455
459template <typename T, unsigned Dim>
460Inform& PartBunch<T, Dim>::print(Inform& os) {
461 // if (pc->getLocalNum() != 0) { // to suppress Nans
462 Inform::FmtFlags_t ff = os.flags();
463
464 const auto& containers = this->getParticleContainers();
465 for (size_t ci = 0; ci < containers.size(); ++ci) {
466 const auto& pc = containers[ci];
467 if (!pc) {
468 os << level1 << "Skipping null particle container: " << ci << endl;
469 continue;
470 }
471
472 const double ek = pc->getMeanKineticEnergy();
473 const double dek = pc->getStdKineticEnergy();
474
475 // ParticleContainer tracks charge/mass storage mode for QM attributes.
476 std::string qmStorageModeStr = "SINGLE";
477 const auto qmMode = pc->getQMStorageMode();
479 qmStorageModeStr = "ATTRIBUTES";
480 }
481
482 os << level1 << std::scientific << "\n"
483 << "* ************** B U N C H "
484 "********************************************************* \n"
485 << "* CONTAINER = " << ci << "\n"
486 << "* PARTICLES = " << pc->getTotalNum() << "\n"
487 << "* CHARGE = " << pc->getTotalCharge() << " (Cb) \n"
488 << "* QM STORAGE MODE = " << qmStorageModeStr << "\n"
489 << "* <EKIN> = " << Util::getEnergyString(ek) << "\n"
490 << "* <dEKIN> = " << Util::getEnergyString(dek) << "\n"
491 << "* INTEGRATOR = " << integration_method_m << "\n"
492 << "* MIN R (origin) = " << Util::getLengthString(pc->getMinR(), 5) << "\n"
493 << "* MAX R (max ext) = " << Util::getLengthString(pc->getMaxR(), 5) << "\n"
494 << "* RMS R = " << Util::getLengthString(pc->getRmsR(), 5) << "\n"
495 << "* RMS P = " << pc->getRmsP() << " [beta gamma]\n"
496 << "* Mean R = " << pc->getMeanR() << " [m]\n"
497 << "* Mean P = " << pc->getMeanP() << " [beta gamma]\n"
498 << "* MESH SPACING = "
499 << Util::getLengthString(this->fcontainer_m->getMesh().getMeshSpacing(), 5) << "\n"
500 << "* COMPDOM INCR = " << this->OPALFieldSolver_m->getBoxIncr() << " (%) \n"
501 << "* FIELD LAYOUT = " << this->fcontainer_m->getFL() << "\n"
502 << "* Centroid : \n* ";
503 for (unsigned int i = 0; i < 2 * Dim; i++) {
504 os << level1 << pc->getCentroid()[i] << " ";
505 }
506 os << level1 << endl << "* Cov Matrix : \n* ";
507 for (unsigned int i = 0; i < 2 * Dim; i++) {
508 for (unsigned int j = 0; j < 2 * Dim; j++) {
509 os << level1 << pc->getCovMatrix()(i, j) << " ";
510 }
511 os << level1 << "\n* ";
512 }
513 os << level1
514 << "* "
515 "********************************************************************************"
516 "** \n"
517 << endl;
518 }
519
520 os.flags(ff);
521 return os;
522}
523
527template <typename T, unsigned Dim>
529 if (nr_m[Dim - 1] == nrZ) {
530 return;
531 }
532
533 Inform m("PartBunch::reinitializeGridZ");
534 m << level3 << "Resizing z grid: " << nr_m[Dim - 1] << " -> " << nrZ << " cells." << endl;
535
536 nr_m[Dim - 1] = nrZ;
537 domain_m[Dim - 1] = ippl::Index(nrZ);
538
539 const bool isAllPeriodic = this->getBCHandler()->isAll(BCHandler_t::PERIODIC);
540 const auto decomp = this->fcontainer_m->getDecomp();
541
542 // Rebuild the field layout with the new z extent.
543 this->fcontainer_m->getFL().initialize(domain_m, decomp, isAllPeriodic);
544
545 // BareField::initialize() is a no-op on already-initialized fields, so use
546 // updateLayout() to resize the underlying Kokkos views to the new domain.
547 auto& fl = this->fcontainer_m->getFL();
548 this->fcontainer_m->getRho().updateLayout(fl);
549 this->fcontainer_m->getE().updateLayout(fl);
550 if (solver_m == "CG") {
551 this->fcontainer_m->getPhi().updateLayout(fl);
552 }
553 this->getTempEField()->updateLayout(fl);
554 this->getTempBField()->updateLayout(fl);
555
556 m << level3 << "Grid z reinit complete (nrZ=" << nrZ << ")." << endl;
557}
558
559template <typename T, unsigned Dim>
561 Inform m("PartBunch::bunchUpdate");
562 m << level4 << "Updating bunch and doing repartitioning if needed." << endl;
563
564 // Double the longitudinal grid resolution while image charges are active.
565 // computeBoundsForFieldSolve already extends the z domain to include mirrored
566 // particles, so without this the z cell size would be twice as large as normal.
567 const BinnedFieldSolver_t* bsolver = this->getFieldSolver();
568 const bool imageActive =
569 bsolver && bsolver->isImageChargeActiveForStep(this->getGlobalTrackStep());
570 reinitializeGridZ(imageActive ? nrZBase_m * 2 : nrZBase_m);
571
572 Vector_t<double, Dim> lower(0.0);
573 Vector_t<double, Dim> upper(0.0);
574 computeBoundsForFieldSolve(lower, upper);
575 applyGridUpdate(lower, upper);
576
577 isFirstRepartition_m = true;
578 m << level5 << "Bunch grid update done without load-balancer repartitioning." << endl;
579
580 // Always request moments update; DistributionMoments decides whether it
581 // actually needs to recompute based on the dirty flag.
582 const auto& containers = this->getParticleContainers();
583 for (size_t i = 0; i < containers.size(); ++i) {
584 if (!containers[i]) {
585 continue;
586 }
587 this->getParticleContainer(i)->updateMoments();
588 }
589 m << level5 << "Moments updated for all particle containers." << endl;
590}
591
592template <typename T, unsigned Dim>
595 Inform m("PartBunch::computeBoundsForFieldSolve");
596
597 const auto& containers = this->getParticleContainers();
598
599 bool hasNonEmptyContainer = false;
600 for (const auto& pc : containers) {
601 if (!pc || pc->getTotalNum() == 0) {
602 continue;
603 }
604
605 pc->computeMinMaxR();
606 const ippl::Vector<double, 3> minR = pc->getMinR();
607 const ippl::Vector<double, 3> maxR = pc->getMaxR();
608
609 if (!hasNonEmptyContainer) {
610 lower = minR;
611 upper = maxR;
612 hasNonEmptyContainer = true;
613 } else {
614 for (int i = 0; i < 3; ++i) {
615 lower[i] = std::min(lower[i], minR[i]);
616 upper[i] = std::max(upper[i], maxR[i]);
617 }
618 }
619 }
620
621 if (!hasNonEmptyContainer) {
622 if (containers.empty() || !containers[0]) {
623 throw OpalException(
624 "PartBunch::bunchUpdate",
625 "No valid particle container available for bunch update.");
626 }
627 containers[0]->computeMinMaxR();
628 lower = containers[0]->getMinR();
629 upper = containers[0]->getMaxR();
630 }
631
632 const BinnedFieldSolver_t* bsolver = this->getFieldSolver();
633
634 // Include mirrored particles in the domain envelope when image-charge mode is active for this
635 // step.
636 if (bsolver && bsolver->isImageChargeActiveForStep(this->getGlobalTrackStep())) {
637 const double planeZ = bsolver->getImageChargePlaneZ();
638 const double mirroredMinZ = 2.0 * planeZ - upper[2];
639 const double mirroredMaxZ = 2.0 * planeZ - lower[2];
640 lower[2] = std::min(lower[2], mirroredMinZ);
641 upper[2] = std::max(upper[2], mirroredMaxZ);
642 m << level4 << "Image-charge bounds enabled at zPlane=" << planeZ << endl;
643 }
644
645 Vector_t<double, Dim> span = upper - lower;
646 for (unsigned i = 0; i < Dim; ++i) {
647 if (span[i] < 1e-6) {
648 span[i] = 1e-6;
649 m << level3 << "Mesh spacing in dimension " << i << " too small. Set to 1e-6." << endl;
650 }
651 }
652
653 lower = lower - span * this->OPALFieldSolver_m->getBoxIncr() / 100.0;
654 upper = upper + span * this->OPALFieldSolver_m->getBoxIncr() / 100.0;
655}
656
657template <typename T, unsigned Dim>
659 const Vector_t<double, Dim>& lower, const Vector_t<double, Dim>& upper) {
660 Inform m("PartBunch::applyGridUpdate");
661 auto* mesh = &this->fcontainer_m->getMesh();
662 auto* FL = &this->fcontainer_m->getFL();
663 std::shared_ptr<ParticleContainer_t> pc = this->getParticleContainer();
664
665 const Vector_t<double, Dim> span = upper - lower;
666 hr_m = span / this->nr_m;
667
668 mesh->setMeshSpacing(hr_m);
669 mesh->setOrigin(lower);
670
671 this->getFieldContainer()->setRMin(lower);
672 this->getFieldContainer()->setRMax(upper);
673 this->getFieldContainer()->setHr(hr_m);
674
675 m << level3 << "Field Container updated with new mesh boundaries and spacing:" << endl;
676 m << level3 << "\t\t> Mesh origin: " << mesh->getOrigin() << endl;
677 m << level3 << "\t\t> Mesh spacing: " << hr_m << endl;
678 m << level3 << "\t\t> Box increment: " << this->OPALFieldSolver_m->getBoxIncr() << "%" << endl;
679
680 const auto& containers = this->getParticleContainers();
681 for (size_t i = 0; i < containers.size(); ++i) {
682 const auto& pc = containers[i];
683 if (!pc) {
684 continue;
685 }
686 pc->getLayout().updateLayout(*FL, *mesh);
687 pc->update();
688 pc->markMomentsDirty(); // IPPL migration may have re-indexed R across ranks
691 m << level5 << "Particle container " << i << " updated with new layout." << endl;
692 }
693}
694
695template <typename T, unsigned Dim>
696void PartBunch<T, Dim>::setImageChargeConfiguration(bool enabled, double zPlane) {
697 this->getFieldSolver()->setImageChargeConfiguration(enabled, zPlane);
698}
699
700template <typename T, unsigned Dim>
701void PartBunch<T, Dim>::setShiftedGreensConfiguration(bool enabled, double zPlane) {
702 this->getFieldSolver()->setShiftedGreensConfiguration(enabled, zPlane);
703}
704
705template <typename T, unsigned Dim>
707 this->getFieldSolver()->setZeroFacePlaneDumpFrequency(frequency);
708}
709
710template <typename T, unsigned Dim>
712 this->getFieldSolver()->setZerofaceMaxSteps(maxSteps);
713}
714
718template <typename T, unsigned Dim>
720 BinnedFieldSolver_t* bsolver = this->getFieldSolver();
721
722 bsolver->computeSelfFields(*this);
723}
724
728template <typename T, unsigned Dim>
730 if (!hasBinning() || !dataSink_m) {
731 throw OpalException(
732 "PartBunch::dumpBinConfig",
733 "No binning or data sink set, but dumpBinConfig() was called.");
734 }
735
736 Inform m("PartBunch::dumpBinConfig");
737
738 BinningCmd* binningCmd = OPALFieldSolver_m->getBinningCmd();
739 if (!binningCmd) {
740 return;
741 }
742
743 // If BINNING is configured with DUMPBINSFILE="NONE", skip all file dumping.
744 // (BinnedFieldSolver may still call dumpBinConfig() during rebin/merge steps.)
745 if (!binningCmd->dumpBinsToFile()) {
746 return;
747 }
748
749 const long long step = getGlobalTrackStep();
750 const int dumpFreq = binningCmd->getDumpBinsFrequency();
751 if (dumpFreq <= 0 || (step % dumpFreq) != 0) {
752 return;
753 }
754
755 std::shared_ptr<AdaptBins_t> bins = getBins();
756 if (!bins) {
757 return;
758 }
759
760 std::vector<typename AdaptBins_t::size_type> countsHost;
761 std::vector<typename AdaptBins_t::value_type> widthsHost;
762 const auto xMin = bins->getBinConfigHost(countsHost, widthsHost);
763
764 std::vector<std::size_t> counts(countsHost.begin(), countsHost.end());
765 std::vector<double> widths(widthsHost.begin(), widthsHost.end());
766
767 m << level5 << "Dumping bin configuration (preMerge=" << (preMerge ? 1 : 0)
768 << ") at globalTrackStep=" << step << " with nBins=" << counts.size() << " to file \""
769 << binningCmd->getDumpBinsFileName() << "\"." << endl;
770
771 dataSink_m->dumpBinConfig(
772 step, getT(), preMerge, counts, widths, static_cast<double>(xMin),
773 binningCmd->getDumpBinsFileName());
774}
775
779template <typename T, unsigned Dim>
781 Inform ms("PartBunch::performBunchSanityChecks");
782 ms << level4 << "========== Performing sanity checks on PartBunch... ==========" << endl;
783 // TODO: extend checks; prefer throwing OpalException with clear messages.
784
785 // Check if bc handler was initialized properly
786 if (!this->getBCHandler()) {
787 throw OpalException(
788 "PartBunch::performBunchSanityChecks", "BC Handler not initialized properly.");
789 }
790 ms << level4 << "BC Handler initialized properly." << endl;
791
792 if (!this->getBunchStateHandler()) {
793 throw OpalException(
794 "PartBunch::performBunchSanityChecks", "BunchStateHandler not initialized.");
795 }
796 ms << level4 << "BunchStateHandler initialized." << endl;
797
798 if (!hasFieldSolver()) {
799 throw OpalException(
800 "PartBunch::performBunchSanityChecks", "Field Solver was not initialized.");
801 }
802 ms << level4 << "Field Solver object was initialized." << endl;
803
804 // Verify we can access the concrete FieldSolver and its internals.
805 auto fs = std::dynamic_pointer_cast<BinnedFieldSolver_t>(this->fsolver_m);
806 if (!fs) {
807 throw OpalException(
808 "PartBunch::performBunchSanityChecks", "FieldSolver is not set in PartBunch.");
809 }
810
811 // cannot use getFieldContainer, since this getter cannot be const!
812 const std::shared_ptr<FieldContainer<T, Dim>> fctr = this->fcontainer_m;
813 if (!fctr) {
814 throw OpalException(
815 "PartBunch::performBunchSanityChecks",
816 "FieldContainer isn't initialized correctly.");
817 }
818
819 // Check internal field pointers are set
820 if (fs->getRho() == nullptr || fs->getE() == nullptr || fs->getPhi() == nullptr) {
821 throw OpalException(
822 "PartBunch::performBunchSanityChecks",
823 "FieldSolver internal fields (rho/E/phi) not assigned.");
824 }
825 ms << level4 << "FieldSolver internal field pointers are set." << endl;
826
827 // Ensure FieldSolver fields point to our FieldContainer's fields
828 if (fs->getRho() != &fctr->getRho() || fs->getE() != &fctr->getE()
829 || fs->getPhi() != &fctr->getPhi()) {
830 throw OpalException(
831 "PartBunch::performBunchSanityChecks",
832 "FieldSolver fields do not match FieldContainer.");
833 }
834 ms << level4 << "FieldSolver fields match FieldContainer." << endl;
835
836 /*
837 // Check if all three fields (rho, E, phi) have the same mesh and layout
838 auto rhoMesh = fs->getRho()->get_mesh();
839 auto EMesh = fs->getE()->get_mesh();
840 auto phiMesh = fs->getPhi()->get_mesh();
841 if (rhoMesh->getOrigin() != EMesh->getOrigin() ||
842 rhoMesh->getOrigin() != phiMesh->getOrigin() ||
843 rhoMesh->getMeshSpacing() != EMesh->getMeshSpacing() ||
844 rhoMesh->getMeshSpacing() != phiMesh->getMeshSpacing()) {
845 throw OpalException("PartBunch::performBunchSanityChecks",
846 "FieldSolver fields do not share the same mesh.");
847 }
848 ms << "FieldSolver fields share the same mesh." << endl;*/
849
850 // Check solver type string and that a backend was emplaced
851 const std::string stype = fs->getStype();
852 if (stype.empty()) {
853 throw OpalException(
854 "PartBunch::performBunchSanityChecks", "FieldSolver type string is empty.");
855 }
856 if (stype != "FFT" && stype != "OPEN" && stype != "CG" && stype != "NONE") {
857 throw OpalException(
858 "PartBunch::performBunchSanityChecks", "Unsupported FieldSolver type: " + stype);
859 }
860 ms << level4 << "FieldSolver type: " << stype << endl;
861
862 // Basic check that the E-field layout has non-zero extent
863 auto Eview = fctr->getE().getView();
864 if (stype != "NONE" && (Eview.extent(0) == 0 || Eview.extent(1) == 0 || Eview.extent(2) == 0)) {
865 throw OpalException(
866 "PartBunch::performBunchSanityChecks",
867 "E-field layout not initialized (zero extent). ");
868 }
869 ms << level4 << "E-field layout initialized." << endl;
870
871 // Temporary E/B accumulation fields (binned solver path)
872 if (!this->Etmp_m || !this->Btmp_m) {
873 throw OpalException(
874 "PartBunch::performBunchSanityChecks",
875 "Temporary E field (Etmp) and/or B field (Btmp) not initialized.");
876 }
877 auto EtmpView = this->Etmp_m->getView();
878 auto BtmpView = this->Btmp_m->getView();
879 if (EtmpView.extent(0) == 0 || EtmpView.extent(1) == 0 || EtmpView.extent(2) == 0) {
880 throw OpalException(
881 "PartBunch::performBunchSanityChecks",
882 "Etmp field layout not initialized (zero extent). ");
883 }
884 if (BtmpView.extent(0) == 0 || BtmpView.extent(1) == 0 || BtmpView.extent(2) == 0) {
885 throw OpalException(
886 "PartBunch::performBunchSanityChecks",
887 "Btmp field layout not initialized (zero extent). ");
888 }
889 if (&this->Etmp_m->get_mesh() != &fctr->getMesh()
890 || &this->Btmp_m->get_mesh() != &fctr->getMesh()) {
891 throw OpalException(
892 "PartBunch::performBunchSanityChecks",
893 "Etmp/Btmp fields do not use the FieldContainer mesh.");
894 }
895 ms << level4 << "Etmp and Btmp fields initialized on the FieldContainer mesh." << endl;
896
897 if (!this->pcontainer_m) {
898 throw OpalException(
899 "PartBunch::performBunchSanityChecks",
900 "Primary ParticleContainer not initialized.");
901 }
902 ms << level4 << "Primary ParticleContainer present." << endl;
903
904 ms << level2 << "========= Done performing PartBunch sanity checks... =========" << endl;
905}
906
908template class PartBunch<double, 3>;
ippl::Vector< T, Dim > Vector_t
typename ippl::detail::ViewType< ippl::Vector< double, Dim >, 1 >::view_type view_type
double T
Definition OPALTypes.h:8
constexpr unsigned Dim
Definition OPALTypes.h:7
ippl::Field< ippl::Vector< T, Dim >, Dim, ViewArgs... > VField_t
Definition PBunchDefs.h:31
Template PIC bunch: IPPL PicManager, shared field mesh/solver, and multiple particle containers.
typename ippl::detail::ViewType< ippl::Vector< double, 3 >, 1 >::view_type view_type
Definition PartBunch.h:40
Inform * gmsg
Definition changes.cpp:7
Field solver wrapper that implements the full binned self-field algorithm.
double getImageChargePlaneZ() const
bool isImageChargeActiveForStep(size_t step) const
Check whether the explicit image-charge pass should run for a given timestep.
void computeSelfFields(PartBunch_t &bunch)
Compute space-charge self-fields for the given particle bunch.
double getDesiredWidth() const
int getDumpBinsFrequency() const
Get the frequency of dumping bins to a file.
double getBinningAlpha() const
int getTablePrintFrequency() const
int getMaxBins() const
bool getAdaptiveBinning() const
BinningParameter getParameterType() const
bool dumpBinsToFile() const
Check if dumping bins to a file is enabled.
std::string getParameter()
std::string getDumpBinsFileName() const
Get the file name for dumping bins to a file.
double getBinningBeta() const
BCHandler< 3 > constructBCHandler() const
Returns solver boundary conditions handler object.
double getNX() const
Return meshsize.
double getNY() const
Return meshsize.
double getNZ() const
Return meshsize.
ippl::Vector< bool, 3 > getDomDec() const
const std::string & getOpalName() const
Return object name.
Definition Object.cpp:267
void computeBoundsForFieldSolve(Vector_t< double, Dim > &lower, Vector_t< double, Dim > &upper)
Computes the spatial bounds for the field solver based on the current particle distribution.
Vector_t< double, Dim > rmax_m
Current bunch spatial maximum (from primary container stats).
Definition PartBunch.h:83
typename ParticleBinning::CoordinateSelector< ParticleContainer_t > CoordinateSelector_t
Definition PartBunch.h:63
void gatherLoadBalanceStatistics()
void performBunchSanityChecks() const
Validate BC handler, solver wiring, field pointers, and layout extents.
std::string getParticleName(size_t i) const
Particle species name for container i (from BEAM PARTICLE input).
Definition PartBunch.h:603
std::shared_ptr< VField_t< T, Dim > > getTempEField()
Scratch E field used by the binned solver path.
Definition PartBunch.h:307
void computeSelfFields()
Compute the bunch self-fields (binned when available).
std::vector< std::string > particleNames_m
Per-container beam particle names.
Definition PartBunch.h:94
void pre_run() override
Warm-up: zero rho and run the field solver once (skip full dumps).
void setT(double t)
Set the current simulation time.
Definition PartBunch.h:645
Vector_t< int, Dim > nr_m
Mesh cell count per dimension.
Definition PartBunch.h:77
DataSink * dataSink_m
Borrowed diagnostics and dump output sink.
Definition PartBunch.h:107
std::array< bool, Dim > decomp_m
Domain decomposition flags (per axis).
Definition PartBunch.h:89
Vector_t< double, Dim > rmin_m
Definition PartBunch.h:80
void dumpBinConfig(bool preMerge)
Write bin edges/counts to the data sink when configured.
FieldSolverCmd * OPALFieldSolver_m
Borrowed parsed FIELD_SOLVER command.
Definition PartBunch.h:106
void reinitializeGridZ(int nrZ)
Reinitialize the z dimension of the field grid to nrZ cells.
Vector_t< double, Dim > origin_m
Mesh origin (lab coordinates).
Definition PartBunch.h:79
void calcBeamParameters()
Update moments and set rmin_m / rmax_m from the primary (first) container.
int nrZBase_m
Base z grid count before any image-charge doubling; used to reset nr_m.
Definition PartBunch.h:78
void do_binaryRepart()
ORB/binary repartition when the load balancer requests it (primary container).
Vector_t< double, Dim > hr_m
Mesh spacing (m).
Definition PartBunch.h:84
typename ParticleBinning::GammaSelector< ParticleContainer_t > GammaSelector_t
Definition PartBunch.h:64
void setBCHandler(std::shared_ptr< BCHandler_t > bcHandler)
Definition PartBunch.h:328
void setSolver()
Build field solver and load balancer from OPALFieldSolver_m.
double dt_m
Global time step (s).
Definition PartBunch.h:73
void setPcAtZStop(size_t i)
Deactivate container i until the next step-size segment (z-stop reached).
ippl::NDIndex< Dim > domain_m
Global mesh index extent per dimension.
Definition PartBunch.h:88
void setImageChargeConfiguration(bool enabled, double zPlane)
Set the image-charge configuration for the field solver.
std::shared_ptr< BunchStateHandler > bunchState_m
Bunch state: unitless flag, repartition flag, etc.
Definition PartBunch.h:102
void setTempEField(std::shared_ptr< VField_t< T, Dim > > Etmp)
Definition PartBunch.h:310
std::shared_ptr< VField_t< T, Dim > > getTempBField()
Scratch B field used by the binned solver path.
Definition PartBunch.h:313
void setTempBField(std::shared_ptr< VField_t< T, Dim > > Btmp)
Definition PartBunch.h:316
void setShiftedGreensConfiguration(bool enabled, double zPlane)
Set the shifted Green's function Dirichlet-correction configuration.
PartBunch(std::vector< double > qi, std::vector< double > mi, const std::vector< Beam * > &beams, std::vector< size_t > totalParticlesPerBeam, double lbt, std::string integration_method, FieldSolverCmd *OPALFieldSolver, DataSink *dataSink)
Construct a multi-beam bunch: mesh, solver, containers, and capacity.
Definition PartBunch.cpp:22
const PartData * getReference() const
Definition PartBunch.h:494
void setZerofaceMaxSteps(int maxSteps)
Set the maximum number of timesteps for which image charges are active (0 = unlimited).
void bunchUpdate()
Refresh mesh from particle extents, update layouts, and recompute moments.
std::string getFieldSolverType()
Backend type string (e.g. FFT, OPEN, CG, NONE).
void refreshPcActiveAfterEmit()
After emission: reactivate non-empty containers not marked at z-stop.
std::shared_ptr< BCHandler_t > getBCHandler() const
Current boundary-condition handler.
Definition PartBunch.h:331
Inform & print(Inform &os)
Human-readable dump of each container to os.
BinnedFieldSolver_t * getFieldSolver()
Non-const pointer to the concrete BinnedFieldSolver.
void applyGridUpdate(const Vector_t< double, Dim > &lower, const Vector_t< double, Dim > &upper)
Updates the mesh/grid and internal data structures to match the given spatial bounds.
void setBins()
Create adaptive bins from the binning command (VELOCITYZ / GAMMAZ).
std::vector< double > qi_m
Charge per macroparticle [C], one entry per container.
Definition PartBunch.h:96
void setZeroFacePlaneDumpFrequency(int frequency)
Configure diagnostic dump frequency for the ZEROFACE plane potential.
void resetPcActive()
At segment start: active if container is non-empty; inactive if empty.
std::unique_ptr< size_t[]> globalPartPerNode_m
Per-rank particle counts for load-balance stats.
Definition PartBunch.h:119
std::vector< double > mi_m
Mass per macroparticle [GeV], one entry per container.
Definition PartBunch.h:97
A class that bins particles in energy bins and allows for adaptive runtime rebinning.
Definition AdaptBins.h:79
Container for all per-particle (and per-simulation) fields tracked during OPALX tracking.
static ParticleType getParticleType(const std::string &str)
std::string getEnergyString(double energyInMeV, unsigned int precision=3)
Definition Util.h:135
std::string getLengthString(double spos, unsigned int precision=3)
Definition Util.h:88