Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-22 07:57: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 #include "Acts/Propagator/EigenStepper.hpp"
0012 
0013 #include "Acts/Definitions/Direction.hpp"
0014 #include "Acts/EventData/TransformationHelpers.hpp"
0015 #include "Acts/Propagator/ConstrainedStep.hpp"
0016 #include "Acts/Propagator/EigenStepperError.hpp"
0017 #include "Acts/Propagator/detail/CovarianceEngine.hpp"
0018 
0019 template <typename E>
0020 Acts::EigenStepper<E>::EigenStepper(
0021     std::shared_ptr<const MagneticFieldProvider> bField)
0022     : m_bField(std::move(bField)) {}
0023 
0024 template <typename E>
0025 auto Acts::EigenStepper<E>::makeState(const Options& options) const -> State {
0026   State state{options, m_bField->makeCache(options.magFieldContext)};
0027   return state;
0028 }
0029 
0030 template <typename E>
0031 void Acts::EigenStepper<E>::initialize(State& state,
0032                                        const BoundParameters& par) const {
0033   initialize(state, par.parameters(), par.covariance(),
0034              par.particleHypothesis(), par.referenceSurface());
0035 }
0036 
0037 template <typename E>
0038 void Acts::EigenStepper<E>::initialize(State& state,
0039                                        const BoundVector& boundParams,
0040                                        const std::optional<BoundMatrix>& cov,
0041                                        ParticleHypothesis particleHypothesis,
0042                                        const Surface& surface) const {
0043   FreeVector freeParams = transformBoundToFreeParameters(
0044       surface, state.options.geoContext, boundParams);
0045 
0046   state.particleHypothesis = particleHypothesis;
0047 
0048   state.pathAccumulated = 0;
0049   state.nSteps = 0;
0050   state.nStepTrials = 0;
0051   state.stepSize = ConstrainedStep();
0052   state.stepSize.setAccuracy(state.options.initialStepSize);
0053   state.stepSize.setUser(state.options.maxStepSize);
0054   state.previousStepSize = 0;
0055   state.statistics = StepperStatistics();
0056 
0057   state.pars = freeParams;
0058 
0059   // Init the jacobian matrix if needed
0060   state.covTransport = cov.has_value();
0061   if (state.covTransport) {
0062     state.cov = *cov;
0063     state.jacToGlobal = surface.boundToFreeJacobian(
0064         state.options.geoContext, freeParams.segment<3>(eFreePos0),
0065         freeParams.segment<3>(eFreeDir0));
0066     state.jacobian = BoundMatrix::Identity();
0067     state.jacTransport = FreeMatrix::Identity();
0068     state.derivative = FreeVector::Zero();
0069   }
0070 }
0071 
0072 template <typename E>
0073 auto Acts::EigenStepper<E>::boundState(
0074     State& state, const Surface& surface, bool transportCov,
0075     const FreeToBoundCorrection& freeToBoundCorrection) const
0076     -> Result<BoundState> {
0077   return detail::boundState(
0078       state.options.geoContext, surface, state.cov, state.jacobian,
0079       state.jacTransport, state.derivative, state.jacToGlobal, std::nullopt,
0080       state.pars, state.particleHypothesis, state.covTransport && transportCov,
0081       state.pathAccumulated, freeToBoundCorrection);
0082 }
0083 
0084 template <typename E>
0085 bool Acts::EigenStepper<E>::prepareCurvilinearState(State& state) const {
0086   // test whether the accumulated path has still its initial value.
0087   if (state.pathAccumulated != 0) {
0088     return true;
0089   }
0090 
0091   // if no step was executed the path length derivates have not been
0092   // computed but are needed to compute the curvilinear covariance. The
0093   // derivates are given by k1 for a zero step width.
0094   // First Runge-Kutta point (at current position)
0095   auto& sd = state.stepData;
0096   auto pos = position(state);
0097   auto fieldRes = getField(state, pos);
0098   if (!fieldRes.ok()) {
0099     return false;
0100   }
0101 
0102   sd.B_first = *fieldRes;
0103   if (!state.extension.template k<0>(state, *this, nullptr, sd.k1, sd.B_first,
0104                                      sd.kQoP)) {
0105     return false;
0106   }
0107 
0108   // dr/ds :
0109   state.derivative.template head<3>() =
0110       state.pars.template segment<3>(eFreeDir0);
0111   // d (dr/ds) / ds :
0112   state.derivative.template segment<3>(4) = sd.k1;
0113   // to set dt/ds :
0114   state.extension.finalize(state, *this, nullptr, state.pathAccumulated);
0115   return true;
0116 }
0117 
0118 template <typename E>
0119 auto Acts::EigenStepper<E>::curvilinearState(State& state,
0120                                              bool transportCov) const
0121     -> BoundState {
0122   return detail::curvilinearState(
0123       state.cov, state.jacobian, state.jacTransport, state.derivative,
0124       state.jacToGlobal, std::nullopt, state.pars, state.particleHypothesis,
0125       state.covTransport && transportCov, state.pathAccumulated);
0126 }
0127 
0128 template <typename E>
0129 void Acts::EigenStepper<E>::update(State& state, const FreeVector& freeParams,
0130                                    const BoundVector& /*boundParams*/,
0131                                    const Covariance& covariance,
0132                                    const Surface& surface) const {
0133   state.pars = freeParams;
0134   state.cov = covariance;
0135   state.jacToGlobal = surface.boundToFreeJacobian(
0136       state.options.geoContext, freeParams.template segment<3>(eFreePos0),
0137       freeParams.template segment<3>(eFreeDir0));
0138 }
0139 
0140 template <typename E>
0141 void Acts::EigenStepper<E>::update(State& state, const Vector3& uposition,
0142                                    const Vector3& udirection, double qOverP,
0143                                    double time) const {
0144   state.pars.template segment<3>(eFreePos0) = uposition;
0145   state.pars.template segment<3>(eFreeDir0) = udirection;
0146   state.pars[eFreeTime] = time;
0147   state.pars[eFreeQOverP] = qOverP;
0148 }
0149 
0150 template <typename E>
0151 void Acts::EigenStepper<E>::transportCovarianceToCurvilinear(
0152     State& state) const {
0153   detail::transportCovarianceToCurvilinear(
0154       state.cov, state.jacobian, state.jacTransport, state.derivative,
0155       state.jacToGlobal, std::nullopt, direction(state));
0156 }
0157 
0158 template <typename E>
0159 Acts::Result<void> Acts::EigenStepper<E>::transportCovarianceToBound(
0160     State& state, const Surface& surface,
0161     const FreeToBoundCorrection& freeToBoundCorrection) const {
0162   return detail::transportCovarianceToBound(
0163       state.options.geoContext, surface, state.cov, state.jacobian,
0164       state.jacTransport, state.derivative, state.jacToGlobal, std::nullopt,
0165       state.pars, freeToBoundCorrection);
0166 }
0167 
0168 template <typename E>
0169 Acts::Result<double> Acts::EigenStepper<E>::step(
0170     State& state, Direction propDir, const IVolumeMaterial* material) const {
0171   // Runge-Kutta integrator state
0172   auto& sd = state.stepData;
0173 
0174   double errorEstimate = 0;
0175   double h2 = 0;
0176   double half_h = 0;
0177 
0178   auto pos = position(state);
0179   auto dir = direction(state);
0180 
0181   // First Runge-Kutta point (at current position)
0182   auto fieldRes = getField(state, pos);
0183   if (!fieldRes.ok()) {
0184     return fieldRes.error();
0185   }
0186   sd.B_first = *fieldRes;
0187   if (!state.extension.template k<0>(state, *this, material, sd.k1, sd.B_first,
0188                                      sd.kQoP)) {
0189     return 0.;
0190   }
0191 
0192   const auto calcStepSizeScaling = [&](const double errorEstimate_) -> double {
0193     // For details about these values see ATL-SOFT-PUB-2009-001
0194     constexpr double lower = 0.25;
0195     constexpr double upper = 4.0;
0196     // This is given by the order of the Runge-Kutta method
0197     constexpr double exponent = 0.25;
0198 
0199     double x = state.options.stepTolerance / errorEstimate_;
0200 
0201     if constexpr (exponent == 0.25) {
0202       // This is 3x faster than std::pow
0203       x = std::sqrt(std::sqrt(x));
0204     } else {
0205       x = std::pow(x, exponent);
0206     }
0207 
0208     return std::clamp(x, lower, upper);
0209   };
0210 
0211   const auto isErrorTolerable = [&](const double errorEstimate_) {
0212     // For details about these values see ATL-SOFT-PUB-2009-001
0213     constexpr double marginFactor = 4.0;
0214 
0215     return errorEstimate_ <= marginFactor * state.options.stepTolerance;
0216   };
0217 
0218   // The following functor starts to perform a Runge-Kutta step of a certain
0219   // size, going up to the point where it can return an estimate of the local
0220   // integration error. The results are stated in the local variables above,
0221   // allowing integration to continue once the error is deemed satisfactory
0222   const auto tryRungeKuttaStep = [&](const double h) -> Result<bool> {
0223     // helpers because bool and std::error_code are ambiguous
0224     constexpr auto success = &Result<bool>::success;
0225     constexpr auto failure = &Result<bool>::failure;
0226 
0227     // State the square and half of the step size
0228     h2 = h * h;
0229     half_h = h * 0.5;
0230 
0231     // Second Runge-Kutta point
0232     const Vector3 pos1 = pos + half_h * dir + h2 * 0.125 * sd.k1;
0233     auto field = getField(state, pos1);
0234     if (!field.ok()) {
0235       return failure(field.error());
0236     }
0237     sd.B_middle = *field;
0238 
0239     if (!state.extension.template k<1>(state, *this, material, sd.k2,
0240                                        sd.B_middle, sd.kQoP, half_h, sd.k1)) {
0241       return success(false);
0242     }
0243 
0244     // Third Runge-Kutta point
0245     if (!state.extension.template k<2>(state, *this, material, sd.k3,
0246                                        sd.B_middle, sd.kQoP, half_h, sd.k2)) {
0247       return success(false);
0248     }
0249 
0250     // Last Runge-Kutta point
0251     const Vector3 pos2 = pos + h * dir + h2 * 0.5 * sd.k3;
0252     field = getField(state, pos2);
0253     if (!field.ok()) {
0254       return failure(field.error());
0255     }
0256     sd.B_last = *field;
0257     if (!state.extension.template k<3>(state, *this, material, sd.k4, sd.B_last,
0258                                        sd.kQoP, h, sd.k3)) {
0259       return success(false);
0260     }
0261 
0262     // Compute and check the local integration error estimate
0263     errorEstimate =
0264         h2 * ((sd.k1 - sd.k2 - sd.k3 + sd.k4).template lpNorm<1>() +
0265               std::abs(sd.kQoP[0] - sd.kQoP[1] - sd.kQoP[2] + sd.kQoP[3]));
0266     // Protect against division by zero
0267     errorEstimate = std::max(1e-20, errorEstimate);
0268 
0269     return success(isErrorTolerable(errorEstimate));
0270   };
0271 
0272   const double initialH = state.stepSize.value() * propDir;
0273   double h = initialH;
0274   std::size_t nStepTrials = 0;
0275   // Select and adjust the appropriate Runge-Kutta step size as given
0276   // ATL-SOFT-PUB-2009-001
0277   while (true) {
0278     ++nStepTrials;
0279     ++state.statistics.nAttemptedSteps;
0280 
0281     auto res = tryRungeKuttaStep(h);
0282     if (!res.ok()) {
0283       return res.error();
0284     }
0285     if (!!res.value()) {
0286       break;
0287     }
0288 
0289     ++state.statistics.nRejectedSteps;
0290 
0291     const double stepSizeScaling = calcStepSizeScaling(errorEstimate);
0292     h *= stepSizeScaling;
0293 
0294     // If step size becomes too small the particle remains at the initial
0295     // place
0296     if (std::abs(h) < std::abs(state.options.stepSizeCutOff)) {
0297       // Not moving due to too low momentum needs an aborter
0298       return EigenStepperError::StepSizeStalled;
0299     }
0300 
0301     // If the parameter is off track too much or given stepSize is not
0302     // appropriate
0303     if (nStepTrials > state.options.maxRungeKuttaStepTrials) {
0304       // Too many trials, have to abort
0305       return EigenStepperError::StepSizeAdjustmentFailed;
0306     }
0307   }
0308 
0309   // When doing error propagation, update the associated Jacobian matrix
0310   if (state.covTransport) {
0311     // using the direction before updated below
0312 
0313     // The step transport matrix in global coordinates
0314     FreeMatrix D;
0315     if (!state.extension.finalize(state, *this, material, h, D)) {
0316       return EigenStepperError::StepInvalid;
0317     }
0318 
0319     // See the documentation of Acts::blockedMult for a description of blocked
0320     // matrix multiplication. However, we can go one further. Let's assume that
0321     // some of these sub-matrices are zero matrices 0₈ and identity matrices
0322     // I₈, namely:
0323     //
0324     // D₁₁ = I₈, J₁₁ = I₈, D₂₁ = 0₈, J₂₁ = 0₈
0325     //
0326     // Which gives:
0327     //
0328     // K₁₁ = I₈  * I₈  + D₁₂ * 0₈  = I₈
0329     // K₁₂ = I₈  * J₁₂ + D₁₂ * J₂₂ = J₁₂ + D₁₂ * J₂₂
0330     // K₂₁ = 0₈  * I₈  + D₂₂ * 0₈  = 0₈
0331     // K₂₂ = 0₈  * J₁₂ + D₂₂ * J₂₂ = D₂₂ * J₂₂
0332     //
0333     // Furthermore, we're constructing K in place of J, and since
0334     // K₁₁ = I₈ = J₁₁ and K₂₁ = 0₈ = D₂₁, we don't actually need to touch those
0335     // sub-matrices at all!
0336     assert((D.topLeftCorner<4, 4>().isIdentity()));
0337     assert((D.bottomLeftCorner<4, 4>().isZero()));
0338     assert((state.jacTransport.template topLeftCorner<4, 4>().isIdentity()));
0339     assert((state.jacTransport.template bottomLeftCorner<4, 4>().isZero()));
0340 
0341     state.jacTransport.template topRightCorner<4, 4>() +=
0342         D.topRightCorner<4, 4>() *
0343         state.jacTransport.template bottomRightCorner<4, 4>();
0344     state.jacTransport.template bottomRightCorner<4, 4>() =
0345         (D.bottomRightCorner<4, 4>() *
0346          state.jacTransport.template bottomRightCorner<4, 4>())
0347             .eval();
0348   } else {
0349     if (!state.extension.finalize(state, *this, material, h)) {
0350       return EigenStepperError::StepInvalid;
0351     }
0352   }
0353 
0354   // Update the track parameters according to the equations of motion
0355   state.pars.template segment<3>(eFreePos0) +=
0356       h * dir + h2 / 6. * (sd.k1 + sd.k2 + sd.k3);
0357   state.pars.template segment<3>(eFreeDir0) +=
0358       h / 6. * (sd.k1 + 2. * (sd.k2 + sd.k3) + sd.k4);
0359   (state.pars.template segment<3>(eFreeDir0)).normalize();
0360 
0361   if (state.covTransport) {
0362     // using the updated direction
0363     state.derivative.template head<3>() =
0364         state.pars.template segment<3>(eFreeDir0);
0365     state.derivative.template segment<3>(4) = sd.k4;
0366   }
0367 
0368   state.pathAccumulated += h;
0369   ++state.nSteps;
0370   state.nStepTrials += nStepTrials;
0371 
0372   ++state.statistics.nSuccessfulSteps;
0373   if (propDir != Direction::fromScalarZeroAsPositive(initialH)) {
0374     ++state.statistics.nReverseSteps;
0375   }
0376   state.statistics.pathLength += h;
0377   state.statistics.absolutePathLength += std::abs(h);
0378 
0379   const double stepSizeScaling = calcStepSizeScaling(errorEstimate);
0380   const double nextAccuracy = std::abs(h * stepSizeScaling);
0381   const double previousAccuracy = std::abs(state.stepSize.accuracy());
0382   const double initialStepLength = std::abs(initialH);
0383   if (nextAccuracy < initialStepLength || nextAccuracy > previousAccuracy) {
0384     state.stepSize.setAccuracy(nextAccuracy);
0385   }
0386 
0387   return h;
0388 }