File indexing completed on 2025-01-18 09:11:26
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Material/Interactions.hpp"
0010
0011 #include "Acts/Definitions/PdgParticle.hpp"
0012 #include "Acts/Material/Material.hpp"
0013 #include "Acts/Utilities/MathHelpers.hpp"
0014
0015 #include <cassert>
0016 #include <cmath>
0017
0018 using namespace Acts::UnitLiterals;
0019
0020 namespace {
0021
0022
0023
0024 constexpr float Me = 0.5109989461_MeV;
0025
0026 constexpr float K = 0.307075_MeV * 1_cm * 1_cm;
0027
0028 constexpr float PlasmaEnergyScale = 28.816_eV;
0029
0030
0031 struct RelativisticQuantities {
0032 float q2OverBeta2 = 0.0f;
0033 float beta2 = 0.0f;
0034 float betaGamma = 0.0f;
0035 float gamma = 0.0f;
0036
0037 RelativisticQuantities(float mass, float qOverP, float absQ) {
0038 assert((0 < mass) && "Mass must be positive");
0039 assert((qOverP != 0) && "q/p must be non-zero");
0040 assert((absQ > 0) && "Absolute charge must be non-zero and positive");
0041
0042
0043 q2OverBeta2 = absQ * absQ + (mass * qOverP) * (mass * qOverP);
0044 assert((q2OverBeta2 >= 0) && "Negative q2OverBeta2");
0045
0046 const float mOverP = mass * std::abs(qOverP / absQ);
0047 const float pOverM = 1.0f / mOverP;
0048
0049 beta2 = 1.0f / (1.0f + mOverP * mOverP);
0050 assert((beta2 >= 0) && "Negative beta2");
0051
0052 betaGamma = pOverM;
0053 assert((betaGamma >= 0) && "Negative betaGamma");
0054
0055 gamma = Acts::fastHypot(1.0f, pOverM);
0056 }
0057 };
0058
0059
0060 inline float deriveBeta2(float qOverP, const RelativisticQuantities& rq) {
0061 return -2 / (qOverP * rq.gamma * rq.gamma);
0062 }
0063
0064
0065 inline float computeMassTerm(float mass, const RelativisticQuantities& rq) {
0066 return 2 * mass * rq.betaGamma * rq.betaGamma;
0067 }
0068
0069
0070 inline float logDeriveMassTerm(float qOverP) {
0071
0072 return -2 / qOverP;
0073 }
0074
0075
0076
0077
0078 inline float computeWMax(float mass, const RelativisticQuantities& rq) {
0079 const float mfrac = Me / mass;
0080 const float nominator = 2 * Me * rq.betaGamma * rq.betaGamma;
0081 const float denonimator = 1.0f + 2 * rq.gamma * mfrac + mfrac * mfrac;
0082 return nominator / denonimator;
0083 }
0084
0085
0086 inline float logDeriveWMax(float mass, float qOverP,
0087 const RelativisticQuantities& rq) {
0088
0089
0090
0091 const float a = std::abs(qOverP / std::sqrt(rq.q2OverBeta2));
0092
0093 const float b = Me * (1.0f + (mass / Me) * (mass / Me));
0094 return -2 * (a * b - 2 + rq.beta2) / (qOverP * (a * b + 2));
0095 }
0096
0097
0098
0099
0100
0101
0102
0103
0104
0105 inline float computeEpsilon(float molarElectronDensity, float thickness,
0106 const RelativisticQuantities& rq) {
0107 return 0.5f * K * molarElectronDensity * thickness * rq.q2OverBeta2;
0108 }
0109
0110
0111 inline float logDeriveEpsilon(float qOverP, const RelativisticQuantities& rq) {
0112
0113 return 2 / (qOverP * rq.gamma * rq.gamma);
0114 }
0115
0116
0117 inline float computeDeltaHalf(float meanExitationPotential,
0118 float molarElectronDensity,
0119 const RelativisticQuantities& rq) {
0120
0121
0122 if (rq.betaGamma < 10.0f) {
0123 return 0.0f;
0124 }
0125
0126 const float plasmaEnergy =
0127 PlasmaEnergyScale * std::sqrt(1000.f * molarElectronDensity);
0128 return std::log(rq.betaGamma) +
0129 std::log(plasmaEnergy / meanExitationPotential) - 0.5f;
0130 }
0131
0132
0133 inline float deriveDeltaHalf(float qOverP, const RelativisticQuantities& rq) {
0134
0135
0136
0137
0138 return (rq.betaGamma < 10.0f) ? 0.0f : (-1.0f / qOverP);
0139 }
0140
0141
0142
0143
0144
0145
0146
0147
0148 inline float convertLandauFwhmToGaussianSigma(float fwhm) {
0149
0150 return fwhm * 0.42466090014400953f;
0151 }
0152
0153 namespace detail {
0154
0155 inline float computeEnergyLossLandauFwhm(const Acts::MaterialSlab& slab,
0156 const RelativisticQuantities& rq) {
0157
0158 if (!slab.isValid()) {
0159 return 0.0f;
0160 }
0161
0162 const float Ne = slab.material().molarElectronDensity();
0163 const float thickness = slab.thickness();
0164
0165 return 4 * computeEpsilon(Ne, thickness, rq);
0166 }
0167
0168 }
0169
0170 }
0171
0172 float Acts::computeEnergyLossBethe(const MaterialSlab& slab, float m,
0173 float qOverP, float absQ) {
0174
0175 if (!slab.isValid()) {
0176 return 0.0f;
0177 }
0178
0179 const RelativisticQuantities rq{m, qOverP, absQ};
0180 const float I = slab.material().meanExcitationEnergy();
0181 const float Ne = slab.material().molarElectronDensity();
0182 const float thickness = slab.thickness();
0183 const float eps = computeEpsilon(Ne, thickness, rq);
0184 const float dhalf = computeDeltaHalf(I, Ne, rq);
0185 const float u = computeMassTerm(Me, rq);
0186 const float wmax = computeWMax(m, rq);
0187
0188
0189
0190
0191
0192 const float running =
0193 std::log(u / I) + std::log(wmax / I) - 2.0f * rq.beta2 - 2.0f * dhalf;
0194 return eps * running;
0195 }
0196
0197 float Acts::deriveEnergyLossBetheQOverP(const MaterialSlab& slab, float m,
0198 float qOverP, float absQ) {
0199
0200 if (!slab.isValid()) {
0201 return 0.0f;
0202 }
0203
0204 const RelativisticQuantities rq{m, qOverP, absQ};
0205 const float I = slab.material().meanExcitationEnergy();
0206 const float Ne = slab.material().molarElectronDensity();
0207 const float thickness = slab.thickness();
0208 const float eps = computeEpsilon(Ne, thickness, rq);
0209 const float dhalf = computeDeltaHalf(I, Ne, rq);
0210 const float u = computeMassTerm(Me, rq);
0211 const float wmax = computeWMax(m, rq);
0212
0213
0214
0215
0216
0217
0218
0219
0220
0221
0222
0223 const float logDerEps = logDeriveEpsilon(qOverP, rq);
0224 const float derDHalf = deriveDeltaHalf(qOverP, rq);
0225 const float logDerU = logDeriveMassTerm(qOverP);
0226 const float logDerWmax = logDeriveWMax(m, qOverP, rq);
0227 const float derBeta2 = deriveBeta2(qOverP, rq);
0228 const float rel = logDerEps * (std::log(u / I) + std::log(wmax / I) -
0229 2.0f * rq.beta2 - 2.0f * dhalf) +
0230 logDerU + logDerWmax - 2.0f * derBeta2 - 2.0f * derDHalf;
0231 return eps * rel;
0232 }
0233
0234 float Acts::computeEnergyLossLandau(const MaterialSlab& slab, float m,
0235 float qOverP, float absQ) {
0236
0237 if (!slab.isValid()) {
0238 return 0.0f;
0239 }
0240
0241 const RelativisticQuantities rq{m, qOverP, absQ};
0242 const float I = slab.material().meanExcitationEnergy();
0243 const float Ne = slab.material().molarElectronDensity();
0244 const float thickness = slab.thickness();
0245 const float eps = computeEpsilon(Ne, thickness, rq);
0246 const float dhalf = computeDeltaHalf(I, Ne, rq);
0247 const float u = computeMassTerm(Me, rq);
0248
0249 const float running =
0250 std::log(u / I) + std::log(eps / I) + 0.2f - rq.beta2 - 2 * dhalf;
0251 return eps * running;
0252 }
0253
0254 float Acts::deriveEnergyLossLandauQOverP(const MaterialSlab& slab, float m,
0255 float qOverP, float absQ) {
0256
0257 if (!slab.isValid()) {
0258 return 0.0f;
0259 }
0260
0261 const RelativisticQuantities rq{m, qOverP, absQ};
0262 const float I = slab.material().meanExcitationEnergy();
0263 const float Ne = slab.material().molarElectronDensity();
0264 const float thickness = slab.thickness();
0265 const float eps = computeEpsilon(Ne, thickness, rq);
0266 const float dhalf = computeDeltaHalf(I, Ne, rq);
0267 const float t = computeMassTerm(Me, rq);
0268
0269
0270
0271
0272
0273
0274
0275
0276
0277
0278
0279 const float logDerEps = logDeriveEpsilon(qOverP, rq);
0280 const float derDHalf = deriveDeltaHalf(qOverP, rq);
0281 const float logDerT = logDeriveMassTerm(qOverP);
0282 const float derBeta2 = deriveBeta2(qOverP, rq);
0283 const float rel = logDerEps * (std::log(t / I) + std::log(eps / I) - 0.2f -
0284 rq.beta2 - 2 * dhalf) +
0285 logDerT + logDerEps - derBeta2 - 2 * derDHalf;
0286 return eps * rel;
0287 }
0288
0289 float Acts::computeEnergyLossLandauSigma(const MaterialSlab& slab, float m,
0290 float qOverP, float absQ) {
0291
0292 if (!slab.isValid()) {
0293 return 0.0f;
0294 }
0295
0296 const RelativisticQuantities rq{m, qOverP, absQ};
0297 const float Ne = slab.material().molarElectronDensity();
0298 const float thickness = slab.thickness();
0299
0300 const float fwhm = 4 * computeEpsilon(Ne, thickness, rq);
0301 return convertLandauFwhmToGaussianSigma(fwhm);
0302 }
0303
0304 float Acts::computeEnergyLossLandauFwhm(const MaterialSlab& slab, float m,
0305 float qOverP, float absQ) {
0306 const RelativisticQuantities rq{m, qOverP, absQ};
0307 return detail::computeEnergyLossLandauFwhm(slab, rq);
0308 }
0309
0310 float Acts::computeEnergyLossLandauSigmaQOverP(const MaterialSlab& slab,
0311 float m, float qOverP,
0312 float absQ) {
0313 const RelativisticQuantities rq{m, qOverP, absQ};
0314 const float fwhm = detail::computeEnergyLossLandauFwhm(slab, rq);
0315 const float sigmaE = convertLandauFwhmToGaussianSigma(fwhm);
0316
0317
0318
0319
0320
0321
0322
0323 const float pInv = qOverP / absQ;
0324 const float qOverBeta = std::sqrt(rq.q2OverBeta2);
0325 return qOverBeta * pInv * pInv * sigmaE;
0326 }
0327
0328 namespace {
0329
0330
0331 inline float computeBremsstrahlungLossMean(float mass, float energy) {
0332 return energy * (Me / mass) * (Me / mass);
0333 }
0334
0335
0336 inline float deriveBremsstrahlungLossMeanE(float mass) {
0337 return (Me / mass) * (Me / mass);
0338 }
0339
0340
0341
0342
0343
0344
0345
0346
0347
0348 constexpr float MuonHighLowThreshold = 1_TeV;
0349
0350 constexpr double MuonLow0 = -0.5345_MeV;
0351
0352 constexpr double MuonLow1 = 6.803e-5;
0353
0354 constexpr double MuonLow2 = 2.278e-11 / 1_MeV;
0355
0356 constexpr double MuonLow3 = -9.899e-18 / (1_MeV * 1_MeV);
0357
0358 constexpr double MuonHigh0 = -2.986_MeV;
0359
0360 constexpr double MuonHigh1 = 9.253e-5;
0361
0362
0363 inline float computeMuonDirectPairPhotoNuclearLossMean(double energy) {
0364 if (energy < MuonHighLowThreshold) {
0365 return MuonLow0 +
0366 (MuonLow1 + (MuonLow2 + MuonLow3 * energy) * energy) * energy;
0367 } else {
0368 return MuonHigh0 + MuonHigh1 * energy;
0369 }
0370 }
0371
0372
0373 inline float deriveMuonDirectPairPhotoNuclearLossMeanE(double energy) {
0374 if (energy < MuonHighLowThreshold) {
0375 return MuonLow1 + (2 * MuonLow2 + 3 * MuonLow3 * energy) * energy;
0376 } else {
0377 return MuonHigh1;
0378 }
0379 }
0380
0381 }
0382
0383 float Acts::computeEnergyLossRadiative(const MaterialSlab& slab,
0384 PdgParticle absPdg, float m,
0385 float qOverP, float absQ) {
0386 assert((absPdg == Acts::makeAbsolutePdgParticle(absPdg)) &&
0387 "pdg is not absolute");
0388
0389
0390 if (!slab.isValid()) {
0391 return 0.0f;
0392 }
0393
0394
0395 const float x = slab.thicknessInX0();
0396
0397
0398 const float momentum = absQ / qOverP;
0399 const float energy = fastHypot(m, momentum);
0400
0401 float dEdx = computeBremsstrahlungLossMean(m, energy);
0402
0403
0404
0405 if ((absPdg == PdgParticle::eMuon) && (8_GeV < energy)) {
0406 dEdx += computeMuonDirectPairPhotoNuclearLossMean(energy);
0407 }
0408
0409 return dEdx * x;
0410 }
0411
0412 float Acts::deriveEnergyLossRadiativeQOverP(const MaterialSlab& slab,
0413 PdgParticle absPdg, float m,
0414 float qOverP, float absQ) {
0415 assert((absPdg == Acts::makeAbsolutePdgParticle(absPdg)) &&
0416 "pdg is not absolute");
0417
0418
0419 if (!slab.isValid()) {
0420 return 0.0f;
0421 }
0422
0423
0424 const float x = slab.thicknessInX0();
0425
0426
0427 const float momentum = absQ / qOverP;
0428 const float energy = fastHypot(m, momentum);
0429
0430
0431 float derE = deriveBremsstrahlungLossMeanE(m);
0432
0433
0434
0435 if ((absPdg == PdgParticle::eMuon) && (8_GeV < energy)) {
0436 derE += deriveMuonDirectPairPhotoNuclearLossMeanE(energy);
0437 }
0438
0439
0440
0441
0442
0443
0444 const float derQOverP = -(absQ * absQ) / (qOverP * qOverP * qOverP * energy);
0445 return derE * derQOverP * x;
0446 }
0447
0448 float Acts::computeEnergyLossMean(const MaterialSlab& slab, PdgParticle absPdg,
0449 float m, float qOverP, float absQ) {
0450 return computeEnergyLossBethe(slab, m, qOverP, absQ) +
0451 computeEnergyLossRadiative(slab, absPdg, m, qOverP, absQ);
0452 }
0453
0454 float Acts::deriveEnergyLossMeanQOverP(const MaterialSlab& slab,
0455 PdgParticle absPdg, float m,
0456 float qOverP, float absQ) {
0457 return deriveEnergyLossBetheQOverP(slab, m, qOverP, absQ) +
0458 deriveEnergyLossRadiativeQOverP(slab, absPdg, m, qOverP, absQ);
0459 }
0460
0461 float Acts::computeEnergyLossMode(const MaterialSlab& slab, PdgParticle absPdg,
0462 float m, float qOverP, float absQ) {
0463
0464
0465 return 0.9f * computeEnergyLossLandau(slab, m, qOverP, absQ) +
0466 0.15f * computeEnergyLossRadiative(slab, absPdg, m, qOverP, absQ);
0467 }
0468
0469 float Acts::deriveEnergyLossModeQOverP(const MaterialSlab& slab,
0470 PdgParticle absPdg, float m,
0471 float qOverP, float absQ) {
0472
0473
0474 return 0.9f * deriveEnergyLossLandauQOverP(slab, m, qOverP, absQ) +
0475 0.15f * deriveEnergyLossRadiativeQOverP(slab, absPdg, m, qOverP, absQ);
0476 }
0477
0478 namespace {
0479
0480
0481 inline float theta0Highland(float xOverX0, float momentumInv,
0482 float q2OverBeta2) {
0483
0484 const float t = std::sqrt(xOverX0 * q2OverBeta2);
0485
0486
0487 return 13.6_MeV * momentumInv * t * (1.0f + 0.038f * 2 * std::log(t));
0488 }
0489
0490
0491 inline float theta0RossiGreisen(float xOverX0, float momentumInv,
0492 float q2OverBeta2) {
0493
0494 const float t = std::sqrt(xOverX0 * q2OverBeta2);
0495 return 17.5_MeV * momentumInv * t *
0496 (1.0f + 0.125f * std::log10(10.0f * xOverX0));
0497 }
0498
0499 }
0500
0501 float Acts::computeMultipleScatteringTheta0(const MaterialSlab& slab,
0502 PdgParticle absPdg, float m,
0503 float qOverP, float absQ) {
0504 assert((absPdg == Acts::makeAbsolutePdgParticle(absPdg)) &&
0505 "pdg is not absolute");
0506
0507
0508 if (!slab.isValid()) {
0509 return 0.0f;
0510 }
0511
0512
0513 const float xOverX0 = slab.thicknessInX0();
0514
0515 const float momentumInv = std::abs(qOverP / absQ);
0516
0517 const float q2OverBeta2 = RelativisticQuantities(m, qOverP, absQ).q2OverBeta2;
0518
0519
0520 if (absPdg == PdgParticle::eElectron) {
0521 return theta0RossiGreisen(xOverX0, momentumInv, q2OverBeta2);
0522 } else {
0523 return theta0Highland(xOverX0, momentumInv, q2OverBeta2);
0524 }
0525 }