Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:27:52

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2016-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/Common.hpp"
0016 #include "Acts/EventData/Measurement.hpp"
0017 #include "Acts/EventData/MeasurementHelpers.hpp"
0018 #include "Acts/EventData/MultiTrajectory.hpp"
0019 #include "Acts/EventData/MultiTrajectoryHelpers.hpp"
0020 #include "Acts/EventData/TrackContainer.hpp"
0021 #include "Acts/EventData/TrackHelpers.hpp"
0022 #include "Acts/EventData/TrackParameters.hpp"
0023 #include "Acts/EventData/TrackStatePropMask.hpp"
0024 #include "Acts/Geometry/GeometryContext.hpp"
0025 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0026 #include "Acts/Material/MaterialSlab.hpp"
0027 #include "Acts/Propagator/AbortList.hpp"
0028 #include "Acts/Propagator/ActionList.hpp"
0029 #include "Acts/Propagator/ConstrainedStep.hpp"
0030 #include "Acts/Propagator/Propagator.hpp"
0031 #include "Acts/Propagator/StandardAborters.hpp"
0032 #include "Acts/Propagator/detail/LoopProtection.hpp"
0033 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0034 #include "Acts/Surfaces/BoundaryCheck.hpp"
0035 #include "Acts/TrackFinding/CombinatorialKalmanFilterError.hpp"
0036 #include "Acts/TrackFitting/KalmanFitter.hpp"
0037 #include "Acts/TrackFitting/detail/VoidFitterComponents.hpp"
0038 #include "Acts/Utilities/CalibrationContext.hpp"
0039 #include "Acts/Utilities/Logger.hpp"
0040 #include "Acts/Utilities/Result.hpp"
0041 #include "Acts/Utilities/Zip.hpp"
0042 
0043 #include <functional>
0044 #include <limits>
0045 #include <memory>
0046 #include <type_traits>
0047 #include <unordered_map>
0048 
0049 namespace Acts {
0050 
0051 /// Track quality summary for one trajectory.
0052 ///
0053 /// This could be used to decide if a track is to be recorded when the
0054 /// filtering is done or to be terminated due to its bad quality
0055 /// @todo: add other useful info, e.g. chi2
0056 struct CombinatorialKalmanFilterTipState {
0057   // Number of passed sensitive surfaces
0058   std::size_t nSensitiveSurfaces = 0;
0059   // Number of track states
0060   std::size_t nStates = 0;
0061   // Number of (non-outlier) measurements
0062   std::size_t nMeasurements = 0;
0063   // Number of outliers
0064   std::size_t nOutliers = 0;
0065   // Number of holes
0066   std::size_t nHoles = 0;
0067 };
0068 
0069 enum class CombinatorialKalmanFilterBranchStopperResult {
0070   Continue,
0071   StopAndDrop,
0072   StopAndKeep,
0073 };
0074 
0075 /// Extension struct which holds the delegates to customize the CKF behavior
0076 template <typename traj_t>
0077 struct CombinatorialKalmanFilterExtensions {
0078   using candidate_container_t =
0079       typename std::vector<typename traj_t::TrackStateProxy>;
0080   using BranchStopperResult = CombinatorialKalmanFilterBranchStopperResult;
0081 
0082   using Calibrator = typename KalmanFitterExtensions<traj_t>::Calibrator;
0083   using Updater = typename KalmanFitterExtensions<traj_t>::Updater;
0084   using MeasurementSelector =
0085       Delegate<Result<std::pair<typename candidate_container_t::iterator,
0086                                 typename candidate_container_t::iterator>>(
0087           candidate_container_t& trackStates, bool&, const Logger&)>;
0088   using BranchStopper =
0089       Delegate<BranchStopperResult(const CombinatorialKalmanFilterTipState&,
0090                                    typename traj_t::TrackStateProxy&)>;
0091 
0092   /// The Calibrator is a dedicated calibration algorithm that allows to
0093   /// calibrate measurements using track information, this could be e.g. sagging
0094   /// for wires, module deformations, etc.
0095   Calibrator calibrator{
0096       DelegateFuncTag<detail::voidFitterCalibrator<traj_t>>{}};
0097 
0098   /// The updater incorporates measurement information into the track parameters
0099   Updater updater{DelegateFuncTag<detail::voidFitterUpdater<traj_t>>{}};
0100 
0101   /// The measurement selector is called during the filtering by the Actor.
0102   MeasurementSelector measurementSelector{
0103       DelegateFuncTag<voidMeasurementSelector>{}};
0104 
0105   /// The branch stopper is called during the filtering by the Actor.
0106   BranchStopper branchStopper{DelegateFuncTag<voidBranchStopper>{}};
0107 
0108  private:
0109   /// Default measurement selector which will return all measurements
0110   /// @param candidates Measurement track state candidates
0111   static Result<std::pair<
0112       typename std::vector<typename traj_t::TrackStateProxy>::iterator,
0113       typename std::vector<typename traj_t::TrackStateProxy>::iterator>>
0114   voidMeasurementSelector(
0115       typename std::vector<typename traj_t::TrackStateProxy>& candidates,
0116       bool& /*isOutlier*/, const Logger& /*logger*/) {
0117     return std::pair{candidates.begin(), candidates.end()};
0118   };
0119 
0120   /// Default branch stopper which will never stop
0121   /// @return false
0122   static BranchStopperResult voidBranchStopper(
0123       const CombinatorialKalmanFilterTipState& /*tipState*/,
0124       typename traj_t::TrackStateProxy& /*trackState*/) {
0125     return BranchStopperResult::Continue;
0126   }
0127 };
0128 
0129 /// Delegate type that retrieves a range of source links to for a given surface
0130 /// to be processed by the CKF
0131 template <typename source_link_iterator_t>
0132 using SourceLinkAccessorDelegate =
0133     Delegate<std::pair<source_link_iterator_t, source_link_iterator_t>(
0134         const Surface&)>;
0135 
0136 /// expected max number of track states that are expected to be added by
0137 /// stateCandidateCreator
0138 /// @note if the number of states exceeds this number dynamic memory allocation will occur.
0139 //.       the number is chosen to yield a container size of 64 bytes.
0140 static constexpr std::size_t s_maxBranchesPerSurface = 10;
0141 
0142 /// Combined options for the combinatorial Kalman filter.
0143 ///
0144 /// @tparam source_link_iterator_t Type of the source link iterator
0145 /// @tparam traj_t Type of the trajectory
0146 template <typename source_link_iterator_t, typename traj_t>
0147 struct CombinatorialKalmanFilterOptions {
0148   using SourceLinkIterator = source_link_iterator_t;
0149   using SourceLinkAccessor = SourceLinkAccessorDelegate<source_link_iterator_t>;
0150 
0151   /// PropagatorOptions with context
0152   ///
0153   /// @param gctx The geometry context for this track finding/fitting
0154   /// @param mctx The magnetic context for this track finding/fitting
0155   /// @param cctx The calibration context for this track finding/fitting
0156   /// @param accessor_ The source link accessor
0157   /// @param extensions_ The extension struct
0158   /// @param pOptions The plain propagator options
0159   /// @param mScattering Whether to include multiple scattering
0160   /// @param eLoss Whether to include energy loss
0161   CombinatorialKalmanFilterOptions(
0162       const GeometryContext& gctx, const MagneticFieldContext& mctx,
0163       std::reference_wrapper<const CalibrationContext> cctx,
0164       SourceLinkAccessor accessor_,
0165       CombinatorialKalmanFilterExtensions<traj_t> extensions_,
0166       const PropagatorPlainOptions& pOptions, bool mScattering = true,
0167       bool eLoss = true)
0168       : geoContext(gctx),
0169         magFieldContext(mctx),
0170         calibrationContext(cctx),
0171         sourcelinkAccessor(std::move(accessor_)),
0172         extensions(extensions_),
0173         propagatorPlainOptions(pOptions),
0174         multipleScattering(mScattering),
0175         energyLoss(eLoss) {}
0176 
0177   /// Contexts are required and the options must not be default-constructible.
0178   CombinatorialKalmanFilterOptions() = delete;
0179 
0180   /// Context object for the geometry
0181   std::reference_wrapper<const GeometryContext> geoContext;
0182   /// Context object for the magnetic field
0183   std::reference_wrapper<const MagneticFieldContext> magFieldContext;
0184   /// context object for the calibration
0185   std::reference_wrapper<const CalibrationContext> calibrationContext;
0186 
0187   /// The source link accessor
0188   SourceLinkAccessor sourcelinkAccessor;
0189 
0190   /// The filter extensions
0191   CombinatorialKalmanFilterExtensions<traj_t> extensions;
0192 
0193   /// The trivial propagator options
0194   PropagatorPlainOptions propagatorPlainOptions;
0195 
0196   /// The target surface
0197   /// @note This is useful if the filtering should be terminated at a
0198   ///       certain surface
0199   const Surface* targetSurface = nullptr;
0200 
0201   using BoundState = std::tuple<BoundTrackParameters, BoundMatrix, double>;
0202 
0203   /// Delegate definition to create track states for selected measurements
0204   /// @note expected to iterator over the given sourcelink range,
0205   ///       select measurements, and create track states for
0206   ///       which new tips are to be created, more over the outlier
0207   ///       flag should be set for states that are outlier.
0208   ///
0209   /// @param geoContext The current geometry context
0210   /// @param calibrationContext pointer to the current calibration context
0211   /// @param surface the surface at which new track states are to be created
0212   /// @param boundState the current bound state of the trajectory
0213   /// @param slBegin Begin iterator for sourcelinks
0214   /// @param slEnd End iterator for sourcelinks
0215   /// @param prevTip Index pointing at previous trajectory state (i.e. tip)
0216   /// @param bufferTrajectory a temporary trajectory which can be used to create temporary track states
0217   /// @param trackStateCandidates a temporary buffer that can be used to collect track states
0218   /// @param trajectory the trajectory to which the new states are to be added
0219   /// @param logger a logger for messages
0220   using TrackStateCandidateCreator =
0221       Delegate<Result<boost::container::small_vector<
0222           typename traj_t::TrackStateProxy::IndexType,
0223           s_maxBranchesPerSurface>>(
0224           const GeometryContext& geoContext,
0225           const CalibrationContext& calibrationContext, const Surface& surface,
0226           const BoundState& boundState, source_link_iterator_t slBegin,
0227           source_link_iterator_t slEnd, std::size_t prevTip,
0228           traj_t& bufferTrajectory,
0229           std::vector<typename traj_t::TrackStateProxy>& trackStateCandidates,
0230           traj_t& trajectory, const Logger& logger)>;
0231 
0232   /// The delegate to create new track states.
0233   TrackStateCandidateCreator trackStateCandidateCreator;
0234 
0235   /// Whether to consider multiple scattering.
0236   bool multipleScattering = true;
0237 
0238   /// Whether to consider energy loss.
0239   bool energyLoss = true;
0240 };
0241 
0242 template <typename traj_t>
0243 struct CombinatorialKalmanFilterResult {
0244   /// Fitted states that the actor has handled.
0245   traj_t* fittedStates{nullptr};
0246 
0247   /// This is used internally to store candidate trackstates
0248   std::shared_ptr<traj_t> stateBuffer;
0249   std::vector<typename traj_t::TrackStateProxy> trackStateCandidates;
0250 
0251   /// This is the indices of the 'tip' of the tracks stored in multitrajectory.
0252   /// This corresponds to the last measurement state in the multitrajectory.
0253   std::vector<MultiTrajectoryTraits::IndexType> lastMeasurementIndices;
0254 
0255   /// This is the indices of the 'tip' of the tracks stored in multitrajectory.
0256   /// This corresponds to the last state in the multitrajectory.
0257   std::vector<MultiTrajectoryTraits::IndexType> lastTrackIndices;
0258 
0259   /// The Parameters at the provided surface for separate tracks
0260   std::unordered_map<MultiTrajectoryTraits::IndexType, BoundTrackParameters>
0261       fittedParameters;
0262 
0263   /// The indices of the 'tip' of the unfinished tracks
0264   std::vector<std::pair<MultiTrajectoryTraits::IndexType,
0265                         CombinatorialKalmanFilterTipState>>
0266       activeTips;
0267 
0268   /// The indices of track states and corresponding source links on different
0269   /// surfaces
0270   std::unordered_map<const Surface*,
0271                      std::unordered_map<std::size_t, std::size_t>>
0272       sourcelinkTips;
0273 
0274   /// Indicator if track finding has been done
0275   bool finished = false;
0276 
0277   /// Last encountered error
0278   Result<void> lastError{Result<void>::success()};
0279 
0280   /// Path limit aborter
0281   PathLimitReached pathLimitReached;
0282 };
0283 
0284 /// Combinatorial Kalman filter to find tracks.
0285 ///
0286 /// @tparam propagator_t Type of the propagator
0287 ///
0288 /// The CombinatorialKalmanFilter contains an Actor and a Sequencer sub-class.
0289 /// The Sequencer has to be part of the Navigator of the Propagator in order to
0290 /// initialize and provide the measurement surfaces.
0291 ///
0292 /// The Actor is part of the Propagation call and does the Kalman update and
0293 /// eventually the smoothing. Updater and Calibrator are given to the Actor for
0294 /// further use:
0295 /// - The Updater is the implemented kalman updater formalism, it
0296 ///   runs via a visitor pattern through the measurements.
0297 ///
0298 /// Measurements are not required to be ordered for the
0299 /// CombinatorialKalmanFilter, measurement ordering needs to be figured out by
0300 /// the navigation of the propagator.
0301 ///
0302 /// The void components are provided mainly for unit testing.
0303 ///
0304 template <typename propagator_t, typename traj_t>
0305 class CombinatorialKalmanFilter {
0306  public:
0307   /// Default constructor is deleted
0308   CombinatorialKalmanFilter() = delete;
0309   /// Constructor from arguments
0310   CombinatorialKalmanFilter(propagator_t pPropagator,
0311                             std::unique_ptr<const Logger> _logger =
0312                                 getDefaultLogger("CKF", Logging::INFO))
0313       : m_propagator(std::move(pPropagator)),
0314         m_logger(std::move(_logger)),
0315         m_actorLogger{m_logger->cloneWithSuffix("Actor")},
0316         m_updaterLogger{m_logger->cloneWithSuffix("Updater")} {}
0317 
0318  private:
0319   using KalmanNavigator = typename propagator_t::Navigator;
0320 
0321   using BoundState = std::tuple<BoundTrackParameters, BoundMatrix, double>;
0322 
0323   /// The propagator for the transport and material update
0324   propagator_t m_propagator;
0325 
0326   std::unique_ptr<const Logger> m_logger;
0327   std::shared_ptr<const Logger> m_actorLogger;
0328   std::shared_ptr<const Logger> m_updaterLogger;
0329 
0330   const Logger& logger() const { return *m_logger; }
0331 
0332   struct DefaultTrackStateCreator {
0333     typename CombinatorialKalmanFilterExtensions<traj_t>::Calibrator calibrator;
0334     typename CombinatorialKalmanFilterExtensions<traj_t>::MeasurementSelector
0335         measurementSelector;
0336 
0337     /// Create track states for selected measurements given by the source links
0338     ///
0339     /// @param gctx The current geometry context
0340     /// @param calibrationContext pointer to the current calibration context
0341     /// @param surface the surface the sourceLinks are associated to
0342     /// @param boundState Bound state from the propagation on this surface
0343     /// @param slBegin Begin iterator for sourcelinks
0344     /// @param slEnd End iterator for sourcelinks
0345     /// @param prevTip Index pointing at previous trajectory state (i.e. tip)
0346     /// @param bufferTrajectory a buffer for temporary candidate track states
0347     /// @param trackStateCandidates a buffer for temporary track state proxies for candidates
0348     /// @param trajectory the trajectory to which new track states for selected measurements will be added
0349     /// @param logger the logger for messages.
0350     template <typename source_link_iterator_t>
0351     Result<boost::container::small_vector<
0352         typename traj_t::TrackStateProxy::IndexType, s_maxBranchesPerSurface>>
0353     createSourceLinkTrackStates(
0354         const GeometryContext& gctx,
0355         const CalibrationContext& calibrationContext,
0356         [[maybe_unused]] const Surface& surface, const BoundState& boundState,
0357         source_link_iterator_t slBegin, source_link_iterator_t slEnd,
0358         std::size_t prevTip, traj_t& bufferTrajectory,
0359         std::vector<typename traj_t::TrackStateProxy>& trackStateCandidates,
0360         traj_t& trajectory, const Logger& logger) const {
0361       using ResultTrackStateList = Acts::Result<boost::container::small_vector<
0362           typename traj_t::TrackStateProxy::IndexType,
0363           s_maxBranchesPerSurface>>;
0364       ResultTrackStateList resultTrackStateList{boost::container::small_vector<
0365           typename traj_t::TrackStateProxy::IndexType,
0366           s_maxBranchesPerSurface>()};
0367       const auto& [boundParams, jacobian, pathLength] = boundState;
0368 
0369       trackStateCandidates.clear();
0370       if constexpr (std::is_same_v<
0371                         typename std::iterator_traits<
0372                             source_link_iterator_t>::iterator_category,
0373                         std::random_access_iterator_tag>) {
0374         trackStateCandidates.reserve(std::distance(slBegin, slEnd));
0375       }
0376 
0377       bufferTrajectory.clear();
0378 
0379       using PM = TrackStatePropMask;
0380 
0381       // Calibrate all the source links on the surface since the selection has
0382       // to be done based on calibrated measurement
0383       for (auto it = slBegin; it != slEnd; ++it) {
0384         // get the source link
0385         const auto sourceLink = *it;
0386 
0387         // prepare the track state
0388         PM mask = PM::Predicted | PM::Jacobian | PM::Calibrated;
0389 
0390         if (it != slBegin) {
0391           // not the first TrackState, only need uncalibrated and calibrated
0392           mask = PM::Calibrated;
0393         }
0394 
0395         ACTS_VERBOSE("Create temp track state with mask: " << mask);
0396         // CAREFUL! This trackstate has a previous index that is not in this
0397         // MultiTrajectory Visiting brackwards from this track state will
0398         // fail!
0399         auto ts = bufferTrajectory.makeTrackState(mask, prevTip);
0400 
0401         if (it == slBegin) {
0402           // only set these for first
0403           ts.predicted() = boundParams.parameters();
0404           if (boundParams.covariance()) {
0405             ts.predictedCovariance() = *boundParams.covariance();
0406           }
0407           ts.jacobian() = jacobian;
0408         } else {
0409           // subsequent track states can reuse
0410           auto& first = trackStateCandidates.front();
0411           ts.shareFrom(first, PM::Predicted);
0412           ts.shareFrom(first, PM::Jacobian);
0413         }
0414 
0415         ts.pathLength() = pathLength;
0416 
0417         ts.setReferenceSurface(boundParams.referenceSurface().getSharedPtr());
0418 
0419         // now calibrate the track state
0420         calibrator(gctx, calibrationContext, sourceLink, ts);
0421 
0422         trackStateCandidates.push_back(ts);
0423       }
0424       bool isOutlier = false;
0425       Result<std::pair<
0426           typename std::vector<typename traj_t::TrackStateProxy>::iterator,
0427           typename std::vector<typename traj_t::TrackStateProxy>::iterator>>
0428           selectorResult =
0429               measurementSelector(trackStateCandidates, isOutlier, logger);
0430       if (!selectorResult.ok()) {
0431         ACTS_ERROR("Selection of calibrated measurements failed: "
0432                    << selectorResult.error());
0433         resultTrackStateList =
0434             ResultTrackStateList::failure(selectorResult.error());
0435       } else {
0436         auto selectedTrackStateRange = *selectorResult;
0437 
0438         resultTrackStateList = processSelectedTrackStates(
0439             selectedTrackStateRange.first, selectedTrackStateRange.second,
0440             trajectory, isOutlier, logger);
0441       }
0442 
0443       return resultTrackStateList;
0444     }
0445 
0446     /// Create track states for the given trajectory from candidate track states
0447     ///
0448     /// @param begin begin iterator of the list of candidate track states
0449     /// @param end end iterator of the list of candidate track states
0450     /// @param fittedStates the trajectory to which the new track states are added
0451     /// @param isOutlier true if the candidate(s) is(are) an outlier(s).
0452     /// @param logger the logger for messages
0453     Result<boost::container::small_vector<
0454         typename traj_t::TrackStateProxy::IndexType, s_maxBranchesPerSurface>>
0455     processSelectedTrackStates(
0456         typename std::vector<typename traj_t::TrackStateProxy>::const_iterator
0457             begin,
0458         typename std::vector<typename traj_t::TrackStateProxy>::const_iterator
0459             end,
0460         traj_t& fittedStates, bool isOutlier, const Logger& logger) const {
0461       Acts::Result<boost::container::small_vector<
0462           typename traj_t::TrackStateProxy::IndexType, s_maxBranchesPerSurface>>
0463           resultTrackStateList{boost::container::small_vector<
0464               typename traj_t::TrackStateProxy::IndexType,
0465               s_maxBranchesPerSurface>()};
0466       boost::container::small_vector<
0467           typename traj_t::TrackStateProxy::IndexType, s_maxBranchesPerSurface>&
0468           trackStateList = *resultTrackStateList;
0469       trackStateList.reserve(end - begin);
0470       using PM = TrackStatePropMask;
0471 
0472       std::optional<typename traj_t::TrackStateProxy> firstTrackState{
0473           std::nullopt};
0474       for (auto it = begin; it != end; ++it) {
0475         auto& candidateTrackState = *it;
0476 
0477         PM mask = PM::Predicted | PM::Filtered | PM::Jacobian | PM::Calibrated;
0478 
0479         if (it != begin) {
0480           // subsequent track states don't need storage for these as they will
0481           // be shared
0482           mask &= ~PM::Predicted & ~PM::Jacobian;
0483         }
0484 
0485         if (isOutlier) {
0486           // outlier won't have separate filtered parameters
0487           mask &= ~PM::Filtered;
0488         }
0489 
0490         // copy this trackstate into fitted states MultiTrajectory
0491         typename traj_t::TrackStateProxy trackState =
0492             fittedStates.makeTrackState(mask, candidateTrackState.previous());
0493         ACTS_VERBOSE("Create SourceLink output track state #"
0494                      << trackState.index() << " with mask: " << mask);
0495 
0496         if (it != begin) {
0497           // assign indices pointing to first track state
0498           trackState.shareFrom(*firstTrackState, PM::Predicted);
0499           trackState.shareFrom(*firstTrackState, PM::Jacobian);
0500         } else {
0501           firstTrackState = trackState;
0502         }
0503 
0504         // either copy ALL or everything except for predicted and jacobian
0505         trackState.allocateCalibrated(candidateTrackState.calibratedSize());
0506         trackState.copyFrom(candidateTrackState, mask, false);
0507 
0508         auto typeFlags = trackState.typeFlags();
0509         if (trackState.referenceSurface().surfaceMaterial() != nullptr) {
0510           typeFlags.set(TrackStateFlag::MaterialFlag);
0511         }
0512         typeFlags.set(TrackStateFlag::ParameterFlag);
0513 
0514         if (isOutlier) {
0515           // propagate information that this is an outlier state
0516           ACTS_VERBOSE(
0517               "Creating outlier track state with tip = " << trackState.index());
0518           // Set the outlier flag needed by the next step to identify outlier
0519           // states
0520           typeFlags.set(TrackStateFlag::OutlierFlag);
0521         }
0522         trackStateList.push_back(trackState.index());
0523       }
0524       return resultTrackStateList;
0525     }
0526   };
0527 
0528   /// @brief Propagator Actor plugin for the CombinatorialKalmanFilter
0529   ///
0530   /// @tparam source_link_accessor_t The type of source link accessor
0531   /// @tparam parameters_t The type of parameters used for "local" parameters.
0532   ///
0533   /// The CombinatorialKalmanFilter Actor does not rely on the measurements to
0534   /// be sorted along the track.
0535   template <typename source_link_accessor_t, typename parameters_t>
0536   class Actor {
0537    public:
0538     using TipState = CombinatorialKalmanFilterTipState;
0539     using BoundState = std::tuple<parameters_t, BoundMatrix, double>;
0540     using CurvilinearState =
0541         std::tuple<CurvilinearTrackParameters, BoundMatrix, double>;
0542     /// Broadcast the result_type
0543     using result_type = CombinatorialKalmanFilterResult<traj_t>;
0544 
0545     /// The target surface aborter
0546     SurfaceReached targetReached{std::numeric_limits<double>::lowest()};
0547 
0548     /// Whether to consider multiple scattering.
0549     bool multipleScattering = true;
0550 
0551     /// Whether to consider energy loss.
0552     bool energyLoss = true;
0553 
0554     /// Calibration context for the finding run
0555     const CalibrationContext* calibrationContextPtr{nullptr};
0556 
0557     /// @brief CombinatorialKalmanFilter actor operation
0558     ///
0559     /// @tparam propagator_state_t Type of the Propagator state
0560     /// @tparam stepper_t Type of the stepper
0561     ///
0562     /// @param state is the mutable propagator state object
0563     /// @param stepper is the stepper in use
0564     /// @param navigator is the navigator in use
0565     /// @param result is the mutable result state object
0566     template <typename propagator_state_t, typename stepper_t,
0567               typename navigator_t>
0568     void operator()(propagator_state_t& state, const stepper_t& stepper,
0569                     const navigator_t& navigator, result_type& result,
0570                     const Logger& /*logger*/) const {
0571       assert(result.fittedStates && "No MultiTrajectory set");
0572 
0573       if (result.finished) {
0574         return;
0575       }
0576 
0577       ACTS_VERBOSE("CombinatorialKalmanFilter step");
0578 
0579       // Initialize path limit reached aborter
0580       if (result.pathLimitReached.internalLimit ==
0581           std::numeric_limits<double>::max()) {
0582         detail::setupLoopProtection(state, stepper, result.pathLimitReached,
0583                                     true, logger());
0584       }
0585 
0586       // Update:
0587       // - Waiting for a current surface
0588       if (auto surface = navigator.currentSurface(state.navigation);
0589           surface != nullptr) {
0590         // There are three scenarios:
0591         // 1) The surface is in the measurement map
0592         // -> Select source links
0593         // -> Perform the kalman update for selected non-outlier source links
0594         // -> Add track states in multitrajectory. Multiple states mean branch
0595         // splitting.
0596         // -> Call branch stopper to justify each branch
0597         // -> If there is non-outlier state, update stepper information
0598         // 2) The surface is not in the measurement map but with material or is
0599         // an active surface
0600         // -> Add a hole or passive material state in multitrajectory
0601         // -> Call branch stopper to justify the branch
0602         // 3) The surface is neither in the measurement map nor with material
0603         // -> Do nothing
0604         ACTS_VERBOSE("Perform filter step");
0605         auto res = filter(surface, state, stepper, navigator, result);
0606         if (!res.ok()) {
0607           ACTS_ERROR("Error in filter: " << res.error());
0608           result.lastError = res.error();
0609         }
0610       }
0611 
0612       const bool isEndOfWorldReached =
0613           endOfWorldReached(state, stepper, navigator, logger());
0614       const bool isPathLimitReached =
0615           result.pathLimitReached(state, stepper, navigator, logger());
0616       const bool isTargetReached =
0617           targetReached(state, stepper, navigator, logger());
0618       if (isEndOfWorldReached || isPathLimitReached || isTargetReached) {
0619         if (isEndOfWorldReached) {
0620           ACTS_VERBOSE("End of world reached");
0621         } else if (isPathLimitReached) {
0622           ACTS_VERBOSE("Path limit reached");
0623         } else if (isTargetReached) {
0624           ACTS_VERBOSE("Target surface reached");
0625 
0626           // Bind the parameter to the target surface
0627           auto res = stepper.boundState(state.stepping, *targetReached.surface);
0628           if (!res.ok()) {
0629             ACTS_ERROR("Error while acquiring bound state for target surface: "
0630                        << res.error() << " " << res.error().message());
0631             result.lastError = res.error();
0632           } else if (!result.activeTips.empty()) {
0633             const auto& fittedState = *res;
0634             std::size_t currentTip = result.activeTips.back().first;
0635             // Assign the fitted parameters
0636             result.fittedParameters.emplace(
0637                 currentTip, std::get<BoundTrackParameters>(fittedState));
0638           }
0639 
0640           stepper.releaseStepSize(state.stepping, ConstrainedStep::actor);
0641         }
0642 
0643         if (!result.activeTips.empty()) {
0644           // Record the active tip as trajectory entry indices and remove it
0645           // from the list
0646           storeLastActiveTip(result);
0647           // Remove the tip from list of active tips
0648           result.activeTips.erase(result.activeTips.end() - 1);
0649         }
0650         // If no more active tip, done with filtering; Otherwise, reset
0651         // propagation state to track state at last tip of active tips
0652         if (result.activeTips.empty()) {
0653           ACTS_VERBOSE("Kalman filtering finds "
0654                        << result.lastTrackIndices.size() << " tracks");
0655           result.finished = true;
0656         } else {
0657           ACTS_VERBOSE("Propagation jumps to branch with tip = "
0658                        << result.activeTips.back().first);
0659           reset(state, stepper, navigator, result);
0660         }
0661       }
0662     }
0663 
0664     /// @brief CombinatorialKalmanFilter actor operation: reset propagation
0665     ///
0666     /// @tparam propagator_state_t Type of Propagator state
0667     /// @tparam stepper_t Type of the stepper
0668     /// @tparam navigator_t Type of the navigator
0669     ///
0670     /// @param state is the mutable propagator state object
0671     /// @param stepper is the stepper in use
0672     /// @param navigator is the navigator in use
0673     /// @param result is the mutable result state object
0674     template <typename propagator_state_t, typename stepper_t,
0675               typename navigator_t>
0676     void reset(propagator_state_t& state, const stepper_t& stepper,
0677                const navigator_t& navigator, result_type& result) const {
0678       auto currentState =
0679           result.fittedStates->getTrackState(result.activeTips.back().first);
0680 
0681       // Reset the stepping state
0682       stepper.resetState(state.stepping, currentState.filtered(),
0683                          currentState.filteredCovariance(),
0684                          currentState.referenceSurface(),
0685                          state.options.maxStepSize);
0686 
0687       // Reset the navigation state
0688       // Set targetSurface to nullptr for forward filtering; it's only needed
0689       // after smoothing
0690       state.navigation =
0691           navigator.makeState(&currentState.referenceSurface(), nullptr);
0692       navigator.initialize(state, stepper);
0693 
0694       // No Kalman filtering for the starting surface, but still need
0695       // to consider the material effects here
0696       materialInteractor(navigator.currentSurface(state.navigation), state,
0697                          stepper, navigator, MaterialUpdateStage::PostUpdate);
0698 
0699       detail::setupLoopProtection(state, stepper, result.pathLimitReached, true,
0700                                   logger());
0701     }
0702 
0703     /// @brief CombinatorialKalmanFilter actor operation:
0704     /// - filtering for all measurement(s) on surface
0705     /// - store selected track states in multiTrajectory
0706     /// - update propagator state to the (last) selected track state
0707     ///
0708     /// @tparam propagator_state_t Type of the Propagator state
0709     /// @tparam stepper_t Type of the stepper
0710     /// @tparam navigator_t Type of the navigator
0711     ///
0712     /// @param surface The surface where the update happens
0713     /// @param state The mutable propagator state object
0714     /// @param stepper The stepper in use
0715     /// @param navigator The navigator in use
0716     /// @param result The mutable result state object
0717     template <typename propagator_state_t, typename stepper_t,
0718               typename navigator_t>
0719     Result<void> filter(const Surface* surface, propagator_state_t& state,
0720                         const stepper_t& stepper, const navigator_t& navigator,
0721                         result_type& result) const {
0722       std::size_t nBranchesOnSurface = 0;
0723       // Count the number of source links on the surface
0724       auto [slBegin, slEnd] = m_sourcelinkAccessor(*surface);
0725       if (slBegin != slEnd) {
0726         // Screen output message
0727         ACTS_VERBOSE("Measurement surface " << surface->geometryId()
0728                                             << " detected.");
0729 
0730         // Transport the covariance to the surface
0731         stepper.transportCovarianceToBound(state.stepping, *surface);
0732 
0733         // Update state and stepper with pre material effects
0734         materialInteractor(surface, state, stepper, navigator,
0735                            MaterialUpdateStage::PreUpdate);
0736 
0737         // Bind the transported state to the current surface
0738         auto boundStateRes =
0739             stepper.boundState(state.stepping, *surface, false);
0740         if (!boundStateRes.ok()) {
0741           return boundStateRes.error();
0742         }
0743         auto& boundState = *boundStateRes;
0744         auto& [boundParams, jacobian, pathLength] = boundState;
0745         boundParams.covariance() = state.stepping.cov;
0746 
0747         // Retrieve the previous tip and its state
0748         // The states created on this surface will have the common previous tip
0749         std::size_t prevTip = kTrackIndexInvalid;
0750         TipState prevTipState;
0751         if (!result.activeTips.empty()) {
0752           prevTip = result.activeTips.back().first;
0753           prevTipState = result.activeTips.back().second;
0754           // New state is to be added. Remove the last tip from active tips
0755           result.activeTips.erase(result.activeTips.end() - 1);
0756         }
0757 
0758         // Create trackstates for all source links (will be filtered later)
0759         // Results are stored in result => no return value
0760 
0761         using TrackStatesResult = Acts::Result<boost::container::small_vector<
0762             typename traj_t::TrackStateProxy::IndexType,
0763             s_maxBranchesPerSurface>>;
0764 
0765         TrackStatesResult tsRes = trackStateCandidateCreator(
0766             state.geoContext, *calibrationContextPtr, *surface, boundState,
0767             slBegin, slEnd, prevTip, *result.stateBuffer,
0768             result.trackStateCandidates, *result.fittedStates, logger());
0769 
0770         if (!tsRes.ok()) {
0771           ACTS_ERROR(
0772               "Processing of selected track states failed: " << tsRes.error())
0773           return tsRes.error();
0774         }
0775         Result<std::tuple<unsigned int, bool>> procRes = processNewTrackStates(
0776             state.geoContext, prevTipState, *tsRes, result);
0777         if (!procRes.ok()) {
0778           ACTS_ERROR(
0779               "Processing of selected track states failed: " << procRes.error())
0780           return procRes.error();
0781         }
0782         auto [nNewBranchesOnSurface, isOutlier] = *procRes;
0783         nBranchesOnSurface = nNewBranchesOnSurface;
0784 
0785         if (nBranchesOnSurface > 0 && !isOutlier) {
0786           // If there are measurement track states on this surface
0787           ACTS_VERBOSE("Filtering step successful with " << nBranchesOnSurface
0788                                                          << " branches");
0789           // Update stepping state using filtered parameters of last track
0790           // state on this surface
0791           auto ts = result.fittedStates->getTrackState(
0792               result.activeTips.back().first);
0793           stepper.update(state.stepping,
0794                          MultiTrajectoryHelpers::freeFiltered(
0795                              state.options.geoContext, ts),
0796                          ts.filtered(), ts.filteredCovariance(), *surface);
0797           ACTS_VERBOSE("Stepping state is updated with filtered parameter:");
0798           ACTS_VERBOSE("-> " << ts.filtered().transpose()
0799                              << " of track state with tip = "
0800                              << result.activeTips.back().first);
0801         }
0802 
0803         // Update state and stepper with post material effects
0804         materialInteractor(surface, state, stepper, navigator,
0805                            MaterialUpdateStage::PostUpdate);
0806       } else if (surface->associatedDetectorElement() != nullptr ||
0807                  surface->surfaceMaterial() != nullptr) {
0808         // No splitting on the surface without source links. Set it to one
0809         // first, but could be changed later
0810         nBranchesOnSurface = 1;
0811 
0812         // Retrieve the previous tip and its state
0813         std::size_t prevTip = kTrackIndexInvalid;
0814         TipState tipState;
0815         if (!result.activeTips.empty()) {
0816           prevTip = result.activeTips.back().first;
0817           tipState = result.activeTips.back().second;
0818         }
0819 
0820         // The surface could be either sensitive or passive
0821         bool isSensitive = (surface->associatedDetectorElement() != nullptr);
0822         bool isMaterial = (surface->surfaceMaterial() != nullptr);
0823         ACTS_VERBOSE("Detected " << (isSensitive ? "sensitive" : "passive")
0824                                  << " surface: " << surface->geometryId());
0825         if (isSensitive) {
0826           // Increment of number of passed sensitive surfaces
0827           tipState.nSensitiveSurfaces++;
0828         }
0829         // Add state if there is already measurement detected on this branch
0830         if (tipState.nMeasurements > 0 || isMaterial) {
0831           // New state is to be added. Remove the last tip from active tips now
0832           if (!result.activeTips.empty()) {
0833             result.activeTips.erase(result.activeTips.end() - 1);
0834           }
0835           // No source links on surface, add either hole or passive material
0836           // TrackState. No storage allocation for uncalibrated/calibrated
0837           // measurement and filtered parameter
0838           auto stateMask =
0839               TrackStatePropMask::Predicted | TrackStatePropMask::Jacobian;
0840 
0841           // Increment of number of processed states
0842           tipState.nStates++;
0843           if (isSensitive) {
0844             // Increment of number of holes
0845             tipState.nHoles++;
0846           }
0847 
0848           // Transport the covariance to a curvilinear surface
0849           stepper.transportCovarianceToCurvilinear(state.stepping);
0850 
0851           // Update state and stepper with pre material effects
0852           materialInteractor(surface, state, stepper, navigator,
0853                              MaterialUpdateStage::PreUpdate);
0854 
0855           // Transport & bind the state to the current surface
0856           auto boundStateRes =
0857               stepper.boundState(state.stepping, *surface, false);
0858           if (!boundStateRes.ok()) {
0859             return boundStateRes.error();
0860           }
0861           auto& boundState = *boundStateRes;
0862           auto& [boundParams, jacobian, pathLength] = boundState;
0863           boundParams.covariance() = state.stepping.cov;
0864 
0865           // Add a hole or material track state to the multitrajectory
0866           std::size_t currentTip = addNonSourcelinkState(
0867               stateMask, boundState, result, isSensitive, prevTip);
0868           result.activeTips.emplace_back(currentTip, tipState);
0869 
0870           auto nonSourcelinkState =
0871               result.fittedStates->getTrackState(currentTip);
0872 
0873           using BranchStopperResult =
0874               CombinatorialKalmanFilterBranchStopperResult;
0875           BranchStopperResult branchStopperResult =
0876               m_extensions.branchStopper(tipState, nonSourcelinkState);
0877 
0878           // Check the branch
0879           if (branchStopperResult == BranchStopperResult::Continue) {
0880             // Remembered the active tip and its state
0881           } else {
0882             // No branch on this surface
0883             nBranchesOnSurface = 0;
0884 
0885             if (branchStopperResult == BranchStopperResult::StopAndKeep) {
0886               storeLastActiveTip(result);
0887             }
0888 
0889             // Remove the tip from list of active tips
0890             result.activeTips.erase(result.activeTips.end() - 1);
0891           }
0892 
0893           // Update state and stepper with post material effects
0894           materialInteractor(surface, state, stepper, navigator,
0895                              MaterialUpdateStage::PostUpdate);
0896         }
0897       } else {
0898         // Neither measurement nor material on surface, this branch is still
0899         // valid. Count the branch on current surface
0900         nBranchesOnSurface = 1;
0901       }
0902 
0903       // Reset current tip if there is no branch on current surface
0904       if (nBranchesOnSurface == 0) {
0905         ACTS_DEBUG("Branch on surface " << surface->geometryId()
0906                                         << " is stopped");
0907         if (!result.activeTips.empty()) {
0908           ACTS_VERBOSE("Propagation jumps to branch with tip = "
0909                        << result.activeTips.back().first);
0910           reset(state, stepper, navigator, result);
0911         } else {
0912           ACTS_VERBOSE("Stop Kalman filtering with "
0913                        << result.lastMeasurementIndices.size()
0914                        << " found tracks");
0915           result.finished = true;
0916         }
0917       }
0918 
0919       return Result<void>::success();
0920     }
0921 
0922     /// process new, incompomplete track states and set the filtered state
0923     /// @note will process the given list of new states, run the updater
0924     ///     or share the predicted state for states flagged as outliers
0925     ///     and add them to the list of active tips
0926     ///
0927     /// @param gctx The geometry context for this track finding/fitting
0928     /// @param prevTipState the previous tip state
0929     /// @param newTrackStateList index list of new track states
0930     /// @param result which contains among others the new states, and the list of active tips
0931     /// @return tuple of the number of newly added tips and outlier flag or an error
0932     Result<std::tuple<unsigned int, bool>> processNewTrackStates(
0933         const Acts::GeometryContext& gctx, const TipState& prevTipState,
0934         const boost::container::small_vector<
0935             typename traj_t::TrackStateProxy::IndexType,
0936             s_maxBranchesPerSurface>& newTrackStateList,
0937         result_type& result) const {
0938       unsigned int nBranchesOnSurface = 0;
0939       bool isOutlier = false;
0940       for (typename traj_t::TrackStateProxy::IndexType tipIndex :
0941            newTrackStateList) {
0942         // Inherit the tip state from the previous and will be updated
0943         // later
0944         typename traj_t::TrackStateProxy trackState(
0945             result.fittedStates->getTrackState(tipIndex));
0946         TipState tipState = prevTipState;
0947 
0948         // Increment of number of processedState and passed sensitive surfaces
0949         tipState.nSensitiveSurfaces++;
0950         tipState.nStates++;
0951 
0952         using PM = Acts::TrackStatePropMask;
0953         TrackStateType typeFlags(trackState.typeFlags());
0954         if (typeFlags.test(TrackStateFlag::OutlierFlag)) {
0955           // Increment number of outliers
0956           isOutlier = true;
0957           tipState.nOutliers++;
0958           // No Kalman update for outlier
0959           // Set the filtered parameter index to be the same with predicted
0960           // parameter
0961           trackState.shareFrom(PM::Predicted, PM::Filtered);
0962         } else {
0963           // Kalman update
0964           auto updateRes = m_extensions.updater(
0965               gctx, trackState, Direction::Forward, *updaterLogger);
0966           if (!updateRes.ok()) {
0967             ACTS_ERROR("Update step failed: " << updateRes.error());
0968             return updateRes.error();
0969           }
0970           ACTS_VERBOSE(
0971               "Creating measurement track state with tip = " << tipIndex);
0972           // Set the measurement flag
0973           typeFlags.set(TrackStateFlag::MeasurementFlag);
0974           // Increment number of measurements
0975           tipState.nMeasurements++;
0976         }
0977 
0978         // Put tipstate back into active tips to continue with it
0979         result.activeTips.emplace_back(tipIndex, tipState);
0980 
0981         using BranchStopperResult =
0982             CombinatorialKalmanFilterBranchStopperResult;
0983         BranchStopperResult branchStopperResult =
0984             m_extensions.branchStopper(tipState, trackState);
0985 
0986         // Check if need to stop this branch
0987         if (branchStopperResult == BranchStopperResult::Continue) {
0988           // Record the number of branches on surface
0989           nBranchesOnSurface++;
0990         } else {
0991           if (branchStopperResult == BranchStopperResult::StopAndKeep) {
0992             storeLastActiveTip(result);
0993           }
0994 
0995           // Remove the tip from list of active tips
0996           result.activeTips.erase(result.activeTips.end() - 1);
0997         }
0998       }
0999       return std::make_tuple(nBranchesOnSurface, isOutlier);
1000     }
1001 
1002     /// @brief CombinatorialKalmanFilter actor operation: add a hole or material track state
1003     ///
1004     /// @param stateMask The bitmask that instructs which components to allocate
1005     /// @param boundState The bound state on current surface
1006     /// @param result is the mutable result state object and which to leave invalid
1007     /// @param isSensitive The surface is sensitive or passive
1008     /// @param prevTip The index of the previous state
1009     ///
1010     /// @return The tip of added state
1011     std::size_t addNonSourcelinkState(TrackStatePropMask stateMask,
1012                                       const BoundState& boundState,
1013                                       result_type& result, bool isSensitive,
1014                                       std::size_t prevTip) const {
1015       // Add a track state
1016       auto trackStateProxy =
1017           result.fittedStates->makeTrackState(stateMask, prevTip);
1018       ACTS_VERBOSE("Create " << (isSensitive ? "Hole" : "Material")
1019                              << " output track state #"
1020                              << trackStateProxy.index()
1021                              << " with mask: " << stateMask);
1022 
1023       const auto& [boundParams, jacobian, pathLength] = boundState;
1024       // Fill the track state
1025       trackStateProxy.predicted() = boundParams.parameters();
1026       trackStateProxy.predictedCovariance() = boundParams.covariance().value();
1027       trackStateProxy.jacobian() = jacobian;
1028       trackStateProxy.pathLength() = pathLength;
1029       // Set the surface
1030       trackStateProxy.setReferenceSurface(
1031           boundParams.referenceSurface().getSharedPtr());
1032 
1033       // Set the track state flags
1034       auto typeFlags = trackStateProxy.typeFlags();
1035       if (trackStateProxy.referenceSurface().surfaceMaterial() != nullptr) {
1036         typeFlags.set(TrackStateFlag::MaterialFlag);
1037       }
1038       typeFlags.set(TrackStateFlag::ParameterFlag);
1039       if (isSensitive) {
1040         typeFlags.set(TrackStateFlag::HoleFlag);
1041       }
1042 
1043       // Set the filtered parameter index to be the same with predicted
1044       // parameter
1045       trackStateProxy.shareFrom(TrackStatePropMask::Predicted,
1046                                 TrackStatePropMask::Filtered);
1047 
1048       return trackStateProxy.index();
1049     }
1050 
1051     /// @brief CombinatorialKalmanFilter actor operation: material interaction
1052     ///
1053     /// @tparam propagator_state_t is the type of Propagator state
1054     /// @tparam stepper_t Type of the stepper
1055     /// @tparam navigator_t Type of the navigator
1056     ///
1057     /// @param surface The surface where the material interaction happens
1058     /// @param state The mutable propagator state object
1059     /// @param stepper The stepper in use
1060     /// @param navigator The navigator in use
1061     /// @param updateStage The material update stage
1062     ///
1063     template <typename propagator_state_t, typename stepper_t,
1064               typename navigator_t>
1065     void materialInteractor(const Surface* surface, propagator_state_t& state,
1066                             const stepper_t& stepper,
1067                             const navigator_t& navigator,
1068                             const MaterialUpdateStage& updateStage) const {
1069       if (surface == nullptr) {
1070         return;
1071       }
1072 
1073       // Indicator if having material
1074       bool hasMaterial = false;
1075 
1076       if (surface->surfaceMaterial() != nullptr) {
1077         // Prepare relevant input particle properties
1078         detail::PointwiseMaterialInteraction interaction(surface, state,
1079                                                          stepper);
1080         // Evaluate the material properties
1081         if (interaction.evaluateMaterialSlab(state, navigator, updateStage)) {
1082           // Surface has material at this stage
1083           hasMaterial = true;
1084 
1085           // Evaluate the material effects
1086           interaction.evaluatePointwiseMaterialInteraction(multipleScattering,
1087                                                            energyLoss);
1088 
1089           // Screen out material effects info
1090           ACTS_VERBOSE("Material effects on surface: "
1091                        << surface->geometryId()
1092                        << " at update stage: " << updateStage << " are :");
1093           ACTS_VERBOSE("eLoss = "
1094                        << interaction.Eloss * interaction.navDir << ", "
1095                        << "variancePhi = " << interaction.variancePhi << ", "
1096                        << "varianceTheta = " << interaction.varianceTheta
1097                        << ", "
1098                        << "varianceQoverP = " << interaction.varianceQoverP);
1099 
1100           // Update the state and stepper with material effects
1101           interaction.updateState(state, stepper, addNoise);
1102         }
1103       }
1104 
1105       if (!hasMaterial) {
1106         // Screen out message
1107         ACTS_VERBOSE("No material effects on surface: " << surface->geometryId()
1108                                                         << " at update stage: "
1109                                                         << updateStage);
1110       }
1111     }
1112 
1113     void storeLastActiveTip(result_type& result) const {
1114       const auto& [currentTip, tipState] = result.activeTips.back();
1115       // @TODO: Keep information on tip state around so we don't have to
1116       //        recalculate it later
1117       ACTS_VERBOSE("Find track with entry index = "
1118                    << currentTip << " and there are nMeasurements = "
1119                    << tipState.nMeasurements
1120                    << ", nOutliers = " << tipState.nOutliers
1121                    << ", nHoles = " << tipState.nHoles << " on track");
1122       result.lastTrackIndices.emplace_back(currentTip);
1123 
1124       std::optional<MultiTrajectoryTraits::IndexType>
1125           lastMeasurementIndexCandidate;
1126       result.fittedStates->visitBackwards(
1127           currentTip, [&](const auto& trackState) {
1128             bool isMeasurement =
1129                 trackState.typeFlags().test(TrackStateFlag::MeasurementFlag);
1130             if (isMeasurement) {
1131               lastMeasurementIndexCandidate = trackState.index();
1132               return false;
1133             }
1134             return true;
1135           });
1136       if (lastMeasurementIndexCandidate.has_value()) {
1137         ACTS_VERBOSE("Last measurement found on track with entry index = "
1138                      << currentTip << " and measurement index = "
1139                      << lastMeasurementIndexCandidate.value());
1140         result.lastMeasurementIndices.emplace_back(
1141             lastMeasurementIndexCandidate.value());
1142       } else {
1143         ACTS_VERBOSE(
1144             "No measurement found on track with entry index = " << currentTip);
1145       }
1146     }
1147 
1148     CombinatorialKalmanFilterExtensions<traj_t> m_extensions;
1149 
1150     /// The source link accessor
1151     source_link_accessor_t m_sourcelinkAccessor;
1152 
1153     using source_link_iterator_t =
1154         decltype(std::declval<decltype(m_sourcelinkAccessor(
1155                      *static_cast<const Surface*>(nullptr)))>()
1156                      .first);
1157 
1158     using TrackStateCandidateCreator =
1159         typename CombinatorialKalmanFilterOptions<
1160             source_link_iterator_t, traj_t>::TrackStateCandidateCreator;
1161 
1162     /// the stateCandidator to be used
1163     /// @note will be set to a default trackStateCandidateCreator or the one
1164     //        provided via the extension
1165     TrackStateCandidateCreator trackStateCandidateCreator;
1166 
1167     /// End of world aborter
1168     EndOfWorldReached endOfWorldReached;
1169 
1170     /// Actor logger instance
1171     const Logger* actorLogger{nullptr};
1172     /// Updater logger instance
1173     const Logger* updaterLogger{nullptr};
1174 
1175     const Logger& logger() const { return *actorLogger; }
1176   };
1177 
1178   template <typename source_link_accessor_t, typename parameters_t>
1179   class Aborter {
1180    public:
1181     /// Broadcast the action type
1182     using action_type = Actor<source_link_accessor_t, parameters_t>;
1183 
1184     template <typename propagator_state_t, typename stepper_t,
1185               typename navigator_t, typename result_t>
1186     bool operator()(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
1187                     const navigator_t& /*navigator*/, const result_t& result,
1188                     const Logger& /*logger*/) const {
1189       if (result.finished) {
1190         return true;
1191       }
1192       return false;
1193     }
1194   };
1195 
1196   /// Void path limit reached aborter to replace the default since the path
1197   /// limit is handled in the CKF actor internally.
1198   struct StubPathLimitReached {
1199     double internalLimit{};
1200 
1201     template <typename propagator_state_t, typename stepper_t,
1202               typename navigator_t>
1203     bool operator()(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
1204                     const navigator_t& /*navigator*/,
1205                     const Logger& /*logger*/) const {
1206       return false;
1207     }
1208   };
1209 
1210  public:
1211   /// Combinatorial Kalman Filter implementation, calls the Kalman filter
1212   ///
1213   /// @tparam source_link_iterator_t Type of the source link iterator
1214   /// @tparam start_parameters_container_t Type of the initial parameters
1215   ///                                      container
1216   /// @tparam calibrator_t Type of the source link calibrator
1217   /// @tparam measurement_selector_t Type of the measurement selector
1218   /// @tparam track_container_t Type of the track container backend
1219   /// @tparam holder_t Type defining track container backend ownership
1220   /// @tparam parameters_t Type of parameters used for local parameters
1221   ///
1222   /// @param initialParameters The initial track parameters
1223   /// @param tfOptions CombinatorialKalmanFilterOptions steering the track
1224   ///                  finding
1225   /// @param trackContainer Input track container to use
1226   /// @note The input measurements are given in the form of @c SourceLinks.
1227   ///       It's @c calibrator_t's job to turn them into calibrated measurements
1228   ///       used in the track finding.
1229   ///
1230   /// @return a container of track finding result for all the initial track
1231   /// parameters
1232   template <typename source_link_iterator_t, typename start_parameters_t,
1233             typename track_container_t, template <typename> class holder_t,
1234             typename parameters_t = BoundTrackParameters>
1235   auto findTracks(
1236       const start_parameters_t& initialParameters,
1237       const CombinatorialKalmanFilterOptions<source_link_iterator_t, traj_t>&
1238           tfOptions,
1239       TrackContainer<track_container_t, traj_t, holder_t>& trackContainer) const
1240       -> Result<std::vector<
1241           typename std::decay_t<decltype(trackContainer)>::TrackProxy>> {
1242     using TrackContainer = typename std::decay_t<decltype(trackContainer)>;
1243     using SourceLinkAccessor =
1244         SourceLinkAccessorDelegate<source_link_iterator_t>;
1245 
1246     // Create the ActionList and AbortList
1247     using CombinatorialKalmanFilterAborter =
1248         Aborter<SourceLinkAccessor, parameters_t>;
1249     using CombinatorialKalmanFilterActor =
1250         Actor<SourceLinkAccessor, parameters_t>;
1251     using Actors = ActionList<CombinatorialKalmanFilterActor>;
1252     using Aborters = AbortList<CombinatorialKalmanFilterAborter>;
1253 
1254     // Create relevant options for the propagation options
1255     PropagatorOptions<Actors, Aborters> propOptions(tfOptions.geoContext,
1256                                                     tfOptions.magFieldContext);
1257 
1258     // Set the trivial propagator options
1259     propOptions.setPlainOptions(tfOptions.propagatorPlainOptions);
1260 
1261     // Catch the actor
1262     auto& combKalmanActor =
1263         propOptions.actionList.template get<CombinatorialKalmanFilterActor>();
1264     combKalmanActor.targetReached.surface = tfOptions.targetSurface;
1265     combKalmanActor.multipleScattering = tfOptions.multipleScattering;
1266     combKalmanActor.energyLoss = tfOptions.energyLoss;
1267     combKalmanActor.actorLogger = m_actorLogger.get();
1268     combKalmanActor.updaterLogger = m_updaterLogger.get();
1269     combKalmanActor.calibrationContextPtr = &tfOptions.calibrationContext.get();
1270 
1271     // copy source link accessor, calibrator and measurement selector
1272     combKalmanActor.m_sourcelinkAccessor = tfOptions.sourcelinkAccessor;
1273     combKalmanActor.m_extensions = tfOptions.extensions;
1274     combKalmanActor.trackStateCandidateCreator =
1275         tfOptions.trackStateCandidateCreator;
1276     DefaultTrackStateCreator defaultTrackStateCreator;
1277     // connect a default state candidate creator if no state candidate creator
1278     // was provided via the extension
1279     if (!combKalmanActor.trackStateCandidateCreator.connected()) {
1280       defaultTrackStateCreator.calibrator = tfOptions.extensions.calibrator;
1281       defaultTrackStateCreator.measurementSelector =
1282           tfOptions.extensions.measurementSelector;
1283       combKalmanActor.trackStateCandidateCreator.template connect<
1284           &DefaultTrackStateCreator::template createSourceLinkTrackStates<
1285               source_link_iterator_t>>(&defaultTrackStateCreator);
1286     }
1287 
1288     auto propState =
1289         m_propagator.template makeState(initialParameters, propOptions);
1290 
1291     auto& r = propState.template get<CombinatorialKalmanFilterResult<traj_t>>();
1292     r.fittedStates = &trackContainer.trackStateContainer();
1293     r.stateBuffer = std::make_shared<traj_t>();
1294 
1295     auto propagationResult = m_propagator.propagate(propState);
1296 
1297     auto result = m_propagator.makeResult(
1298         std::move(propState), propagationResult, propOptions, false);
1299 
1300     if (!result.ok()) {
1301       ACTS_ERROR("Propagation failed: " << result.error() << " "
1302                                         << result.error().message()
1303                                         << " with the initial parameters: \n"
1304                                         << initialParameters.parameters());
1305       return result.error();
1306     }
1307 
1308     auto& propRes = *result;
1309 
1310     /// Get the result of the CombinatorialKalmanFilter
1311     auto combKalmanResult = std::move(
1312         propRes.template get<CombinatorialKalmanFilterResult<traj_t>>());
1313 
1314     /// The propagation could already reach max step size
1315     /// before the track finding is finished during two phases:
1316     // -> filtering for track finding;
1317     // -> surface targeting to get fitted parameters at target surface.
1318     // This is regarded as a failure.
1319     // @TODO: Implement distinguishment between the above two cases if
1320     // necessary
1321     if (combKalmanResult.lastError.ok() && !combKalmanResult.finished) {
1322       combKalmanResult.lastError = Result<void>(
1323           CombinatorialKalmanFilterError::PropagationReachesMaxSteps);
1324     }
1325 
1326     if (!combKalmanResult.lastError.ok()) {
1327       ACTS_ERROR("CombinatorialKalmanFilter failed: "
1328                  << combKalmanResult.lastError.error() << " "
1329                  << combKalmanResult.lastError.error().message()
1330                  << " with the initial parameters: \n"
1331                  << initialParameters.parameters());
1332     }
1333 
1334     std::vector<typename TrackContainer::TrackProxy> tracks;
1335     tracks.reserve(combKalmanResult.lastMeasurementIndices.size());
1336 
1337     for (auto tip : combKalmanResult.lastMeasurementIndices) {
1338       auto track = trackContainer.makeTrack();
1339       track.tipIndex() = tip;
1340 
1341       // Set fitted track parameters if available. This will only be the case if
1342       // a target surface is set. Without a target surface there cannot be
1343       // fitted parameters and the user will have to extrapolate the track to a
1344       // target surface themselves.
1345       if (auto it = combKalmanResult.fittedParameters.find(tip);
1346           it != combKalmanResult.fittedParameters.end()) {
1347         const BoundTrackParameters& parameters = it->second;
1348         track.parameters() = parameters.parameters();
1349         track.covariance() = *parameters.covariance();
1350         track.setReferenceSurface(parameters.referenceSurface().getSharedPtr());
1351       }
1352 
1353       calculateTrackQuantities(track);
1354 
1355       tracks.push_back(std::move(track));
1356     }
1357 
1358     return tracks;
1359   }
1360 };
1361 
1362 }  // namespace Acts