1#ifndef OPALX_TEST_LINEAR_BREIT_WHEELER_BENCHMARK_COMMON_H
2#define OPALX_TEST_LINEAR_BREIT_WHEELER_BENCHMARK_COMMON_H
158 const double transverse = std::hypot(momentum(0), momentum(1));
159 return std::atan2(transverse, momentum(2));
171 :
event.electronEnergyLabGeV;
189 double centralEnergyGeV,
double relativeEnergySpread, std::mt19937_64& engine);
196 if (std::abs(referenceDirection(0)) > 0.9) {
200 axis1 =
cross(referenceDirection, axis1);
201 const double axis1Norm = std::sqrt(
dot(axis1, axis1));
203 axis2 =
cross(referenceDirection, axis1);
204 const double axis2Norm = std::sqrt(
dot(axis2, axis2));
210 double sigmaThetaYRad,
double sigmaX_m,
double sigmaY_m,
double sigmaS_m,
211 double centralEnergyGeV,
double relativeEnergySpread, std::mt19937_64& engine) {
212 std::normal_distribution<double> unitNormal(0.0, 1.0);
214 state.
slopeXRad = sigmaThetaXRad * unitNormal(engine);
215 state.
slopeYRad = sigmaThetaYRad * unitNormal(engine);
216 state.
x_m = sigmaX_m > 0.0 ? sigmaX_m * unitNormal(engine) : 0.0;
217 state.
y_m = sigmaY_m > 0.0 ? sigmaY_m * unitNormal(engine) : 0.0;
218 state.
s_m = sigmaS_m > 0.0 ? sigmaS_m * unitNormal(engine) : 0.0;
233 double sigmaThetaYRad, std::mt19937_64& engine) {
235 referenceDirection, sigmaThetaXRad, sigmaThetaYRad, 0.0, 0.0, 0.0, 1.0, 0.0,
241 if (beamSigma_m <= 0.0) {
244 if (waist_m <= 0.0) {
248 const double inverseVariance =
249 1.0 / (beamSigma_m * beamSigma_m) + 4.0 / (waist_m * waist_m);
250 return std::sqrt(1.0 / inverseVariance);
254 if (beamSigmaS_m <= 0.0) {
257 if (laserSigmaT_m <= 0.0) {
261 const double inverseVariance =
262 1.0 / (beamSigmaS_m * beamSigmaS_m) + 1.0 / (laserSigmaT_m * laserSigmaT_m);
263 return std::sqrt(1.0 / inverseVariance);
268 constexpr double pi = 3.14159265358979323846;
269 const double waistX_m =
273 const double waistY_m =
287 constexpr double pi = 3.14159265358979323846;
288 const double waistX_m =
292 const double waistY_m =
305 double centralEnergyGeV,
double relativeEnergySpread, std::mt19937_64& engine) {
306 if (relativeEnergySpread <= 0.0) {
307 return centralEnergyGeV;
310 std::normal_distribution<double> unitNormal(0.0, 1.0);
311 double energyGeV = centralEnergyGeV;
313 energyGeV = centralEnergyGeV * (1.0 + relativeEnergySpread * unitNormal(engine));
314 }
while (energyGeV <= 0.0);
329 std::size_t sampleCount, std::uint64_t streamIndex = 0) {
330 if (config.
bins == 0) {
331 throw std::runtime_error(
332 "LinearBreitWheelerBenchmark: number of bins must be positive.");
335 throw std::runtime_error(
336 "LinearBreitWheelerBenchmark: histogram range must satisfy max > min.");
338 if (sampleCount == 0) {
339 throw std::runtime_error(
"LinearBreitWheelerBenchmark: sample count must be positive.");
347 for (std::size_t i = 0; i < config.
bins; ++i) {
352 const double laserPhotonEnergyGeV =
357 for (std::size_t i = 0; i < sampleCount; ++i) {
358 const auto beamState =
366 constexpr double overlapWeight = 1.0;
369 beamState.energyGeV, laserPhotonEnergyGeV, beamState.direction,
373 if (value < config.minValue || value >= config.
maxValue) {
376 const std::size_t bin =
378 if (bin < histogram.
counts.size()) {
379 histogram.
counts[bin] += overlapWeight;
384 throw std::runtime_error(
385 "LinearBreitWheelerBenchmark: zero accepted finite-photon-beam events.");
387 for (std::size_t i = 0; i < histogram.
counts.size(); ++i) {
405 std::size_t sampleCount, std::uint64_t streamIndex = 0) {
406 if (config.
bins == 0) {
407 throw std::runtime_error(
408 "LinearBreitWheelerBenchmark: number of bins must be positive.");
411 throw std::runtime_error(
412 "LinearBreitWheelerBenchmark: histogram range must satisfy max > min.");
414 if (sampleCount == 0) {
415 throw std::runtime_error(
"LinearBreitWheelerBenchmark: sample count must be positive.");
423 for (std::size_t i = 0; i < config.
bins; ++i) {
428 const double laserPhotonEnergyGeV =
435 histogram.
totalWeight =
static_cast<double>(sampleCount);
436 for (std::size_t i = 0; i < sampleCount; ++i) {
439 if (value < config.minValue || value >= config.
maxValue) {
442 const std::size_t bin =
444 if (bin < histogram.
counts.size()) {
445 histogram.
counts[bin] += 1.0;
449 for (std::size_t i = 0; i < histogram.
counts.size(); ++i) {
467 std::uint64_t streamIndex = 0) {
469 throw std::runtime_error(
470 "LinearBreitWheelerBenchmark: joint histogram bin counts must be positive.");
474 throw std::runtime_error(
475 "LinearBreitWheelerBenchmark: joint histogram ranges must satisfy max > min.");
477 if (sampleCount == 0) {
478 throw std::runtime_error(
"LinearBreitWheelerBenchmark: sample count must be positive.");
492 for (std::size_t i = 0; i < config.
energyBins; ++i) {
497 for (std::size_t j = 0; j < config.
thetaBins; ++j) {
503 const double laserPhotonEnergyGeV =
508 for (std::size_t i = 0; i < sampleCount; ++i) {
509 const auto beamState =
517 constexpr double overlapWeight = 1.0;
520 beamState.energyGeV, laserPhotonEnergyGeV, beamState.direction,
525 if (energyGeV < config.energyMinGeV || energyGeV >= config.
energyMaxGeV
526 || thetaRad < config.thetaMinRad || thetaRad >= config.
thetaMaxRad) {
530 const std::size_t ie =
static_cast<std::size_t
>(
532 const std::size_t jt =
static_cast<std::size_t
>(
534 if (ie < histogram.
counts.size() && jt < histogram.
counts[ie].size()) {
535 histogram.
counts[ie][jt] += overlapWeight;
540 throw std::runtime_error(
541 "LinearBreitWheelerBenchmark: zero accepted finite-photon-beam events.");
544 for (std::size_t i = 0; i < histogram.
counts.size(); ++i) {
545 for (std::size_t j = 0; j < histogram.
counts[i].size(); ++j) {
556 std::uint64_t streamIndex = 0) {
558 throw std::runtime_error(
559 "LinearBreitWheelerBenchmark: joint histogram bin counts must be positive.");
563 throw std::runtime_error(
564 "LinearBreitWheelerBenchmark: joint histogram ranges must satisfy max > min.");
566 if (sampleCount == 0) {
567 throw std::runtime_error(
"LinearBreitWheelerBenchmark: sample count must be positive.");
581 for (std::size_t i = 0; i < config.
energyBins; ++i) {
586 for (std::size_t j = 0; j < config.
thetaBins; ++j) {
592 const double laserPhotonEnergyGeV =
599 histogram.
totalWeight =
static_cast<double>(sampleCount);
600 for (std::size_t i = 0; i < sampleCount; ++i) {
604 if (energyGeV < config.energyMinGeV || energyGeV >= config.
energyMaxGeV
605 || thetaRad < config.thetaMinRad || thetaRad >= config.
thetaMaxRad) {
609 const std::size_t ie =
static_cast<std::size_t
>(
611 const std::size_t jt =
static_cast<std::size_t
>(
613 if (ie < histogram.
counts.size() && jt < histogram.
counts[ie].size()) {
614 histogram.
counts[ie][jt] += 1.0;
619 for (std::size_t i = 0; i < histogram.
counts.size(); ++i) {
620 for (std::size_t j = 0; j < histogram.
counts[i].size(); ++j) {
630 const Histogram& histogram,
const std::filesystem::path& outputPath) {
631 std::ofstream output(outputPath);
633 throw std::runtime_error(
634 "LinearBreitWheelerBenchmark: unable to open output file "
635 + outputPath.string());
637 output <<
"# center,density,count\n";
638 output.precision(17);
639 for (std::size_t i = 0; i < histogram.
centers.size(); ++i) {
640 output << histogram.
centers[i] <<
',' << histogram.
density[i] <<
','
641 << histogram.
counts[i] <<
'\n';
646 const JointHistogram& histogram,
const std::filesystem::path& outputPath) {
647 std::ofstream output(outputPath);
649 throw std::runtime_error(
650 "LinearBreitWheelerBenchmark: unable to open output file "
651 + outputPath.string());
653 output <<
"# energy_center_GeV,theta_center_rad,density_per_GeV_rad,count\n";
654 output.precision(17);
666 std::ifstream input(inputPath);
668 throw std::runtime_error(
669 "LinearBreitWheelerBenchmark: unable to open input file " + inputPath.string());
673 while (std::getline(input, line)) {
674 if (line.empty() || line[0] ==
'#') {
677 std::stringstream parser(line);
679 std::vector<double> values;
680 while (std::getline(parser, field,
',')) {
681 values.push_back(std::stod(field));
683 if (values.size() < 2) {
686 histogram.
centers.push_back(values[0]);
687 histogram.
density.push_back(values[1]);
688 histogram.
counts.push_back(values.size() >= 3 ? values[2] : 0.0);
691 if (histogram.
centers.size() >= 2) {
695 for (
double value : histogram.
density) {
703 std::ifstream input(inputPath);
705 throw std::runtime_error(
706 "LinearBreitWheelerBenchmark: unable to open input file " + inputPath.string());
710 double energyCenter = 0.0;
711 double thetaCenter = 0.0;
712 double density = 0.0;
715 std::vector<Row> rows;
717 while (std::getline(input, line)) {
718 if (line.empty() || line[0] ==
'#') {
721 std::stringstream parser(line);
723 std::vector<double> values;
724 while (std::getline(parser, field,
',')) {
725 values.push_back(std::stod(field));
727 if (values.size() < 3) {
731 Row{values[0], values[1], values[2], values.size() >= 4 ? values[3] : 0.0});
736 bool seenTheta =
false;
738 if (existing == values[1]) {
754 for (
const auto& row : rows) {
767 histogram.
counts[i][j] = row.count;
781 for (
double value : row) {
791 for (
double density : histogram.
density) {
792 area += density * histogram.
binWidth;
799 for (std::size_t i = 0; i < histogram.
centers.size(); ++i) {
807 throw std::runtime_error(
"LinearBreitWheelerBenchmark: histogram sizes do not match.");
809 double distance = 0.0;
810 for (std::size_t i = 0; i < lhs.
centers.size(); ++i) {
819 for (
double density : row) {
851 throw std::runtime_error(
852 "LinearBreitWheelerBenchmark: joint histogram sizes do not match.");
854 double distance = 0.0;
ippl::Vector< T, Dim > Vector_t
Vector3D cross(const Vector3D &lhs, const Vector3D &rhs)
Vector cross product.
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
Vector_t< double, 3 > direction
std::vector< double > thetaCentersRad
double effectiveTemporalOverlapSigma(double beamSigmaS_m, double laserSigmaT_m)
double histogramMean(const Histogram &histogram)
Vector_t< double, 3 > laserDirection
double highEnergyPhotonEnergyGeV
Vector_t< double, 3 > highEnergyDirection
double sampledObservable(const Physics::LinearBreitWheeler::SampledEvent &event, FinalState state, Observable observable)
double histogramL1Distance(const Histogram &lhs, const Histogram &rhs)
double relativeEnergySpread
double polarAngleRad(const Vector_t< double, 3 > &momentum)
JointHistogram sampleFinitePhotonBeamJointHistogram(const FinitePhotonBeamJointConfig &config, FinalState state, std::size_t sampleCount, std::uint64_t streamIndex=0)
Sample the joint Breit-Wheeler laboratory distribution in energy and polar angle.
void writeJointHistogramCSV(const JointHistogram &histogram, const std::filesystem::path &outputPath)
Histogram sampleFinitePhotonBeamHistogram(const FinitePhotonBeamConfig &config, FinalState state, Observable observable, std::size_t sampleCount, std::uint64_t streamIndex=0)
Sample a one-dimensional Breit-Wheeler histogram for a finite incoming photon beam.
Vector_t< double, 3 > highEnergyDirection
double relativeEnergySpread
Vector_t< double, 3 > laserDirection
void buildTransverseBasis(const Vector_t< double, 3 > &referenceDirection, Vector_t< double, 3 > &axis1, Vector_t< double, 3 > &axis2)
Histogram sampleHistogram(const HistogramConfig &config, FinalState state, Observable observable, std::size_t sampleCount, std::uint64_t streamIndex=0)
Sample a one-dimensional Breit-Wheeler benchmark histogram.
std::vector< double > centers
Vector_t< double, 3 > referenceHighEnergyDirection
Vector_t< double, 3 > samplePhotonBeamDirection(const Vector_t< double, 3 > &referenceDirection, double sigmaThetaXRad, double sigmaThetaYRad, std::mt19937_64 &engine)
std::vector< std::vector< double > > counts
SampledPhotonBeamState samplePhotonBeamState(const Vector_t< double, 3 > &referenceDirection, double sigmaThetaXRad, double sigmaThetaYRad, double sigmaX_m, double sigmaY_m, double sigmaS_m, double centralEnergyGeV, double relativeEnergySpread, std::mt19937_64 &engine)
Vector_t< double, 3 > referenceHighEnergyDirection
double sampleHighEnergyPhotonEnergyGeV(double centralEnergyGeV, double relativeEnergySpread, std::mt19937_64 &engine)
JointHistogram sampleJointHistogram(const JointHistogramConfig &config, FinalState state, std::size_t sampleCount, std::uint64_t streamIndex=0)
Vector_t< double, 3 > laserDirection
double centralHighEnergyPhotonEnergyGeV
double jointHistogramMeanEnergyGeV(const JointHistogram &histogram)
SampledPhotonBeamState sampleOverlapPhotonBeamState(const FinitePhotonBeamConfig &config, std::mt19937_64 &engine)
std::vector< double > density
Vector_t< double, 3 > laserDirection
double histogramArea(const Histogram &histogram)
JointHistogram readJointHistogramCSV(const std::filesystem::path &inputPath)
double effectiveSpatialOverlapSigma(double beamSigma_m, double waist_m)
double jointHistogramL1Distance(const JointHistogram &lhs, const JointHistogram &rhs)
double jointHistogramArea(const JointHistogram &histogram)
std::vector< double > energyCentersGeV
Histogram readHistogramCSV(const std::filesystem::path &inputPath)
std::vector< double > counts
double highEnergyPhotonEnergyGeV
double centralHighEnergyPhotonEnergyGeV
void writeHistogramCSV(const Histogram &histogram, const std::filesystem::path &outputPath)
std::vector< std::vector< double > > densityPerGeVRad
double jointHistogramMeanThetaRad(const JointHistogram &histogram)
Configuration for folding the linear Breit-Wheeler kernel over a finite incoming photon beam.
Vector_t< double, 3 > electronMomentumLabGeV
Outgoing electron three-momentum in the lab frame [GeV/c].
SampledEvent sampleEvent(const SamplingKernel &kernel, std::mt19937_64 &engine)
Sample one unpolarized linear Breit-Wheeler event.
SamplingKernel makeSamplingKernel(double highEnergyPhotonEnergyGeV, double laserPhotonEnergyGeV, const Vector_t< double, 3 > &highEnergyDirection, const Vector_t< double, 3 > &laserDirection)
Build a cached sampling kernel for repeated sampled events.
Vector_t< double, 3 > positronMomentumLabGeV
Outgoing positron three-momentum in the lab frame [GeV/c].
std::mt19937_64 makeHostRandomEngine(std::uint64_t streamIndex)
Build a deterministic host RNG from Options::seed.
double photonEnergyFromWavelengthGeV(double wavelength_m)
Convert a laser wavelength to a single-photon energy.
One sampled linear Breit-Wheeler event.
constexpr double c
The velocity of light in m/s.