32 std::filesystem::path output =
"opalx-linear-compton-90deg-xi029-spectrum.csv";
36 bool finiteBeam =
false;
37 bool overlapWeighting =
false;
38 std::size_t samples = 250000;
39 std::size_t beamParticles = 250000;
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;
57 CliOptions parseArguments(
int argc,
char** argv) {
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") {
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") {
74 throw std::runtime_error(
"Missing value after --samples");
76 options.samples =
static_cast<std::size_t
>(std::stoull(argv[++i]));
77 }
else if (arg ==
"--beam-particles") {
79 throw std::runtime_error(
"Missing value after --beam-particles");
81 options.beamParticles =
static_cast<std::size_t
>(std::stoull(argv[++i]));
82 }
else if (arg ==
"--seed") {
84 throw std::runtime_error(
"Missing value after --seed");
86 options.seed = std::stoi(argv[++i]);
87 }
else if (arg ==
"--beam-sigma-transverse") {
89 throw std::runtime_error(
"Missing value after --beam-sigma-transverse");
91 options.beamSigmaTransverse_m = std::stod(argv[++i]);
92 }
else if (arg ==
"--beam-sigma-longitudinal") {
94 throw std::runtime_error(
"Missing value after --beam-sigma-longitudinal");
96 options.beamSigmaLongitudinal_m = std::stod(argv[++i]);
97 }
else if (arg ==
"--beam-rms-angle") {
99 throw std::runtime_error(
"Missing value after --beam-rms-angle");
101 options.beamRmsAngle_rad = std::stod(argv[++i]);
102 }
else if (arg ==
"--beam-relative-energy-spread") {
104 throw std::runtime_error(
"Missing value after --beam-relative-energy-spread");
106 options.beamRelativeEnergySpread = std::stod(argv[++i]);
107 }
else if (arg ==
"--laser-rayleigh") {
109 throw std::runtime_error(
"Missing value after --laser-rayleigh");
111 options.laserRayleigh_m = std::stod(argv[++i]);
112 }
else if (arg ==
"--laser-sigma-t") {
114 throw std::runtime_error(
"Missing value after --laser-sigma-t");
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 "
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: "
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 "
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 "
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 "
152 }
else if (!arg.empty() && arg[0] ==
'-') {
153 throw std::runtime_error(
"Unknown option: " + arg);
155 options.output = arg;
172 void preallocateParticleCapacity(
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;
178 pc->allocateParticles(maxLocalNum);
194 if (beamSigma_m <= 0.0) {
197 if (waist_m <= 0.0) {
201 const double inverseVariance =
202 1.0 / (beamSigma_m * beamSigma_m) + 4.0 / (waist_m * waist_m);
203 return std::sqrt(1.0 / inverseVariance);
207 if (beamSigmaS_m <= 0.0) {
210 if (laserSigmaT_m <= 0.0) {
214 const double inverseVariance =
215 1.0 / (beamSigmaS_m * beamSigmaS_m) + 1.0 / (laserSigmaT_m * laserSigmaT_m);
216 return std::sqrt(1.0 / inverseVariance);
220 if (!options.overlapWeighting) {
222 options.beamSigmaTransverse_m, options.beamSigmaTransverse_m,
223 std::max(1.0e-15, options.beamSigmaLongitudinal_m));
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)
235 options.beamSigmaLongitudinal_m, options.laserSigmaT_m)));
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);
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};
256 ippl::NDIndex<3> domain;
257 for (
unsigned i = 0; i < 3; ++i) {
258 domain[i] = ippl::Index(
nr[i]);
261 auto fc = std::make_shared<FieldContainer_t>(hr, rmin, rmax, decomp, domain, origin,
true);
266 return std::make_shared<ParticleContainer<double, 3>>(mesh, fl);
287 if (options.beamSigmaTransverse_m <= 0.0) {
288 throw std::runtime_error(
289 "Finite-beam benchmark requires positive transverse beam size.");
291 if (options.beamRmsAngle_rad <= 0.0) {
292 throw std::runtime_error(
293 "Finite-beam benchmark requires positive transverse RMS angle.");
295 if (options.beamRelativeEnergySpread < 0.0) {
296 throw std::runtime_error(
297 "Finite-beam benchmark requires nonnegative relative energy spread.");
300 const double p0GeV = std::sqrt(
303 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
306 const double sigmaPzGeV =
307 std::max(1.0e-15, sigmaEGeV > 0.0 ? sigmaEGeV / beta0 : 1.0e-12 * p0GeV);
316 auto pc = makeParticleContainer(sigmaR);
317 preallocateParticleCapacity(pc, options.beamParticles);
318 ippl::Vector<int, 3>
nr = 32;
321 pc, meanR, meanP, sigmaR, sigmaP, cutoffR, cutoffP,
true,
true);
322 std::size_t numberOfParticles = options.beamParticles;
323 sampler.generateParticles(numberOfParticles,
nr);
331 for (std::size_t i = 0; i < config.
bins; ++i) {
336 auto rView = pc->R.getView();
337 auto pView = pc->P.getView();
339 const double laserPhotonEnergyGeV =
342 histogram.
totalWeight =
static_cast<double>(pc->getLocalNum());
343 for (std::size_t i = 0; i < pc->getLocalNum(); ++i) {
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) {
354 beamDirection /= momentum;
357 electronTotalEnergyGeV, laserPhotonEnergyGeV, beamDirection,
360 const double energyGeV =
event.scatteredPhotonEnergyLabGeV;
361 if (energyGeV < config.energyMinGeV || energyGeV >= config.
energyMaxGeV) {
364 const std::size_t bin =
static_cast<std::size_t
>(
366 if (bin < histogram.
counts.size()) {
367 histogram.
counts[bin] += 1.0;
371 double globalTotalWeight = 0.0;
373 &histogram.
totalWeight, &globalTotalWeight, 1, MPI_DOUBLE, MPI_SUM,
374 ippl::Comm->getCommunicator());
377 std::vector<double> globalCounts(histogram.
counts.size(), 0.0);
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);
384 for (std::size_t i = 0; i < histogram.
densityPerGeV.size(); ++i) {
402 if (options.beamSigmaTransverse_m <= 0.0) {
403 throw std::runtime_error(
404 "Finite-beam benchmark requires positive transverse beam size.");
406 if (options.beamRmsAngle_rad <= 0.0) {
407 throw std::runtime_error(
408 "Finite-beam benchmark requires positive transverse RMS angle.");
410 if (options.beamRelativeEnergySpread < 0.0) {
411 throw std::runtime_error(
412 "Finite-beam benchmark requires nonnegative relative energy spread.");
415 const double p0GeV = std::sqrt(
418 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
421 const double sigmaPzGeV =
422 std::max(1.0e-15, sigmaEGeV > 0.0 ? sigmaEGeV / beta0 : 1.0e-12 * p0GeV);
431 auto pc = makeParticleContainer(sigmaR);
432 preallocateParticleCapacity(pc, options.beamParticles);
433 ippl::Vector<int, 3>
nr = 32;
436 pc, meanR, meanP, sigmaR, sigmaP, cutoffR, cutoffP,
true,
true);
437 std::size_t numberOfParticles = options.beamParticles;
438 sampler.generateParticles(numberOfParticles,
nr);
446 for (std::size_t i = 0; i < config.
bins; ++i) {
451 auto pView = pc->P.getView();
453 const double laserPhotonEnergyGeV =
456 histogram.
totalWeight =
static_cast<double>(pc->getLocalNum());
457 for (std::size_t i = 0; i < pc->getLocalNum(); ++i) {
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) {
467 beamDirection /= momentum;
470 electronTotalEnergyGeV, laserPhotonEnergyGeV, beamDirection,
475 if (thetaRad < config.thetaMinRad || thetaRad >= config.
thetaMaxRad) {
478 const std::size_t bin =
static_cast<std::size_t
>(
480 if (bin < histogram.
counts.size()) {
481 histogram.
counts[bin] += 1.0;
485 double globalTotalWeight = 0.0;
487 &histogram.
totalWeight, &globalTotalWeight, 1, MPI_DOUBLE, MPI_SUM,
488 ippl::Comm->getCommunicator());
491 std::vector<double> globalCounts(histogram.
counts.size(), 0.0);
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);
498 for (std::size_t i = 0; i < histogram.
densityPerRad.size(); ++i) {
506 if (options.beamSigmaTransverse_m <= 0.0) {
507 throw std::runtime_error(
508 "Finite-beam benchmark requires positive transverse beam size.");
510 if (options.beamRmsAngle_rad <= 0.0) {
511 throw std::runtime_error(
512 "Finite-beam benchmark requires positive transverse RMS angle.");
514 if (options.beamRelativeEnergySpread < 0.0) {
515 throw std::runtime_error(
516 "Finite-beam benchmark requires nonnegative relative energy spread.");
519 const double p0GeV = std::sqrt(
522 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
525 const double sigmaPzGeV =
526 std::max(1.0e-15, sigmaEGeV > 0.0 ? sigmaEGeV / beta0 : 1.0e-12 * p0GeV);
536 auto pc = makeParticleContainer(sigmaR);
537 preallocateParticleCapacity(pc, options.beamParticles);
538 ippl::Vector<int, 3>
nr = 32;
541 pc, meanR, meanP, sigmaR, sigmaP, cutoffR, cutoffP,
true,
true);
542 std::size_t numberOfParticles = options.beamParticles;
543 sampler.generateParticles(numberOfParticles,
nr);
554 for (std::size_t i = 0; i < config.
energyBins; ++i) {
559 for (std::size_t j = 0; j < config.
thetaBins; ++j) {
565 auto pView = pc->P.getView();
567 const double laserPhotonEnergyGeV =
570 histogram.
totalWeight =
static_cast<double>(pc->getLocalNum());
571 for (std::size_t i = 0; i < pc->getLocalNum(); ++i) {
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) {
581 beamDirection /= momentum;
584 electronTotalEnergyGeV, laserPhotonEnergyGeV, beamDirection,
587 const double energyGeV =
event.scatteredPhotonEnergyLabGeV;
590 if (energyGeV < config.energyMinGeV || energyGeV >= config.
energyMaxGeV
591 || thetaRad < config.thetaMinRad || thetaRad >= config.
thetaMaxRad) {
594 const std::size_t energyBin =
static_cast<std::size_t
>(
596 const std::size_t thetaBin =
static_cast<std::size_t
>(
601 histogram, energyBin, thetaBin)] += 1.0;
605 double globalTotalWeight = 0.0;
607 &histogram.
totalWeight, &globalTotalWeight, 1, MPI_DOUBLE, MPI_SUM,
608 ippl::Comm->getCommunicator());
611 std::vector<double> globalCounts(histogram.
counts.size(), 0.0);
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);
641int main(
int argc,
char** argv) {
642 const CliOptions options = parseArguments(argc, argv);
644 if (options.finiteBeam) {
646 char** ipplArgv = argv;
647 ippl::initialize(ipplArgc, ipplArgv);
654 const auto histogram = sampleFiniteBeamJointSpectrum(config, options);
656 if (ippl::Comm->rank() == 0) {
657 const double p0GeV = std::sqrt(
660 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
661 const double sigmaEGeV =
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"
685 <<
"Mean energy [GeV] = "
687 <<
"Mean angle [rad] = "
690 }
else if (options.angular) {
692 const auto histogram = sampleFiniteBeamAngularSpectrum(config, options);
694 if (ippl::Comm->rank() == 0) {
695 const double p0GeV = std::sqrt(
698 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
699 const double sigmaEGeV =
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'
719 <<
"Mean angle [rad] = "
724 const auto histogram = sampleFiniteBeamEnergySpectrum(config, options);
726 if (ippl::Comm->rank() == 0) {
727 const double p0GeV = std::sqrt(
730 const double sigmaPxGeV = p0GeV * options.beamRmsAngle_rad;
731 const double sigmaEGeV =
733 const double geomEmittance =
734 options.beamSigmaTransverse_m * options.beamRmsAngle_rad;
735 std::cout <<
"Wrote OPALX finite-beam linear-Compton spectrum to " << options.output
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'
750 <<
"Mean energy [GeV] = "
763 if (options.sampled) {
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"
778 <<
"Mean energy [GeV] = "
780 <<
"Mean angle [rad] = "
782 if (options.sampled) {
783 std::cout <<
"Samples = " << options.samples <<
'\n'
784 <<
"Seed = " << options.seed <<
'\n';
789 if (options.angular) {
792 if (options.sampled) {
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"
807 <<
"Mean angle [rad] = "
809 if (options.sampled) {
810 std::cout <<
"Samples = " << options.samples <<
'\n'
811 <<
"Seed = " << options.seed <<
'\n';
818 if (options.sampled) {
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"
835 if (options.sampled) {
836 std::cout <<
"Samples = " << options.samples <<
'\n' <<
"Seed = " << options.seed <<
'\n';
ippl::Vector< T, Dim > Vector_t
int main(int argc, char **argv)
Entry point for the standalone linear-Compton benchmark executable.
ippl::FieldLayout< Dim > FieldLayout_t
ippl::UniformCartesian< double, 3 > Mesh_t
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
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)
std::vector< double > energyCentersGeV
void writeAngleCSV(const AngleHistogram &histogram, const std::filesystem::path &outputPath)
Vector_t< double, 3 > laserDirection
Vector_t< double, 3 > laserDirection
double electronTotalEnergyGeV
JointHistogram integrateLabJointSpectrum(const JointConfig &config)
std::vector< double > densityPerGeVRad
std::vector< double > centersGeV
void writeJointCSV(const JointHistogram &histogram, const std::filesystem::path &outputPath)
std::vector< double > densityPerRad
double photonPolarAngleRad(const Vector_t< double, 3 > &photonDirection, const Vector_t< double, 3 > &beamDirection)
std::vector< double > thetaCentersRad
double angleHistogramMeanRad(const AngleHistogram &histogram)
Vector_t< double, 3 > beamDirection
Vector_t< double, 3 > laserDirection
double jointHistogramArea(const JointHistogram &histogram)
double angleHistogramArea(const AngleHistogram &histogram)
std::vector< double > densityPerGeV
std::size_t jointHistogramIndex(const JointHistogram &histogram, std::size_t energyBin, std::size_t thetaBin)
Vector_t< double, 3 > beamDirection
std::vector< double > counts
SpectrumHistogram integrateLabSpectrum(const SpectrumConfig &config)
std::vector< double > counts
double jointHistogramMeanThetaRad(const JointHistogram &histogram)
double histogramMeanEnergyGeV(const SpectrumHistogram &histogram)
std::vector< double > centersRad
double electronTotalEnergyGeV
JointHistogram sampleLabJointSpectrum(const JointConfig &config, std::size_t sampleCount, std::uint64_t streamIndex=0)
double electronTotalEnergyGeV
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)
std::vector< double > counts
int seed
The current random seed.
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.
constexpr double c
The velocity of light in m/s.
constexpr double pi
The value of.