Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-06 08:10:07

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2021-2023 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/Direction.hpp"
0016 #include "Acts/Definitions/TrackParametrization.hpp"
0017 #include "Acts/Definitions/Units.hpp"
0018 #include "Acts/EventData/MultiComponentTrackParameters.hpp"
0019 #include "Acts/EventData/TrackParameters.hpp"
0020 #include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
0021 #include "Acts/MagneticField/MagneticFieldProvider.hpp"
0022 #include "Acts/Propagator/ConstrainedStep.hpp"
0023 #include "Acts/Propagator/EigenStepper.hpp"
0024 #include "Acts/Propagator/EigenStepperError.hpp"
0025 #include "Acts/Propagator/Propagator.hpp"
0026 #include "Acts/Propagator/detail/LoopStepperUtils.hpp"
0027 #include "Acts/Surfaces/Surface.hpp"
0028 #include "Acts/Utilities/Intersection.hpp"
0029 #include "Acts/Utilities/Result.hpp"
0030 
0031 #include <algorithm>
0032 #include <cmath>
0033 #include <cstddef>
0034 #include <functional>
0035 #include <limits>
0036 #include <numeric>
0037 #include <sstream>
0038 #include <vector>
0039 
0040 #include <boost/container/small_vector.hpp>
0041 
0042 #include "MultiStepperError.hpp"
0043 
0044 namespace Acts {
0045 
0046 using namespace Acts::UnitLiterals;
0047 
0048 /// @brief Reducer struct for the Loop MultiEigenStepper which reduces the
0049 /// multicomponent state to simply by summing the weighted values
0050 struct WeightedComponentReducerLoop {
0051   template <typename component_range_t>
0052   static Vector3 toVector3(const component_range_t& comps,
0053                            const FreeIndices i) {
0054     return std::accumulate(
0055         comps.begin(), comps.end(), Vector3{Vector3::Zero()},
0056         [i](const auto& sum, const auto& cmp) -> Vector3 {
0057           return sum + cmp.weight * cmp.state.pars.template segment<3>(i);
0058         });
0059   }
0060 
0061   template <typename stepper_state_t>
0062   static Vector3 position(const stepper_state_t& s) {
0063     return toVector3(s.components, eFreePos0);
0064   }
0065 
0066   template <typename stepper_state_t>
0067   static Vector3 direction(const stepper_state_t& s) {
0068     return toVector3(s.components, eFreeDir0).normalized();
0069   }
0070 
0071   // TODO: Maybe we can cache this value and only update it when the parameters
0072   // change
0073   template <typename stepper_state_t>
0074   static ActsScalar qOverP(const stepper_state_t& s) {
0075     return std::accumulate(
0076         s.components.begin(), s.components.end(), ActsScalar{0.},
0077         [](const auto& sum, const auto& cmp) -> ActsScalar {
0078           return sum + cmp.weight * cmp.state.pars[eFreeQOverP];
0079         });
0080   }
0081 
0082   template <typename stepper_state_t>
0083   static ActsScalar absoluteMomentum(const stepper_state_t& s) {
0084     return std::accumulate(
0085         s.components.begin(), s.components.end(), ActsScalar{0.},
0086         [&s](const auto& sum, const auto& cmp) -> ActsScalar {
0087           return sum + cmp.weight * s.particleHypothesis.extractMomentum(
0088                                         cmp.state.pars[eFreeQOverP]);
0089         });
0090   }
0091 
0092   template <typename stepper_state_t>
0093   static Vector3 momentum(const stepper_state_t& s) {
0094     return std::accumulate(
0095         s.components.begin(), s.components.end(), Vector3::Zero().eval(),
0096         [&s](const auto& sum, const auto& cmp) -> Vector3 {
0097           return sum + cmp.weight *
0098                            s.particleHypothesis.extractMomentum(
0099                                cmp.state.pars[eFreeQOverP]) *
0100                            cmp.state.pars.template segment<3>(eFreeDir0);
0101         });
0102   }
0103 
0104   template <typename stepper_state_t>
0105   static ActsScalar charge(const stepper_state_t& s) {
0106     return std::accumulate(
0107         s.components.begin(), s.components.end(), ActsScalar{0.},
0108         [&s](const auto& sum, const auto& cmp) -> ActsScalar {
0109           return sum + cmp.weight * s.particleHypothesis.extractCharge(
0110                                         cmp.state.pars[eFreeQOverP]);
0111         });
0112   }
0113 
0114   template <typename stepper_state_t>
0115   static ActsScalar time(const stepper_state_t& s) {
0116     return std::accumulate(
0117         s.components.begin(), s.components.end(), ActsScalar{0.},
0118         [](const auto& sum, const auto& cmp) -> ActsScalar {
0119           return sum + cmp.weight * cmp.state.pars[eFreeTime];
0120         });
0121   }
0122 
0123   template <typename stepper_state_t>
0124   static FreeVector pars(const stepper_state_t& s) {
0125     return std::accumulate(s.components.begin(), s.components.end(),
0126                            FreeVector{FreeVector::Zero()},
0127                            [](const auto& sum, const auto& cmp) -> FreeVector {
0128                              return sum + cmp.weight * cmp.state.pars;
0129                            });
0130   }
0131 
0132   template <typename stepper_state_t>
0133   static FreeVector cov(const stepper_state_t& s) {
0134     return std::accumulate(s.components.begin(), s.components.end(),
0135                            FreeMatrix{FreeMatrix::Zero()},
0136                            [](const auto& sum, const auto& cmp) -> FreeMatrix {
0137                              return sum + cmp.weight * cmp.state.cov;
0138                            });
0139   }
0140 };
0141 
0142 struct MaxMomentumReducerLoop {
0143   template <typename component_range_t>
0144   static const auto& maxAbsoluteMomentumIt(const component_range_t& cmps) {
0145     return *std::max_element(cmps.begin(), cmps.end(),
0146                              [&](const auto& a, const auto& b) {
0147                                return std::abs(a.state.pars[eFreeQOverP]) >
0148                                       std::abs(b.state.pars[eFreeQOverP]);
0149                              });
0150   }
0151 
0152   template <typename stepper_state_t>
0153   static Vector3 position(const stepper_state_t& s) {
0154     return maxAbsoluteMomentumIt(s.components)
0155         .state.pars.template segment<3>(eFreePos0);
0156   }
0157 
0158   template <typename stepper_state_t>
0159   static Vector3 direction(const stepper_state_t& s) {
0160     return maxAbsoluteMomentumIt(s.components)
0161         .state.pars.template segment<3>(eFreeDir0);
0162   }
0163 
0164   template <typename stepper_state_t>
0165   static ActsScalar qOverP(const stepper_state_t& s) {
0166     const auto& cmp = maxAbsoluteMomentumIt(s.components);
0167     return cmp.state.pars[eFreeQOverP];
0168   }
0169 
0170   template <typename stepper_state_t>
0171   static ActsScalar absoluteMomentum(const stepper_state_t& s) {
0172     const auto& cmp = maxAbsoluteMomentumIt(s.components);
0173     return std::abs(cmp.state.absCharge / cmp.state.pars[eFreeQOverP]);
0174   }
0175 
0176   template <typename stepper_state_t>
0177   static Vector3 momentum(const stepper_state_t& s) {
0178     const auto& cmp = maxAbsoluteMomentumIt(s.components);
0179     return std::abs(cmp.state.absCharge / cmp.state.pars[eFreeQOverP]) *
0180            cmp.state.pars.template segment<3>(eFreeDir0);
0181   }
0182 
0183   template <typename stepper_state_t>
0184   static ActsScalar charge(const stepper_state_t& s) {
0185     return maxAbsoluteMomentumIt(s.components).state.absCharge;
0186   }
0187 
0188   template <typename stepper_state_t>
0189   static ActsScalar time(const stepper_state_t& s) {
0190     return maxAbsoluteMomentumIt(s.components).state.pars[eFreeTime];
0191   }
0192 
0193   template <typename stepper_state_t>
0194   static FreeVector pars(const stepper_state_t& s) {
0195     return maxAbsoluteMomentumIt(s.components).state.pars;
0196   }
0197 
0198   template <typename stepper_state_t>
0199   static FreeVector cov(const stepper_state_t& s) {
0200     return maxAbsoluteMomentumIt(s.components).state.cov;
0201   }
0202 };
0203 
0204 /// @brief Stepper based on the EigenStepper, but handles Multi-Component Tracks
0205 /// (e.g., for the GSF). Internally, this only manages a vector of
0206 /// EigenStepper::States. This simplifies implementation, but has several
0207 /// drawbacks:
0208 /// * There are certain redundancies between the global State and the component
0209 /// states
0210 /// * The components do not share a single magnetic-field-cache
0211 /// @tparam extensionlist_t See EigenStepper for details
0212 /// @tparam component_reducer_t How to map the multi-component state to a single
0213 /// component
0214 /// @tparam auctioneer_t See EigenStepper for details
0215 /// @tparam small_vector_size A size-hint how much memory should be allocated
0216 /// by the small vector
0217 template <typename extensionlist_t = StepperExtensionList<DefaultExtension>,
0218           typename component_reducer_t = WeightedComponentReducerLoop,
0219           typename auctioneer_t = detail::VoidAuctioneer>
0220 class MultiEigenStepperLoop
0221     : public EigenStepper<extensionlist_t, auctioneer_t> {
0222   /// Limits the number of steps after at least one component reached the
0223   /// surface
0224   std::size_t m_stepLimitAfterFirstComponentOnSurface = 50;
0225 
0226   /// The logger (used if no logger is provided by caller of methods)
0227   std::unique_ptr<const Acts::Logger> m_logger;
0228 
0229   /// Small vector type for speeding up some computations where we need to
0230   /// accumulate stuff of components. We think 16 is a reasonable amount here.
0231   template <typename T>
0232   using SmallVector = boost::container::small_vector<T, 16>;
0233 
0234  public:
0235   /// @brief Typedef to the Single-Component Eigen Stepper
0236   using SingleStepper = EigenStepper<extensionlist_t, auctioneer_t>;
0237 
0238   /// @brief Typedef to the State of the single component Stepper
0239   using SingleState = typename SingleStepper::State;
0240 
0241   /// @brief Use the definitions from the Single-stepper
0242   using typename SingleStepper::Covariance;
0243   using typename SingleStepper::Jacobian;
0244 
0245   /// @brief Define an own bound state
0246   using BoundState =
0247       std::tuple<MultiComponentBoundTrackParameters, Jacobian, ActsScalar>;
0248 
0249   /// @brief Define an own curvilinear state
0250   using CurvilinearState = std::tuple<MultiComponentCurvilinearTrackParameters,
0251                                       Jacobian, ActsScalar>;
0252 
0253   /// @brief The reducer type
0254   using Reducer = component_reducer_t;
0255 
0256   /// @brief How many components can this stepper manage?
0257   static constexpr int maxComponents = std::numeric_limits<int>::max();
0258 
0259   /// Constructor from a magnetic field and a optionally provided Logger
0260   MultiEigenStepperLoop(std::shared_ptr<const MagneticFieldProvider> bField,
0261                         std::unique_ptr<const Logger> logger =
0262                             getDefaultLogger("GSF", Logging::INFO))
0263       : EigenStepper<extensionlist_t, auctioneer_t>(std::move(bField)),
0264         m_logger(std::move(logger)) {}
0265 
0266   struct State {
0267     /// The struct that stores the individual components
0268     struct Component {
0269       SingleState state;
0270       ActsScalar weight;
0271       Intersection3D::Status status;
0272     };
0273 
0274     /// Particle hypothesis
0275     ParticleHypothesis particleHypothesis = ParticleHypothesis::pion();
0276 
0277     /// The components of which the state consists
0278     SmallVector<Component> components;
0279 
0280     bool covTransport = false;
0281     double pathAccumulated = 0.;
0282     std::size_t steps = 0;
0283 
0284     /// geoContext
0285     std::reference_wrapper<const GeometryContext> geoContext;
0286 
0287     /// MagneticFieldContext
0288     std::reference_wrapper<const MagneticFieldContext> magContext;
0289 
0290     /// Step-limit counter which limits the number of steps when one component
0291     /// reached a surface
0292     std::optional<std::size_t> stepCounterAfterFirstComponentOnSurface;
0293 
0294     /// No default constructor is provided
0295     State() = delete;
0296 
0297     /// Constructor from the initial bound track parameters
0298     ///
0299     /// @param [in] gctx is the context object for the geometry
0300     /// @param [in] mctx is the context object for the magnetic field
0301     /// @param [in] bfield the shared magnetic filed provider
0302     /// @param [in] multipars The track multi-component track-parameters at start
0303     /// @param [in] ssize is the maximum step size
0304     ///
0305     /// @note the covariance matrix is copied when needed
0306     explicit State(const GeometryContext& gctx,
0307                    const MagneticFieldContext& mctx,
0308                    const std::shared_ptr<const MagneticFieldProvider>& bfield,
0309                    const MultiComponentBoundTrackParameters& multipars,
0310                    double ssize = std::numeric_limits<double>::max())
0311         : particleHypothesis(multipars.particleHypothesis()),
0312           geoContext(gctx),
0313           magContext(mctx) {
0314       if (multipars.components().empty()) {
0315         throw std::invalid_argument(
0316             "Cannot construct MultiEigenStepperLoop::State with empty "
0317             "multi-component parameters");
0318       }
0319 
0320       const auto surface = multipars.referenceSurface().getSharedPtr();
0321 
0322       for (auto i = 0ul; i < multipars.components().size(); ++i) {
0323         const auto& [weight, singlePars] = multipars[i];
0324         components.push_back(
0325             {SingleState(gctx, bfield->makeCache(mctx), singlePars, ssize),
0326              weight, Intersection3D::Status::onSurface});
0327       }
0328 
0329       if (std::get<2>(multipars.components().front())) {
0330         covTransport = true;
0331       }
0332     }
0333   };
0334 
0335   /// Construct and initialize a state
0336   State makeState(std::reference_wrapper<const GeometryContext> gctx,
0337                   std::reference_wrapper<const MagneticFieldContext> mctx,
0338                   const MultiComponentBoundTrackParameters& par,
0339                   double ssize = std::numeric_limits<double>::max()) const {
0340     return State(gctx, mctx, SingleStepper::m_bField, par, ssize);
0341   }
0342 
0343   /// @brief Resets the state
0344   ///
0345   /// @param [in, out] state State of the stepper
0346   /// @param [in] boundParams Parameters in bound parametrisation
0347   /// @param [in] cov Covariance matrix
0348   /// @param [in] surface The reference surface of the bound parameters
0349   /// @param [in] stepSize Step size
0350   void resetState(
0351       State& state, const BoundVector& boundParams,
0352       const BoundSquareMatrix& cov, const Surface& surface,
0353       const double stepSize = std::numeric_limits<double>::max()) const {
0354     for (auto& component : state.components) {
0355       SingleStepper::resetState(component.state, boundParams, cov, surface,
0356                                 stepSize);
0357     }
0358   }
0359 
0360   /// A proxy struct which allows access to a single component of the
0361   /// multi-component state. It has the semantics of a const reference, i.e.
0362   /// it requires a const reference of the single-component state it
0363   /// represents
0364   using ConstComponentProxy =
0365       detail::LoopComponentProxyBase<const typename State::Component,
0366                                      MultiEigenStepperLoop>;
0367 
0368   /// A proxy struct which allows access to a single component of the
0369   /// multi-component state. It has the semantics of a mutable reference, i.e.
0370   /// it requires a mutable reference of the single-component state it
0371   /// represents
0372   using ComponentProxy = detail::LoopComponentProxy<typename State::Component,
0373                                                     MultiEigenStepperLoop>;
0374 
0375   /// Creates an iterable which can be plugged into a range-based for-loop to
0376   /// iterate over components
0377   /// @note Use a for-loop with by-value semantics, since the Iterable returns a
0378   /// proxy internally holding a reference
0379   auto componentIterable(State& state) const {
0380     struct Iterator {
0381       using difference_type = std::ptrdiff_t;
0382       using value_type = ComponentProxy;
0383       using reference = ComponentProxy;
0384       using pointer = void;
0385       using iterator_category = std::forward_iterator_tag;
0386 
0387       typename decltype(state.components)::iterator it;
0388       const State& s;
0389 
0390       // clang-format off
0391       auto& operator++() { ++it; return *this; }
0392       auto operator!=(const Iterator& other) const { return it != other.it; }
0393       auto operator==(const Iterator& other) const { return it == other.it; }
0394       auto operator*() const { return ComponentProxy(*it, s); }
0395       // clang-format on
0396     };
0397 
0398     struct Iterable {
0399       using iterator = Iterator;
0400 
0401       State& s;
0402 
0403       // clang-format off
0404       auto begin() { return Iterator{s.components.begin(), s}; }
0405       auto end() { return Iterator{s.components.end(), s}; }
0406       // clang-format on
0407     };
0408 
0409     return Iterable{state};
0410   }
0411 
0412   /// Creates an constant iterable which can be plugged into a range-based
0413   /// for-loop to iterate over components
0414   /// @note Use a for-loop with by-value semantics, since the Iterable returns a
0415   /// proxy internally holding a reference
0416   auto constComponentIterable(const State& state) const {
0417     struct ConstIterator {
0418       using difference_type = std::ptrdiff_t;
0419       using value_type = ConstComponentProxy;
0420       using reference = ConstComponentProxy;
0421       using pointer = void;
0422       using iterator_category = std::forward_iterator_tag;
0423 
0424       typename decltype(state.components)::const_iterator it;
0425       const State& s;
0426 
0427       // clang-format off
0428       auto& operator++() { ++it; return *this; }
0429       auto operator!=(const ConstIterator& other) const { return it != other.it; }
0430       auto operator==(const ConstIterator& other) const { return it == other.it; }
0431       auto operator*() const { return ConstComponentProxy{*it}; }
0432       // clang-format on
0433     };
0434 
0435     struct Iterable {
0436       using iterator = ConstIterator;
0437       const State& s;
0438 
0439       // clang-format off
0440       auto begin() const { return ConstIterator{s.components.cbegin(), s}; }
0441       auto end() const { return ConstIterator{s.components.cend(), s}; }
0442       // clang-format on
0443     };
0444 
0445     return Iterable{state};
0446   }
0447 
0448   /// Get the number of components
0449   ///
0450   /// @param state [in,out] The stepping state (thread-local cache)
0451   std::size_t numberComponents(const State& state) const {
0452     return state.components.size();
0453   }
0454 
0455   /// Remove missed components from the component state
0456   ///
0457   /// @param state [in,out] The stepping state (thread-local cache)
0458   void removeMissedComponents(State& state) const {
0459     auto new_end = std::remove_if(
0460         state.components.begin(), state.components.end(), [](const auto& cmp) {
0461           return cmp.status == Intersection3D::Status::missed;
0462         });
0463 
0464     state.components.erase(new_end, state.components.end());
0465   }
0466 
0467   /// Reweight the components
0468   ///
0469   /// @param [in,out] state The stepping state (thread-local cache)
0470   void reweightComponents(State& state) const {
0471     ActsScalar sumOfWeights = 0.0;
0472     for (const auto& cmp : state.components) {
0473       sumOfWeights += cmp.weight;
0474     }
0475     for (auto& cmp : state.components) {
0476       cmp.weight /= sumOfWeights;
0477     }
0478   }
0479 
0480   /// Reset the number of components
0481   ///
0482   /// @param [in,out] state  The stepping state (thread-local cache)
0483   void clearComponents(State& state) const { state.components.clear(); }
0484 
0485   /// Add a component to the Multistepper
0486   ///
0487   /// @param [in,out] state  The stepping state (thread-local cache)
0488   /// @param [in] pars Parameters of the component to add
0489   /// @param [in] weight Weight of the component to add
0490   ///
0491   /// @note: It is not ensured that the weights are normalized afterwards
0492   /// @note This function makes no garantuees about how new components are
0493   /// initialized, it is up to the caller to ensure that all components are
0494   /// valid in the end.
0495   /// @note The returned component-proxy is only garantueed to be valid until
0496   /// the component number is again modified
0497   Result<ComponentProxy> addComponent(State& state,
0498                                       const BoundTrackParameters& pars,
0499                                       double weight) const {
0500     state.components.push_back(
0501         {SingleState(state.geoContext,
0502                      SingleStepper::m_bField->makeCache(state.magContext),
0503                      pars),
0504          weight, Intersection3D::Status::onSurface});
0505 
0506     return ComponentProxy{state.components.back(), state};
0507   }
0508 
0509   /// Get the field for the stepping, it checks first if the access is still
0510   /// within the Cell, and updates the cell if necessary.
0511   ///
0512   /// @param [in,out] state is the propagation state associated with the track
0513   ///                 the magnetic field cell is used (and potentially updated)
0514   /// @param [in] pos is the field position
0515   ///
0516   /// @note This uses the cache of the first component stored in the state
0517   Result<Vector3> getField(State& state, const Vector3& pos) const {
0518     return SingleStepper::getField(state.components.front().state, pos);
0519   }
0520 
0521   /// Global particle position accessor
0522   ///
0523   /// @param state [in] The stepping state (thread-local cache)
0524   Vector3 position(const State& state) const {
0525     return Reducer::position(state);
0526   }
0527 
0528   /// Momentum direction accessor
0529   ///
0530   /// @param state [in] The stepping state (thread-local cache)
0531   Vector3 direction(const State& state) const {
0532     return Reducer::direction(state);
0533   }
0534 
0535   /// QoP access
0536   ///
0537   /// @param state [in] The stepping state (thread-local cache)
0538   double qOverP(const State& state) const { return Reducer::qOverP(state); }
0539 
0540   /// Absolute momentum accessor
0541   ///
0542   /// @param state [in] The stepping state (thread-local cache)
0543   double absoluteMomentum(const State& state) const {
0544     return Reducer::absoluteMomentum(state);
0545   }
0546 
0547   /// Momentum accessor
0548   ///
0549   /// @param state [in] The stepping state (thread-local cache)
0550   Vector3 momentum(const State& state) const {
0551     return Reducer::momentum(state);
0552   }
0553 
0554   /// Charge access
0555   ///
0556   /// @param state [in] The stepping state (thread-local cache)
0557   double charge(const State& state) const { return Reducer::charge(state); }
0558 
0559   /// Particle hypothesis
0560   ///
0561   /// @param state [in] The stepping state (thread-local cache)
0562   ParticleHypothesis particleHypothesis(const State& state) const {
0563     return state.particleHypothesis;
0564   }
0565 
0566   /// Time access
0567   ///
0568   /// @param state [in] The stepping state (thread-local cache)
0569   double time(const State& state) const { return Reducer::time(state); }
0570 
0571   /// Update surface status
0572   ///
0573   /// It checks the status to the reference surface & updates
0574   /// the step size accordingly
0575   ///
0576   /// @param [in,out] state The stepping state (thread-local cache)
0577   /// @param [in] surface The surface provided
0578   /// @param [in] index The surface intersection index
0579   /// @param [in] navDir The navigation direction
0580   /// @param [in] bcheck The boundary check for this status update
0581   /// @param [in] surfaceTolerance Surface tolerance used for intersection
0582   /// @param [in] logger A @c Logger instance
0583   Intersection3D::Status updateSurfaceStatus(
0584       State& state, const Surface& surface, std::uint8_t index,
0585       Direction navDir, const BoundaryCheck& bcheck,
0586       ActsScalar surfaceTolerance = s_onSurfaceTolerance,
0587       const Logger& logger = getDummyLogger()) const {
0588     using Status = Intersection3D::Status;
0589 
0590     std::array<int, 3> counts = {0, 0, 0};
0591 
0592     for (auto& component : state.components) {
0593       component.status = detail::updateSingleSurfaceStatus<SingleStepper>(
0594           *this, component.state, surface, index, navDir, bcheck,
0595           surfaceTolerance, logger);
0596       ++counts[static_cast<std::size_t>(component.status)];
0597     }
0598 
0599     // If at least one component is on a surface, we can remove all missed
0600     // components before the step. If not, we must keep them for the case that
0601     // all components miss and we need to retarget
0602     if (counts[static_cast<std::size_t>(Status::onSurface)] > 0) {
0603       removeMissedComponents(state);
0604       reweightComponents(state);
0605     }
0606 
0607     ACTS_VERBOSE("Component status wrt "
0608                  << surface.geometryId() << " at {"
0609                  << surface.center(state.geoContext).transpose() << "}:\t"
0610                  << [&]() {
0611                       std::stringstream ss;
0612                       for (auto& component : state.components) {
0613                         ss << component.status << "\t";
0614                       }
0615                       return ss.str();
0616                     }());
0617 
0618     // Switch on stepCounter if one or more components reached a surface, but
0619     // some are still in progress of reaching the surface
0620     if (!state.stepCounterAfterFirstComponentOnSurface &&
0621         counts[static_cast<std::size_t>(Status::onSurface)] > 0 &&
0622         counts[static_cast<std::size_t>(Status::reachable)] > 0) {
0623       state.stepCounterAfterFirstComponentOnSurface = 0;
0624       ACTS_VERBOSE("started stepCounterAfterFirstComponentOnSurface");
0625     }
0626 
0627     // If there are no components onSurface, but the counter is switched on
0628     // (e.g., if the navigator changes the target surface), we need to switch it
0629     // off again
0630     if (state.stepCounterAfterFirstComponentOnSurface &&
0631         counts[static_cast<std::size_t>(Status::onSurface)] == 0) {
0632       state.stepCounterAfterFirstComponentOnSurface.reset();
0633       ACTS_VERBOSE("switch off stepCounterAfterFirstComponentOnSurface");
0634     }
0635 
0636     // This is a 'any_of' criterium. As long as any of the components has a
0637     // certain state, this determines the total state (in the order of a
0638     // somewhat importance)
0639     if (counts[static_cast<std::size_t>(Status::reachable)] > 0) {
0640       return Status::reachable;
0641     } else if (counts[static_cast<std::size_t>(Status::onSurface)] > 0) {
0642       state.stepCounterAfterFirstComponentOnSurface.reset();
0643       return Status::onSurface;
0644     } else if (counts[static_cast<std::size_t>(Status::unreachable)] > 0) {
0645       return Status::unreachable;
0646     } else {
0647       return Status::missed;
0648     }
0649   }
0650 
0651   /// Update step size
0652   ///
0653   /// This method intersects the provided surface and update the navigation
0654   /// step estimation accordingly (hence it changes the state). It also
0655   /// returns the status of the intersection to trigger onSurface in case
0656   /// the surface is reached.
0657   ///
0658   /// @param state [in,out] The stepping state (thread-local cache)
0659   /// @param oIntersection [in] The ObjectIntersection to layer, boundary, etc
0660   /// @param direction [in] The propagation direction
0661   /// @param release [in] boolean to trigger step size release
0662   template <typename object_intersection_t>
0663   void updateStepSize(State& state, const object_intersection_t& oIntersection,
0664                       Direction direction, bool release = true) const {
0665     const Surface& surface = *oIntersection.object();
0666 
0667     for (auto& component : state.components) {
0668       auto intersection = surface.intersect(
0669           component.state.geoContext, SingleStepper::position(component.state),
0670           direction * SingleStepper::direction(component.state),
0671           BoundaryCheck(true))[oIntersection.index()];
0672 
0673       SingleStepper::updateStepSize(component.state, intersection, direction,
0674                                     release);
0675     }
0676   }
0677 
0678   /// Update step size - explicitly with a double
0679   ///
0680   /// @param state [in,out] The stepping state (thread-local cache)
0681   /// @param stepSize [in] The step size value
0682   /// @param stype [in] The step size type to be set
0683   /// @param release [in] Do we release the step size?
0684   void updateStepSize(State& state, double stepSize,
0685                       ConstrainedStep::Type stype, bool release = true) const {
0686     for (auto& component : state.components) {
0687       SingleStepper::updateStepSize(component.state, stepSize, stype, release);
0688     }
0689   }
0690 
0691   /// Get the step size
0692   ///
0693   /// @param state [in] The stepping state (thread-local cache)
0694   /// @param stype [in] The step size type to be returned
0695   /// @note This returns the smallest step size of all components. It uses
0696   /// std::abs for comparison to handle backward propagation and negative
0697   /// step sizes correctly.
0698   double getStepSize(const State& state, ConstrainedStep::Type stype) const {
0699     return std::min_element(state.components.begin(), state.components.end(),
0700                             [=](const auto& a, const auto& b) {
0701                               return std::abs(a.state.stepSize.value(stype)) <
0702                                      std::abs(b.state.stepSize.value(stype));
0703                             })
0704         ->state.stepSize.value(stype);
0705   }
0706 
0707   /// Release the step-size for all components
0708   ///
0709   /// @param state [in,out] The stepping state (thread-local cache)
0710   /// @param [in] stype The step size type to be released
0711   void releaseStepSize(State& state, ConstrainedStep::Type stype) const {
0712     for (auto& component : state.components) {
0713       SingleStepper::releaseStepSize(component.state, stype);
0714     }
0715   }
0716 
0717   /// Output the Step Size of all components into one std::string
0718   ///
0719   /// @param state [in,out] The stepping state (thread-local cache)
0720   std::string outputStepSize(const State& state) const {
0721     std::stringstream ss;
0722     for (const auto& component : state.components) {
0723       ss << component.state.stepSize.toString() << " || ";
0724     }
0725 
0726     return ss.str();
0727   }
0728 
0729   /// Overstep limit
0730   ///
0731   /// @param state [in] The stepping state (thread-local cache)
0732   double overstepLimit(const State& state) const {
0733     // A dynamic overstep limit could sit here
0734     return SingleStepper::overstepLimit(state.components.front().state);
0735   }
0736 
0737   /// Create and return the bound state at the current position
0738   ///
0739   /// @brief This transports (if necessary) the covariance
0740   /// to the surface and creates a bound state. It does not check
0741   /// if the transported state is at the surface, this needs to
0742   /// be guaranteed by the propagator.
0743   /// @note This is done by combining the gaussian mixture on the specified
0744   /// surface. If the conversion to bound states of some components
0745   /// fails, these components are ignored unless all components fail. In this
0746   /// case an error code is returned.
0747   ///
0748   /// @param [in] state State that will be presented as @c BoundState
0749   /// @param [in] surface The surface to which we bind the state
0750   /// @param [in] transportCov Flag steering covariance transport
0751   /// @param [in] freeToBoundCorrection Flag steering non-linear correction during global to local correction
0752   ///
0753   /// @return A bound state:
0754   ///   - the parameters at the surface
0755   ///   - the stepwise jacobian towards it (from last bound)
0756   ///   - and the path length (from start - for ordering)
0757   Result<BoundState> boundState(
0758       State& state, const Surface& surface, bool transportCov = true,
0759       const FreeToBoundCorrection& freeToBoundCorrection =
0760           FreeToBoundCorrection(false)) const;
0761 
0762   /// @brief If necessary fill additional members needed for curvilinearState
0763   ///
0764   /// Compute path length derivatives in case they have not been computed
0765   /// yet, which is the case if no step has been executed yet.
0766   ///
0767   /// @param [in, out] prop_state State that will be presented as @c BoundState
0768   /// @param [in] navigator the navigator of the propagation
0769   /// @return true if nothing is missing after this call, false otherwise.
0770   template <typename propagator_state_t, typename navigator_t>
0771   bool prepareCurvilinearState(
0772       [[maybe_unused]] propagator_state_t& prop_state,
0773       [[maybe_unused]] const navigator_t& navigator) const {
0774     return true;
0775   }
0776 
0777   /// Create and return a curvilinear state at the current position
0778   ///
0779   /// @brief This transports (if necessary) the covariance
0780   /// to the current position and creates a curvilinear state.
0781   /// @note This is done as a simple average over the free representation
0782   /// and covariance of the components.
0783   ///
0784   /// @param [in] state State that will be presented as @c CurvilinearState
0785   /// @param [in] transportCov Flag steering covariance transport
0786   ///
0787   /// @return A curvilinear state:
0788   ///   - the curvilinear parameters at given position
0789   ///   - the stepweise jacobian towards it (from last bound)
0790   ///   - and the path length (from start - for ordering)
0791   CurvilinearState curvilinearState(State& state,
0792                                     bool transportCov = true) const;
0793 
0794   /// Method for on-demand transport of the covariance
0795   /// to a new curvilinear frame at current  position,
0796   /// or direction of the state
0797   ///
0798   /// @param [in,out] state State of the stepper
0799   void transportCovarianceToCurvilinear(State& state) const {
0800     for (auto& component : state.components) {
0801       SingleStepper::transportCovarianceToCurvilinear(component.state);
0802     }
0803   }
0804 
0805   /// Method for on-demand transport of the covariance
0806   /// to a new curvilinear frame at current position,
0807   /// or direction of the state
0808   ///
0809   /// @tparam surface_t the Surface type
0810   ///
0811   /// @param [in,out] state State of the stepper
0812   /// @param [in] surface is the surface to which the covariance is forwarded
0813   /// @param [in] freeToBoundCorrection Flag steering non-linear correction during global to local correction
0814   /// to
0815   /// @note no check is done if the position is actually on the surface
0816   void transportCovarianceToBound(
0817       State& state, const Surface& surface,
0818       const FreeToBoundCorrection& freeToBoundCorrection =
0819           FreeToBoundCorrection(false)) const {
0820     for (auto& component : state.components) {
0821       SingleStepper::transportCovarianceToBound(component.state, surface,
0822                                                 freeToBoundCorrection);
0823     }
0824   }
0825 
0826   /// Perform a Runge-Kutta track parameter propagation step
0827   ///
0828   /// @param [in,out] state is the propagation state associated with the track
0829   /// parameters that are being propagated.
0830   /// @param [in] navigator is the navigator of the propagation
0831   ///
0832   /// The state contains the desired step size. It can be negative during
0833   /// backwards track propagation, and since we're using an adaptive
0834   /// algorithm, it can be modified by the stepper class during propagation.
0835   template <typename propagator_state_t, typename navigator_t>
0836   Result<double> step(propagator_state_t& state,
0837                       const navigator_t& navigator) const;
0838 };
0839 
0840 }  // namespace Acts
0841 
0842 #include "MultiEigenStepperLoop.ipp"