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