Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-12 07:51:34

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 // Workaround for building on clang+libstdc++
0012 #include "Acts/Utilities/detail/ReferenceWrapperAnyCompat.hpp"
0013 
0014 #include "Acts/Definitions/Algebra.hpp"
0015 #include "Acts/Material/IVolumeMaterial.hpp"
0016 #include "Acts/Material/Interactions.hpp"
0017 #include "Acts/Material/Material.hpp"
0018 #include "Acts/Material/MaterialSlab.hpp"
0019 #include "Acts/Propagator/EigenStepperDefaultExtension.hpp"
0020 #include "Acts/Utilities/MathHelpers.hpp"
0021 
0022 namespace Acts {
0023 
0024 /// @brief Evaluator of the k_i's and elements of the transport matrix
0025 /// D of the RKN4 stepping. This implementation involves energy loss due to
0026 /// ionisation, bremsstrahlung, pair production and photonuclear interaction
0027 /// in the propagation and the Jacobian. These effects will only occur if the
0028 /// propagation is in a TrackingVolume with attached material.
0029 struct EigenStepperDenseExtension {
0030   /// Fallback extension
0031   EigenStepperDefaultExtension defaultExtension;
0032 
0033   /// Momentum at a certain point
0034   double currentMomentum = 0.;
0035   /// Particles momentum at k1
0036   double initialMomentum = 0.;
0037   /// Material that will be passed
0038   /// TODO : Might not be needed anymore
0039   Material material = Material::Vacuum();
0040   /// Derivatives dLambda''dlambda at each sub-step point
0041   std::array<double, 4> dLdl{};
0042   /// q/p at each sub-step
0043   std::array<double, 4> qop{};
0044   /// Derivatives dPds at each sub-step
0045   std::array<double, 4> dPds{};
0046   /// Derivative d(dEds)d(q/p) evaluated at the initial point
0047   double dgdqopValue = 0.;
0048   /// Derivative dEds at the initial point
0049   double g = 0.;
0050   /// k_i equivalent for the time propagation
0051   std::array<double, 4> tKi{};
0052   /// Lambda''_i
0053   std::array<double, 4> Lambdappi{};
0054   /// Energy at each sub-step
0055   std::array<double, 4> energy{};
0056 
0057   /// @brief Evaluator of the k_i's of the RKN4. For the case of i = 0 this
0058   /// step sets up member parameters, too.
0059   ///
0060   /// @tparam i Index of the k_i, i = [0, 3]
0061   /// @tparam stepper_t Type of the stepper
0062   ///
0063   /// @param [in] state State of the stepper
0064   /// @param [in] stepper Stepper of the propagator
0065   /// @param [in] volumeMaterial Material of the volume
0066   /// @param [out] knew Next k_i that is evaluated
0067   /// @param [out] kQoP k_i elements of the momenta
0068   /// @param [in] bField B-Field at the evaluation position
0069   /// @param [in] h Step size (= 0. ^ 0.5 * StepSize ^ StepSize)
0070   /// @param [in] kprev Evaluated k_{i - 1}
0071   ///
0072   /// @return Boolean flag if the calculation is valid
0073   template <int i, typename stepper_t>
0074   bool k(const typename stepper_t::State& state, const stepper_t& stepper,
0075          const IVolumeMaterial* volumeMaterial, Vector3& knew,
0076          const Vector3& bField, std::array<double, 4>& kQoP,
0077          const double h = 0., const Vector3& kprev = Vector3::Zero())
0078     requires(i >= 0 && i <= 3)
0079   {
0080     if (volumeMaterial == nullptr) {
0081       return defaultExtension.template k<i>(state, stepper, volumeMaterial,
0082                                             knew, bField, kQoP, h, kprev);
0083     }
0084 
0085     double q = stepper.charge(state);
0086     const auto& particleHypothesis = stepper.particleHypothesis(state);
0087     float mass = particleHypothesis.mass();
0088 
0089     // i = 0 is used for setup and evaluation of k
0090     if constexpr (i == 0) {
0091       // Set up for energy loss
0092       Vector3 position = stepper.position(state);
0093       material = volumeMaterial->material(position.template cast<double>());
0094       initialMomentum = stepper.absoluteMomentum(state);
0095       currentMomentum = initialMomentum;
0096       qop[0] = stepper.qOverP(state);
0097       initializeEnergyLoss(state, stepper);
0098       // Evaluate k
0099       knew = qop[0] * stepper.direction(state).cross(bField);
0100       // Evaluate k for the time propagation
0101       Lambdappi[0] = -qop[0] * qop[0] * qop[0] * g * energy[0] / (q * q);
0102       //~ tKi[0] = std::hypot(1, mass / initialMomentum);
0103       tKi[0] = fastHypot(1, mass * qop[0]);
0104       kQoP[0] = Lambdappi[0];
0105     } else {
0106       // Update parameters and check for momentum condition
0107       updateEnergyLoss(mass, h, state, stepper, i);
0108       if (currentMomentum < state.options.dense.momentumCutOff) {
0109         return false;
0110       }
0111       // Evaluate k
0112       knew = qop[i] * (stepper.direction(state) + h * kprev).cross(bField);
0113       // Evaluate k_i for the time propagation
0114       auto qopNew = qop[0] + h * Lambdappi[i - 1];
0115       Lambdappi[i] = -qopNew * qopNew * qopNew * g * energy[i] / (q * q);
0116       tKi[i] = fastHypot(1, mass * qopNew);
0117       kQoP[i] = Lambdappi[i];
0118     }
0119     return true;
0120   }
0121 
0122   /// @brief After a RKN4 step was accepted by the stepper this method has an
0123   /// additional veto on the quality of the step. The veto lies in evaluation
0124   /// of the energy loss and the therewith constrained to keep the momentum
0125   /// after the step in reasonable values.
0126   ///
0127   /// @tparam stepper_t Type of the stepper
0128   ///
0129   /// @param [in] state State of the stepper
0130   /// @param [in] stepper Stepper of the propagator
0131   /// @param [in] volumeMaterial Material of the volume
0132   /// @param [in] h Step size
0133   ///
0134   /// @return Boolean flag if the calculation is valid
0135   template <typename stepper_t>
0136   bool finalize(typename stepper_t::State& state, const stepper_t& stepper,
0137                 const IVolumeMaterial* volumeMaterial, const double h) const {
0138     if (volumeMaterial == nullptr) {
0139       return defaultExtension.finalize(state, stepper, volumeMaterial, h);
0140     }
0141 
0142     const auto& particleHypothesis = stepper.particleHypothesis(state);
0143     float mass = particleHypothesis.mass();
0144 
0145     // Evaluate the new momentum
0146     auto newMomentum =
0147         stepper.absoluteMomentum(state) +
0148         (h / 6.) * (dPds[0] + 2. * (dPds[1] + dPds[2]) + dPds[3]);
0149 
0150     // Break propagation if momentum becomes below cut-off
0151     if (newMomentum < state.options.dense.momentumCutOff) {
0152       return false;
0153     }
0154 
0155     // Add derivative dlambda/ds = Lambda''
0156     state.derivative(7) = -fastHypot(mass, newMomentum) * g /
0157                           (newMomentum * newMomentum * newMomentum);
0158 
0159     // Update momentum
0160     state.pars[eFreeQOverP] = stepper.charge(state) / newMomentum;
0161     // Add derivative dt/ds = 1/(beta * c) = sqrt(m^2 * p^{-2} + c^{-2})
0162     state.derivative(3) = fastHypot(1, mass / newMomentum);
0163     // Update time
0164     state.pars[eFreeTime] +=
0165         (h / 6.) * (tKi[0] + 2. * (tKi[1] + tKi[2]) + tKi[3]);
0166 
0167     return true;
0168   }
0169 
0170   /// @brief After a RKN4 step was accepted by the stepper this method has an
0171   /// additional veto on the quality of the step. The veto lies in the
0172   /// evaluation
0173   /// of the energy loss, the therewith constrained to keep the momentum
0174   /// after the step in reasonable values and the evaluation of the transport
0175   /// matrix.
0176   ///
0177   /// @tparam stepper_t Type of the stepper
0178   ///
0179   /// @param [in] state State of the stepper
0180   /// @param [in] stepper Stepper of the propagator
0181   /// @param [in] volumeMaterial Material of the volume
0182   /// @param [in] h Step size
0183   /// @param [out] D Transport matrix
0184   ///
0185   /// @return Boolean flag if the calculation is valid
0186   template <typename stepper_t>
0187   bool finalize(typename stepper_t::State& state, const stepper_t& stepper,
0188                 const IVolumeMaterial* volumeMaterial, const double h,
0189                 FreeMatrix& D) const {
0190     if (volumeMaterial == nullptr) {
0191       return defaultExtension.finalize(state, stepper, volumeMaterial, h, D);
0192     }
0193 
0194     return finalize(state, stepper, volumeMaterial, h) &&
0195            transportMatrix(state, stepper, h, D);
0196   }
0197 
0198  private:
0199   /// @brief Evaluates the transport matrix D for the Jacobian
0200   ///
0201   /// @tparam propagator_state_t Type of the state of the propagator
0202   /// @tparam stepper_t Type of the stepper
0203   ///
0204   /// @param [in] state State of the propagator
0205   /// @param [in] stepper Stepper of the propagator
0206   /// @param [in] h Step size
0207   /// @param [out] D Transport matrix
0208   ///
0209   /// @return Boolean flag if evaluation is valid
0210   template <typename stepper_t>
0211   bool transportMatrix(typename stepper_t::State& state,
0212                        const stepper_t& stepper, const double h,
0213                        FreeMatrix& D) const {
0214     /// The calculations are based on ATL-SOFT-PUB-2009-002. The update of the
0215     /// Jacobian matrix is requires only the calculation of eq. 17 and 18.
0216     /// Since the terms of eq. 18 are currently 0, this matrix is not needed
0217     /// in the calculation. The matrix A from eq. 17 consists out of 3
0218     /// different parts. The first one is given by the upper left 3x3 matrix
0219     /// that are calculated by dFdT and dGdT. The second is given by the top 3
0220     /// lines of the rightmost column. This is calculated by dFdL and dGdL.
0221     /// The remaining non-zero term is calculated directly. The naming of the
0222     /// variables is explained in eq. 11 and are directly related to the
0223     /// initial problem in eq. 7.
0224     /// The evaluation is based on propagating the parameters T and lambda
0225     /// (including g(lambda) and E(lambda)) as given in eq. 16 and evaluating
0226     /// the derivations for matrix A.
0227     /// @note The translation for u_{n+1} in eq. 7 is in this case a
0228     /// 3-dimensional vector without a dependency of Lambda or lambda neither in
0229     /// u_n nor in u_n'. The second and fourth eq. in eq. 14 have the constant
0230     /// offset matrices h * Id and Id respectively. This involves that the
0231     /// constant offset does not exist for rectangular matrix dFdu' (due to the
0232     /// missing Lambda part) and only exists for dGdu' in dlambda/dlambda.
0233 
0234     auto& sd = state.stepData;
0235     auto dir = stepper.direction(state);
0236     const auto& particleHypothesis = stepper.particleHypothesis(state);
0237     float mass = particleHypothesis.mass();
0238 
0239     D = FreeMatrix::Identity();
0240     const double half_h = h * 0.5;
0241 
0242     // This sets the reference to the sub matrices
0243     // dFdx is already initialised as (3x3) zero
0244     auto dFdT = D.block<3, 3>(0, 4);
0245     auto dFdL = D.block<3, 1>(0, 7);
0246     // dGdx is already initialised as (3x3) identity
0247     auto dGdT = D.block<3, 3>(4, 4);
0248     auto dGdL = D.block<3, 1>(4, 7);
0249 
0250     SquareMatrix3 dk1dT = SquareMatrix3::Zero();
0251     SquareMatrix3 dk2dT = SquareMatrix3::Identity();
0252     SquareMatrix3 dk3dT = SquareMatrix3::Identity();
0253     SquareMatrix3 dk4dT = SquareMatrix3::Identity();
0254 
0255     Vector3 dk1dL = Vector3::Zero();
0256     Vector3 dk2dL = Vector3::Zero();
0257     Vector3 dk3dL = Vector3::Zero();
0258     Vector3 dk4dL = Vector3::Zero();
0259 
0260     /// Propagation of derivatives of dLambda''dlambda at each sub-step
0261     std::array<double, 4> jdL{};
0262 
0263     // Evaluation of the rightmost column without the last term.
0264     jdL[0] = dLdl[0];
0265     dk1dL = dir.cross(sd.B_first);
0266 
0267     jdL[1] = dLdl[1] * (1. + half_h * jdL[0]);
0268     dk2dL = (1. + half_h * jdL[0]) * (dir + half_h * sd.k1).cross(sd.B_middle) +
0269             qop[1] * half_h * dk1dL.cross(sd.B_middle);
0270 
0271     jdL[2] = dLdl[2] * (1. + half_h * jdL[1]);
0272     dk3dL = (1. + half_h * jdL[1]) * (dir + half_h * sd.k2).cross(sd.B_middle) +
0273             qop[2] * half_h * dk2dL.cross(sd.B_middle);
0274 
0275     jdL[3] = dLdl[3] * (1. + h * jdL[2]);
0276     dk4dL = (1. + h * jdL[2]) * (dir + h * sd.k3).cross(sd.B_last) +
0277             qop[3] * h * dk3dL.cross(sd.B_last);
0278 
0279     dk1dT(0, 1) = sd.B_first.z();
0280     dk1dT(0, 2) = -sd.B_first.y();
0281     dk1dT(1, 0) = -sd.B_first.z();
0282     dk1dT(1, 2) = sd.B_first.x();
0283     dk1dT(2, 0) = sd.B_first.y();
0284     dk1dT(2, 1) = -sd.B_first.x();
0285     dk1dT *= qop[0];
0286 
0287     dk2dT += half_h * dk1dT;
0288     dk2dT = qop[1] * VectorHelpers::cross(dk2dT, sd.B_middle);
0289 
0290     dk3dT += half_h * dk2dT;
0291     dk3dT = qop[2] * VectorHelpers::cross(dk3dT, sd.B_middle);
0292 
0293     dk4dT += h * dk3dT;
0294     dk4dT = qop[3] * VectorHelpers::cross(dk4dT, sd.B_last);
0295 
0296     dFdT.setIdentity();
0297     dFdT += h / 6. * (dk1dT + dk2dT + dk3dT);
0298     dFdT *= h;
0299 
0300     dFdL = h * h / 6. * (dk1dL + dk2dL + dk3dL);
0301 
0302     dGdT += h / 6. * (dk1dT + 2. * (dk2dT + dk3dT) + dk4dT);
0303 
0304     dGdL = h / 6. * (dk1dL + 2. * (dk2dL + dk3dL) + dk4dL);
0305 
0306     // Evaluation of the dLambda''/dlambda term
0307     D(7, 7) += (h / 6.) * (jdL[0] + 2. * (jdL[1] + jdL[2]) + jdL[3]);
0308 
0309     // The following comment lines refer to the application of the time being
0310     // treated as a position. Since t and qop are treated independently for now,
0311     // this just serves as entry point for building their relation
0312     //~ double dtpp1dl = -mass * mass * qop[0] * qop[0] *
0313     //~ (3. * g + qop[0] * dgdqop(energy[0], .mass,
0314     //~ absPdg, meanEnergyLoss));
0315 
0316     double dtp1dl = qop[0] * mass * mass / fastHypot(1, qop[0] * mass);
0317     double qopNew = qop[0] + half_h * Lambdappi[0];
0318 
0319     //~ double dtpp2dl = -mass * mass * qopNew *
0320     //~ qopNew *
0321     //~ (3. * g * (1. + half_h * jdL[0]) +
0322     //~ qopNew * dgdqop(energy[1], mass, absPdgCode, meanEnergyLoss));
0323 
0324     double dtp2dl = qopNew * mass * mass / fastHypot(1, qopNew * mass);
0325     qopNew = qop[0] + half_h * Lambdappi[1];
0326 
0327     //~ double dtpp3dl = -mass * mass * qopNew *
0328     //~ qopNew *
0329     //~ (3. * g * (1. + half_h * jdL[1]) +
0330     //~ qopNew * dgdqop(energy[2], mass, absPdg, meanEnergyLoss));
0331 
0332     double dtp3dl = qopNew * mass * mass / fastHypot(1, qopNew * mass);
0333     qopNew = qop[0] + half_h * Lambdappi[2];
0334     double dtp4dl = qopNew * mass * mass / fastHypot(1, qopNew * mass);
0335 
0336     //~ D(3, 7) = h * mass * mass * qop[0] /
0337     //~ std::hypot(1., mass * qop[0])
0338     //~ + h * h / 6. * (dtpp1dl + dtpp2dl + dtpp3dl);
0339 
0340     D(3, 7) = (h / 6.) * (dtp1dl + 2. * (dtp2dl + dtp3dl) + dtp4dl);
0341     return true;
0342   }
0343 
0344   /// @brief Initializer of all parameters related to a RKN4 step with energy
0345   /// loss of a particle in material
0346   ///
0347   /// @tparam stepper_t Type of the stepper
0348   ///
0349   /// @param [in] state Deliverer of configurations
0350   /// @param [in] stepper Stepper of the propagator
0351   template <typename stepper_t>
0352   void initializeEnergyLoss(const typename stepper_t::State& state,
0353                             const stepper_t& stepper) {
0354     const auto& particleHypothesis = stepper.particleHypothesis(state);
0355     float mass = particleHypothesis.mass();
0356     PdgParticle absPdg = particleHypothesis.absolutePdg();
0357     float absQ = particleHypothesis.absoluteCharge();
0358 
0359     energy[0] = fastHypot(initialMomentum, mass);
0360     // use unit length as thickness to compute the energy loss per unit length
0361     MaterialSlab slab(material, 1);
0362     // Use the same energy loss throughout the step.
0363     if (state.options.dense.meanEnergyLoss) {
0364       g = -computeEnergyLossMean(slab, absPdg, mass, static_cast<float>(qop[0]),
0365                                  absQ);
0366     } else {
0367       // TODO using the unit path length is not quite right since the most
0368       //      probably energy loss is not independent from the path length.
0369       g = -computeEnergyLossMode(slab, absPdg, mass, static_cast<float>(qop[0]),
0370                                  absQ);
0371     }
0372     // Change of the momentum per path length
0373     // dPds = dPdE * dEds
0374     dPds[0] = g * energy[0] / initialMomentum;
0375     if (state.covTransport) {
0376       // Calculate the change of the energy loss per path length and
0377       // inverse momentum
0378       if (state.options.dense.includeGradient) {
0379         if (state.options.dense.meanEnergyLoss) {
0380           dgdqopValue = deriveEnergyLossMeanQOverP(
0381               slab, absPdg, mass, static_cast<float>(qop[0]), absQ);
0382         } else {
0383           // TODO path length dependence; see above
0384           dgdqopValue = deriveEnergyLossModeQOverP(
0385               slab, absPdg, mass, static_cast<float>(qop[0]), absQ);
0386         }
0387       }
0388       // Calculate term for later error propagation
0389       dLdl[0] = (-qop[0] * qop[0] * g * energy[0] *
0390                      (3. - (initialMomentum * initialMomentum) /
0391                                (energy[0] * energy[0])) -
0392                  qop[0] * qop[0] * qop[0] * energy[0] * dgdqopValue);
0393     }
0394   }
0395 
0396   /// @brief Update of the kinematic parameters of the RKN4 sub-steps after
0397   /// initialization with energy loss of a particle in material
0398   ///
0399   /// @tparam stepper_t Type of the stepper
0400   ///
0401   /// @param [in] h Stepped distance of the sub-step (1-3)
0402   /// @param [in] mass Mass of the particle
0403   /// @param [in] state State of the stepper
0404   /// @param [in] stepper Stepper of the propagator
0405   /// @param [in] i Index of the sub-step (1-3)
0406   template <typename stepper_t>
0407   void updateEnergyLoss(const double mass, const double h,
0408                         const typename stepper_t::State& state,
0409                         const stepper_t& stepper, const int i) {
0410     // Update parameters related to a changed momentum
0411     currentMomentum = initialMomentum + h * dPds[i - 1];
0412     energy[i] = fastHypot(currentMomentum, mass);
0413     dPds[i] = g * energy[i] / currentMomentum;
0414     qop[i] = stepper.charge(state) / currentMomentum;
0415     // Calculate term for later error propagation
0416     if (state.covTransport) {
0417       dLdl[i] = (-qop[i] * qop[i] * g * energy[i] *
0418                      (3. - (currentMomentum * currentMomentum) /
0419                                (energy[i] * energy[i])) -
0420                  qop[i] * qop[i] * qop[i] * energy[i] * dgdqopValue);
0421     }
0422   }
0423 };
0424 
0425 }  // namespace Acts