Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-15 09:24:02

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