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