1#ifndef OPALX_TEST_LINEAR_COMPTON_SPECTRUM_COMMON_H
2#define OPALX_TEST_LINEAR_COMPTON_SPECTRUM_COMMON_H
56 for (std::size_t i = 0; i < config.
bins; ++i) {
61 const double laserPhotonEnergyGeV =
63 const double incomingPhotonEnergyERFGeV =
68 const double dMu = 2.0 /
static_cast<double>(config.
cosinePanels);
72 const double mu = -1.0 + (
static_cast<double>(i) + 0.5) * dMu;
74 incomingPhotonEnergyERFGeV, mu);
77 const double phi = (
static_cast<double>(j) + 0.5) * dPhi;
81 const double weight = kernel * dMu * dPhi;
84 if (energyGeV < config.energyMinGeV || energyGeV >= config.
energyMaxGeV) {
88 const std::size_t bin =
static_cast<std::size_t
>(
90 if (bin < histogram.
counts.size()) {
91 histogram.
counts[bin] += weight;
97 throw std::runtime_error(
98 "LinearComptonBenchmark: total histogram weight is not positive.");
101 for (std::size_t i = 0; i < histogram.
densityPerGeV.size(); ++i) {
110 const SpectrumConfig& config, std::size_t sampleCount, std::uint64_t streamIndex = 0) {
118 for (std::size_t i = 0; i < config.
bins; ++i) {
123 const double laserPhotonEnergyGeV =
130 histogram.
totalWeight =
static_cast<double>(sampleCount);
131 for (std::size_t i = 0; i < sampleCount; ++i) {
133 const double energyGeV =
event.scatteredPhotonEnergyLabGeV;
134 if (energyGeV < config.energyMinGeV || energyGeV >= config.
energyMaxGeV) {
138 const std::size_t bin =
static_cast<std::size_t
>(
140 if (bin < histogram.
counts.size()) {
141 histogram.
counts[bin] += 1.0;
146 throw std::runtime_error(
"LinearComptonBenchmark: sample count must be positive.");
149 for (std::size_t i = 0; i < histogram.
densityPerGeV.size(); ++i) {
159 std::ofstream output(outputPath);
161 throw std::runtime_error(
162 "LinearComptonBenchmark: unable to open output file " + outputPath.string());
165 output <<
"# center_GeV,density_per_GeV,count\n";
166 output << std::setprecision(17);
167 for (std::size_t i = 0; i < histogram.
centersGeV.size(); ++i) {
169 << histogram.
counts[i] <<
'\n';
175 std::ifstream input(inputPath);
177 throw std::runtime_error(
178 "LinearComptonBenchmark: unable to open input file " + inputPath.string());
182 while (std::getline(input, line)) {
183 if (line.empty() || line[0] ==
'#') {
187 std::stringstream parser(line);
189 std::vector<double> fields;
190 while (std::getline(parser, field,
',')) {
191 fields.push_back(std::stod(field));
193 if (fields.size() < 2) {
199 histogram.
counts.push_back(fields.size() >= 3 ? fields[2] : 0.0);
215 for (std::size_t i = 0; i < histogram.
centersGeV.size(); ++i) {
231 throw std::runtime_error(
"LinearComptonBenchmark: histogram sizes do not match.");
234 double distance = 0.0;
235 for (std::size_t i = 0; i < lhs.
centersGeV.size(); ++i) {
271 const double beamNorm = std::sqrt(
dot(beamDirection, beamDirection));
272 const double photonNorm = std::sqrt(
dot(photonDirection, photonDirection));
273 if (beamNorm <= 0.0 || photonNorm <= 0.0) {
274 throw std::runtime_error(
275 "LinearComptonBenchmark: beam/photon direction norm must be positive.");
277 const double cosine = std::max(
278 -1.0, std::min(1.0,
dot(photonDirection, beamDirection) / (beamNorm * photonNorm)));
279 return std::acos(cosine);
290 for (std::size_t i = 0; i < config.
bins; ++i) {
295 const double laserPhotonEnergyGeV =
297 const double incomingPhotonEnergyERFGeV =
302 const double dMu = 2.0 /
static_cast<double>(config.
cosinePanels);
306 const double mu = -1.0 + (
static_cast<double>(i) + 0.5) * dMu;
308 incomingPhotonEnergyERFGeV, mu);
311 const double phi = (
static_cast<double>(j) + 0.5) * dPhi;
316 const double weight = kernel * dMu * dPhi;
319 if (thetaRad < config.thetaMinRad || thetaRad >= config.
thetaMaxRad) {
323 const std::size_t bin =
static_cast<std::size_t
>(
325 if (bin < histogram.
counts.size()) {
326 histogram.
counts[bin] += weight;
332 throw std::runtime_error(
333 "LinearComptonBenchmark: angular histogram total weight is not positive.");
336 for (std::size_t i = 0; i < histogram.
densityPerRad.size(); ++i) {
345 const AngleConfig& config, std::size_t sampleCount, std::uint64_t streamIndex = 0) {
353 for (std::size_t i = 0; i < config.
bins; ++i) {
358 const double laserPhotonEnergyGeV =
365 histogram.
totalWeight =
static_cast<double>(sampleCount);
366 for (std::size_t i = 0; i < sampleCount; ++i) {
368 const double thetaRad =
370 if (thetaRad < config.thetaMinRad || thetaRad >= config.
thetaMaxRad) {
374 const std::size_t bin =
static_cast<std::size_t
>(
376 if (bin < histogram.
counts.size()) {
377 histogram.
counts[bin] += 1.0;
382 throw std::runtime_error(
383 "LinearComptonBenchmark: angular sample count must be positive.");
386 for (std::size_t i = 0; i < histogram.
densityPerRad.size(); ++i) {
395 const AngleHistogram& histogram,
const std::filesystem::path& outputPath) {
396 std::ofstream output(outputPath);
398 throw std::runtime_error(
399 "LinearComptonBenchmark: unable to open angular output file "
400 + outputPath.string());
403 output <<
"# center_rad,density_per_rad,count\n";
404 output << std::setprecision(17);
405 for (std::size_t i = 0; i < histogram.
centersRad.size(); ++i) {
407 << histogram.
counts[i] <<
'\n';
413 std::ifstream input(inputPath);
415 throw std::runtime_error(
416 "LinearComptonBenchmark: unable to open angular input file "
417 + inputPath.string());
421 while (std::getline(input, line)) {
422 if (line.empty() || line[0] ==
'#') {
426 std::stringstream parser(line);
428 std::vector<double> fields;
429 while (std::getline(parser, field,
',')) {
430 fields.push_back(std::stod(field));
432 if (fields.size() < 2) {
438 histogram.
counts.push_back(fields.size() >= 3 ? fields[2] : 0.0);
454 for (std::size_t i = 0; i < histogram.
centersRad.size(); ++i) {
470 throw std::runtime_error(
471 "LinearComptonBenchmark: angular histogram sizes do not match.");
474 double distance = 0.0;
475 for (std::size_t i = 0; i < lhs.
centersRad.size(); ++i) {
515 const JointHistogram& histogram, std::size_t energyBin, std::size_t thetaBin) {
530 for (std::size_t i = 0; i < config.
energyBins; ++i) {
535 for (std::size_t j = 0; j < config.
thetaBins; ++j) {
541 const double laserPhotonEnergyGeV =
543 const double incomingPhotonEnergyERFGeV =
548 const double dMu = 2.0 /
static_cast<double>(config.
cosinePanels);
552 const double mu = -1.0 + (
static_cast<double>(i) + 0.5) * dMu;
554 incomingPhotonEnergyERFGeV, mu);
557 const double phi = (
static_cast<double>(j) + 0.5) * dPhi;
565 const double weight = kernel * dMu * dPhi;
568 if (energyGeV < config.energyMinGeV || energyGeV >= config.
energyMaxGeV
569 || thetaRad < config.thetaMinRad || thetaRad >= config.
thetaMaxRad) {
573 const std::size_t energyBin =
static_cast<std::size_t
>(
575 const std::size_t thetaBin =
static_cast<std::size_t
>(
585 throw std::runtime_error(
586 "LinearComptonBenchmark: joint histogram total weight is not positive.");
599 const JointConfig& config, std::size_t sampleCount, std::uint64_t streamIndex = 0) {
610 for (std::size_t i = 0; i < config.
energyBins; ++i) {
615 for (std::size_t j = 0; j < config.
thetaBins; ++j) {
621 const double laserPhotonEnergyGeV =
628 histogram.
totalWeight =
static_cast<double>(sampleCount);
629 for (std::size_t i = 0; i < sampleCount; ++i) {
631 const double energyGeV =
event.scatteredPhotonEnergyLabGeV;
632 const double thetaRad =
635 if (energyGeV < config.energyMinGeV || energyGeV >= config.
energyMaxGeV
636 || thetaRad < config.thetaMinRad || thetaRad >= config.
thetaMaxRad) {
640 const std::size_t energyBin =
static_cast<std::size_t
>(
642 const std::size_t thetaBin =
static_cast<std::size_t
>(
651 throw std::runtime_error(
652 "LinearComptonBenchmark: sampled joint histogram total weight is not "
666 const JointHistogram& histogram,
const std::filesystem::path& outputPath) {
667 std::ofstream output(outputPath);
669 throw std::runtime_error(
670 "LinearComptonBenchmark: unable to open joint output file "
671 + outputPath.string());
674 output << std::setprecision(17);
675 output <<
"# energy_center_GeV,theta_center_rad,density_per_GeV_rad,count\n";
688 double energyCenter = 0.0;
689 double thetaCenter = 0.0;
690 double density = 0.0;
695 std::ifstream input(inputPath);
697 throw std::runtime_error(
698 "LinearComptonBenchmark: unable to open joint input file "
699 + inputPath.string());
702 std::vector<Row> rows;
704 while (std::getline(input, line)) {
705 if (line.empty() || line[0] ==
'#') {
709 std::stringstream parser(line);
711 std::vector<double> fields;
712 while (std::getline(parser, field,
',')) {
713 fields.push_back(std::stod(field));
715 if (fields.size() < 3) {
720 Row{fields[0], fields[1], fields[2], fields.size() >= 4 ? fields[3] : 0.0});
723 auto containsCenter = [](
const std::vector<double>& values,
double target) {
724 for (
double value : values) {
725 if (std::abs(value - target)
726 <= 1.0e-12 * std::max(1.0, std::max(std::abs(value), std::abs(target)))) {
733 for (
const Row& row : rows) {
747 auto findIndex = [](
const std::vector<double>& values,
double target) {
748 for (std::size_t i = 0; i < values.size(); ++i) {
749 if (std::abs(values[i] - target)
750 <= 1.0e-12 * std::max(1.0, std::max(std::abs(values[i]), std::abs(target)))) {
754 throw std::runtime_error(
755 "LinearComptonBenchmark: unable to locate joint histogram center.");
758 for (
const Row& row : rows) {
759 const std::size_t energyBin = findIndex(histogram.
energyCentersGeV, row.energyCenter);
760 const std::size_t thetaBin = findIndex(histogram.
thetaCentersRad, row.thetaCenter);
763 histogram.
counts[index] = row.count;
788 area += density * cellArea;
822 throw std::runtime_error(
"LinearComptonBenchmark: joint histogram sizes do not match.");
825 double distance = 0.0;
ippl::Vector< T, Dim > Vector_t
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
double histogramArea(const SpectrumHistogram &histogram)
void writeSpectrumCSV(const SpectrumHistogram &histogram, const std::filesystem::path &outputPath)
SpectrumHistogram readSpectrumCSV(const std::filesystem::path &inputPath)
std::vector< double > energyCentersGeV
void writeAngleCSV(const AngleHistogram &histogram, const std::filesystem::path &outputPath)
double angleHistogramL1Distance(const AngleHistogram &lhs, const AngleHistogram &rhs)
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
Vector_t< double, 3 > beamDirection
void writeJointCSV(const JointHistogram &histogram, const std::filesystem::path &outputPath)
AngleHistogram readAngleCSV(const std::filesystem::path &inputPath)
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
double jointHistogramL1Distance(const JointHistogram &lhs, const JointHistogram &rhs)
Vector_t< double, 3 > laserDirection
double histogramL1Distance(const SpectrumHistogram &lhs, const SpectrumHistogram &rhs)
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)
JointHistogram readJointCSV(const std::filesystem::path &inputPath)
AngleHistogram sampleLabAngularSpectrum(const AngleConfig &config, std::size_t sampleCount, std::uint64_t streamIndex=0)
double jointHistogramMeanEnergyGeV(const JointHistogram &histogram)
std::vector< double > counts
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.
Vector_t< double, 3 > labPhotonDirection(double electronTotalEnergyGeV, const Vector_t< double, 3 > &beamDirection, const Vector_t< double, 3 > &laserDirection, double scatteringCosineERF, double azimuthERF)
Laboratory-frame photon direction from electron-rest-frame scattering angles.
double differentialCrossSectionSolidAngleERF(double incomingPhotonEnergyERFGeV, double scatteringCosineERF)
Unpolarized Klein-Nishina differential cross section in solid angle.
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 labPhotonEnergyGeV(double electronTotalEnergyGeV, double laserPhotonEnergyGeV, const Vector_t< double, 3 > &beamDirection, const Vector_t< double, 3 > &laserDirection, double scatteringCosineERF, double azimuthERF)
Laboratory-frame photon energy from electron-rest-frame scattering angles.
double restFrameIncomingPhotonEnergyGeV(double electronTotalEnergyGeV, double laserPhotonEnergyGeV, const Vector_t< double, 3 > &beamDirection, const Vector_t< double, 3 > &laserDirection)
Compute the incoming photon energy in the electron rest frame.
double photonEnergyFromWavelengthGeV(double wavelength_m)
Convert a laser wavelength to a single-photon energy.
constexpr double two_pi
The value of.