Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-17 08:01:43

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