OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
LinearBreitWheeler.cpp
Go to the documentation of this file.
2
3#include "Physics/Physics.h"
5#include "Utilities/Options.h"
6
7#include <algorithm>
8#include <cmath>
9#include <cstdint>
10#include <limits>
11#include <random>
12#include <string>
13
14namespace {
15 Vector_t<double, 3> normalizeDirection(
16 const Vector_t<double, 3>& direction, const char* where, const char* argument) {
17 const double norm = std::sqrt(dot(direction, direction));
18 if (norm <= 0.0) {
19 throw OpalException(
20 where, std::string("\"") + argument + "\" must be a non-zero vector.");
21 }
22 return direction / norm;
23 }
24
25 void ensureStrictlyPositive(double value, const char* where, const char* argument) {
26 if (value <= 0.0) {
27 throw OpalException(where, std::string("\"") + argument + "\" must be greater than 0.");
28 }
29 }
30
31 double clampCosine(double value) { return std::max(-1.0, std::min(1.0, value)); }
32
33 void ensureProposalZ(double value, const char* where) {
34 if (value < -1.0 || value > 1.0) {
35 throw OpalException(where, "\"proposalZ\" must lie in [-1, 1].");
36 }
37 }
38
39 std::uint64_t deterministicHostSeed() {
40 return Options::seed >= 0 ? static_cast<std::uint64_t>(Options::seed) : 123456789ULL;
41 }
42
43 std::uint64_t mixSeed(std::uint64_t seed, std::uint64_t streamIndex) {
44 const std::uint64_t golden = 0x9E3779B97F4A7C15ULL;
45 std::uint64_t mixed = seed + golden * (streamIndex + 1ULL);
46 mixed ^= (mixed >> 30);
47 mixed *= 0xBF58476D1CE4E5B9ULL;
48 mixed ^= (mixed >> 27);
49 mixed *= 0x94D049BB133111EBULL;
50 mixed ^= (mixed >> 31);
51 return mixed;
52 }
53
54 Vector_t<double, 3> chooseReferenceDirection(const Vector_t<double, 3>& direction) {
55 Vector_t<double, 3> reference(0.0);
56 reference(0) = 1.0;
57 if (std::abs(direction(0)) > 0.9) {
58 reference(0) = 0.0;
59 reference(1) = 1.0;
60 }
61 return reference;
62 }
63
64 void orthonormalFrame(
66 const char* where) {
67 e1 = cross(axis, chooseReferenceDirection(axis));
68 e1 = normalizeDirection(e1, where, "e1");
69 e2 = normalizeDirection(cross(axis, e1), where, "e2");
70 }
71
72 double cainProposalY(double invariantSGeV2, double z) {
73 const double cz = 2.0 * std::log(invariantSGeV2 / (4.0 * Physics::m_e * Physics::m_e));
74 const double expCZ = std::exp(cz);
75 const double expCZZ = std::exp(cz * z);
76 return (1.0 + (1.0 - expCZZ) / (expCZ - 1.0)) / (1.0 + expCZZ);
77 }
78
79 double cainProposalJacobian(double invariantSGeV2, double z, double y) {
80 const double cz = 2.0 * std::log(invariantSGeV2 / (4.0 * Physics::m_e * Physics::m_e));
81 const double expCZ = std::exp(cz);
82 const double expCZZ = std::exp(cz * z);
83 return -cz * expCZZ / ((expCZ - 1.0) * (1.0 + expCZZ)) - y * cz * expCZZ / (1.0 + expCZZ);
84 }
85
86 double cainUnpolarizedWeight(double invariantSGeV2, double z) {
87 const double threshold = 4.0 * Physics::m_e * Physics::m_e;
88 if (invariantSGeV2 <= threshold) {
89 return 0.0;
90 }
91
92 const double w = 0.5 * std::sqrt(invariantSGeV2);
93 const double beta = std::sqrt(std::max(0.0, 1.0 - threshold / invariantSGeV2));
94 const double root = std::sqrt(std::max(0.0, w * w - Physics::m_e * Physics::m_e));
95 const double x1 = Physics::m_e * Physics::m_e / (w * w + w * root);
96 const double y = cainProposalY(invariantSGeV2, z);
97 const double sm = 2.0 * Physics::m_e * Physics::m_e / ((1.0 + beta) * x1)
98 * (beta * (1.0 - 2.0 * y) - 1.0);
99 const double um = -2.0 * Physics::m_e * Physics::m_e / ((1.0 + beta) * x1)
100 * (beta * (1.0 - 2.0 * y) + 1.0);
101 const double costh1 = 1.0 + 2.0 * Physics::m_e * Physics::m_e * (1.0 / um + 1.0 / sm);
102 const double d = -(sm / um + um / sm);
103 const double f0 = d - (1.0 - costh1 * costh1);
104 const double jacobian = cainProposalJacobian(invariantSGeV2, z, y);
105 const double weight = f0 * beta * (1.0 + beta) * x1 * jacobian / 4.0;
106 return std::max(0.0, weight);
107 }
108
109 double rejectionUpperBound(double invariantSGeV2) {
110 constexpr int samples = 4096;
111 double upper = 0.0;
112 for (int i = 0; i <= samples; ++i) {
113 const double z = -1.0 + 2.0 * static_cast<double>(i) / static_cast<double>(samples);
114 upper = std::max(upper, cainUnpolarizedWeight(invariantSGeV2, z));
115 }
116 return std::max(upper * 1.0001, std::numeric_limits<double>::min());
117 }
118
119 Vector_t<double, 3> centerOfMomentumVelocity(
120 double energy1GeV, double energy2GeV, const Vector_t<double, 3>& dir1,
121 const Vector_t<double, 3>& dir2) {
122 return (energy1GeV * dir1 + energy2GeV * dir2) / (energy1GeV + energy2GeV);
123 }
124
125 Vector_t<double, 3> boostMomentum(
126 const Vector_t<double, 3>& momentumCM, double energyCMGeV,
127 const Vector_t<double, 3>& betaCM, double gammaCM, const char* where) {
128 const double beta2 = dot(betaCM, betaCM);
129 if (beta2 <= 0.0) {
130 return momentumCM;
131 }
132 if (beta2 >= 1.0) {
133 throw OpalException(where, "CM boost speed must stay below c.");
134 }
135
136 const double betaDotP = dot(betaCM, momentumCM);
137 const double factor = ((gammaCM - 1.0) * betaDotP / beta2) + gammaCM * energyCMGeV;
138 return momentumCM + factor * betaCM;
139 }
140} // namespace
141
142namespace Physics {
143 namespace LinearBreitWheeler {
144
145 double photonEnergyFromWavelengthGeV(double wavelength_m) {
146 constexpr const char* where =
147 "Physics::LinearBreitWheeler::photonEnergyFromWavelengthGeV()";
148 ensureStrictlyPositive(wavelength_m, where, "wavelength_m");
149 return Physics::two_pi * Physics::h_bar * Physics::c / wavelength_m;
150 }
151
153 double photon1EnergyGeV, double photon2EnergyGeV,
154 const Vector_t<double, 3>& photon1Direction,
155 const Vector_t<double, 3>& photon2Direction) {
156 constexpr const char* where = "Physics::LinearBreitWheeler::invariantSGeV2()";
157 ensureStrictlyPositive(photon1EnergyGeV, where, "photon1EnergyGeV");
158 ensureStrictlyPositive(photon2EnergyGeV, where, "photon2EnergyGeV");
159 const auto dir1 = normalizeDirection(photon1Direction, where, "photon1Direction");
160 const auto dir2 = normalizeDirection(photon2Direction, where, "photon2Direction");
161 return 2.0 * photon1EnergyGeV * photon2EnergyGeV * (1.0 - clampCosine(dot(dir1, dir2)));
162 }
163
165
166 bool isAboveThreshold(double sGeV2) { return sGeV2 >= thresholdInvariantSGeV2(); }
167
168 double pairBetaCM(double sGeV2) {
169 constexpr const char* where = "Physics::LinearBreitWheeler::pairBetaCM()";
170 ensureStrictlyPositive(sGeV2, where, "sGeV2");
171 if (!isAboveThreshold(sGeV2)) {
172 return 0.0;
173 }
174 return std::sqrt(std::max(0.0, 1.0 - thresholdInvariantSGeV2() / sGeV2));
175 }
176
177 double totalCrossSection(double sGeV2) {
178 constexpr const char* where = "Physics::LinearBreitWheeler::totalCrossSection()";
179 ensureStrictlyPositive(sGeV2, where, "sGeV2");
180 if (!isAboveThreshold(sGeV2)) {
181 return 0.0;
182 }
183
184 const double beta = pairBetaCM(sGeV2);
185 const double oneMinusBeta2 = std::max(0.0, 1.0 - beta * beta);
186 const double logTerm = std::log((1.0 + beta) / (1.0 - beta));
187 return 0.5 * Physics::pi * Physics::r_e * Physics::r_e * oneMinusBeta2
188 * ((3.0 - std::pow(beta, 4)) * logTerm - 2.0 * beta * (2.0 - beta * beta));
189 }
190
191 double proposalZToScatteringCosineCM(double sGeV2, double proposalZ) {
192 constexpr const char* where =
193 "Physics::LinearBreitWheeler::proposalZToScatteringCosineCM()";
194 ensureStrictlyPositive(sGeV2, where, "sGeV2");
195 ensureProposalZ(proposalZ, where);
196 if (!isAboveThreshold(sGeV2)) {
197 return 0.0;
198 }
199 const double y = cainProposalY(sGeV2, proposalZ);
200 return 1.0 - 2.0 * y;
201 }
202
203 double unpolarizedAngularWeight(double sGeV2, double proposalZ) {
204 constexpr const char* where = "Physics::LinearBreitWheeler::unpolarizedAngularWeight()";
205 ensureStrictlyPositive(sGeV2, where, "sGeV2");
206 ensureProposalZ(proposalZ, where);
207 if (!isAboveThreshold(sGeV2)) {
208 return 0.0;
209 }
210 return cainUnpolarizedWeight(sGeV2, proposalZ);
211 }
212
214 double highEnergyPhotonEnergyGeV, double laserPhotonEnergyGeV,
215 const Vector_t<double, 3>& highEnergyDirection,
216 const Vector_t<double, 3>& laserDirection) {
217 constexpr const char* where = "Physics::LinearBreitWheeler::makeSamplingKernel()";
218 ensureStrictlyPositive(highEnergyPhotonEnergyGeV, where, "highEnergyPhotonEnergyGeV");
219 ensureStrictlyPositive(laserPhotonEnergyGeV, where, "laserPhotonEnergyGeV");
220
221 SamplingKernel kernel;
222 kernel.highEnergyPhotonEnergyGeV = highEnergyPhotonEnergyGeV;
223 kernel.laserPhotonEnergyGeV = laserPhotonEnergyGeV;
224 kernel.highEnergyDirection =
225 normalizeDirection(highEnergyDirection, where, "highEnergyDirection");
226 kernel.laserDirection = normalizeDirection(laserDirection, where, "laserDirection");
228 highEnergyPhotonEnergyGeV, laserPhotonEnergyGeV, kernel.highEnergyDirection,
229 kernel.laserDirection);
230 if (!isAboveThreshold(kernel.invariantSGeV2)) {
231 throw OpalException(
232 where, "Photon-photon invariant is below the Breit-Wheeler threshold.");
233 }
234
235 kernel.pairBetaCM = pairBetaCM(kernel.invariantSGeV2);
236 kernel.cmVelocity = centerOfMomentumVelocity(
237 highEnergyPhotonEnergyGeV, laserPhotonEnergyGeV, kernel.highEnergyDirection,
238 kernel.laserDirection);
239 const double beta2 = dot(kernel.cmVelocity, kernel.cmVelocity);
240 if (beta2 >= 1.0) {
241 throw OpalException(where, "Center-of-momentum boost speed must stay below c.");
242 }
243 kernel.cmLorentzGamma = 1.0 / std::sqrt(std::max(1.0e-30, 1.0 - beta2));
244 kernel.rejectionUpperBound = rejectionUpperBound(kernel.invariantSGeV2);
245 return kernel;
246 }
247
248 std::mt19937_64 makeHostRandomEngine(std::uint64_t streamIndex) {
249 return std::mt19937_64(mixSeed(deterministicHostSeed(), streamIndex));
250 }
251
252 SampledEvent sampleEvent(const SamplingKernel& kernel, std::mt19937_64& engine) {
253 constexpr const char* where = "Physics::LinearBreitWheeler::sampleEvent()";
254 if (!isAboveThreshold(kernel.invariantSGeV2)) {
255 throw OpalException(where, "Sampling kernel is below the Breit-Wheeler threshold.");
256 }
257
258 std::uniform_real_distribution<double> unit(0.0, 1.0);
259 const double sqrtS = std::sqrt(kernel.invariantSGeV2);
260 const double energyCMGeV = 0.5 * sqrtS;
261 const double momentumCM = std::sqrt(
262 std::max(0.0, energyCMGeV * energyCMGeV - Physics::m_e * Physics::m_e));
263
264 double z = 0.0;
265 double y = 0.0;
266 double scatteringCosine = 0.0;
267 while (true) {
268 z = 1.0 - 2.0 * unit(engine);
269 y = cainProposalY(kernel.invariantSGeV2, z);
270 scatteringCosine = 1.0 - 2.0 * y;
271 const double accept = cainUnpolarizedWeight(kernel.invariantSGeV2, z)
272 / kernel.rejectionUpperBound;
273 if (unit(engine) <= accept) {
274 break;
275 }
276 }
277
278 const double azimuth = Physics::two_pi * unit(engine);
279 Vector_t<double, 3> cmAxis1(0.0);
280 Vector_t<double, 3> cmAxis2(0.0);
281 const Vector_t<double, 3> highEnergyCM = normalizeDirection(
282 kernel.highEnergyDirection - kernel.cmVelocity, where, "highEnergyCM");
283 orthonormalFrame(highEnergyCM, cmAxis1, cmAxis2, where);
284 const double sinTheta =
285 std::sqrt(std::max(0.0, 1.0 - scatteringCosine * scatteringCosine));
286 const Vector_t<double, 3> electronDirectionCM = normalizeDirection(
287 scatteringCosine * highEnergyCM
288 + sinTheta
289 * (std::cos(azimuth) * cmAxis1 + std::sin(azimuth) * cmAxis2),
290 where, "electronDirectionCM");
291
292 const Vector_t<double, 3> electronMomentumCM = momentumCM * electronDirectionCM;
293 const Vector_t<double, 3> positronMomentumCM = -electronMomentumCM;
294 const Vector_t<double, 3> electronMomentumLab = boostMomentum(
295 electronMomentumCM, energyCMGeV, kernel.cmVelocity, kernel.cmLorentzGamma,
296 where);
297 const Vector_t<double, 3> positronMomentumLab = boostMomentum(
298 positronMomentumCM, energyCMGeV, kernel.cmVelocity, kernel.cmLorentzGamma,
299 where);
300
301 SampledEvent event;
302 event.scatteringCosineCM = scatteringCosine;
303 event.azimuthCM = azimuth;
304 event.electronMomentumLabGeV = electronMomentumLab;
305 event.positronMomentumLabGeV = positronMomentumLab;
306 event.electronEnergyLabGeV = std::sqrt(
307 dot(electronMomentumLab, electronMomentumLab) + Physics::m_e * Physics::m_e);
308 event.positronEnergyLabGeV = std::sqrt(
309 dot(positronMomentumLab, positronMomentumLab) + Physics::m_e * Physics::m_e);
310 return event;
311 }
312
313 } // namespace LinearBreitWheeler
314} // namespace Physics
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
int seed
The current random seed.
Definition Options.cpp:37
double proposalZToScatteringCosineCM(double sGeV2, double proposalZ)
Map CAIN's proposal parameter to the CM scattering cosine.
double unpolarizedAngularWeight(double sGeV2, double proposalZ)
Evaluate the unpolarized CAIN-aligned angular kernel in proposal coordinates.
double invariantSGeV2
Two-photon invariant in GeV^2.
double thresholdInvariantSGeV2()
Threshold invariant for .
Vector_t< double, 3 > highEnergyDirection
Normalized incoming high-energy photon direction.
double cmLorentzGamma
Lorentz factor of the CM frame relative to the laboratory frame.
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 > laserDirection
Normalized incoming laser-photon direction.
double highEnergyPhotonEnergyGeV
Incoming high-energy photon energy in the lab frame [GeV].
double scatteringCosineCM
Center-of-momentum scattering cosine .
Vector_t< double, 3 > cmVelocity
Dimensionless CM boost velocity.
double laserPhotonEnergyGeV
Incoming laser-photon energy in the lab frame [GeV].
double invariantSGeV2(double photon1EnergyGeV, double photon2EnergyGeV, const Vector_t< double, 3 > &photon1Direction, const Vector_t< double, 3 > &photon2Direction)
Two-photon invariant for Breit-Wheeler pair production.
std::mt19937_64 makeHostRandomEngine(std::uint64_t streamIndex)
Build a deterministic host RNG from Options::seed.
double totalCrossSection(double sGeV2)
Total unpolarized linear Breit-Wheeler cross section.
double photonEnergyFromWavelengthGeV(double wavelength_m)
Convert a laser wavelength to a single-photon energy.
bool isAboveThreshold(double sGeV2)
Check whether pair creation is kinematically allowed.
double pairBetaCM(double sGeV2)
Outgoing lepton speed in the pair center-of-momentum frame.
One sampled linear Breit-Wheeler event.
Host-only cached data for repeated linear Breit-Wheeler event sampling.
Definition Air.h:27
constexpr double two_pi
The value of.
Definition Physics.h:40
constexpr double h_bar
The reduced Planck constant in GeVs.
Definition Physics.h:69
constexpr double m_e
The electron rest mass in GeV.
Definition Physics.h:93
constexpr double c
The velocity of light in m/s.
Definition Physics.h:60
constexpr double pi
The value of.
Definition Physics.h:36
constexpr double r_e
The classical electron radius in m.
Definition Physics.h:96