OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
LinearComptonSpectrumBenchmark.cpp
Go to the documentation of this file.
2#include "Ippl.h"
4#include "Physics/Physics.h"
5#include "Utilities/Options.h"
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <filesystem>
11#include <iostream>
12#include <memory>
13#include <stdexcept>
14#include <string>
15
16namespace {
31 struct CliOptions {
32 std::filesystem::path output = "opalx-linear-compton-90deg-xi029-spectrum.csv";
33 bool sampled = false;
34 bool angular = false;
35 bool joint = false;
36 bool finiteBeam = false;
37 bool overlapWeighting = false;
38 std::size_t samples = 250000;
39 std::size_t beamParticles = 250000;
40 int seed = 13579;
41 double beamSigmaTransverse_m = 1.0e-3;
42 double beamSigmaLongitudinal_m = 0.0;
43 double beamRmsAngle_rad = 1.0e-3;
44 double beamRelativeEnergySpread = 0.0;
45 double laserRayleigh_m = 12.5;
46 double laserSigmaT_m = 1.0e-12 * Physics::c;
47 };
48
57 CliOptions parseArguments(int argc, char** argv) {
58 CliOptions options;
59
60 for (int i = 1; i < argc; ++i) {
61 const std::string arg(argv[i]);
62 if (arg == "--sampled") {
63 options.sampled = true;
64 } else if (arg == "--angular") {
65 options.angular = true;
66 } else if (arg == "--joint") {
67 options.joint = true;
68 } else if (arg == "--finite-beam") {
69 options.finiteBeam = true;
70 } else if (arg == "--overlap-weighting") {
71 options.overlapWeighting = true;
72 } else if (arg == "--samples") {
73 if (i + 1 >= argc) {
74 throw std::runtime_error("Missing value after --samples");
75 }
76 options.samples = static_cast<std::size_t>(std::stoull(argv[++i]));
77 } else if (arg == "--beam-particles") {
78 if (i + 1 >= argc) {
79 throw std::runtime_error("Missing value after --beam-particles");
80 }
81 options.beamParticles = static_cast<std::size_t>(std::stoull(argv[++i]));
82 } else if (arg == "--seed") {
83 if (i + 1 >= argc) {
84 throw std::runtime_error("Missing value after --seed");
85 }
86 options.seed = std::stoi(argv[++i]);
87 } else if (arg == "--beam-sigma-transverse") {
88 if (i + 1 >= argc) {
89 throw std::runtime_error("Missing value after --beam-sigma-transverse");
90 }
91 options.beamSigmaTransverse_m = std::stod(argv[++i]);
92 } else if (arg == "--beam-sigma-longitudinal") {
93 if (i + 1 >= argc) {
94 throw std::runtime_error("Missing value after --beam-sigma-longitudinal");
95 }
96 options.beamSigmaLongitudinal_m = std::stod(argv[++i]);
97 } else if (arg == "--beam-rms-angle") {
98 if (i + 1 >= argc) {
99 throw std::runtime_error("Missing value after --beam-rms-angle");
100 }
101 options.beamRmsAngle_rad = std::stod(argv[++i]);
102 } else if (arg == "--beam-relative-energy-spread") {
103 if (i + 1 >= argc) {
104 throw std::runtime_error("Missing value after --beam-relative-energy-spread");
105 }
106 options.beamRelativeEnergySpread = std::stod(argv[++i]);
107 } else if (arg == "--laser-rayleigh") {
108 if (i + 1 >= argc) {
109 throw std::runtime_error("Missing value after --laser-rayleigh");
110 }
111 options.laserRayleigh_m = std::stod(argv[++i]);
112 } else if (arg == "--laser-sigma-t") {
113 if (i + 1 >= argc) {
114 throw std::runtime_error("Missing value after --laser-sigma-t");
115 }
116 options.laserSigmaT_m = std::stod(argv[++i]);
117 } else if (arg == "-h" || arg == "--help") {
118 std::cout << "Usage: LinearComptonSpectrumBenchmark [output.csv] "
119 "[--angular|--joint] [--sampled] [--finite-beam] "
120 "[--overlap-weighting] [--samples N] [--beam-particles N] [--seed S]\n"
121 << " default mode : deterministic single-electron energy "
122 "spectrum\n"
123 << " --angular : write the lab polar-angle histogram "
124 "instead of the energy histogram\n"
125 << " --joint : write the joint E_gamma versus "
126 "theta_gamma histogram\n"
127 << " --sampled : host-only Monte Carlo benchmark using "
128 "LinearCompton::sampleEvent\n"
129 << " --finite-beam : sample a finite-emittance electron beam "
130 "with MultiVariateGaussian\n"
131 << " --samples N : number of Monte Carlo samples in "
132 "single-electron sampled mode\n"
133 << " --beam-particles N : number of sampled beam macroparticles "
134 "in finite-beam mode\n"
135 << " --beam-sigma-transverse : transverse RMS beam size in m (default: "
136 "1e-3)\n"
137 << " --beam-sigma-longitudinal: longitudinal RMS beam size in m "
138 "(default: 0.0, internally clamped)\n"
139 << " --beam-rms-angle : transverse RMS beam angle in rad "
140 "(default: 1e-3)\n"
141 << " --beam-relative-energy-spread: RMS relative energy spread sigma_E "
142 "/ E (default: 0.0)\n"
143 << " --overlap-weighting : condition beam positions on a Gaussian "
144 "laser envelope\n"
145 << " --laser-rayleigh : laser Rayleigh length in m for "
146 "overlap-conditioned finite-beam mode\n"
147 << " --laser-sigma-t : laser RMS pulse length in m for "
148 "overlap-conditioned finite-beam mode\n"
149 << " --seed S : Options::seed value used in sampled "
150 "modes\n";
151 std::exit(0);
152 } else if (!arg.empty() && arg[0] == '-') {
153 throw std::runtime_error("Unknown option: " + arg);
154 } else {
155 options.output = arg;
156 }
157 }
158
159 return options;
160 }
161
172 void preallocateParticleCapacity(
173 const std::shared_ptr<ParticleContainer<double, 3>>& pc, std::size_t totalParticles) {
174 const int nranks = std::max(1, ippl::Comm->size());
175 const std::size_t nranksU = static_cast<std::size_t>(nranks);
176 const std::size_t maxLocalNum = totalParticles / nranksU + 2 * nranksU + 1;
177
178 pc->allocateParticles(maxLocalNum);
179 }
180
193 double effectiveSpatialOverlapSigma(double beamSigma_m, double waist_m) {
194 if (beamSigma_m <= 0.0) {
195 return 0.0;
196 }
197 if (waist_m <= 0.0) {
198 return beamSigma_m;
199 }
200
201 const double inverseVariance =
202 1.0 / (beamSigma_m * beamSigma_m) + 4.0 / (waist_m * waist_m);
203 return std::sqrt(1.0 / inverseVariance);
204 }
205
206 double effectiveTemporalOverlapSigma(double beamSigmaS_m, double laserSigmaT_m) {
207 if (beamSigmaS_m <= 0.0) {
208 return 0.0;
209 }
210 if (laserSigmaT_m <= 0.0) {
211 return beamSigmaS_m;
212 }
213
214 const double inverseVariance =
215 1.0 / (beamSigmaS_m * beamSigmaS_m) + 1.0 / (laserSigmaT_m * laserSigmaT_m);
216 return std::sqrt(1.0 / inverseVariance);
217 }
218
219 Vector_t<double, 3> makeFiniteBeamSigmaR(const CliOptions& options, double wavelength_m) {
220 if (!options.overlapWeighting) {
221 return Vector_t<double, 3>(
222 options.beamSigmaTransverse_m, options.beamSigmaTransverse_m,
223 std::max(1.0e-15, options.beamSigmaLongitudinal_m));
224 }
225
226 constexpr double pi = 3.14159265358979323846;
227 const double waist_m = options.laserRayleigh_m > 0.0
228 ? std::sqrt(wavelength_m * options.laserRayleigh_m / pi)
229 : 0.0;
230 return Vector_t<double, 3>(
231 effectiveSpatialOverlapSigma(options.beamSigmaTransverse_m, waist_m),
232 effectiveSpatialOverlapSigma(options.beamSigmaTransverse_m, waist_m),
233 std::max(
235 options.beamSigmaLongitudinal_m, options.laserSigmaT_m)));
236 }
237
238 std::shared_ptr<ParticleContainer<double, 3>> makeParticleContainer(
239 const ippl::Vector<double, 3>& sigmaR) {
240 ippl::Vector<int, 3> nr = 32;
241 const double sigmaScaleXY = std::max(8.0 * sigmaR[0], 1.0e-2);
242 const double sigmaScaleZ = std::max(8.0 * sigmaR[2], 1.0e-6);
243
244 ippl::Vector<double, 3> rmin;
245 rmin[0] = -sigmaScaleXY;
246 rmin[1] = -sigmaScaleXY;
247 rmin[2] = -sigmaScaleZ;
248 ippl::Vector<double, 3> rmax;
249 rmax[0] = sigmaScaleXY;
250 rmax[1] = sigmaScaleXY;
251 rmax[2] = sigmaScaleZ;
252 ippl::Vector<double, 3> origin = rmin;
253 ippl::Vector<double, 3> hr = (rmax - rmin) / ippl::Vector<double, 3>(nr);
254 std::array<bool, 3> decomp = {true, true, true};
255
256 ippl::NDIndex<3> domain;
257 for (unsigned i = 0; i < 3; ++i) {
258 domain[i] = ippl::Index(nr[i]);
259 }
260
261 auto fc = std::make_shared<FieldContainer_t>(hr, rmin, rmax, decomp, domain, origin, true);
262 (void)fc;
263
264 Mesh_t<3> mesh(domain, hr, origin);
265 FieldLayout_t<3> fl(MPI_COMM_WORLD, domain, decomp, true);
266 return std::make_shared<ParticleContainer<double, 3>>(mesh, fl);
267 }
268
285 LinearComptonBenchmark::SpectrumHistogram sampleFiniteBeamEnergySpectrum(
286 const LinearComptonBenchmark::SpectrumConfig& config, const CliOptions& options) {
287 if (options.beamSigmaTransverse_m <= 0.0) {
288 throw std::runtime_error(
289 "Finite-beam benchmark requires positive transverse beam size.");
290 }
291 if (options.beamRmsAngle_rad <= 0.0) {
292 throw std::runtime_error(
293 "Finite-beam benchmark requires positive transverse RMS angle.");
294 }
295 if (options.beamRelativeEnergySpread < 0.0) {
296 throw std::runtime_error(
297 "Finite-beam benchmark requires nonnegative relative energy spread.");
298 }
299
300 const double p0GeV = std::sqrt(
303 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
304 const double beta0 = p0GeV / config.electronTotalEnergyGeV;
305 const double sigmaEGeV = options.beamRelativeEnergySpread * config.electronTotalEnergyGeV;
306 const double sigmaPzGeV =
307 std::max(1.0e-15, sigmaEGeV > 0.0 ? sigmaEGeV / beta0 : 1.0e-12 * p0GeV);
308 const Vector_t<double, 3> meanR = 0.0;
309 Vector_t<double, 3> meanP(0.0);
310 meanP[2] = p0GeV;
311 const Vector_t<double, 3> sigmaR = makeFiniteBeamSigmaR(options, config.wavelength_m);
312 Vector_t<double, 3> sigmaP(sigmaPxGeV, sigmaPxGeV, sigmaPzGeV);
313 const Vector_t<double, 3> cutoffR = 4.0;
314 const Vector_t<double, 3> cutoffP = 4.0;
315
316 auto pc = makeParticleContainer(sigmaR);
317 preallocateParticleCapacity(pc, options.beamParticles);
318 ippl::Vector<int, 3> nr = 32;
319
320 MultiVariateGaussian sampler(
321 pc, meanR, meanP, sigmaR, sigmaP, cutoffR, cutoffP, true, true);
322 std::size_t numberOfParticles = options.beamParticles;
323 sampler.generateParticles(numberOfParticles, nr);
324
326 histogram.binWidthGeV =
327 (config.energyMaxGeV - config.energyMinGeV) / static_cast<double>(config.bins);
328 histogram.centersGeV.resize(config.bins);
329 histogram.densityPerGeV.assign(config.bins, 0.0);
330 histogram.counts.assign(config.bins, 0.0);
331 for (std::size_t i = 0; i < config.bins; ++i) {
332 histogram.centersGeV[i] =
333 config.energyMinGeV + (static_cast<double>(i) + 0.5) * histogram.binWidthGeV;
334 }
335
336 auto rView = pc->R.getView();
337 auto pView = pc->P.getView();
339 const double laserPhotonEnergyGeV =
341
342 histogram.totalWeight = static_cast<double>(pc->getLocalNum());
343 for (std::size_t i = 0; i < pc->getLocalNum(); ++i) {
344 (void)rView;
345 Vector_t<double, 3> beamDirection(0.0);
346 beamDirection[0] = pView(i)[0];
347 beamDirection[1] = pView(i)[1];
348 beamDirection[2] = pView(i)[2];
349 const double p2 = dot(beamDirection, beamDirection);
350 const double momentum = std::sqrt(p2);
351 if (momentum <= 0.0) {
352 continue;
353 }
354 beamDirection /= momentum;
355 const double electronTotalEnergyGeV = std::sqrt(p2 + Physics::m_e * Physics::m_e);
357 electronTotalEnergyGeV, laserPhotonEnergyGeV, beamDirection,
358 config.laserDirection);
359 const auto event = Physics::LinearCompton::sampleEvent(kernel, engine);
360 const double energyGeV = event.scatteredPhotonEnergyLabGeV;
361 if (energyGeV < config.energyMinGeV || energyGeV >= config.energyMaxGeV) {
362 continue;
363 }
364 const std::size_t bin = static_cast<std::size_t>(
365 (energyGeV - config.energyMinGeV) / histogram.binWidthGeV);
366 if (bin < histogram.counts.size()) {
367 histogram.counts[bin] += 1.0;
368 }
369 }
370
371 double globalTotalWeight = 0.0;
372 MPI_Allreduce(
373 &histogram.totalWeight, &globalTotalWeight, 1, MPI_DOUBLE, MPI_SUM,
374 ippl::Comm->getCommunicator());
375 histogram.totalWeight = globalTotalWeight;
376
377 std::vector<double> globalCounts(histogram.counts.size(), 0.0);
378 MPI_Allreduce(
379 histogram.counts.data(), globalCounts.data(),
380 static_cast<int>(histogram.counts.size()), MPI_DOUBLE, MPI_SUM,
381 ippl::Comm->getCommunicator());
382 histogram.counts = std::move(globalCounts);
383
384 for (std::size_t i = 0; i < histogram.densityPerGeV.size(); ++i) {
385 histogram.densityPerGeV[i] =
386 histogram.counts[i] / (histogram.totalWeight * histogram.binWidthGeV);
387 }
388 return histogram;
389 }
390
400 LinearComptonBenchmark::AngleHistogram sampleFiniteBeamAngularSpectrum(
401 const LinearComptonBenchmark::AngleConfig& config, const CliOptions& options) {
402 if (options.beamSigmaTransverse_m <= 0.0) {
403 throw std::runtime_error(
404 "Finite-beam benchmark requires positive transverse beam size.");
405 }
406 if (options.beamRmsAngle_rad <= 0.0) {
407 throw std::runtime_error(
408 "Finite-beam benchmark requires positive transverse RMS angle.");
409 }
410 if (options.beamRelativeEnergySpread < 0.0) {
411 throw std::runtime_error(
412 "Finite-beam benchmark requires nonnegative relative energy spread.");
413 }
414
415 const double p0GeV = std::sqrt(
418 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
419 const double beta0 = p0GeV / config.electronTotalEnergyGeV;
420 const double sigmaEGeV = options.beamRelativeEnergySpread * config.electronTotalEnergyGeV;
421 const double sigmaPzGeV =
422 std::max(1.0e-15, sigmaEGeV > 0.0 ? sigmaEGeV / beta0 : 1.0e-12 * p0GeV);
423 const Vector_t<double, 3> meanR = 0.0;
424 Vector_t<double, 3> meanP(0.0);
425 meanP[2] = p0GeV;
426 const Vector_t<double, 3> sigmaR = makeFiniteBeamSigmaR(options, config.wavelength_m);
427 Vector_t<double, 3> sigmaP(sigmaPxGeV, sigmaPxGeV, sigmaPzGeV);
428 const Vector_t<double, 3> cutoffR = 4.0;
429 const Vector_t<double, 3> cutoffP = 4.0;
430
431 auto pc = makeParticleContainer(sigmaR);
432 preallocateParticleCapacity(pc, options.beamParticles);
433 ippl::Vector<int, 3> nr = 32;
434
435 MultiVariateGaussian sampler(
436 pc, meanR, meanP, sigmaR, sigmaP, cutoffR, cutoffP, true, true);
437 std::size_t numberOfParticles = options.beamParticles;
438 sampler.generateParticles(numberOfParticles, nr);
439
441 histogram.binWidthRad =
442 (config.thetaMaxRad - config.thetaMinRad) / static_cast<double>(config.bins);
443 histogram.centersRad.resize(config.bins);
444 histogram.densityPerRad.assign(config.bins, 0.0);
445 histogram.counts.assign(config.bins, 0.0);
446 for (std::size_t i = 0; i < config.bins; ++i) {
447 histogram.centersRad[i] =
448 config.thetaMinRad + (static_cast<double>(i) + 0.5) * histogram.binWidthRad;
449 }
450
451 auto pView = pc->P.getView();
453 const double laserPhotonEnergyGeV =
455
456 histogram.totalWeight = static_cast<double>(pc->getLocalNum());
457 for (std::size_t i = 0; i < pc->getLocalNum(); ++i) {
458 Vector_t<double, 3> beamDirection(0.0);
459 beamDirection[0] = pView(i)[0];
460 beamDirection[1] = pView(i)[1];
461 beamDirection[2] = pView(i)[2];
462 const double p2 = dot(beamDirection, beamDirection);
463 const double momentum = std::sqrt(p2);
464 if (momentum <= 0.0) {
465 continue;
466 }
467 beamDirection /= momentum;
468 const double electronTotalEnergyGeV = std::sqrt(p2 + Physics::m_e * Physics::m_e);
470 electronTotalEnergyGeV, laserPhotonEnergyGeV, beamDirection,
471 config.laserDirection);
472 const auto event = Physics::LinearCompton::sampleEvent(kernel, engine);
473 const double thetaRad = LinearComptonBenchmark::photonPolarAngleRad(
474 event.scatteredPhotonDirectionLab, config.beamDirection);
475 if (thetaRad < config.thetaMinRad || thetaRad >= config.thetaMaxRad) {
476 continue;
477 }
478 const std::size_t bin = static_cast<std::size_t>(
479 (thetaRad - config.thetaMinRad) / histogram.binWidthRad);
480 if (bin < histogram.counts.size()) {
481 histogram.counts[bin] += 1.0;
482 }
483 }
484
485 double globalTotalWeight = 0.0;
486 MPI_Allreduce(
487 &histogram.totalWeight, &globalTotalWeight, 1, MPI_DOUBLE, MPI_SUM,
488 ippl::Comm->getCommunicator());
489 histogram.totalWeight = globalTotalWeight;
490
491 std::vector<double> globalCounts(histogram.counts.size(), 0.0);
492 MPI_Allreduce(
493 histogram.counts.data(), globalCounts.data(),
494 static_cast<int>(histogram.counts.size()), MPI_DOUBLE, MPI_SUM,
495 ippl::Comm->getCommunicator());
496 histogram.counts = std::move(globalCounts);
497
498 for (std::size_t i = 0; i < histogram.densityPerRad.size(); ++i) {
499 histogram.densityPerRad[i] =
500 histogram.counts[i] / (histogram.totalWeight * histogram.binWidthRad);
501 }
502 return histogram;
503 }
504 LinearComptonBenchmark::JointHistogram sampleFiniteBeamJointSpectrum(
505 const LinearComptonBenchmark::JointConfig& config, const CliOptions& options) {
506 if (options.beamSigmaTransverse_m <= 0.0) {
507 throw std::runtime_error(
508 "Finite-beam benchmark requires positive transverse beam size.");
509 }
510 if (options.beamRmsAngle_rad <= 0.0) {
511 throw std::runtime_error(
512 "Finite-beam benchmark requires positive transverse RMS angle.");
513 }
514 if (options.beamRelativeEnergySpread < 0.0) {
515 throw std::runtime_error(
516 "Finite-beam benchmark requires nonnegative relative energy spread.");
517 }
518
519 const double p0GeV = std::sqrt(
522 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
523 const double beta0 = p0GeV / config.electronTotalEnergyGeV;
524 const double sigmaEGeV = options.beamRelativeEnergySpread * config.electronTotalEnergyGeV;
525 const double sigmaPzGeV =
526 std::max(1.0e-15, sigmaEGeV > 0.0 ? sigmaEGeV / beta0 : 1.0e-12 * p0GeV);
527
528 const Vector_t<double, 3> meanR = 0.0;
529 Vector_t<double, 3> meanP(0.0);
530 meanP[2] = p0GeV;
531 const Vector_t<double, 3> sigmaR = makeFiniteBeamSigmaR(options, config.wavelength_m);
532 Vector_t<double, 3> sigmaP(sigmaPxGeV, sigmaPxGeV, sigmaPzGeV);
533 const Vector_t<double, 3> cutoffR = 4.0;
534 const Vector_t<double, 3> cutoffP = 4.0;
535
536 auto pc = makeParticleContainer(sigmaR);
537 preallocateParticleCapacity(pc, options.beamParticles);
538 ippl::Vector<int, 3> nr = 32;
539
540 MultiVariateGaussian sampler(
541 pc, meanR, meanP, sigmaR, sigmaP, cutoffR, cutoffP, true, true);
542 std::size_t numberOfParticles = options.beamParticles;
543 sampler.generateParticles(numberOfParticles, nr);
544
546 histogram.energyBinWidthGeV = (config.energyMaxGeV - config.energyMinGeV)
547 / static_cast<double>(config.energyBins);
548 histogram.thetaBinWidthRad =
549 (config.thetaMaxRad - config.thetaMinRad) / static_cast<double>(config.thetaBins);
550 histogram.energyCentersGeV.resize(config.energyBins);
551 histogram.thetaCentersRad.resize(config.thetaBins);
552 histogram.densityPerGeVRad.assign(config.energyBins * config.thetaBins, 0.0);
553 histogram.counts.assign(config.energyBins * config.thetaBins, 0.0);
554 for (std::size_t i = 0; i < config.energyBins; ++i) {
555 histogram.energyCentersGeV[i] =
556 config.energyMinGeV
557 + (static_cast<double>(i) + 0.5) * histogram.energyBinWidthGeV;
558 }
559 for (std::size_t j = 0; j < config.thetaBins; ++j) {
560 histogram.thetaCentersRad[j] =
561 config.thetaMinRad
562 + (static_cast<double>(j) + 0.5) * histogram.thetaBinWidthRad;
563 }
564
565 auto pView = pc->P.getView();
567 const double laserPhotonEnergyGeV =
569
570 histogram.totalWeight = static_cast<double>(pc->getLocalNum());
571 for (std::size_t i = 0; i < pc->getLocalNum(); ++i) {
572 Vector_t<double, 3> beamDirection(0.0);
573 beamDirection[0] = pView(i)[0];
574 beamDirection[1] = pView(i)[1];
575 beamDirection[2] = pView(i)[2];
576 const double p2 = dot(beamDirection, beamDirection);
577 const double momentum = std::sqrt(p2);
578 if (momentum <= 0.0) {
579 continue;
580 }
581 beamDirection /= momentum;
582 const double electronTotalEnergyGeV = std::sqrt(p2 + Physics::m_e * Physics::m_e);
584 electronTotalEnergyGeV, laserPhotonEnergyGeV, beamDirection,
585 config.laserDirection);
586 const auto event = Physics::LinearCompton::sampleEvent(kernel, engine);
587 const double energyGeV = event.scatteredPhotonEnergyLabGeV;
588 const double thetaRad = LinearComptonBenchmark::photonPolarAngleRad(
589 event.scatteredPhotonDirectionLab, config.beamDirection);
590 if (energyGeV < config.energyMinGeV || energyGeV >= config.energyMaxGeV
591 || thetaRad < config.thetaMinRad || thetaRad >= config.thetaMaxRad) {
592 continue;
593 }
594 const std::size_t energyBin = static_cast<std::size_t>(
595 (energyGeV - config.energyMinGeV) / histogram.energyBinWidthGeV);
596 const std::size_t thetaBin = static_cast<std::size_t>(
597 (thetaRad - config.thetaMinRad) / histogram.thetaBinWidthRad);
598 if (energyBin < histogram.energyCentersGeV.size()
599 && thetaBin < histogram.thetaCentersRad.size()) {
601 histogram, energyBin, thetaBin)] += 1.0;
602 }
603 }
604
605 double globalTotalWeight = 0.0;
606 MPI_Allreduce(
607 &histogram.totalWeight, &globalTotalWeight, 1, MPI_DOUBLE, MPI_SUM,
608 ippl::Comm->getCommunicator());
609 histogram.totalWeight = globalTotalWeight;
610
611 std::vector<double> globalCounts(histogram.counts.size(), 0.0);
612 MPI_Allreduce(
613 histogram.counts.data(), globalCounts.data(),
614 static_cast<int>(histogram.counts.size()), MPI_DOUBLE, MPI_SUM,
615 ippl::Comm->getCommunicator());
616 histogram.counts = std::move(globalCounts);
617
618 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
619 for (std::size_t i = 0; i < histogram.densityPerGeVRad.size(); ++i) {
620 histogram.densityPerGeVRad[i] =
621 histogram.counts[i] / (histogram.totalWeight * cellArea);
622 }
623 return histogram;
624 }
625
626} // namespace
627
641int main(int argc, char** argv) {
642 const CliOptions options = parseArguments(argc, argv);
643
644 if (options.finiteBeam) {
645 int ipplArgc = argc;
646 char** ipplArgv = argv;
647 ippl::initialize(ipplArgc, ipplArgv);
648
649 const int previousSeed = Options::seed;
650 Options::seed = options.seed;
651
652 if (options.joint) {
654 const auto histogram = sampleFiniteBeamJointSpectrum(config, options);
655 LinearComptonBenchmark::writeJointCSV(histogram, options.output);
656 if (ippl::Comm->rank() == 0) {
657 const double p0GeV = std::sqrt(
660 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
661 const double sigmaEGeV =
662 options.beamRelativeEnergySpread * config.electronTotalEnergyGeV;
663 const double geomEmittance =
664 options.beamSigmaTransverse_m * options.beamRmsAngle_rad;
665 std::cout << "Wrote OPALX finite-beam linear-Compton joint spectrum to "
666 << options.output << "\n"
667 << "Mode = sampled finite beam (MultiVariateGaussian)\n"
668 << "Observable = (E_gamma [GeV], theta_lab [rad])\n"
669 << "Beam sigma_x,y [m] = " << options.beamSigmaTransverse_m << "\n"
670 << "Beam sigma_z [m] = " << options.beamSigmaLongitudinal_m
671 << " (internally clamped if zero)\n"
672 << "Beam sigma_x',y' [rad] = " << options.beamRmsAngle_rad << "\n"
673 << "Beam sigma_px,py [GeV] = " << sigmaPxGeV << "\n"
674 << "Beam sigma_E / E = " << options.beamRelativeEnergySpread << "\n"
675 << "Beam sigma_E [GeV] = " << sigmaEGeV << "\n"
676 << "Beam geometric emittance [m rad] = " << geomEmittance << "\n"
677 << "Overlap-conditioned positions = "
678 << (options.overlapWeighting ? "yes" : "no") << "\n"
679 << "Laser Rayleigh [m] = " << options.laserRayleigh_m << "\n"
680 << "Laser sigma_t [m] = " << options.laserSigmaT_m << "\n"
681 << "Beam macroparticles = " << options.beamParticles << "\n"
682 << "Seed = " << options.seed << "\n"
683 << "Area = " << LinearComptonBenchmark::jointHistogramArea(histogram)
684 << "\n"
685 << "Mean energy [GeV] = "
687 << "Mean angle [rad] = "
689 }
690 } else if (options.angular) {
692 const auto histogram = sampleFiniteBeamAngularSpectrum(config, options);
693 LinearComptonBenchmark::writeAngleCSV(histogram, options.output);
694 if (ippl::Comm->rank() == 0) {
695 const double p0GeV = std::sqrt(
698 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
699 const double sigmaEGeV =
700 options.beamRelativeEnergySpread * config.electronTotalEnergyGeV;
701 const double geomEmittance =
702 options.beamSigmaTransverse_m * options.beamRmsAngle_rad;
703 std::cout << "Wrote OPALX finite-beam linear-Compton angular spectrum to "
704 << options.output << '\n'
705 << "Mode = sampled finite beam (MultiVariateGaussian)\n"
706 << "Observable = theta_lab [rad]\n"
707 << "Beam sigma_x,y [m] = " << options.beamSigmaTransverse_m << '\n'
708 << "Beam sigma_z [m] = " << options.beamSigmaLongitudinal_m
709 << " (internally clamped if zero)\n"
710 << "Beam sigma_x',y' [rad] = " << options.beamRmsAngle_rad << '\n'
711 << "Beam sigma_px,py [GeV] = " << sigmaPxGeV << '\n'
712 << "Beam sigma_E / E = " << options.beamRelativeEnergySpread << '\n'
713 << "Beam sigma_E [GeV] = " << sigmaEGeV << '\n'
714 << "Beam geometric emittance [m rad] = " << geomEmittance << '\n'
715 << "Beam macroparticles = " << options.beamParticles << '\n'
716 << "Seed = " << options.seed << '\n'
717 << "Area = " << LinearComptonBenchmark::angleHistogramArea(histogram)
718 << '\n'
719 << "Mean angle [rad] = "
721 }
722 } else {
724 const auto histogram = sampleFiniteBeamEnergySpectrum(config, options);
725 LinearComptonBenchmark::writeSpectrumCSV(histogram, options.output);
726 if (ippl::Comm->rank() == 0) {
727 const double p0GeV = std::sqrt(
730 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
731 const double sigmaEGeV =
732 options.beamRelativeEnergySpread * config.electronTotalEnergyGeV;
733 const double geomEmittance =
734 options.beamSigmaTransverse_m * options.beamRmsAngle_rad;
735 std::cout << "Wrote OPALX finite-beam linear-Compton spectrum to " << options.output
736 << '\n'
737 << "Mode = sampled finite beam (MultiVariateGaussian)\n"
738 << "Observable = E_gamma [GeV]\n"
739 << "Beam sigma_x,y [m] = " << options.beamSigmaTransverse_m << '\n'
740 << "Beam sigma_z [m] = " << options.beamSigmaLongitudinal_m
741 << " (internally clamped if zero)\n"
742 << "Beam sigma_x',y' [rad] = " << options.beamRmsAngle_rad << '\n'
743 << "Beam sigma_px,py [GeV] = " << sigmaPxGeV << '\n'
744 << "Beam sigma_E / E = " << options.beamRelativeEnergySpread << '\n'
745 << "Beam sigma_E [GeV] = " << sigmaEGeV << '\n'
746 << "Beam geometric emittance [m rad] = " << geomEmittance << '\n'
747 << "Beam macroparticles = " << options.beamParticles << '\n'
748 << "Seed = " << options.seed << '\n'
749 << "Area = " << LinearComptonBenchmark::histogramArea(histogram) << '\n'
750 << "Mean energy [GeV] = "
752 }
753 }
754
755 Options::seed = previousSeed;
756 ippl::finalize();
757 return 0;
758 }
759
760 if (options.joint) {
763 if (options.sampled) {
764 const int previousSeed = Options::seed;
765 Options::seed = options.seed;
766 histogram = LinearComptonBenchmark::sampleLabJointSpectrum(config, options.samples);
767 Options::seed = previousSeed;
768 } else {
770 }
771
772 LinearComptonBenchmark::writeJointCSV(histogram, options.output);
773
774 std::cout << "Wrote OPALX linear-Compton joint spectrum to " << options.output << '\n'
775 << "Mode = " << (options.sampled ? "sampled" : "deterministic") << '\n'
776 << "Observable = (E_gamma [GeV], theta_lab [rad])\n"
777 << "Area = " << LinearComptonBenchmark::jointHistogramArea(histogram) << '\n'
778 << "Mean energy [GeV] = "
780 << "Mean angle [rad] = "
782 if (options.sampled) {
783 std::cout << "Samples = " << options.samples << '\n'
784 << "Seed = " << options.seed << '\n';
785 }
786 return 0;
787 }
788
789 if (options.angular) {
792 if (options.sampled) {
793 const int previousSeed = Options::seed;
794 Options::seed = options.seed;
795 histogram = LinearComptonBenchmark::sampleLabAngularSpectrum(config, options.samples);
796 Options::seed = previousSeed;
797 } else {
799 }
800
801 LinearComptonBenchmark::writeAngleCSV(histogram, options.output);
802
803 std::cout << "Wrote OPALX linear-Compton angular spectrum to " << options.output << '\n'
804 << "Mode = " << (options.sampled ? "sampled" : "deterministic") << '\n'
805 << "Observable = theta_lab [rad]\n"
806 << "Area = " << LinearComptonBenchmark::angleHistogramArea(histogram) << '\n'
807 << "Mean angle [rad] = "
809 if (options.sampled) {
810 std::cout << "Samples = " << options.samples << '\n'
811 << "Seed = " << options.seed << '\n';
812 }
813 return 0;
814 }
815
818 if (options.sampled) {
819 const int previousSeed = Options::seed;
820 Options::seed = options.seed;
821 histogram = LinearComptonBenchmark::sampleLabSpectrum(config, options.samples);
822 Options::seed = previousSeed;
823 } else {
825 }
826
827 LinearComptonBenchmark::writeSpectrumCSV(histogram, options.output);
828
829 std::cout << "Wrote OPALX linear-Compton spectrum to " << options.output << '\n'
830 << "Mode = " << (options.sampled ? "sampled" : "deterministic") << '\n'
831 << "Observable = E_gamma [GeV]\n"
832 << "Area = " << LinearComptonBenchmark::histogramArea(histogram) << '\n'
833 << "Mean energy [GeV] = " << LinearComptonBenchmark::histogramMeanEnergyGeV(histogram)
834 << '\n';
835 if (options.sampled) {
836 std::cout << "Samples = " << options.samples << '\n' << "Seed = " << options.seed << '\n';
837 }
838 return 0;
839}
ippl::Vector< T, Dim > Vector_t
const int nr
int main(int argc, char **argv)
Entry point for the standalone linear-Compton benchmark executable.
ippl::FieldLayout< Dim > FieldLayout_t
Definition PBunchDefs.h:25
ippl::UniformCartesian< double, 3 > Mesh_t
Definition PBunchDefs.h:18
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
Definition Vector3D.cpp:95
A particle generation method following multivariate Gaussian distribution.
Container for all per-particle (and per-simulation) fields tracked during OPALX tracking.
double effectiveTemporalOverlapSigma(double beamSigmaS_m, double laserSigmaT_m)
double effectiveSpatialOverlapSigma(double beamSigma_m, double waist_m)
double histogramArea(const SpectrumHistogram &histogram)
void writeSpectrumCSV(const SpectrumHistogram &histogram, const std::filesystem::path &outputPath)
void writeAngleCSV(const AngleHistogram &histogram, const std::filesystem::path &outputPath)
JointHistogram integrateLabJointSpectrum(const JointConfig &config)
void writeJointCSV(const JointHistogram &histogram, const std::filesystem::path &outputPath)
double photonPolarAngleRad(const Vector_t< double, 3 > &photonDirection, const Vector_t< double, 3 > &beamDirection)
double angleHistogramMeanRad(const AngleHistogram &histogram)
double jointHistogramArea(const JointHistogram &histogram)
double angleHistogramArea(const AngleHistogram &histogram)
std::size_t jointHistogramIndex(const JointHistogram &histogram, std::size_t energyBin, std::size_t thetaBin)
SpectrumHistogram integrateLabSpectrum(const SpectrumConfig &config)
double jointHistogramMeanThetaRad(const JointHistogram &histogram)
double histogramMeanEnergyGeV(const SpectrumHistogram &histogram)
JointHistogram sampleLabJointSpectrum(const JointConfig &config, std::size_t sampleCount, std::uint64_t streamIndex=0)
SpectrumHistogram sampleLabSpectrum(const SpectrumConfig &config, std::size_t sampleCount, std::uint64_t streamIndex=0)
AngleHistogram integrateLabAngularSpectrum(const AngleConfig &config)
AngleHistogram sampleLabAngularSpectrum(const AngleConfig &config, std::size_t sampleCount, std::uint64_t streamIndex=0)
double jointHistogramMeanEnergyGeV(const JointHistogram &histogram)
int seed
The current random seed.
Definition Options.cpp:37
SamplingKernel makeSamplingKernel(double electronTotalEnergyGeV, double laserPhotonEnergyGeV, const Vector_t< double, 3 > &beamDirection, const Vector_t< double, 3 > &laserDirection)
Build a cached sampling kernel for repeated event generation.
std::mt19937_64 makeHostRandomEngine(std::uint64_t streamIndex)
Create a deterministic host-side random engine for Monte Carlo validation.
SampledEvent sampleEvent(const SamplingKernel &kernel, std::mt19937_64 &engine)
Sample one unpolarized linear-Compton event on the host.
double photonEnergyFromWavelengthGeV(double wavelength_m)
Convert a laser wavelength to a single-photon energy.
constexpr double m_e
The electron rest mass in GeV.
Definition Physics.h:93
constexpr double c
The velocity of light in m/s.
Definition Physics.h:60
constexpr double pi
The value of.
Definition Physics.h:36