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