17 const double norm = std::sqrt(
dot(direction, direction));
20 where, std::string(
"\"") + argument +
"\" must be a non-zero vector.");
22 return direction / norm;
25 void ensureStrictlyPositive(
double value,
const char* where,
const char* argument) {
27 throw OpalException(where, std::string(
"\"") + argument +
"\" must be greater than 0.");
31 double clampCosine(
double value) {
return std::max(-1.0, std::min(1.0, value)); }
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].");
39 std::uint64_t deterministicHostSeed() {
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);
57 if (std::abs(direction(0)) > 0.9) {
64 void orthonormalFrame(
67 e1 =
cross(axis, chooseReferenceDirection(axis));
68 e1 = normalizeDirection(e1, where,
"e1");
69 e2 = normalizeDirection(
cross(axis, e1), where,
"e2");
72 double cainProposalY(
double invariantSGeV2,
double z) {
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);
79 double cainProposalJacobian(
double invariantSGeV2,
double z,
double y) {
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);
86 double cainUnpolarizedWeight(
double invariantSGeV2,
double z) {
88 if (invariantSGeV2 <= threshold) {
92 const double w = 0.5 * std::sqrt(invariantSGeV2);
93 const double beta = std::sqrt(std::max(0.0, 1.0 - threshold / invariantSGeV2));
96 const double y = cainProposalY(invariantSGeV2, z);
98 * (beta * (1.0 - 2.0 * y) - 1.0);
100 * (beta * (1.0 - 2.0 * y) + 1.0);
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);
109 double rejectionUpperBound(
double invariantSGeV2) {
110 constexpr int samples = 4096;
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));
116 return std::max(upper * 1.0001, std::numeric_limits<double>::min());
122 return (energy1GeV * dir1 + energy2GeV * dir2) / (energy1GeV + energy2GeV);
128 const double beta2 =
dot(betaCM, betaCM);
133 throw OpalException(where,
"CM boost speed must stay below c.");
136 const double betaDotP =
dot(betaCM, momentumCM);
137 const double factor = ((gammaCM - 1.0) * betaDotP / beta2) + gammaCM * energyCMGeV;
138 return momentumCM + factor * betaCM;
143 namespace LinearBreitWheeler {
146 constexpr const char* where =
147 "Physics::LinearBreitWheeler::photonEnergyFromWavelengthGeV()";
148 ensureStrictlyPositive(wavelength_m, where,
"wavelength_m");
153 double photon1EnergyGeV,
double photon2EnergyGeV,
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)));
169 constexpr const char* where =
"Physics::LinearBreitWheeler::pairBetaCM()";
170 ensureStrictlyPositive(sGeV2, where,
"sGeV2");
178 constexpr const char* where =
"Physics::LinearBreitWheeler::totalCrossSection()";
179 ensureStrictlyPositive(sGeV2, where,
"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));
188 * ((3.0 - std::pow(beta, 4)) * logTerm - 2.0 * beta * (2.0 - beta * beta));
192 constexpr const char* where =
193 "Physics::LinearBreitWheeler::proposalZToScatteringCosineCM()";
194 ensureStrictlyPositive(sGeV2, where,
"sGeV2");
195 ensureProposalZ(proposalZ, where);
199 const double y = cainProposalY(sGeV2, proposalZ);
200 return 1.0 - 2.0 * y;
204 constexpr const char* where =
"Physics::LinearBreitWheeler::unpolarizedAngularWeight()";
205 ensureStrictlyPositive(sGeV2, where,
"sGeV2");
206 ensureProposalZ(proposalZ, where);
210 return cainUnpolarizedWeight(sGeV2, proposalZ);
214 double highEnergyPhotonEnergyGeV,
double laserPhotonEnergyGeV,
217 constexpr const char* where =
"Physics::LinearBreitWheeler::makeSamplingKernel()";
218 ensureStrictlyPositive(highEnergyPhotonEnergyGeV, where,
"highEnergyPhotonEnergyGeV");
219 ensureStrictlyPositive(laserPhotonEnergyGeV, where,
"laserPhotonEnergyGeV");
225 normalizeDirection(highEnergyDirection, where,
"highEnergyDirection");
226 kernel.
laserDirection = normalizeDirection(laserDirection, where,
"laserDirection");
232 where,
"Photon-photon invariant is below the Breit-Wheeler threshold.");
241 throw OpalException(where,
"Center-of-momentum boost speed must stay below c.");
243 kernel.
cmLorentzGamma = 1.0 / std::sqrt(std::max(1.0e-30, 1.0 - beta2));
249 return std::mt19937_64(mixSeed(deterministicHostSeed(), streamIndex));
253 constexpr const char* where =
"Physics::LinearBreitWheeler::sampleEvent()";
255 throw OpalException(where,
"Sampling kernel is below the Breit-Wheeler threshold.");
258 std::uniform_real_distribution<double> unit(0.0, 1.0);
260 const double energyCMGeV = 0.5 * sqrtS;
261 const double momentumCM = std::sqrt(
266 double scatteringCosine = 0.0;
268 z = 1.0 - 2.0 * unit(engine);
270 scatteringCosine = 1.0 - 2.0 * y;
271 const double accept = cainUnpolarizedWeight(kernel.
invariantSGeV2, z)
273 if (unit(engine) <= accept) {
283 orthonormalFrame(highEnergyCM, cmAxis1, cmAxis2, where);
284 const double sinTheta =
285 std::sqrt(std::max(0.0, 1.0 - scatteringCosine * scatteringCosine));
287 scatteringCosine * highEnergyCM
289 * (std::cos(azimuth) * cmAxis1 + std::sin(azimuth) * cmAxis2),
290 where,
"electronDirectionCM");
303 event.azimuthCM = azimuth;
304 event.electronMomentumLabGeV = electronMomentumLab;
305 event.positronMomentumLabGeV = positronMomentumLab;
306 event.electronEnergyLabGeV = std::sqrt(
308 event.positronEnergyLabGeV = std::sqrt(
ippl::Vector< T, Dim > Vector_t
Vector3D cross(const Vector3D &lhs, const Vector3D &rhs)
Vector cross product.
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
int seed
The current random seed.
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 rejectionUpperBound
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.
constexpr double two_pi
The value of.
constexpr double h_bar
The reduced Planck constant in GeVs.
constexpr double m_e
The electron rest mass in GeV.
constexpr double c
The velocity of light in m/s.
constexpr double pi
The value of.
constexpr double r_e
The classical electron radius in m.