OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
LinearBreitWheelerBenchmarkCommon.h
Go to the documentation of this file.
1#ifndef OPALX_TEST_LINEAR_BREIT_WHEELER_BENCHMARK_COMMON_H
2#define OPALX_TEST_LINEAR_BREIT_WHEELER_BENCHMARK_COMMON_H
3
5#include "Physics/Physics.h"
6
7#include <cmath>
8#include <cstdint>
9#include <filesystem>
10#include <fstream>
11#include <random>
12#include <sstream>
13#include <stdexcept>
14#include <string>
15#include <vector>
16
18
19 enum class FinalState {
22 };
23
24 enum class Observable {
25 Energy,
26 Theta,
27 };
28
43 double wavelength_m = 1.0e-9;
45 Vector_t<double, 3> value(0.0);
46 value(2) = 1.0;
47 return value;
48 }();
50 Vector_t<double, 3> value(0.0);
51 value(2) = -1.0;
52 return value;
53 }();
54 double sigmaThetaXRad = 1.0e-3;
55 double sigmaThetaYRad = 1.0e-3;
57 double sigmaX_m = 0.0;
58 double sigmaY_m = 0.0;
59 double sigmaS_m = 0.0;
60 double laserRayleighX_m = 1.0e-6;
61 double laserRayleighY_m = 1.0e-6;
62 double laserSigmaT_m = 5.0e-12 * Physics::c;
63 bool overlapWeighting = false;
64 std::size_t bins = 80;
65 double minValue = 0.0;
66 double maxValue = 0.5;
67 };
68
71 double wavelength_m = 1.0e-9;
73 Vector_t<double, 3> value(0.0);
74 value(2) = 1.0;
75 return value;
76 }();
78 Vector_t<double, 3> value(0.0);
79 value(2) = -1.0;
80 return value;
81 }();
82 std::size_t bins = 80;
83 double minValue = 0.0;
84 double maxValue = 0.5;
85 };
86
87 struct Histogram {
88 std::vector<double> centers;
89 std::vector<double> density;
90 std::vector<double> counts;
91 double binWidth = 0.0;
92 double totalWeight = 0.0;
93 };
94
97 double wavelength_m = 1.0e-9;
99 Vector_t<double, 3> value(0.0);
100 value(2) = 1.0;
101 return value;
102 }();
104 Vector_t<double, 3> value(0.0);
105 value(2) = -1.0;
106 return value;
107 }();
108 std::size_t energyBins = 80;
109 std::size_t thetaBins = 80;
110 double energyMinGeV = 0.0;
111 double energyMaxGeV = 0.5;
112 double thetaMinRad = 0.0;
113 double thetaMaxRad = 0.0045;
114 };
115
118 double wavelength_m = 1.0e-9;
120 Vector_t<double, 3> value(0.0);
121 value(2) = 1.0;
122 return value;
123 }();
125 Vector_t<double, 3> value(0.0);
126 value(2) = -1.0;
127 return value;
128 }();
129 double sigmaThetaXRad = 1.0e-3;
130 double sigmaThetaYRad = 1.0e-3;
132 double sigmaX_m = 0.0;
133 double sigmaY_m = 0.0;
134 double sigmaS_m = 0.0;
135 double laserRayleighX_m = 1.0e-6;
136 double laserRayleighY_m = 1.0e-6;
137 double laserSigmaT_m = 5.0e-12 * Physics::c;
138 bool overlapWeighting = false;
139 std::size_t energyBins = 80;
140 std::size_t thetaBins = 80;
141 double energyMinGeV = 0.0;
142 double energyMaxGeV = 0.5;
143 double thetaMinRad = 0.0;
144 double thetaMaxRad = 0.0060;
145 };
146
148 std::vector<double> energyCentersGeV;
149 std::vector<double> thetaCentersRad;
150 std::vector<std::vector<double>> densityPerGeVRad;
151 std::vector<std::vector<double>> counts;
152 double energyBinWidthGeV = 0.0;
153 double thetaBinWidthRad = 0.0;
154 double totalWeight = 0.0;
155 };
156
157 inline double polarAngleRad(const Vector_t<double, 3>& momentum) {
158 const double transverse = std::hypot(momentum(0), momentum(1));
159 return std::atan2(transverse, momentum(2));
160 }
161
162 inline double sampledObservable(
164 Observable observable) {
165 if (observable == Observable::Theta) {
168 }
169
170 return state == FinalState::Positron ? event.positronEnergyLabGeV
171 : event.electronEnergyLabGeV;
172 }
173
175 double energyGeV = 0.0;
176 double slopeXRad = 0.0;
177 double slopeYRad = 0.0;
178 double x_m = 0.0;
179 double y_m = 0.0;
180 double s_m = 0.0;
182 Vector_t<double, 3> value(0.0);
183 value(2) = 1.0;
184 return value;
185 }();
186 };
187
189 double centralEnergyGeV, double relativeEnergySpread, std::mt19937_64& engine);
190
192 const Vector_t<double, 3>& referenceDirection, Vector_t<double, 3>& axis1,
193 Vector_t<double, 3>& axis2) {
194 axis1 = Vector_t<double, 3>(0.0);
195 axis1(0) = 1.0;
196 if (std::abs(referenceDirection(0)) > 0.9) {
197 axis1(0) = 0.0;
198 axis1(1) = 1.0;
199 }
200 axis1 = cross(referenceDirection, axis1);
201 const double axis1Norm = std::sqrt(dot(axis1, axis1));
202 axis1 /= axis1Norm;
203 axis2 = cross(referenceDirection, axis1);
204 const double axis2Norm = std::sqrt(dot(axis2, axis2));
205 axis2 /= axis2Norm;
206 }
207
209 const Vector_t<double, 3>& referenceDirection, double sigmaThetaXRad,
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;
219 state.energyGeV =
220 sampleHighEnergyPhotonEnergyGeV(centralEnergyGeV, relativeEnergySpread, engine);
221
222 Vector_t<double, 3> axis1(0.0);
223 Vector_t<double, 3> axis2(0.0);
224 buildTransverseBasis(referenceDirection, axis1, axis2);
225 state.direction = referenceDirection + state.slopeXRad * axis1 + state.slopeYRad * axis2;
226 const double directionNorm = std::sqrt(dot(state.direction, state.direction));
227 state.direction /= directionNorm;
228 return state;
229 }
230
232 const Vector_t<double, 3>& referenceDirection, double sigmaThetaXRad,
233 double sigmaThetaYRad, std::mt19937_64& engine) {
235 referenceDirection, sigmaThetaXRad, sigmaThetaYRad, 0.0, 0.0, 0.0, 1.0, 0.0,
236 engine)
237 .direction;
238 }
239
240 inline double effectiveSpatialOverlapSigma(double beamSigma_m, double waist_m) {
241 if (beamSigma_m <= 0.0) {
242 return 0.0;
243 }
244 if (waist_m <= 0.0) {
245 return beamSigma_m;
246 }
247
248 const double inverseVariance =
249 1.0 / (beamSigma_m * beamSigma_m) + 4.0 / (waist_m * waist_m);
250 return std::sqrt(1.0 / inverseVariance);
251 }
252
253 inline double effectiveTemporalOverlapSigma(double beamSigmaS_m, double laserSigmaT_m) {
254 if (beamSigmaS_m <= 0.0) {
255 return 0.0;
256 }
257 if (laserSigmaT_m <= 0.0) {
258 return beamSigmaS_m;
259 }
260
261 const double inverseVariance =
262 1.0 / (beamSigmaS_m * beamSigmaS_m) + 1.0 / (laserSigmaT_m * laserSigmaT_m);
263 return std::sqrt(1.0 / inverseVariance);
264 }
265
267 const FinitePhotonBeamConfig& config, std::mt19937_64& engine) {
268 constexpr double pi = 3.14159265358979323846;
269 const double waistX_m =
270 config.laserRayleighX_m > 0.0
271 ? std::sqrt(config.wavelength_m * config.laserRayleighX_m / pi)
272 : 0.0;
273 const double waistY_m =
274 config.laserRayleighY_m > 0.0
275 ? std::sqrt(config.wavelength_m * config.laserRayleighY_m / pi)
276 : 0.0;
279 effectiveSpatialOverlapSigma(config.sigmaX_m, waistX_m),
280 effectiveSpatialOverlapSigma(config.sigmaY_m, waistY_m),
283 }
284
286 const FinitePhotonBeamJointConfig& config, std::mt19937_64& engine) {
287 constexpr double pi = 3.14159265358979323846;
288 const double waistX_m =
289 config.laserRayleighX_m > 0.0
290 ? std::sqrt(config.wavelength_m * config.laserRayleighX_m / pi)
291 : 0.0;
292 const double waistY_m =
293 config.laserRayleighY_m > 0.0
294 ? std::sqrt(config.wavelength_m * config.laserRayleighY_m / pi)
295 : 0.0;
298 effectiveSpatialOverlapSigma(config.sigmaX_m, waistX_m),
299 effectiveSpatialOverlapSigma(config.sigmaY_m, waistY_m),
302 }
303
305 double centralEnergyGeV, double relativeEnergySpread, std::mt19937_64& engine) {
306 if (relativeEnergySpread <= 0.0) {
307 return centralEnergyGeV;
308 }
309
310 std::normal_distribution<double> unitNormal(0.0, 1.0);
311 double energyGeV = centralEnergyGeV;
312 do {
313 energyGeV = centralEnergyGeV * (1.0 + relativeEnergySpread * unitNormal(engine));
314 } while (energyGeV <= 0.0);
315 return energyGeV;
316 }
317
328 const FinitePhotonBeamConfig& config, FinalState state, Observable observable,
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.");
333 }
334 if (config.maxValue <= config.minValue) {
335 throw std::runtime_error(
336 "LinearBreitWheelerBenchmark: histogram range must satisfy max > min.");
337 }
338 if (sampleCount == 0) {
339 throw std::runtime_error("LinearBreitWheelerBenchmark: sample count must be positive.");
340 }
341
342 Histogram histogram;
343 histogram.binWidth = (config.maxValue - config.minValue) / static_cast<double>(config.bins);
344 histogram.centers.resize(config.bins);
345 histogram.density.assign(config.bins, 0.0);
346 histogram.counts.assign(config.bins, 0.0);
347 for (std::size_t i = 0; i < config.bins; ++i) {
348 histogram.centers[i] =
349 config.minValue + (static_cast<double>(i) + 0.5) * histogram.binWidth;
350 }
351
352 const double laserPhotonEnergyGeV =
354 auto engine = Physics::LinearBreitWheeler::makeHostRandomEngine(streamIndex);
355 histogram.totalWeight = 0.0;
356
357 for (std::size_t i = 0; i < sampleCount; ++i) {
358 const auto beamState =
359 config.overlapWeighting
360 ? sampleOverlapPhotonBeamState(config, engine)
363 config.sigmaThetaYRad, config.sigmaX_m, config.sigmaY_m,
365 config.relativeEnergySpread, engine);
366 constexpr double overlapWeight = 1.0;
367 histogram.totalWeight += overlapWeight;
369 beamState.energyGeV, laserPhotonEnergyGeV, beamState.direction,
370 config.laserDirection);
371 const auto event = Physics::LinearBreitWheeler::sampleEvent(kernel, engine);
372 const double value = sampledObservable(event, state, observable);
373 if (value < config.minValue || value >= config.maxValue) {
374 continue;
375 }
376 const std::size_t bin =
377 static_cast<std::size_t>((value - config.minValue) / histogram.binWidth);
378 if (bin < histogram.counts.size()) {
379 histogram.counts[bin] += overlapWeight;
380 }
381 }
382
383 if (histogram.totalWeight <= 0.0) {
384 throw std::runtime_error(
385 "LinearBreitWheelerBenchmark: zero accepted finite-photon-beam events.");
386 }
387 for (std::size_t i = 0; i < histogram.counts.size(); ++i) {
388 histogram.density[i] =
389 histogram.counts[i] / (histogram.totalWeight * histogram.binWidth);
390 }
391 return histogram;
392 }
393
404 const HistogramConfig& config, FinalState state, Observable observable,
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.");
409 }
410 if (config.maxValue <= config.minValue) {
411 throw std::runtime_error(
412 "LinearBreitWheelerBenchmark: histogram range must satisfy max > min.");
413 }
414 if (sampleCount == 0) {
415 throw std::runtime_error("LinearBreitWheelerBenchmark: sample count must be positive.");
416 }
417
418 Histogram histogram;
419 histogram.binWidth = (config.maxValue - config.minValue) / static_cast<double>(config.bins);
420 histogram.centers.resize(config.bins);
421 histogram.density.assign(config.bins, 0.0);
422 histogram.counts.assign(config.bins, 0.0);
423 for (std::size_t i = 0; i < config.bins; ++i) {
424 histogram.centers[i] =
425 config.minValue + (static_cast<double>(i) + 0.5) * histogram.binWidth;
426 }
427
428 const double laserPhotonEnergyGeV =
431 config.highEnergyPhotonEnergyGeV, laserPhotonEnergyGeV, config.highEnergyDirection,
432 config.laserDirection);
433 auto engine = Physics::LinearBreitWheeler::makeHostRandomEngine(streamIndex);
434
435 histogram.totalWeight = static_cast<double>(sampleCount);
436 for (std::size_t i = 0; i < sampleCount; ++i) {
437 const auto event = Physics::LinearBreitWheeler::sampleEvent(kernel, engine);
438 const double value = sampledObservable(event, state, observable);
439 if (value < config.minValue || value >= config.maxValue) {
440 continue;
441 }
442 const std::size_t bin =
443 static_cast<std::size_t>((value - config.minValue) / histogram.binWidth);
444 if (bin < histogram.counts.size()) {
445 histogram.counts[bin] += 1.0;
446 }
447 }
448
449 for (std::size_t i = 0; i < histogram.counts.size(); ++i) {
450 histogram.density[i] =
451 histogram.counts[i] / (histogram.totalWeight * histogram.binWidth);
452 }
453 return histogram;
454 }
455
466 const FinitePhotonBeamJointConfig& config, FinalState state, std::size_t sampleCount,
467 std::uint64_t streamIndex = 0) {
468 if (config.energyBins == 0 || config.thetaBins == 0) {
469 throw std::runtime_error(
470 "LinearBreitWheelerBenchmark: joint histogram bin counts must be positive.");
471 }
472 if (config.energyMaxGeV <= config.energyMinGeV
473 || config.thetaMaxRad <= config.thetaMinRad) {
474 throw std::runtime_error(
475 "LinearBreitWheelerBenchmark: joint histogram ranges must satisfy max > min.");
476 }
477 if (sampleCount == 0) {
478 throw std::runtime_error("LinearBreitWheelerBenchmark: sample count must be positive.");
479 }
480
481 JointHistogram histogram;
482 histogram.energyBinWidthGeV = (config.energyMaxGeV - config.energyMinGeV)
483 / static_cast<double>(config.energyBins);
484 histogram.thetaBinWidthRad =
485 (config.thetaMaxRad - config.thetaMinRad) / static_cast<double>(config.thetaBins);
486 histogram.energyCentersGeV.resize(config.energyBins);
487 histogram.thetaCentersRad.resize(config.thetaBins);
488 histogram.densityPerGeVRad.assign(
489 config.energyBins, std::vector<double>(config.thetaBins, 0.0));
490 histogram.counts.assign(config.energyBins, std::vector<double>(config.thetaBins, 0.0));
491
492 for (std::size_t i = 0; i < config.energyBins; ++i) {
493 histogram.energyCentersGeV[i] =
494 config.energyMinGeV
495 + (static_cast<double>(i) + 0.5) * histogram.energyBinWidthGeV;
496 }
497 for (std::size_t j = 0; j < config.thetaBins; ++j) {
498 histogram.thetaCentersRad[j] =
499 config.thetaMinRad
500 + (static_cast<double>(j) + 0.5) * histogram.thetaBinWidthRad;
501 }
502
503 const double laserPhotonEnergyGeV =
505 auto engine = Physics::LinearBreitWheeler::makeHostRandomEngine(streamIndex);
506 histogram.totalWeight = 0.0;
507
508 for (std::size_t i = 0; i < sampleCount; ++i) {
509 const auto beamState =
510 config.overlapWeighting
511 ? sampleOverlapPhotonBeamState(config, engine)
514 config.sigmaThetaYRad, config.sigmaX_m, config.sigmaY_m,
516 config.relativeEnergySpread, engine);
517 constexpr double overlapWeight = 1.0;
518 histogram.totalWeight += overlapWeight;
520 beamState.energyGeV, laserPhotonEnergyGeV, beamState.direction,
521 config.laserDirection);
522 const auto event = Physics::LinearBreitWheeler::sampleEvent(kernel, engine);
523 const double energyGeV = sampledObservable(event, state, Observable::Energy);
524 const double thetaRad = sampledObservable(event, state, Observable::Theta);
525 if (energyGeV < config.energyMinGeV || energyGeV >= config.energyMaxGeV
526 || thetaRad < config.thetaMinRad || thetaRad >= config.thetaMaxRad) {
527 continue;
528 }
529
530 const std::size_t ie = static_cast<std::size_t>(
531 (energyGeV - config.energyMinGeV) / histogram.energyBinWidthGeV);
532 const std::size_t jt = static_cast<std::size_t>(
533 (thetaRad - config.thetaMinRad) / histogram.thetaBinWidthRad);
534 if (ie < histogram.counts.size() && jt < histogram.counts[ie].size()) {
535 histogram.counts[ie][jt] += overlapWeight;
536 }
537 }
538
539 if (histogram.totalWeight <= 0.0) {
540 throw std::runtime_error(
541 "LinearBreitWheelerBenchmark: zero accepted finite-photon-beam events.");
542 }
543 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
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) {
546 histogram.densityPerGeVRad[i][j] =
547 histogram.counts[i][j] / (histogram.totalWeight * cellArea);
548 }
549 }
550
551 return histogram;
552 }
553
555 const JointHistogramConfig& config, FinalState state, std::size_t sampleCount,
556 std::uint64_t streamIndex = 0) {
557 if (config.energyBins == 0 || config.thetaBins == 0) {
558 throw std::runtime_error(
559 "LinearBreitWheelerBenchmark: joint histogram bin counts must be positive.");
560 }
561 if (config.energyMaxGeV <= config.energyMinGeV
562 || config.thetaMaxRad <= config.thetaMinRad) {
563 throw std::runtime_error(
564 "LinearBreitWheelerBenchmark: joint histogram ranges must satisfy max > min.");
565 }
566 if (sampleCount == 0) {
567 throw std::runtime_error("LinearBreitWheelerBenchmark: sample count must be positive.");
568 }
569
570 JointHistogram histogram;
571 histogram.energyBinWidthGeV = (config.energyMaxGeV - config.energyMinGeV)
572 / static_cast<double>(config.energyBins);
573 histogram.thetaBinWidthRad =
574 (config.thetaMaxRad - config.thetaMinRad) / static_cast<double>(config.thetaBins);
575 histogram.energyCentersGeV.resize(config.energyBins);
576 histogram.thetaCentersRad.resize(config.thetaBins);
577 histogram.densityPerGeVRad.assign(
578 config.energyBins, std::vector<double>(config.thetaBins, 0.0));
579 histogram.counts.assign(config.energyBins, std::vector<double>(config.thetaBins, 0.0));
580
581 for (std::size_t i = 0; i < config.energyBins; ++i) {
582 histogram.energyCentersGeV[i] =
583 config.energyMinGeV
584 + (static_cast<double>(i) + 0.5) * histogram.energyBinWidthGeV;
585 }
586 for (std::size_t j = 0; j < config.thetaBins; ++j) {
587 histogram.thetaCentersRad[j] =
588 config.thetaMinRad
589 + (static_cast<double>(j) + 0.5) * histogram.thetaBinWidthRad;
590 }
591
592 const double laserPhotonEnergyGeV =
595 config.highEnergyPhotonEnergyGeV, laserPhotonEnergyGeV, config.highEnergyDirection,
596 config.laserDirection);
597 auto engine = Physics::LinearBreitWheeler::makeHostRandomEngine(streamIndex);
598
599 histogram.totalWeight = static_cast<double>(sampleCount);
600 for (std::size_t i = 0; i < sampleCount; ++i) {
601 const auto event = Physics::LinearBreitWheeler::sampleEvent(kernel, engine);
602 const double energyGeV = sampledObservable(event, state, Observable::Energy);
603 const double thetaRad = sampledObservable(event, state, Observable::Theta);
604 if (energyGeV < config.energyMinGeV || energyGeV >= config.energyMaxGeV
605 || thetaRad < config.thetaMinRad || thetaRad >= config.thetaMaxRad) {
606 continue;
607 }
608
609 const std::size_t ie = static_cast<std::size_t>(
610 (energyGeV - config.energyMinGeV) / histogram.energyBinWidthGeV);
611 const std::size_t jt = static_cast<std::size_t>(
612 (thetaRad - config.thetaMinRad) / histogram.thetaBinWidthRad);
613 if (ie < histogram.counts.size() && jt < histogram.counts[ie].size()) {
614 histogram.counts[ie][jt] += 1.0;
615 }
616 }
617
618 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
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) {
621 histogram.densityPerGeVRad[i][j] =
622 histogram.counts[i][j] / (histogram.totalWeight * cellArea);
623 }
624 }
625
626 return histogram;
627 }
628
629 inline void writeHistogramCSV(
630 const Histogram& histogram, const std::filesystem::path& outputPath) {
631 std::ofstream output(outputPath);
632 if (!output) {
633 throw std::runtime_error(
634 "LinearBreitWheelerBenchmark: unable to open output file "
635 + outputPath.string());
636 }
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';
642 }
643 }
644
646 const JointHistogram& histogram, const std::filesystem::path& outputPath) {
647 std::ofstream output(outputPath);
648 if (!output) {
649 throw std::runtime_error(
650 "LinearBreitWheelerBenchmark: unable to open output file "
651 + outputPath.string());
652 }
653 output << "# energy_center_GeV,theta_center_rad,density_per_GeV_rad,count\n";
654 output.precision(17);
655 for (std::size_t i = 0; i < histogram.energyCentersGeV.size(); ++i) {
656 for (std::size_t j = 0; j < histogram.thetaCentersRad.size(); ++j) {
657 output << histogram.energyCentersGeV[i] << ',' << histogram.thetaCentersRad[j]
658 << ',' << histogram.densityPerGeVRad[i][j] << ',' << histogram.counts[i][j]
659 << '\n';
660 }
661 }
662 }
663
664 inline Histogram readHistogramCSV(const std::filesystem::path& inputPath) {
665 Histogram histogram;
666 std::ifstream input(inputPath);
667 if (!input) {
668 throw std::runtime_error(
669 "LinearBreitWheelerBenchmark: unable to open input file " + inputPath.string());
670 }
671
672 std::string line;
673 while (std::getline(input, line)) {
674 if (line.empty() || line[0] == '#') {
675 continue;
676 }
677 std::stringstream parser(line);
678 std::string field;
679 std::vector<double> values;
680 while (std::getline(parser, field, ',')) {
681 values.push_back(std::stod(field));
682 }
683 if (values.size() < 2) {
684 continue;
685 }
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);
689 }
690
691 if (histogram.centers.size() >= 2) {
692 histogram.binWidth = histogram.centers[1] - histogram.centers[0];
693 }
694 histogram.totalWeight = 0.0;
695 for (double value : histogram.density) {
696 histogram.totalWeight += value * histogram.binWidth;
697 }
698 return histogram;
699 }
700
701 inline JointHistogram readJointHistogramCSV(const std::filesystem::path& inputPath) {
702 JointHistogram histogram;
703 std::ifstream input(inputPath);
704 if (!input) {
705 throw std::runtime_error(
706 "LinearBreitWheelerBenchmark: unable to open input file " + inputPath.string());
707 }
708
709 struct Row {
710 double energyCenter = 0.0;
711 double thetaCenter = 0.0;
712 double density = 0.0;
713 double count = 0.0;
714 };
715 std::vector<Row> rows;
716 std::string line;
717 while (std::getline(input, line)) {
718 if (line.empty() || line[0] == '#') {
719 continue;
720 }
721 std::stringstream parser(line);
722 std::string field;
723 std::vector<double> values;
724 while (std::getline(parser, field, ',')) {
725 values.push_back(std::stod(field));
726 }
727 if (values.size() < 3) {
728 continue;
729 }
730 rows.push_back(
731 Row{values[0], values[1], values[2], values.size() >= 4 ? values[3] : 0.0});
732 if (histogram.energyCentersGeV.empty()
733 || histogram.energyCentersGeV.back() != values[0]) {
734 histogram.energyCentersGeV.push_back(values[0]);
735 }
736 bool seenTheta = false;
737 for (double existing : histogram.thetaCentersRad) {
738 if (existing == values[1]) {
739 seenTheta = true;
740 break;
741 }
742 }
743 if (!seenTheta) {
744 histogram.thetaCentersRad.push_back(values[1]);
745 }
746 }
747
748 histogram.densityPerGeVRad.assign(
749 histogram.energyCentersGeV.size(),
750 std::vector<double>(histogram.thetaCentersRad.size(), 0.0));
751 histogram.counts.assign(
752 histogram.energyCentersGeV.size(),
753 std::vector<double>(histogram.thetaCentersRad.size(), 0.0));
754 for (const auto& row : rows) {
755 std::size_t i = 0;
756 while (i < histogram.energyCentersGeV.size()
757 && histogram.energyCentersGeV[i] != row.energyCenter) {
758 ++i;
759 }
760 std::size_t j = 0;
761 while (j < histogram.thetaCentersRad.size()
762 && histogram.thetaCentersRad[j] != row.thetaCenter) {
763 ++j;
764 }
765 if (i < histogram.energyCentersGeV.size() && j < histogram.thetaCentersRad.size()) {
766 histogram.densityPerGeVRad[i][j] = row.density;
767 histogram.counts[i][j] = row.count;
768 }
769 }
770
771 if (histogram.energyCentersGeV.size() >= 2) {
772 histogram.energyBinWidthGeV =
773 histogram.energyCentersGeV[1] - histogram.energyCentersGeV[0];
774 }
775 if (histogram.thetaCentersRad.size() >= 2) {
776 histogram.thetaBinWidthRad =
777 histogram.thetaCentersRad[1] - histogram.thetaCentersRad[0];
778 }
779 histogram.totalWeight = 0.0;
780 for (const auto& row : histogram.densityPerGeVRad) {
781 for (double value : row) {
782 histogram.totalWeight +=
783 value * histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
784 }
785 }
786 return histogram;
787 }
788
789 inline double histogramArea(const Histogram& histogram) {
790 double area = 0.0;
791 for (double density : histogram.density) {
792 area += density * histogram.binWidth;
793 }
794 return area;
795 }
796
797 inline double histogramMean(const Histogram& histogram) {
798 double mean = 0.0;
799 for (std::size_t i = 0; i < histogram.centers.size(); ++i) {
800 mean += histogram.centers[i] * histogram.density[i] * histogram.binWidth;
801 }
802 return mean;
803 }
804
805 inline double histogramL1Distance(const Histogram& lhs, const Histogram& rhs) {
806 if (lhs.centers.size() != rhs.centers.size()) {
807 throw std::runtime_error("LinearBreitWheelerBenchmark: histogram sizes do not match.");
808 }
809 double distance = 0.0;
810 for (std::size_t i = 0; i < lhs.centers.size(); ++i) {
811 distance += std::abs(lhs.density[i] - rhs.density[i]) * lhs.binWidth;
812 }
813 return distance;
814 }
815
816 inline double jointHistogramArea(const JointHistogram& histogram) {
817 double area = 0.0;
818 for (const auto& row : histogram.densityPerGeVRad) {
819 for (double density : row) {
820 area += density * histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
821 }
822 }
823 return area;
824 }
825
826 inline double jointHistogramMeanEnergyGeV(const JointHistogram& histogram) {
827 double mean = 0.0;
828 for (std::size_t i = 0; i < histogram.energyCentersGeV.size(); ++i) {
829 for (std::size_t j = 0; j < histogram.thetaCentersRad.size(); ++j) {
830 mean += histogram.energyCentersGeV[i] * histogram.densityPerGeVRad[i][j]
831 * histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
832 }
833 }
834 return mean;
835 }
836
837 inline double jointHistogramMeanThetaRad(const JointHistogram& histogram) {
838 double mean = 0.0;
839 for (std::size_t i = 0; i < histogram.energyCentersGeV.size(); ++i) {
840 for (std::size_t j = 0; j < histogram.thetaCentersRad.size(); ++j) {
841 mean += histogram.thetaCentersRad[j] * histogram.densityPerGeVRad[i][j]
842 * histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
843 }
844 }
845 return mean;
846 }
847
848 inline double jointHistogramL1Distance(const JointHistogram& lhs, const JointHistogram& rhs) {
849 if (lhs.energyCentersGeV.size() != rhs.energyCentersGeV.size()
850 || lhs.thetaCentersRad.size() != rhs.thetaCentersRad.size()) {
851 throw std::runtime_error(
852 "LinearBreitWheelerBenchmark: joint histogram sizes do not match.");
853 }
854 double distance = 0.0;
855 for (std::size_t i = 0; i < lhs.energyCentersGeV.size(); ++i) {
856 for (std::size_t j = 0; j < lhs.thetaCentersRad.size(); ++j) {
857 distance += std::abs(lhs.densityPerGeVRad[i][j] - rhs.densityPerGeVRad[i][j])
859 }
860 }
861 return distance;
862 }
863
864} // namespace LinearBreitWheelerBenchmark
865
866#endif
ippl::Vector< T, Dim > Vector_t
Vector3D cross(const Vector3D &lhs, const Vector3D &rhs)
Vector cross product.
Definition Vector3D.cpp:89
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
Definition Vector3D.cpp:95
double effectiveTemporalOverlapSigma(double beamSigmaS_m, double laserSigmaT_m)
double histogramMean(const Histogram &histogram)
double sampledObservable(const Physics::LinearBreitWheeler::SampledEvent &event, FinalState state, Observable observable)
double histogramL1Distance(const Histogram &lhs, const Histogram &rhs)
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.
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.
Vector_t< double, 3 > samplePhotonBeamDirection(const Vector_t< double, 3 > &referenceDirection, double sigmaThetaXRad, double sigmaThetaYRad, std::mt19937_64 &engine)
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)
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)
double jointHistogramMeanEnergyGeV(const JointHistogram &histogram)
SampledPhotonBeamState sampleOverlapPhotonBeamState(const FinitePhotonBeamConfig &config, std::mt19937_64 &engine)
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)
Histogram readHistogramCSV(const std::filesystem::path &inputPath)
void writeHistogramCSV(const Histogram &histogram, const std::filesystem::path &outputPath)
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.
Definition Physics.h:60