Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:10:57

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