Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-11 08:04:14

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