Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-27 07:23:59

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 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 https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 // Project include(s)
0012 #include "detray/definitions/algebra.hpp"
0013 #include "detray/definitions/detail/qualifiers.hpp"
0014 #include "detray/definitions/math.hpp"
0015 #include "detray/definitions/pdg_particle.hpp"
0016 #include "detray/definitions/units.hpp"
0017 #include "detray/material/detail/relativistic_quantities.hpp"
0018 
0019 namespace detray {
0020 
0021 template <concepts::scalar scalar_t>
0022 struct interaction {
0023   using scalar_type = scalar_t;
0024   using relativistic_quantities = detail::relativistic_quantities<scalar_type>;
0025 
0026   // @returns the total stopping power (<-dE/dx>)
0027   DETRAY_HOST_DEVICE scalar_type
0028   compute_stopping_power(const detray::material<scalar_type>& mat,
0029                          const pdg_particle<scalar_type>& ptc,
0030                          const relativistic_quantities& rq) const {
0031     scalar_type stopping_power{0.f};
0032 
0033     // Inelastic collisions with atomic electrons
0034     stopping_power += compute_bethe_bloch(mat, ptc, rq);
0035 
0036     // Radiative loss
0037     stopping_power += compute_bremsstrahlung(mat, ptc, rq);
0038 
0039     return stopping_power;
0040   }
0041 
0042   DETRAY_HOST_DEVICE scalar_type
0043   compute_bethe_bloch(const detray::material<scalar_type>& mat,
0044                       const pdg_particle<scalar_type>& /*ptc*/,
0045                       const relativistic_quantities& rq) const {
0046     const scalar_type eps_per_length{rq.compute_epsilon_per_length(mat)};
0047     if (eps_per_length <= 0.f) {
0048       return 0.f;
0049     }
0050 
0051     const scalar_type dhalf{rq.compute_delta_half(mat)};
0052     const scalar_type A = rq.compute_bethe_bloch_log_term(mat);
0053     const scalar_type running{A - rq.m_beta2 - dhalf};
0054     return 2.f * eps_per_length * running;
0055   }
0056 
0057   // Function to calculate the Bremsstrahlung energy loss of electron based on
0058   // Rossi's approximation.
0059   // For more rigorous calculation, check "G4eBremsstrahlungRelModel.cc" of
0060   // Geant4 library. The Fig 34.13 of PDG 2024 shows the difference between
0061   // the approximated and precise calculation.
0062   DETRAY_HOST_DEVICE scalar_type
0063   compute_bremsstrahlung(const detray::material<scalar_type>& mat,
0064                          const pdg_particle<scalar_type>& ptc,
0065                          const relativistic_quantities& rq) const {
0066     scalar_type stopping_power{0.f};
0067 
0068     // Only consider electrons and positrons at the moment
0069     // For middle-heavy particles muons, the bremss is negligibe
0070     //
0071     // Also, over 10 MeV, positron and electron bremss might be similar.
0072     // In ICRU 37, it is written that "In our tabulations, the radiative
0073     // stopping power for positrons has been assumed to be the same as that
0074     // for electrons, which is a good approximation at energies above, say,
0075     // 10 MeV. However, it should be mentioned that exploratory calculations
0076     // by Feng et at. (1981), employing the same method as that previously
0077     // used by them for electrons, indicate significant differences between
0078     // positrons and electrons in regard to the differential bremsstrahlung
0079     // cross sections in oxygen and uranium at 500, 50, and 10 keV"
0080     if (ptc.pdg_num() == electron<scalar_type>().pdg_num() ||
0081         ptc.pdg_num() == positron<scalar_type>().pdg_num()) {
0082       // Stopping power ~ E/X (B. B. Rossi, High-energy particles,1952)
0083       // This approximation gets poor in low energy below 10 MeV
0084       stopping_power = rq.m_gamma * ptc.mass() / mat.X0();
0085     }
0086 
0087     return stopping_power;
0088   }
0089 
0090   DETRAY_HOST_DEVICE scalar_type
0091   derive_stopping_power(const detray::material<scalar_type>& mat,
0092                         const pdg_particle<scalar_type>& ptc,
0093                         const relativistic_quantities& rq) const {
0094     scalar_type derivative{0.f};
0095 
0096     // Inelastic collisions with atomic electrons
0097     derivative += derive_bethe_bloch(mat, ptc, rq);
0098 
0099     // Radiative loss
0100     derivative += derive_bremsstrahlung(mat, ptc, rq);
0101 
0102     return derivative;
0103   }
0104 
0105   DETRAY_HOST_DEVICE scalar_type
0106   derive_bethe_bloch(const detray::material<scalar_type>& mat,
0107                      const pdg_particle<scalar_type>& ptc,
0108                      const relativistic_quantities& rq) const {
0109     const scalar_type bethe_stopping_power = compute_bethe_bloch(mat, ptc, rq);
0110 
0111     // (K/2) * (Z/A) * z^2 / beta^2 * density
0112     const scalar_type eps_per_length{rq.compute_epsilon_per_length(mat)};
0113     if (eps_per_length <= 0.f) {
0114       return 0.f;
0115     }
0116 
0117     /*-----------------------------------------------------------------------
0118     * Calculation of d(-dE/dx)/dqop
0119     *
0120     * d(-dE/dx)/dqop = 2/(qop * gamma^2) * (-dE/dx)
0121     *                  + 2 * (eps/x) * [dA/dqop - dB/dqop - dC/dqop]
0122     *
0123     * where
0124     * A = 1/2 ln ( 2m_e c^2 beta^2 gamma^2 W_max/ I^2)
0125     * B = beta^2
0126     * C = delta/2
0127     *
0128     * dA/dqop = -1 / (2 * qop) * [4 - W_max/ (gamma M c^2) ]
0129     * dB/dqop = -2 * beta^2/(qop gamma^2)
0130     *
0131     * dC/dqop = 1/2*(-2/qop) if (x>x_1)
0132     *         = 1/2*(-2/qop + ak/(qop ln10)*(x_1 - x)^(k-1)) if (x_0<x<x_1)
0133     *         = 0 if (x<x_0) (for nonconductors)
0134     ------------------------------------------------------------------------*/
0135 
0136     const scalar_type first_term =
0137         2.f / (rq.m_qOverP * rq.m_gamma2) * bethe_stopping_power;
0138 
0139     const scalar_type dAdqop = rq.derive_bethe_bloch_log_term();
0140     const scalar_type dBdqop = rq.derive_beta2();
0141     const scalar_type dCdqop = rq.derive_delta_half(mat);
0142 
0143     const scalar_type second_term =
0144         2.f * eps_per_length * (dAdqop - dBdqop - dCdqop);
0145 
0146     return first_term + second_term;
0147   }
0148 
0149   DETRAY_HOST_DEVICE scalar_type
0150   derive_bremsstrahlung(const detray::material<scalar_type>& mat,
0151                         const pdg_particle<scalar_type>& ptc,
0152                         const relativistic_quantities& rq) const {
0153     scalar_type derivative{0.f};
0154 
0155     if (ptc.pdg_num() == electron<scalar_type>().pdg_num() ||
0156         ptc.pdg_num() == positron<scalar_type>().pdg_num()) {
0157       // Stopping power ~ E/X = gamma * m/X
0158       // Derivative = dgamma/dqop * m/X = -beta^2 gamma/qop * m/X
0159       // (Eq (D.5) of arXiv:2403.16720)
0160       derivative =
0161           -rq.m_beta2 * rq.m_gamma / rq.m_qOverP * ptc.mass() / mat.X0();
0162     }
0163 
0164     return derivative;
0165   }
0166 
0167   DETRAY_HOST_DEVICE scalar_type compute_energy_loss_bethe_bloch(
0168       const scalar_type path_segment, const detray::material<scalar_type>& mat,
0169       const pdg_particle<scalar_type>& ptc,
0170       const relativistic_quantities& rq) const {
0171     return path_segment * compute_bethe_bloch(mat, ptc, rq);
0172   }
0173 
0174   DETRAY_HOST_DEVICE scalar_type compute_energy_loss_landau(
0175       const scalar_type path_segment, const material<scalar_type>& mat,
0176       const pdg_particle<scalar_type>& /*ptc*/,
0177       const relativistic_quantities& rq) const {
0178     const scalar_type I{mat.mean_excitation_energy()};
0179     const scalar_type eps{rq.compute_epsilon(mat, path_segment)};
0180 
0181     if (eps <= 0.f) {
0182       return 0.f;
0183     }
0184 
0185     const scalar_type dhalf{rq.compute_delta_half(mat)};
0186     const scalar_type t{rq.compute_mass_term(constant<scalar_type>::m_e)};
0187     // uses RPP2018 eq. 33.11
0188     const scalar_type running{math::log(t / I) + math::log(eps / I) + 0.2f -
0189                               rq.m_beta2 - 2.f * dhalf};
0190     return eps * running;
0191   }
0192 
0193   DETRAY_HOST_DEVICE scalar_type compute_energy_loss_landau_fwhm(
0194       const scalar_type path_segment, const material<scalar_type>& mat,
0195       const pdg_particle<scalar_type>& /*ptc*/,
0196       const relativistic_quantities& rq) const {
0197     // the Landau-Vavilov fwhm is 4*eps (see RPP2018 fig. 33.7)
0198     return 4.f * rq.compute_epsilon(mat, path_segment);
0199   }
0200 
0201   DETRAY_HOST_DEVICE scalar_type compute_energy_loss_landau_sigma(
0202       const scalar_type path_segment, const material<scalar_type>& mat,
0203       const pdg_particle<scalar_type>& ptc,
0204       const relativistic_quantities& rq) const {
0205     const scalar_type fwhm{
0206         compute_energy_loss_landau_fwhm(path_segment, mat, ptc, rq)};
0207 
0208     return convert_landau_fwhm_to_gaussian_sigma(fwhm);
0209   }
0210 
0211   template <typename material_t>
0212   DETRAY_HOST_DEVICE scalar_type compute_energy_loss_landau_sigma_QOverP(
0213       const scalar_type path_segment, const material_t& mat,
0214       const pdg_particle<scalar_type>& ptc,
0215       const relativistic_quantities& rq) const {
0216     const scalar_type sigmaE{
0217         compute_energy_loss_landau_sigma(path_segment, mat, ptc, rq)};
0218 
0219     //  var(q/p) = (d(q/p)/dE)² * var(E)
0220     // d(q/p)/dE = d/dE (q/sqrt(E²-m²))
0221     //           = q * -(1/2) * 1/p³ * 2E
0222     //  var(q/p) = (q/p)^4 * (q/beta)² * (1/q)^4 * var(E)
0223     //           = -q/p² E/p = -(q/p)² * 1/(q*beta) = -(q/p)² * (q/beta)
0224     //           / q² = (1/p)^4 * (q/beta)² * var(E)
0225     // do not need to care about the sign since it is only used squared
0226     const scalar_type pInv{rq.m_qOverP / ptc.charge()};
0227     return math::sqrt(rq.m_q2OverBeta2) * pInv * pInv * sigmaE;
0228   }
0229 
0230   DETRAY_HOST_DEVICE scalar_type compute_multiple_scattering_theta0(
0231       const scalar_type xOverX0, const pdg_particle<scalar_type>& ptc,
0232       const relativistic_quantities& rq) const {
0233     // 1/p = q/(pq) = (q/p)/q
0234     const scalar_type momentumInv{math::fabs(rq.m_qOverP / ptc.charge())};
0235     // q²/beta²; a smart compiler should be able to remove the unused
0236     // computations
0237 
0238     // if electron or positron
0239     if ((ptc.pdg_num() == 11) || (ptc.pdg_num() == -11)) {
0240       //@todo (Beomki): Not sure if we need this function. At least we
0241       // need to find the reference for this equation
0242       return theta0RossiGreisen(xOverX0, momentumInv, rq.m_q2OverBeta2);
0243     } else {
0244       return theta0Highland(xOverX0, momentumInv, rq.m_q2OverBeta2);
0245     }
0246   }
0247 
0248  private:
0249   /// Multiple scattering (mainly due to Coulomb interaction) for charged
0250   /// particles
0251   /// Original source: G. R. Lynch and O. I. Dahl, NIM.B58, 6
0252   DETRAY_HOST_DEVICE scalar_type
0253   theta0Highland(const scalar_type xOverX0, const scalar_type momentumInv,
0254                  const scalar_type q2OverBeta2) const {
0255     if (xOverX0 <= 0.f) {
0256       return 0.f;
0257     }
0258 
0259     // RPP2018 eq. 33.15 (treats beta and q² consistently)
0260     const scalar_type t{math::sqrt(xOverX0 * q2OverBeta2)};
0261     // log((x/X0) * (q²/beta²)) = log((sqrt(x/X0) * (q/beta))²)
0262     //                          = 2 * log(sqrt(x/X0) * (q/beta))
0263     return 13.6f * unit<scalar_type>::MeV * momentumInv * t *
0264            (1.0f + 0.038f * 2.f * math::log(t));
0265   }
0266 
0267   /// Multiple scattering theta0 for electrons.
0268   DETRAY_HOST_DEVICE scalar_type
0269   theta0RossiGreisen(const scalar_type xOverX0, const scalar_type momentumInv,
0270                      const scalar_type q2OverBeta2) const {
0271     if (xOverX0 <= 0.f) {
0272       return 0.f;
0273     }
0274 
0275     // TODO add source paper/ resource
0276     const scalar_type t{math::sqrt(xOverX0 * q2OverBeta2)};
0277     return 17.5f * unit<scalar_type>::MeV * momentumInv * t *
0278            (1.0f + 0.125f * math::log10(10.0f * xOverX0));
0279   }
0280 
0281   /// Convert Landau full-width-half-maximum to an equivalent Gaussian
0282   /// sigma,
0283   ///
0284   /// Full-width-half-maximum for a Gaussian is given as
0285   ///
0286   ///     fwhm = 2 * sqrt(2 * log(2)) * sigma
0287   /// -> sigma = fwhm / (2 * sqrt(2 * log(2)))
0288   ///
0289   /// @todo: Add a unit test for this function
0290   DETRAY_HOST_DEVICE scalar_type
0291   convert_landau_fwhm_to_gaussian_sigma(const scalar_type fwhm) const {
0292     return 0.5f * constant<scalar_type>::inv_sqrt2 * fwhm /
0293            math::sqrt(constant<scalar_type>::ln2);
0294   }
0295 };
0296 
0297 }  // namespace detray