OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
LinearComptonSpectrumCommon.h
Go to the documentation of this file.
1#ifndef OPALX_TEST_LINEAR_COMPTON_SPECTRUM_COMMON_H
2#define OPALX_TEST_LINEAR_COMPTON_SPECTRUM_COMMON_H
3
5#include "Physics/Physics.h"
6
7#include <algorithm>
8#include <cmath>
9#include <cstdint>
10#include <filesystem>
11#include <fstream>
12#include <iomanip>
13#include <sstream>
14#include <stdexcept>
15#include <string>
16#include <vector>
17
19
22 double wavelength_m = 1.03e-6;
24 Vector_t<double, 3> value(0.0);
25 value(2) = 1.0;
26 return value;
27 }();
29 Vector_t<double, 3> value(0.0);
30 value(0) = 1.0;
31 return value;
32 }();
33 std::size_t bins = 80;
34 double energyMinGeV = 0.0;
35 double energyMaxGeV = 0.01;
36 int cosinePanels = 720;
37 int azimuthPanels = 720;
38 };
39
41 std::vector<double> centersGeV;
42 std::vector<double> densityPerGeV;
43 std::vector<double> counts;
44 double binWidthGeV = 0.0;
45 double totalWeight = 0.0;
46 };
47
49 SpectrumHistogram histogram;
50 histogram.binWidthGeV =
51 (config.energyMaxGeV - config.energyMinGeV) / static_cast<double>(config.bins);
52 histogram.centersGeV.resize(config.bins);
53 histogram.densityPerGeV.assign(config.bins, 0.0);
54 histogram.counts.assign(config.bins, 0.0);
55
56 for (std::size_t i = 0; i < config.bins; ++i) {
57 histogram.centersGeV[i] =
58 config.energyMinGeV + (static_cast<double>(i) + 0.5) * histogram.binWidthGeV;
59 }
60
61 const double laserPhotonEnergyGeV =
63 const double incomingPhotonEnergyERFGeV =
65 config.electronTotalEnergyGeV, laserPhotonEnergyGeV, config.beamDirection,
66 config.laserDirection);
67
68 const double dMu = 2.0 / static_cast<double>(config.cosinePanels);
69 const double dPhi = Physics::two_pi / static_cast<double>(config.azimuthPanels);
70
71 for (int i = 0; i < config.cosinePanels; ++i) {
72 const double mu = -1.0 + (static_cast<double>(i) + 0.5) * dMu;
74 incomingPhotonEnergyERFGeV, mu);
75
76 for (int j = 0; j < config.azimuthPanels; ++j) {
77 const double phi = (static_cast<double>(j) + 0.5) * dPhi;
78 const double energyGeV = Physics::LinearCompton::labPhotonEnergyGeV(
79 config.electronTotalEnergyGeV, laserPhotonEnergyGeV, config.beamDirection,
80 config.laserDirection, mu, phi);
81 const double weight = kernel * dMu * dPhi;
82 histogram.totalWeight += weight;
83
84 if (energyGeV < config.energyMinGeV || energyGeV >= config.energyMaxGeV) {
85 continue;
86 }
87
88 const std::size_t bin = static_cast<std::size_t>(
89 (energyGeV - config.energyMinGeV) / histogram.binWidthGeV);
90 if (bin < histogram.counts.size()) {
91 histogram.counts[bin] += weight;
92 }
93 }
94 }
95
96 if (histogram.totalWeight <= 0.0) {
97 throw std::runtime_error(
98 "LinearComptonBenchmark: total histogram weight is not positive.");
99 }
100
101 for (std::size_t i = 0; i < histogram.densityPerGeV.size(); ++i) {
102 histogram.densityPerGeV[i] =
103 histogram.counts[i] / (histogram.totalWeight * histogram.binWidthGeV);
104 }
105
106 return histogram;
107 }
108
110 const SpectrumConfig& config, std::size_t sampleCount, std::uint64_t streamIndex = 0) {
111 SpectrumHistogram histogram;
112 histogram.binWidthGeV =
113 (config.energyMaxGeV - config.energyMinGeV) / static_cast<double>(config.bins);
114 histogram.centersGeV.resize(config.bins);
115 histogram.densityPerGeV.assign(config.bins, 0.0);
116 histogram.counts.assign(config.bins, 0.0);
117
118 for (std::size_t i = 0; i < config.bins; ++i) {
119 histogram.centersGeV[i] =
120 config.energyMinGeV + (static_cast<double>(i) + 0.5) * histogram.binWidthGeV;
121 }
122
123 const double laserPhotonEnergyGeV =
126 config.electronTotalEnergyGeV, laserPhotonEnergyGeV, config.beamDirection,
127 config.laserDirection);
128 auto engine = Physics::LinearCompton::makeHostRandomEngine(streamIndex);
129
130 histogram.totalWeight = static_cast<double>(sampleCount);
131 for (std::size_t i = 0; i < sampleCount; ++i) {
132 const auto event = Physics::LinearCompton::sampleEvent(kernel, engine);
133 const double energyGeV = event.scatteredPhotonEnergyLabGeV;
134 if (energyGeV < config.energyMinGeV || energyGeV >= config.energyMaxGeV) {
135 continue;
136 }
137
138 const std::size_t bin = static_cast<std::size_t>(
139 (energyGeV - config.energyMinGeV) / histogram.binWidthGeV);
140 if (bin < histogram.counts.size()) {
141 histogram.counts[bin] += 1.0;
142 }
143 }
144
145 if (histogram.totalWeight <= 0.0) {
146 throw std::runtime_error("LinearComptonBenchmark: sample count must be positive.");
147 }
148
149 for (std::size_t i = 0; i < histogram.densityPerGeV.size(); ++i) {
150 histogram.densityPerGeV[i] =
151 histogram.counts[i] / (histogram.totalWeight * histogram.binWidthGeV);
152 }
153
154 return histogram;
155 }
156
157 inline void writeSpectrumCSV(
158 const SpectrumHistogram& histogram, const std::filesystem::path& outputPath) {
159 std::ofstream output(outputPath);
160 if (!output) {
161 throw std::runtime_error(
162 "LinearComptonBenchmark: unable to open output file " + outputPath.string());
163 }
164
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) {
168 output << histogram.centersGeV[i] << ',' << histogram.densityPerGeV[i] << ','
169 << histogram.counts[i] << '\n';
170 }
171 }
172
173 inline SpectrumHistogram readSpectrumCSV(const std::filesystem::path& inputPath) {
174 SpectrumHistogram histogram;
175 std::ifstream input(inputPath);
176 if (!input) {
177 throw std::runtime_error(
178 "LinearComptonBenchmark: unable to open input file " + inputPath.string());
179 }
180
181 std::string line;
182 while (std::getline(input, line)) {
183 if (line.empty() || line[0] == '#') {
184 continue;
185 }
186
187 std::stringstream parser(line);
188 std::string field;
189 std::vector<double> fields;
190 while (std::getline(parser, field, ',')) {
191 fields.push_back(std::stod(field));
192 }
193 if (fields.size() < 2) {
194 continue;
195 }
196
197 histogram.centersGeV.push_back(fields[0]);
198 histogram.densityPerGeV.push_back(fields[1]);
199 histogram.counts.push_back(fields.size() >= 3 ? fields[2] : 0.0);
200 }
201
202 if (histogram.centersGeV.size() >= 2) {
203 histogram.binWidthGeV = histogram.centersGeV[1] - histogram.centersGeV[0];
204 }
205 histogram.totalWeight = 0.0;
206 for (double density : histogram.densityPerGeV) {
207 histogram.totalWeight += density * histogram.binWidthGeV;
208 }
209
210 return histogram;
211 }
212
213 inline double histogramMeanEnergyGeV(const SpectrumHistogram& histogram) {
214 double mean = 0.0;
215 for (std::size_t i = 0; i < histogram.centersGeV.size(); ++i) {
216 mean += histogram.centersGeV[i] * histogram.densityPerGeV[i] * histogram.binWidthGeV;
217 }
218 return mean;
219 }
220
221 inline double histogramArea(const SpectrumHistogram& histogram) {
222 double area = 0.0;
223 for (double density : histogram.densityPerGeV) {
224 area += density * histogram.binWidthGeV;
225 }
226 return area;
227 }
228
229 inline double histogramL1Distance(const SpectrumHistogram& lhs, const SpectrumHistogram& rhs) {
230 if (lhs.centersGeV.size() != rhs.centersGeV.size()) {
231 throw std::runtime_error("LinearComptonBenchmark: histogram sizes do not match.");
232 }
233
234 double distance = 0.0;
235 for (std::size_t i = 0; i < lhs.centersGeV.size(); ++i) {
236 distance += std::abs(lhs.densityPerGeV[i] - rhs.densityPerGeV[i]) * lhs.binWidthGeV;
237 }
238 return distance;
239 }
240
241 struct AngleConfig {
243 double wavelength_m = 1.03e-6;
245 Vector_t<double, 3> value(0.0);
246 value(2) = 1.0;
247 return value;
248 }();
250 Vector_t<double, 3> value(0.0);
251 value(0) = 1.0;
252 return value;
253 }();
254 std::size_t bins = 80;
255 double thetaMinRad = 0.0;
256 double thetaMaxRad = 0.02;
257 int cosinePanels = 720;
258 int azimuthPanels = 720;
259 };
260
262 std::vector<double> centersRad;
263 std::vector<double> densityPerRad;
264 std::vector<double> counts;
265 double binWidthRad = 0.0;
266 double totalWeight = 0.0;
267 };
268
269 inline double photonPolarAngleRad(
270 const Vector_t<double, 3>& photonDirection, const Vector_t<double, 3>& beamDirection) {
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.");
276 }
277 const double cosine = std::max(
278 -1.0, std::min(1.0, dot(photonDirection, beamDirection) / (beamNorm * photonNorm)));
279 return std::acos(cosine);
280 }
281
283 AngleHistogram histogram;
284 histogram.binWidthRad =
285 (config.thetaMaxRad - config.thetaMinRad) / static_cast<double>(config.bins);
286 histogram.centersRad.resize(config.bins);
287 histogram.densityPerRad.assign(config.bins, 0.0);
288 histogram.counts.assign(config.bins, 0.0);
289
290 for (std::size_t i = 0; i < config.bins; ++i) {
291 histogram.centersRad[i] =
292 config.thetaMinRad + (static_cast<double>(i) + 0.5) * histogram.binWidthRad;
293 }
294
295 const double laserPhotonEnergyGeV =
297 const double incomingPhotonEnergyERFGeV =
299 config.electronTotalEnergyGeV, laserPhotonEnergyGeV, config.beamDirection,
300 config.laserDirection);
301
302 const double dMu = 2.0 / static_cast<double>(config.cosinePanels);
303 const double dPhi = Physics::two_pi / static_cast<double>(config.azimuthPanels);
304
305 for (int i = 0; i < config.cosinePanels; ++i) {
306 const double mu = -1.0 + (static_cast<double>(i) + 0.5) * dMu;
308 incomingPhotonEnergyERFGeV, mu);
309
310 for (int j = 0; j < config.azimuthPanels; ++j) {
311 const double phi = (static_cast<double>(j) + 0.5) * dPhi;
314 mu, phi);
315 const double thetaRad = photonPolarAngleRad(direction, config.beamDirection);
316 const double weight = kernel * dMu * dPhi;
317 histogram.totalWeight += weight;
318
319 if (thetaRad < config.thetaMinRad || thetaRad >= config.thetaMaxRad) {
320 continue;
321 }
322
323 const std::size_t bin = static_cast<std::size_t>(
324 (thetaRad - config.thetaMinRad) / histogram.binWidthRad);
325 if (bin < histogram.counts.size()) {
326 histogram.counts[bin] += weight;
327 }
328 }
329 }
330
331 if (histogram.totalWeight <= 0.0) {
332 throw std::runtime_error(
333 "LinearComptonBenchmark: angular histogram total weight is not positive.");
334 }
335
336 for (std::size_t i = 0; i < histogram.densityPerRad.size(); ++i) {
337 histogram.densityPerRad[i] =
338 histogram.counts[i] / (histogram.totalWeight * histogram.binWidthRad);
339 }
340
341 return histogram;
342 }
343
345 const AngleConfig& config, std::size_t sampleCount, std::uint64_t streamIndex = 0) {
346 AngleHistogram histogram;
347 histogram.binWidthRad =
348 (config.thetaMaxRad - config.thetaMinRad) / static_cast<double>(config.bins);
349 histogram.centersRad.resize(config.bins);
350 histogram.densityPerRad.assign(config.bins, 0.0);
351 histogram.counts.assign(config.bins, 0.0);
352
353 for (std::size_t i = 0; i < config.bins; ++i) {
354 histogram.centersRad[i] =
355 config.thetaMinRad + (static_cast<double>(i) + 0.5) * histogram.binWidthRad;
356 }
357
358 const double laserPhotonEnergyGeV =
361 config.electronTotalEnergyGeV, laserPhotonEnergyGeV, config.beamDirection,
362 config.laserDirection);
363 auto engine = Physics::LinearCompton::makeHostRandomEngine(streamIndex);
364
365 histogram.totalWeight = static_cast<double>(sampleCount);
366 for (std::size_t i = 0; i < sampleCount; ++i) {
367 const auto event = Physics::LinearCompton::sampleEvent(kernel, engine);
368 const double thetaRad =
369 photonPolarAngleRad(event.scatteredPhotonDirectionLab, config.beamDirection);
370 if (thetaRad < config.thetaMinRad || thetaRad >= config.thetaMaxRad) {
371 continue;
372 }
373
374 const std::size_t bin = static_cast<std::size_t>(
375 (thetaRad - config.thetaMinRad) / histogram.binWidthRad);
376 if (bin < histogram.counts.size()) {
377 histogram.counts[bin] += 1.0;
378 }
379 }
380
381 if (histogram.totalWeight <= 0.0) {
382 throw std::runtime_error(
383 "LinearComptonBenchmark: angular sample count must be positive.");
384 }
385
386 for (std::size_t i = 0; i < histogram.densityPerRad.size(); ++i) {
387 histogram.densityPerRad[i] =
388 histogram.counts[i] / (histogram.totalWeight * histogram.binWidthRad);
389 }
390
391 return histogram;
392 }
393
394 inline void writeAngleCSV(
395 const AngleHistogram& histogram, const std::filesystem::path& outputPath) {
396 std::ofstream output(outputPath);
397 if (!output) {
398 throw std::runtime_error(
399 "LinearComptonBenchmark: unable to open angular output file "
400 + outputPath.string());
401 }
402
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) {
406 output << histogram.centersRad[i] << ',' << histogram.densityPerRad[i] << ','
407 << histogram.counts[i] << '\n';
408 }
409 }
410
411 inline AngleHistogram readAngleCSV(const std::filesystem::path& inputPath) {
412 AngleHistogram histogram;
413 std::ifstream input(inputPath);
414 if (!input) {
415 throw std::runtime_error(
416 "LinearComptonBenchmark: unable to open angular input file "
417 + inputPath.string());
418 }
419
420 std::string line;
421 while (std::getline(input, line)) {
422 if (line.empty() || line[0] == '#') {
423 continue;
424 }
425
426 std::stringstream parser(line);
427 std::string field;
428 std::vector<double> fields;
429 while (std::getline(parser, field, ',')) {
430 fields.push_back(std::stod(field));
431 }
432 if (fields.size() < 2) {
433 continue;
434 }
435
436 histogram.centersRad.push_back(fields[0]);
437 histogram.densityPerRad.push_back(fields[1]);
438 histogram.counts.push_back(fields.size() >= 3 ? fields[2] : 0.0);
439 }
440
441 if (histogram.centersRad.size() >= 2) {
442 histogram.binWidthRad = histogram.centersRad[1] - histogram.centersRad[0];
443 }
444 histogram.totalWeight = 0.0;
445 for (double density : histogram.densityPerRad) {
446 histogram.totalWeight += density * histogram.binWidthRad;
447 }
448
449 return histogram;
450 }
451
452 inline double angleHistogramMeanRad(const AngleHistogram& histogram) {
453 double mean = 0.0;
454 for (std::size_t i = 0; i < histogram.centersRad.size(); ++i) {
455 mean += histogram.centersRad[i] * histogram.densityPerRad[i] * histogram.binWidthRad;
456 }
457 return mean;
458 }
459
460 inline double angleHistogramArea(const AngleHistogram& histogram) {
461 double area = 0.0;
462 for (double density : histogram.densityPerRad) {
463 area += density * histogram.binWidthRad;
464 }
465 return area;
466 }
467
468 inline double angleHistogramL1Distance(const AngleHistogram& lhs, const AngleHistogram& rhs) {
469 if (lhs.centersRad.size() != rhs.centersRad.size()) {
470 throw std::runtime_error(
471 "LinearComptonBenchmark: angular histogram sizes do not match.");
472 }
473
474 double distance = 0.0;
475 for (std::size_t i = 0; i < lhs.centersRad.size(); ++i) {
476 distance += std::abs(lhs.densityPerRad[i] - rhs.densityPerRad[i]) * lhs.binWidthRad;
477 }
478 return distance;
479 }
480
481 struct JointConfig {
483 double wavelength_m = 1.03e-6;
485 Vector_t<double, 3> value(0.0);
486 value(2) = 1.0;
487 return value;
488 }();
490 Vector_t<double, 3> value(0.0);
491 value(0) = 1.0;
492 return value;
493 }();
494 std::size_t energyBins = 60;
495 std::size_t thetaBins = 60;
496 double energyMinGeV = 0.0;
497 double energyMaxGeV = 0.01;
498 double thetaMinRad = 0.0;
499 double thetaMaxRad = 0.02;
500 int cosinePanels = 720;
501 int azimuthPanels = 720;
502 };
503
505 std::vector<double> energyCentersGeV;
506 std::vector<double> thetaCentersRad;
507 std::vector<double> densityPerGeVRad;
508 std::vector<double> counts;
509 double energyBinWidthGeV = 0.0;
510 double thetaBinWidthRad = 0.0;
511 double totalWeight = 0.0;
512 };
513
514 inline std::size_t jointHistogramIndex(
515 const JointHistogram& histogram, std::size_t energyBin, std::size_t thetaBin) {
516 return energyBin * histogram.thetaCentersRad.size() + thetaBin;
517 }
518
520 JointHistogram histogram;
521 histogram.energyBinWidthGeV = (config.energyMaxGeV - config.energyMinGeV)
522 / static_cast<double>(config.energyBins);
523 histogram.thetaBinWidthRad =
524 (config.thetaMaxRad - config.thetaMinRad) / static_cast<double>(config.thetaBins);
525 histogram.energyCentersGeV.resize(config.energyBins);
526 histogram.thetaCentersRad.resize(config.thetaBins);
527 histogram.densityPerGeVRad.assign(config.energyBins * config.thetaBins, 0.0);
528 histogram.counts.assign(config.energyBins * config.thetaBins, 0.0);
529
530 for (std::size_t i = 0; i < config.energyBins; ++i) {
531 histogram.energyCentersGeV[i] =
532 config.energyMinGeV
533 + (static_cast<double>(i) + 0.5) * histogram.energyBinWidthGeV;
534 }
535 for (std::size_t j = 0; j < config.thetaBins; ++j) {
536 histogram.thetaCentersRad[j] =
537 config.thetaMinRad
538 + (static_cast<double>(j) + 0.5) * histogram.thetaBinWidthRad;
539 }
540
541 const double laserPhotonEnergyGeV =
543 const double incomingPhotonEnergyERFGeV =
545 config.electronTotalEnergyGeV, laserPhotonEnergyGeV, config.beamDirection,
546 config.laserDirection);
547
548 const double dMu = 2.0 / static_cast<double>(config.cosinePanels);
549 const double dPhi = Physics::two_pi / static_cast<double>(config.azimuthPanels);
550
551 for (int i = 0; i < config.cosinePanels; ++i) {
552 const double mu = -1.0 + (static_cast<double>(i) + 0.5) * dMu;
554 incomingPhotonEnergyERFGeV, mu);
555
556 for (int j = 0; j < config.azimuthPanels; ++j) {
557 const double phi = (static_cast<double>(j) + 0.5) * dPhi;
558 const double energyGeV = Physics::LinearCompton::labPhotonEnergyGeV(
559 config.electronTotalEnergyGeV, laserPhotonEnergyGeV, config.beamDirection,
560 config.laserDirection, mu, phi);
563 mu, phi);
564 const double thetaRad = photonPolarAngleRad(direction, config.beamDirection);
565 const double weight = kernel * dMu * dPhi;
566 histogram.totalWeight += weight;
567
568 if (energyGeV < config.energyMinGeV || energyGeV >= config.energyMaxGeV
569 || thetaRad < config.thetaMinRad || thetaRad >= config.thetaMaxRad) {
570 continue;
571 }
572
573 const std::size_t energyBin = static_cast<std::size_t>(
574 (energyGeV - config.energyMinGeV) / histogram.energyBinWidthGeV);
575 const std::size_t thetaBin = static_cast<std::size_t>(
576 (thetaRad - config.thetaMinRad) / histogram.thetaBinWidthRad);
577 if (energyBin < histogram.energyCentersGeV.size()
578 && thetaBin < histogram.thetaCentersRad.size()) {
579 histogram.counts[jointHistogramIndex(histogram, energyBin, thetaBin)] += weight;
580 }
581 }
582 }
583
584 if (histogram.totalWeight <= 0.0) {
585 throw std::runtime_error(
586 "LinearComptonBenchmark: joint histogram total weight is not positive.");
587 }
588
589 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
590 for (std::size_t i = 0; i < histogram.densityPerGeVRad.size(); ++i) {
591 histogram.densityPerGeVRad[i] =
592 histogram.counts[i] / (histogram.totalWeight * cellArea);
593 }
594
595 return histogram;
596 }
597
599 const JointConfig& config, std::size_t sampleCount, std::uint64_t streamIndex = 0) {
600 JointHistogram histogram;
601 histogram.energyBinWidthGeV = (config.energyMaxGeV - config.energyMinGeV)
602 / static_cast<double>(config.energyBins);
603 histogram.thetaBinWidthRad =
604 (config.thetaMaxRad - config.thetaMinRad) / static_cast<double>(config.thetaBins);
605 histogram.energyCentersGeV.resize(config.energyBins);
606 histogram.thetaCentersRad.resize(config.thetaBins);
607 histogram.densityPerGeVRad.assign(config.energyBins * config.thetaBins, 0.0);
608 histogram.counts.assign(config.energyBins * config.thetaBins, 0.0);
609
610 for (std::size_t i = 0; i < config.energyBins; ++i) {
611 histogram.energyCentersGeV[i] =
612 config.energyMinGeV
613 + (static_cast<double>(i) + 0.5) * histogram.energyBinWidthGeV;
614 }
615 for (std::size_t j = 0; j < config.thetaBins; ++j) {
616 histogram.thetaCentersRad[j] =
617 config.thetaMinRad
618 + (static_cast<double>(j) + 0.5) * histogram.thetaBinWidthRad;
619 }
620
621 const double laserPhotonEnergyGeV =
624 config.electronTotalEnergyGeV, laserPhotonEnergyGeV, config.beamDirection,
625 config.laserDirection);
626 auto engine = Physics::LinearCompton::makeHostRandomEngine(streamIndex);
627
628 histogram.totalWeight = static_cast<double>(sampleCount);
629 for (std::size_t i = 0; i < sampleCount; ++i) {
630 const auto event = Physics::LinearCompton::sampleEvent(kernel, engine);
631 const double energyGeV = event.scatteredPhotonEnergyLabGeV;
632 const double thetaRad =
633 photonPolarAngleRad(event.scatteredPhotonDirectionLab, config.beamDirection);
634
635 if (energyGeV < config.energyMinGeV || energyGeV >= config.energyMaxGeV
636 || thetaRad < config.thetaMinRad || thetaRad >= config.thetaMaxRad) {
637 continue;
638 }
639
640 const std::size_t energyBin = static_cast<std::size_t>(
641 (energyGeV - config.energyMinGeV) / histogram.energyBinWidthGeV);
642 const std::size_t thetaBin = static_cast<std::size_t>(
643 (thetaRad - config.thetaMinRad) / histogram.thetaBinWidthRad);
644 if (energyBin < histogram.energyCentersGeV.size()
645 && thetaBin < histogram.thetaCentersRad.size()) {
646 histogram.counts[jointHistogramIndex(histogram, energyBin, thetaBin)] += 1.0;
647 }
648 }
649
650 if (histogram.totalWeight <= 0.0) {
651 throw std::runtime_error(
652 "LinearComptonBenchmark: sampled joint histogram total weight is not "
653 "positive.");
654 }
655
656 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
657 for (std::size_t i = 0; i < histogram.densityPerGeVRad.size(); ++i) {
658 histogram.densityPerGeVRad[i] =
659 histogram.counts[i] / (histogram.totalWeight * cellArea);
660 }
661
662 return histogram;
663 }
664
665 inline void writeJointCSV(
666 const JointHistogram& histogram, const std::filesystem::path& outputPath) {
667 std::ofstream output(outputPath);
668 if (!output) {
669 throw std::runtime_error(
670 "LinearComptonBenchmark: unable to open joint output file "
671 + outputPath.string());
672 }
673
674 output << std::setprecision(17);
675 output << "# energy_center_GeV,theta_center_rad,density_per_GeV_rad,count\n";
676 for (std::size_t i = 0; i < histogram.energyCentersGeV.size(); ++i) {
677 for (std::size_t j = 0; j < histogram.thetaCentersRad.size(); ++j) {
678 const std::size_t index = jointHistogramIndex(histogram, i, j);
679 output << histogram.energyCentersGeV[i] << ',' << histogram.thetaCentersRad[j]
680 << ',' << histogram.densityPerGeVRad[index] << ',' << histogram.counts[index]
681 << '\n';
682 }
683 }
684 }
685
686 inline JointHistogram readJointCSV(const std::filesystem::path& inputPath) {
687 struct Row {
688 double energyCenter = 0.0;
689 double thetaCenter = 0.0;
690 double density = 0.0;
691 double count = 0.0;
692 };
693
694 JointHistogram histogram;
695 std::ifstream input(inputPath);
696 if (!input) {
697 throw std::runtime_error(
698 "LinearComptonBenchmark: unable to open joint input file "
699 + inputPath.string());
700 }
701
702 std::vector<Row> rows;
703 std::string line;
704 while (std::getline(input, line)) {
705 if (line.empty() || line[0] == '#') {
706 continue;
707 }
708
709 std::stringstream parser(line);
710 std::string field;
711 std::vector<double> fields;
712 while (std::getline(parser, field, ',')) {
713 fields.push_back(std::stod(field));
714 }
715 if (fields.size() < 3) {
716 continue;
717 }
718
719 rows.push_back(
720 Row{fields[0], fields[1], fields[2], fields.size() >= 4 ? fields[3] : 0.0});
721 }
722
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)))) {
727 return true;
728 }
729 }
730 return false;
731 };
732
733 for (const Row& row : rows) {
734 if (!containsCenter(histogram.energyCentersGeV, row.energyCenter)) {
735 histogram.energyCentersGeV.push_back(row.energyCenter);
736 }
737 if (!containsCenter(histogram.thetaCentersRad, row.thetaCenter)) {
738 histogram.thetaCentersRad.push_back(row.thetaCenter);
739 }
740 }
741
742 histogram.densityPerGeVRad.assign(
743 histogram.energyCentersGeV.size() * histogram.thetaCentersRad.size(), 0.0);
744 histogram.counts.assign(
745 histogram.energyCentersGeV.size() * histogram.thetaCentersRad.size(), 0.0);
746
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)))) {
751 return i;
752 }
753 }
754 throw std::runtime_error(
755 "LinearComptonBenchmark: unable to locate joint histogram center.");
756 };
757
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);
761 const std::size_t index = jointHistogramIndex(histogram, energyBin, thetaBin);
762 histogram.densityPerGeVRad[index] = row.density;
763 histogram.counts[index] = row.count;
764 }
765
766 if (histogram.energyCentersGeV.size() >= 2) {
767 histogram.energyBinWidthGeV =
768 histogram.energyCentersGeV[1] - histogram.energyCentersGeV[0];
769 }
770 if (histogram.thetaCentersRad.size() >= 2) {
771 histogram.thetaBinWidthRad =
772 histogram.thetaCentersRad[1] - histogram.thetaCentersRad[0];
773 }
774
775 histogram.totalWeight = 0.0;
776 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
777 for (double density : histogram.densityPerGeVRad) {
778 histogram.totalWeight += density * cellArea;
779 }
780
781 return histogram;
782 }
783
784 inline double jointHistogramArea(const JointHistogram& histogram) {
785 double area = 0.0;
786 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
787 for (double density : histogram.densityPerGeVRad) {
788 area += density * cellArea;
789 }
790 return area;
791 }
792
793 inline double jointHistogramMeanEnergyGeV(const JointHistogram& histogram) {
794 double mean = 0.0;
795 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
796 for (std::size_t i = 0; i < histogram.energyCentersGeV.size(); ++i) {
797 for (std::size_t j = 0; j < histogram.thetaCentersRad.size(); ++j) {
798 mean += histogram.energyCentersGeV[i]
799 * histogram.densityPerGeVRad[jointHistogramIndex(histogram, i, j)]
800 * cellArea;
801 }
802 }
803 return mean;
804 }
805
806 inline double jointHistogramMeanThetaRad(const JointHistogram& histogram) {
807 double mean = 0.0;
808 const double cellArea = histogram.energyBinWidthGeV * histogram.thetaBinWidthRad;
809 for (std::size_t i = 0; i < histogram.energyCentersGeV.size(); ++i) {
810 for (std::size_t j = 0; j < histogram.thetaCentersRad.size(); ++j) {
811 mean += histogram.thetaCentersRad[j]
812 * histogram.densityPerGeVRad[jointHistogramIndex(histogram, i, j)]
813 * cellArea;
814 }
815 }
816 return mean;
817 }
818
819 inline double jointHistogramL1Distance(const JointHistogram& lhs, const JointHistogram& rhs) {
820 if (lhs.energyCentersGeV.size() != rhs.energyCentersGeV.size()
821 || lhs.thetaCentersRad.size() != rhs.thetaCentersRad.size()) {
822 throw std::runtime_error("LinearComptonBenchmark: joint histogram sizes do not match.");
823 }
824
825 double distance = 0.0;
826 const double cellArea = lhs.energyBinWidthGeV * lhs.thetaBinWidthRad;
827 for (std::size_t i = 0; i < lhs.densityPerGeVRad.size(); ++i) {
828 distance += std::abs(lhs.densityPerGeVRad[i] - rhs.densityPerGeVRad[i]) * cellArea;
829 }
830 return distance;
831 }
832
833} // namespace LinearComptonBenchmark
834
835#endif
ippl::Vector< T, Dim > Vector_t
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
Definition Vector3D.cpp:95
double histogramArea(const SpectrumHistogram &histogram)
void writeSpectrumCSV(const SpectrumHistogram &histogram, const std::filesystem::path &outputPath)
SpectrumHistogram readSpectrumCSV(const std::filesystem::path &inputPath)
void writeAngleCSV(const AngleHistogram &histogram, const std::filesystem::path &outputPath)
double angleHistogramL1Distance(const AngleHistogram &lhs, const AngleHistogram &rhs)
JointHistogram integrateLabJointSpectrum(const JointConfig &config)
void writeJointCSV(const JointHistogram &histogram, const std::filesystem::path &outputPath)
AngleHistogram readAngleCSV(const std::filesystem::path &inputPath)
double photonPolarAngleRad(const Vector_t< double, 3 > &photonDirection, const Vector_t< double, 3 > &beamDirection)
double angleHistogramMeanRad(const AngleHistogram &histogram)
double jointHistogramL1Distance(const JointHistogram &lhs, const JointHistogram &rhs)
double histogramL1Distance(const SpectrumHistogram &lhs, const SpectrumHistogram &rhs)
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)
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)
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.
Definition Physics.h:40