File indexing completed on 2026-09-05 08:37:56
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Common.hpp"
0013 #include "Acts/Definitions/PdgParticle.hpp"
0014 #include "Acts/Definitions/Units.hpp"
0015 #include "Acts/Utilities/MathHelpers.hpp"
0016 #include "Acts/Utilities/UnitVectors.hpp"
0017 #include "ActsFatras/EventData/Barcode.hpp"
0018 #include "ActsFatras/EventData/GenerationProcess.hpp"
0019 #include "ActsFatras/EventData/Particle.hpp"
0020
0021 #include <algorithm>
0022 #include <array>
0023 #include <cmath>
0024 #include <limits>
0025 #include <numbers>
0026 #include <random>
0027 #include <utility>
0028 #include <vector>
0029
0030 namespace ActsFatras {
0031
0032
0033
0034
0035 class PhotonConversion {
0036 public:
0037
0038 double childEnergyScaleFactor = 2.;
0039
0040 double conversionProbScaleFactor = 0.98;
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050 template <typename generator_t>
0051 std::pair<double, double> generatePathLimits(generator_t& generator,
0052 const Particle& particle) const;
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062 template <typename generator_t>
0063 bool run(generator_t& generator, Particle& particle,
0064 std::vector<Particle>& generated) const;
0065
0066 private:
0067
0068
0069
0070
0071
0072
0073
0074 std::array<Particle, 2> generateChildren(
0075 const Particle& photon, double child1Energy,
0076 const Acts::Vector3& child1Direction) const;
0077
0078
0079
0080
0081
0082
0083
0084
0085 template <typename generator_t>
0086 double generateFirstChildEnergyFraction(generator_t& generator,
0087 double gammaMom) const;
0088
0089
0090
0091
0092
0093
0094
0095
0096 template <typename generator_t>
0097 Acts::Vector3 generateChildDirection(generator_t& generator,
0098 const Particle& particle) const;
0099
0100
0101
0102
0103 double screenFunction1(double delta) const;
0104 double screenFunction2(double delta) const;
0105
0106
0107
0108 static float electronMass() {
0109 return Acts::findMass(Acts::PdgParticle::eElectron).value();
0110 }
0111 };
0112
0113 inline double PhotonConversion::screenFunction1(double delta) const {
0114
0115 return (delta > 1.4) ? 42.038 - 8.29 * std::log(delta + 0.958)
0116 : 42.184 - delta * (7.444 - 1.623 * delta);
0117 }
0118
0119 inline double PhotonConversion::screenFunction2(double delta) const {
0120
0121
0122 return (delta > 1.4) ? 42.038 - 8.29 * std::log(delta + 0.958)
0123 : 41.326 - delta * (5.848 - 0.902 * delta);
0124 }
0125
0126 template <typename generator_t>
0127 std::pair<double, double> PhotonConversion::generatePathLimits(
0128 generator_t& generator, const Particle& particle) const {
0129
0130
0131
0132 if (particle.pdg() != Acts::PdgParticle::eGamma ||
0133 particle.absoluteMomentum() < (2 * electronMass())) {
0134 return {std::numeric_limits<double>::infinity(),
0135 std::numeric_limits<double>::infinity()};
0136 }
0137
0138
0139
0140
0141
0142
0143
0144
0145
0146
0147
0148
0149
0150
0151 constexpr double p0 = -7.01612e-03;
0152 constexpr double p1 = 7.69040e-02;
0153 constexpr double p2 = -6.07682e-01;
0154
0155
0156 const double xi = p0 + p1 * std::pow(particle.absoluteMomentum(), p2);
0157
0158 std::uniform_real_distribution<double> uniformDistribution{0., 1.};
0159
0160 return {-9. / 7. *
0161 std::log(conversionProbScaleFactor *
0162 (1 - uniformDistribution(generator))) /
0163 (1. - xi),
0164 std::numeric_limits<double>::infinity()};
0165 }
0166
0167 template <typename generator_t>
0168 double PhotonConversion::generateFirstChildEnergyFraction(
0169 generator_t& generator, double gammaMom) const {
0170
0171
0172
0173
0174
0175 constexpr double k1 = 0.0083;
0176 constexpr double k2 = 0.20206;
0177 constexpr double k3 = 0.0020;
0178 constexpr double k4 = 0.0369;
0179 constexpr double alphaEM = 1. / 137.;
0180 constexpr double m_Z = 13.;
0181 constexpr double az2 = (alphaEM * m_Z) * (alphaEM * m_Z);
0182 constexpr double az4 = az2 * az2;
0183 constexpr double coulombFactor =
0184 (k1 * az4 + k2 + 1. / (1. + az2)) * az2 - (k3 * az4 + k4) * az4;
0185
0186 const double logZ13 = std::log(m_Z) * 1. / 3.;
0187 const double FZ = 8. * (logZ13 + coulombFactor);
0188 const double deltaMax = std::exp((42.038 - FZ) * 0.1206) - 0.958;
0189
0190 const double deltaPreFactor = 136. / std::pow(m_Z, 1. / 3.);
0191 const double eps0 = electronMass() / gammaMom;
0192 const double deltaFactor = deltaPreFactor * eps0;
0193 const double deltaMin = 4. * deltaFactor;
0194
0195
0196 const double epsMin =
0197 std::max(eps0, 0.5 - 0.5 * std::sqrt(1. - deltaMin / deltaMax));
0198 const double epsRange = 0.5 - epsMin;
0199
0200
0201 const double F10 = screenFunction1(deltaMin) - FZ;
0202 const double F20 = screenFunction2(deltaMin) - FZ;
0203 const double NormF1 = F10 * epsRange * epsRange;
0204 const double NormF2 = 1.5 * F20;
0205
0206
0207 double greject = 0.;
0208 double eps = 0.;
0209 std::uniform_real_distribution<double> rndmEngine;
0210 do {
0211 if (NormF1 > rndmEngine(generator) * (NormF1 + NormF2)) {
0212 eps = 0.5 - epsRange * std::pow(rndmEngine(generator), 1. / 3.);
0213 const double delta = deltaFactor / (eps * (1. - eps));
0214 greject = (screenFunction1(delta) - FZ) / F10;
0215 } else {
0216 eps = epsMin + epsRange * rndmEngine(generator);
0217 const double delta = deltaFactor / (eps * (1. - eps));
0218 greject = (screenFunction2(delta) - FZ) / F20;
0219 }
0220 } while (greject < rndmEngine(generator));
0221
0222 return eps * childEnergyScaleFactor;
0223 }
0224
0225 template <typename generator_t>
0226 Acts::Vector3 PhotonConversion::generateChildDirection(
0227 generator_t& generator, const Particle& particle) const {
0228
0229
0230
0231 std::uniform_real_distribution<double> uniformDistribution{0., 1.};
0232 const double u = -std::log(uniformDistribution(generator) *
0233 uniformDistribution(generator)) *
0234 1.6;
0235 const double theta = (electronMass() / particle.energy()) *
0236 ((uniformDistribution(generator) < 0.25)
0237 ? u
0238 : u * 1. / 3.);
0239
0240
0241 const auto psi = std::uniform_real_distribution<double>(
0242 -std::numbers::pi, std::numbers::pi)(generator);
0243
0244
0245 const Acts::RotationMatrix3 rotation(
0246
0247 Acts::AngleAxis3(psi, particle.direction()) *
0248
0249 Acts::AngleAxis3(theta,
0250 Acts::createCurvilinearUnitU(particle.direction())));
0251 return rotation * particle.direction();
0252 }
0253
0254 inline std::array<Particle, 2> PhotonConversion::generateChildren(
0255 const Particle& photon, double child1Energy,
0256 const Acts::Vector3& child1Direction) const {
0257 using namespace Acts::UnitLiterals;
0258
0259
0260 const double massChild = electronMass();
0261 const double absoluteMomentum1 = Acts::fastCathetus(child1Energy, massChild);
0262
0263
0264 const Acts::Vector3 momentum2 =
0265 photon.fourMomentum().template segment<3>(Acts::eMom0) -
0266 absoluteMomentum1 * child1Direction;
0267 const Acts::Vector3 child2Direction = momentum2.normalized();
0268 const double absoluteMomentum2 = momentum2.norm();
0269
0270
0271
0272
0273
0274 return {
0275 Particle(photon.particleId().makeDescendant(0), Acts::eElectron, -1_e,
0276 electronMass())
0277 .setPosition4(photon.fourPosition())
0278 .setDirection(child1Direction)
0279 .setAbsoluteMomentum(absoluteMomentum1)
0280 .setProcess(GenerationProcess::ePhotonConversion)
0281 .setReferenceSurface(photon.referenceSurface()),
0282 Particle(photon.particleId().makeDescendant(1), Acts::ePositron, 1_e,
0283 electronMass())
0284 .setPosition4(photon.fourPosition())
0285 .setDirection(child2Direction)
0286 .setAbsoluteMomentum(absoluteMomentum2)
0287 .setProcess(GenerationProcess::ePhotonConversion)
0288 .setReferenceSurface(photon.referenceSurface()),
0289 };
0290 }
0291
0292 template <typename generator_t>
0293 bool PhotonConversion::run(generator_t& generator, Particle& particle,
0294 std::vector<Particle>& generated) const {
0295
0296 if (particle.pdg() != Acts::PdgParticle::eGamma) {
0297 return false;
0298 }
0299
0300
0301 const double p = particle.absoluteMomentum();
0302 if (p < (2 * electronMass())) {
0303 return false;
0304 }
0305
0306
0307 const double child1Energy =
0308 p * generateFirstChildEnergyFraction(generator, p);
0309
0310
0311 const Acts::Vector3 child1Dir = generateChildDirection(generator, particle);
0312
0313
0314 const std::array<Particle, 2> finalState =
0315 generateChildren(particle, child1Energy, child1Dir);
0316 generated.insert(generated.end(), finalState.begin(), finalState.end());
0317
0318 return true;
0319 }
0320
0321 }