Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-05 08:09:38

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2019-2020 CERN for the benefit of the Acts project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
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 // values from RPP2018 table 33.1
0022 // electron mass
0023 constexpr float Me = 0.5109989461_MeV;
0024 // Bethe formular prefactor. 1/mol unit is just a factor 1 here.
0025 constexpr float K = 0.307075_MeV * 1_cm * 1_cm;
0026 // Energy scale for plasma energy.
0027 constexpr float PlasmaEnergyScale = 28.816_eV;
0028 
0029 /// Additional derived relativistic quantities.
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     // beta²/q² = (p/E)²/q² = p²/(q²m² + q²p²) = 1/(q² + (m²(q/p)²)
0041     // q²/beta² = q² + m²(q/p)²
0042     q2OverBeta2 = absQ * absQ + (mass * qOverP) * (mass * qOverP);
0043     assert((q2OverBeta2 >= 0) && "Negative q2OverBeta2");
0044     // 1/p = q/(qp) = (q/p)/q
0045     const float mOverP = mass * std::abs(qOverP / absQ);
0046     const float pOverM = 1.0f / mOverP;
0047     // beta² = p²/E² = p²/(m² + p²) = 1/(1 + (m/p)²)
0048     beta2 = 1.0f / (1.0f + mOverP * mOverP);
0049     assert((beta2 >= 0) && "Negative beta2");
0050     // beta*gamma = (p/sqrt(m² + p²))*(sqrt(m² + p²)/m) = p/m
0051     betaGamma = pOverM;
0052     assert((betaGamma >= 0) && "Negative betaGamma");
0053     // gamma = sqrt(m² + p²)/m = sqrt(1 + (p/m)²)
0054     gamma = std::sqrt(1.0f + pOverM * pOverM);
0055   }
0056 };
0057 
0058 /// Compute q/p derivative of beta².
0059 inline float deriveBeta2(float qOverP, const RelativisticQuantities& rq) {
0060   return -2 / (qOverP * rq.gamma * rq.gamma);
0061 }
0062 
0063 /// Compute the 2 * mass * (beta * gamma)² mass term.
0064 inline float computeMassTerm(float mass, const RelativisticQuantities& rq) {
0065   return 2 * mass * rq.betaGamma * rq.betaGamma;
0066 }
0067 
0068 /// Compute mass term logarithmic derivative w/ respect to q/p.
0069 inline float logDeriveMassTerm(float qOverP) {
0070   // only need to compute d((beta*gamma)²)/(beta*gamma)²; rest cancels.
0071   return -2 / qOverP;
0072 }
0073 
0074 /// Compute the maximum energy transfer in a single collision.
0075 ///
0076 /// Uses RPP2018 eq. 33.4.
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 /// Compute WMax logarithmic derivative w/ respect to q/p.
0085 inline float logDeriveWMax(float mass, float qOverP,
0086                            const RelativisticQuantities& rq) {
0087   // this is (q/p) * (beta/q).
0088   // both quantities have the same sign and the product must always be
0089   // positive. we can thus reuse the known (unsigned) quantity (q/beta)².
0090   const float a = std::abs(qOverP / std::sqrt(rq.q2OverBeta2));
0091   // (m² + me²) / me = me (1 + (m/me)²)
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 /// Compute epsilon energy pre-factor for RPP2018 eq. 33.11.
0097 ///
0098 /// Defined as
0099 ///
0100 ///     (K/2) * (Z/A)*rho * x * (q²/beta²)
0101 ///
0102 /// where (Z/A)*rho is the electron density in the material and x is the
0103 /// traversed length (thickness) of the material.
0104 inline float computeEpsilon(float molarElectronDensity, float thickness,
0105                             const RelativisticQuantities& rq) {
0106   return 0.5f * K * molarElectronDensity * thickness * rq.q2OverBeta2;
0107 }
0108 
0109 /// Compute epsilon logarithmic derivative w/ respect to q/p.
0110 inline float logDeriveEpsilon(float qOverP, const RelativisticQuantities& rq) {
0111   // only need to compute d(q²/beta²)/(q²/beta²); everything else cancels.
0112   return 2 / (qOverP * rq.gamma * rq.gamma);
0113 }
0114 
0115 /// Compute the density correction factor delta/2.
0116 inline float computeDeltaHalf(float meanExitationPotential,
0117                               float molarElectronDensity,
0118                               const RelativisticQuantities& rq) {
0119   /// Uses RPP2018 eq. 33.6 which is only valid for high energies.
0120   // only relevant for very high ernergies; use arbitrary cutoff
0121   if (rq.betaGamma < 10.0f) {
0122     return 0.0f;
0123   }
0124   // pre-factor according to RPP2019 table 33.1
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 /// Compute derivative w/ respect to q/p for the density correction.
0132 inline float deriveDeltaHalf(float qOverP, const RelativisticQuantities& rq) {
0133   // original equation is of the form
0134   //     log(beta*gamma) + log(eplasma/I) - 1/2
0135   // which the resulting derivative as
0136   //     d(beta*gamma)/(beta*gamma)
0137   return (rq.betaGamma < 10.0f) ? 0.0f : (-1.0f / qOverP);
0138 }
0139 
0140 /// Convert Landau full-width-half-maximum to an equivalent Gaussian sigma,
0141 ///
0142 /// Full-width-half-maximum for a Gaussian is given as
0143 ///
0144 ///     fwhm = 2 * sqrt(2 * log(2)) * sigma
0145 /// -> sigma = fwhm / (2 * sqrt(2 * log(2)))
0146 ///
0147 inline float convertLandauFwhmToGaussianSigma(float fwhm) {
0148   // return fwhm / (2 * std::sqrt(2 * std::log(2.0f)));
0149   return fwhm * 0.42466090014400953f;
0150 }
0151 
0152 namespace detail {
0153 
0154 inline float computeEnergyLossLandauFwhm(const Acts::MaterialSlab& slab,
0155                                          const RelativisticQuantities& rq) {
0156   // return early in case of vacuum or zero thickness
0157   if (!slab) {
0158     return 0.0f;
0159   }
0160 
0161   const float Ne = slab.material().molarElectronDensity();
0162   const float thickness = slab.thickness();
0163   // the Landau-Vavilov fwhm is 4*eps (see RPP2018 fig. 33.7)
0164   return 4 * computeEpsilon(Ne, thickness, rq);
0165 }
0166 
0167 }  // namespace detail
0168 
0169 }  // namespace
0170 
0171 float Acts::computeEnergyLossBethe(const MaterialSlab& slab, float m,
0172                                    float qOverP, float absQ) {
0173   // return early in case of vacuum or zero thickness
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   // uses RPP2018 eq. 33.5 scaled from mass stopping power to linear stopping
0187   // power and multiplied with the material thickness to get a total energy loss
0188   // instead of an energy loss per length.
0189   // the required modification only change the prefactor which becomes
0190   // identical to the prefactor epsilon for the most probable value.
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   // return early in case of vacuum or zero thickness
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   // original equation is of the form
0212   //
0213   //     eps * (log(u/I) + log(wmax/I) - 2 * beta² - delta)
0214   //     = eps * (log(u) + log(wmax) - 2 * log(I) - 2 * beta² - delta)
0215   //
0216   // with the resulting derivative as
0217   //
0218   //     d(eps) * (log(u/I) + log(wmax/I) - 2 * beta² - delta)
0219   //     + eps * (d(u)/(u) + d(wmax)/(wmax) - 2 * d(beta²) - d(delta))
0220   //
0221   // where we can use d(eps) = eps * (d(eps)/eps) for further simplification.
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   // return early in case of vacuum or zero thickness
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   // uses RPP2018 eq. 33.12
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   // return early in case of vacuum or zero thickness
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   // original equation is of the form
0268   //
0269   //     eps * (log(t/I) - log(eps/I) - 0.2 - beta² - delta)
0270   //     = eps * (log(t) - log(eps) - 2*log(I) - 0.2 - beta² - delta)
0271   //
0272   // with the resulting derivative as
0273   //
0274   //     d(eps) * (log(t/I) - log(eps/I) - 0.2 - beta² - delta)
0275   //     + eps * (d(t)/t + d(eps)/eps - d(beta²) - 2*d(delta/2))
0276   //
0277   // where we can use d(eps) = eps * (d(eps)/eps) for further simplification.
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   // return early in case of vacuum or zero thickness
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   // the Landau-Vavilov fwhm is 4*eps (see RPP2018 fig. 33.7)
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   //  var(q/p) = (d(q/p)/dE)² * var(E)
0316   // d(q/p)/dE = d/dE (q/sqrt(E²-m²))
0317   //           = q * -(1/2) * 1/p³ * 2E
0318   //           = -q/p² E/p = -(q/p)² * 1/(q*beta) = -(q/p)² * (q/beta) / q²
0319   //  var(q/p) = (q/p)^4 * (q/beta)² * (1/q)^4 * var(E)
0320   //           = (1/p)^4 * (q/beta)² * var(E)
0321   // do not need to care about the sign since it is only used squared
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 /// Compute mean energy loss from bremsstrahlung per radiation length.
0330 inline float computeBremsstrahlungLossMean(float mass, float energy) {
0331   return energy * (Me / mass) * (Me / mass);
0332 }
0333 
0334 /// Derivative of the bremsstrahlung loss per rad length with respect to energy.
0335 inline float deriveBremsstrahlungLossMeanE(float mass) {
0336   return (Me / mass) * (Me / mass);
0337 }
0338 
0339 /// Expansion coefficients for the muon radiative loss as a function of energy
0340 ///
0341 /// Taken from ATL-SOFT-PUB-2008-003 eq. 7,8 where the expansion is expressed
0342 /// with terms E^n/X0 with fixed units [E] = MeV and [X0] = mm. The evaluated
0343 /// expansion has units MeV/mm. In this implementation, the X0 dependence is
0344 /// factored out and the coefficients must be scaled to the native units such
0345 /// that the evaluated expansion with terms E^n has dimension energy in
0346 /// native units.
0347 constexpr float MuonHighLowThreshold = 1_TeV;
0348 // [low0 / X0] = MeV / mm -> [low0] = MeV
0349 constexpr double MuonLow0 = -0.5345_MeV;
0350 // [low1 * E / X0] = MeV / mm -> [low1] = 1
0351 constexpr double MuonLow1 = 6.803e-5;
0352 // [low2 * E^2 / X0] = MeV / mm -> [low2] = 1/MeV
0353 constexpr double MuonLow2 = 2.278e-11 / 1_MeV;
0354 // [low3 * E^3 / X0] = MeV / mm -> [low3] = 1/MeV^2
0355 constexpr double MuonLow3 = -9.899e-18 / (1_MeV * 1_MeV);
0356 // units are the same as low0
0357 constexpr double MuonHigh0 = -2.986_MeV;
0358 // units are the same as low1
0359 constexpr double MuonHigh1 = 9.253e-5;
0360 
0361 /// Compute additional radiation energy loss for muons per radiation length.
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 /// Derivative of the additional rad loss per rad length with respect to energy.
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 }  // namespace
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   // return early in case of vacuum or zero thickness
0389   if (!slab) {
0390     return 0.0f;
0391   }
0392 
0393   // relative radiation length
0394   const float x = slab.thicknessInX0();
0395   // particle momentum and energy
0396   // do not need to care about the sign since it is only used squared
0397   const float momentum = absQ / qOverP;
0398   const float energy = std::hypot(m, momentum);
0399 
0400   float dEdx = computeBremsstrahlungLossMean(m, energy);
0401 
0402   // muon- or muon+
0403   // TODO magic number 8_GeV
0404   if ((absPdg == PdgParticle::eMuon) && (8_GeV < energy)) {
0405     dEdx += computeMuonDirectPairPhotoNuclearLossMean(energy);
0406   }
0407   // scale from energy loss per unit radiation length to total energy
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   // return early in case of vacuum or zero thickness
0418   if (!slab) {
0419     return 0.0f;
0420   }
0421 
0422   // relative radiation length
0423   const float x = slab.thicknessInX0();
0424   // particle momentum and energy
0425   // do not need to care about the sign since it is only used squared
0426   const float momentum = absQ / qOverP;
0427   const float energy = std::hypot(m, momentum);
0428 
0429   // compute derivative w/ respect to energy.
0430   float derE = deriveBremsstrahlungLossMeanE(m);
0431 
0432   // muon- or muon+
0433   // TODO magic number 8_GeV
0434   if ((absPdg == PdgParticle::eMuon) && (8_GeV < energy)) {
0435     derE += deriveMuonDirectPairPhotoNuclearLossMeanE(energy);
0436   }
0437   // compute derivative w/ respect to q/p by using the chain rule
0438   //     df(E)/d(q/p) = df(E)/dE dE/d(q/p)
0439   // with
0440   //     E = sqrt(m² + p²) = sqrt(m² + q²/(q/p)²)
0441   // and the resulting derivative
0442   //     dE/d(q/p) = -q² / ((q/p)³ * E)
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   // see ATL-SOFT-PUB-2008-003 section 3 for the relative fractions
0463   // TODO this is inconsistent with the text of the note
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   // see ATL-SOFT-PUB-2008-003 section 3 for the relative fractions
0472   // TODO this is inconsistent with the text of the note
0473   return 0.9f * deriveEnergyLossLandauQOverP(slab, m, qOverP, absQ) +
0474          0.15f * deriveEnergyLossRadiativeQOverP(slab, absPdg, m, qOverP, absQ);
0475 }
0476 
0477 namespace {
0478 
0479 /// Multiple scattering theta0 for minimum ionizing particles.
0480 inline float theta0Highland(float xOverX0, float momentumInv,
0481                             float q2OverBeta2) {
0482   // RPP2018 eq. 33.15 (treats beta and q² consistently)
0483   const float t = std::sqrt(xOverX0 * q2OverBeta2);
0484   // log((x/X0) * (q²/beta²)) = log((sqrt(x/X0) * (q/beta))²)
0485   //                          = 2 * log(sqrt(x/X0) * (q/beta))
0486   return 13.6_MeV * momentumInv * t * (1.0f + 0.038f * 2 * std::log(t));
0487 }
0488 
0489 /// Multiple scattering theta0 for electrons.
0490 inline float theta0RossiGreisen(float xOverX0, float momentumInv,
0491                                 float q2OverBeta2) {
0492   // TODO add source paper/ resource
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 }  // namespace
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   // return early in case of vacuum or zero thickness
0507   if (!slab) {
0508     return 0.0f;
0509   }
0510 
0511   // relative radiation length
0512   const float xOverX0 = slab.thicknessInX0();
0513   // 1/p = q/(pq) = (q/p)/q
0514   const float momentumInv = std::abs(qOverP / absQ);
0515   // q²/beta²; a smart compiler should be able to remove the unused computations
0516   const float q2OverBeta2 = RelativisticQuantities(m, qOverP, absQ).q2OverBeta2;
0517 
0518   // electron or positron
0519   if (absPdg == PdgParticle::eElectron) {
0520     return theta0RossiGreisen(xOverX0, momentumInv, q2OverBeta2);
0521   } else {
0522     return theta0Highland(xOverX0, momentumInv, q2OverBeta2);
0523   }
0524 }