Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-27 07:23:36

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/Common.hpp"
0012 #include "Acts/EventData/MultiTrajectory.hpp"
0013 #include "Acts/EventData/MultiTrajectoryHelpers.hpp"
0014 #include "Acts/EventData/SourceLink.hpp"
0015 #include "Acts/EventData/VectorMultiTrajectory.hpp"
0016 #include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
0017 #include "Acts/Geometry/GeometryContext.hpp"
0018 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0019 #include "Acts/Propagator/ActorList.hpp"
0020 #include "Acts/Propagator/DirectNavigator.hpp"
0021 #include "Acts/Propagator/PropagatorOptions.hpp"
0022 #include "Acts/Propagator/StandardAborters.hpp"
0023 #include "Acts/Propagator/detail/LoopProtection.hpp"
0024 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0025 #include "Acts/TrackFitting/KalmanFitterError.hpp"
0026 #include "Acts/TrackFitting/detail/VoidFitterComponents.hpp"
0027 #include "Acts/Utilities/CalibrationContext.hpp"
0028 #include "Acts/Utilities/Delegate.hpp"
0029 #include "Acts/Utilities/Logger.hpp"
0030 #include "Acts/Utilities/Result.hpp"
0031 #include "Acts/Utilities/TrackHelpers.hpp"
0032 
0033 #include <memory>
0034 #include <unordered_map>
0035 #include <vector>
0036 
0037 namespace Acts {
0038 
0039 /// @addtogroup track_fitting
0040 /// @{
0041 
0042 /// Extension struct which holds delegates to customise the KF behavior
0043 template <typename traj_t>
0044 struct KalmanFitterExtensions {
0045   /// Type alias for track state proxy from trajectory
0046   using TrackStateProxy = typename traj_t::TrackStateProxy;
0047   /// Type alias for const track state proxy from trajectory
0048   using ConstTrackStateProxy = typename traj_t::ConstTrackStateProxy;
0049   /// Type alias for track parameters from track state proxy
0050   using Parameters = typename TrackStateProxy::Parameters;
0051 
0052   /// Type alias for measurement calibrator delegate
0053   using Calibrator =
0054       Delegate<void(const GeometryContext&, const CalibrationContext&,
0055                     const SourceLink&, TrackStateProxy)>;
0056 
0057   /// Type alias for Kalman filter update delegate
0058   using Updater = Delegate<Result<void>(const GeometryContext&, TrackStateProxy,
0059                                         const Logger&)>;
0060 
0061   /// Type alias for outlier detection delegate
0062   using OutlierFinder = Delegate<bool(ConstTrackStateProxy)>;
0063 
0064   /// Type alias for reverse filtering decision delegate
0065   using ReverseFilteringLogic = Delegate<bool(ConstTrackStateProxy)>;
0066 
0067   /// Type alias for track smoothing delegate
0068   using Smoother = Delegate<Result<void>(const GeometryContext&, traj_t&,
0069                                          std::size_t, const Logger&)>;
0070 
0071   /// Retrieves the associated surface from a source link
0072   SourceLinkSurfaceAccessor surfaceAccessor;
0073 
0074   /// The Calibrator is a dedicated calibration algorithm that allows
0075   /// to calibrate measurements using track information, this could be
0076   /// e.g. sagging for wires, module deformations, etc.
0077   Calibrator calibrator;
0078 
0079   /// The updater incorporates measurement information into the track parameters
0080   Updater updater;
0081 
0082   /// Determines whether a measurement is supposed to be considered as an
0083   /// outlier
0084   OutlierFinder outlierFinder;
0085 
0086   /// Decides whether the smoothing stage uses linearized transport or full
0087   /// reverse propagation
0088   ReverseFilteringLogic reverseFilteringLogic;
0089 
0090   /// The smoother back-propagates measurement information along the track
0091   Smoother smoother;
0092 
0093   /// Default constructor which connects the default void components
0094   KalmanFitterExtensions() {
0095     surfaceAccessor.connect<&detail::voidSurfaceAccessor>();
0096     calibrator.template connect<&detail::voidFitterCalibrator<traj_t>>();
0097     updater.template connect<&detail::voidFitterUpdater<traj_t>>();
0098     outlierFinder.template connect<&detail::voidOutlierFinder<traj_t>>();
0099     reverseFilteringLogic
0100         .template connect<&detail::voidReverseFilteringLogic<traj_t>>();
0101     smoother.template connect<&detail::voidFitterSmoother<traj_t>>();
0102   }
0103 };
0104 
0105 /// Combined options for the Kalman fitter.
0106 ///
0107 /// @tparam traj_t The trajectory type
0108 template <typename traj_t>
0109 struct KalmanFitterOptions {
0110   /// PropagatorOptions with context.
0111   ///
0112   /// @param gctx The geometry context for this fit
0113   /// @param mctx The magnetic context for this fit
0114   /// @param cctx The calibration context for this fit
0115   /// @param extensions_ The KF extensions
0116   /// @param pOptions The plain propagator options
0117   /// @param tSurface The target surface for the fit
0118   /// @param mScattering Whether to include multiple scattering
0119   /// @param eLoss Whether to include energy loss
0120   /// @param rFiltering Whether to run reversed filtering
0121   /// @param rfScaling Scaling factor for covariance at input of reversed filtering
0122   /// @param freeToBoundCorrection_ Correction for non-linearity effect during transform from free to bound
0123   KalmanFitterOptions(const GeometryContext& gctx,
0124                       const MagneticFieldContext& mctx,
0125                       std::reference_wrapper<const CalibrationContext> cctx,
0126                       KalmanFitterExtensions<traj_t> extensions_,
0127                       const PropagatorPlainOptions& pOptions,
0128                       const Surface* tSurface = nullptr,
0129                       bool mScattering = true, bool eLoss = true,
0130                       bool rFiltering = false, double rfScaling = 1.0,
0131                       const FreeToBoundCorrection& freeToBoundCorrection_ =
0132                           FreeToBoundCorrection(false))
0133       : geoContext(gctx),
0134         magFieldContext(mctx),
0135         calibrationContext(cctx),
0136         extensions(extensions_),
0137         propagatorPlainOptions(pOptions),
0138         referenceSurface(tSurface),
0139         multipleScattering(mScattering),
0140         energyLoss(eLoss),
0141         reverseFiltering(rFiltering),
0142         reverseFilteringCovarianceScaling(rfScaling),
0143         freeToBoundCorrection(freeToBoundCorrection_) {}
0144 
0145   /// Context object for the geometry
0146   std::reference_wrapper<const GeometryContext> geoContext;
0147   /// Context object for the magnetic field
0148   std::reference_wrapper<const MagneticFieldContext> magFieldContext;
0149   /// context object for the calibration
0150   std::reference_wrapper<const CalibrationContext> calibrationContext;
0151 
0152   /// Extensions for calibration and outlier finding
0153   KalmanFitterExtensions<traj_t> extensions;
0154 
0155   /// The trivial propagator options
0156   PropagatorPlainOptions propagatorPlainOptions;
0157 
0158   /// The reference surface
0159   const Surface* referenceSurface = nullptr;
0160 
0161   /// Strategy to propagate to reference surface
0162   TrackExtrapolationStrategy referenceSurfaceStrategy =
0163       TrackExtrapolationStrategy::firstOrLast;
0164 
0165   /// Whether to consider multiple scattering
0166   bool multipleScattering = true;
0167 
0168   /// Whether to consider energy loss
0169   bool energyLoss = true;
0170 
0171   /// Whether to run filtering in reversed direction overwrite the
0172   /// ReverseFilteringLogic
0173   bool reverseFiltering = false;
0174 
0175   /// Factor by which the covariance of the input of the reversed filtering is
0176   /// scaled. This is only used in the backward filtering (if reverseFiltering
0177   /// is true or if the ReverseFilteringLogic return true for the track of
0178   /// interest).
0179   /// Note that the default value is not tuned and might need adjustment for
0180   /// different use cases.
0181   double reverseFilteringCovarianceScaling = 100.0;
0182 
0183   /// Whether to include non-linear correction during global to local
0184   /// transformation
0185   FreeToBoundCorrection freeToBoundCorrection;
0186 };
0187 
0188 /// Result payload returned by the Kalman fitter.
0189 template <typename traj_t>
0190 struct KalmanFitterResult {
0191   /// Fitted states that the actor has handled.
0192   traj_t* fittedStates{nullptr};
0193 
0194   /// This is the index of the 'tip' of the track stored in multitrajectory.
0195   /// This corresponds to the last measurement state in the multitrajectory.
0196   /// Since this KF only stores one trajectory, it is unambiguous.
0197   /// TrackTraits::kInvalid is the start of a trajectory.
0198   std::size_t lastMeasurementIndex = kTrackIndexInvalid;
0199 
0200   /// This is the index of the 'tip' of the states stored in multitrajectory.
0201   /// This corresponds to the last state in the multitrajectory.
0202   /// Since this KF only stores one trajectory, it is unambiguous.
0203   /// TrackTraits::kInvalid is the start of a trajectory.
0204   std::size_t lastTrackIndex = kTrackIndexInvalid;
0205 
0206   /// The optional Parameters at the provided surface
0207   std::optional<BoundTrackParameters> fittedParameters;
0208 
0209   /// Counter for states with non-outlier measurements
0210   std::size_t measurementStates = 0;
0211 
0212   /// Counter for measurements holes
0213   /// A hole correspond to a surface with an associated detector element with no
0214   /// associated measurement. Holes are only taken into account if they are
0215   /// between the first and last measurements.
0216   std::size_t measurementHoles = 0;
0217 
0218   /// Counter for handled states
0219   std::size_t processedStates = 0;
0220 
0221   /// Indicator if track fitting has been done
0222   bool finished = false;
0223 
0224   /// Measurement surfaces without hits
0225   std::vector<const Surface*> missedActiveSurfaces;
0226 
0227   /// Path limit aborter
0228   PathLimitReached pathLimitReached;
0229 };
0230 
0231 /// Kalman fitter implementation.
0232 ///
0233 /// @tparam propagator_t Type of the propagation class, usually an instance of
0234 ///         @ref Acts::Propagator
0235 ///
0236 /// The Kalman filter contains an Actor and a Sequencer sub-class.
0237 /// The Sequencer has to be part of the Navigator of the Propagator
0238 /// in order to initialize and provide the measurement surfaces.
0239 ///
0240 /// The Actor is part of the Propagation call and does the Kalman update
0241 /// and eventually the smoothing.  Updater, Smoother and Calibrator are
0242 /// given to the Actor for further use:
0243 /// - The Updater is the implemented kalman updater formalism, it
0244 ///   runs via a visitor pattern through the measurements.
0245 /// - The Smoother is called at the end of the filtering by the Actor.
0246 ///
0247 /// Measurements are not required to be ordered for the KalmanFilter,
0248 /// measurement ordering needs to be figured out by the navigation of
0249 /// the propagator.
0250 ///
0251 /// The void components are provided mainly for unit testing.
0252 template <typename propagator_t, typename traj_t>
0253 class KalmanFitter {
0254   /// The navigator type
0255   using KalmanNavigator = typename propagator_t::Navigator;
0256 
0257   /// The navigator has DirectNavigator type or not
0258   static constexpr bool isDirectNavigator =
0259       std::is_same_v<KalmanNavigator, DirectNavigator>;
0260 
0261  public:
0262   /// Constructor with propagator and logger
0263   /// @param pPropagator Propagator instance for track propagation
0264   /// @param _logger Logger for diagnostic output
0265   explicit KalmanFitter(propagator_t pPropagator,
0266                         std::unique_ptr<const Logger> _logger =
0267                             getDefaultLogger("KalmanFitter", Logging::INFO))
0268       : m_propagator(std::move(pPropagator)),
0269         m_logger{std::move(_logger)},
0270         m_actorLogger{m_logger->cloneWithSuffix("Actor")} {}
0271 
0272  private:
0273   /// The propagator for the transport and material update
0274   propagator_t m_propagator;
0275 
0276   /// The logger instance
0277   std::unique_ptr<const Logger> m_logger;
0278   std::unique_ptr<const Logger> m_actorLogger;
0279 
0280   const Logger& logger() const { return *m_logger; }
0281 
0282   /// @brief Propagator Actor plugin for the KalmanFilter
0283   ///
0284   /// The KalmanActor does not rely on the measurements to be
0285   /// sorted along the track.
0286   class Actor {
0287    public:
0288     /// Broadcast the result_type
0289     using result_type = KalmanFitterResult<traj_t>;
0290 
0291     /// The target surface aborter
0292     SurfaceReached targetReached{std::numeric_limits<double>::lowest()};
0293 
0294     /// Allows retrieving measurements for a surface
0295     std::unordered_map<const Surface*, SourceLink> inputMeasurements;
0296 
0297     /// Whether to consider multiple scattering.
0298     bool multipleScattering = true;
0299 
0300     /// Whether to consider energy loss.
0301     bool energyLoss = true;
0302 
0303     /// Whether to include non-linear correction during global to local
0304     /// transformation
0305     FreeToBoundCorrection freeToBoundCorrection;
0306 
0307     /// Input MultiTrajectory
0308     std::shared_ptr<traj_t> outputStates;
0309 
0310     KalmanFitterExtensions<traj_t> extensions;
0311 
0312     /// Calibration context for the fit
0313     const CalibrationContext* calibrationContext{nullptr};
0314 
0315     /// End of world aborter
0316     EndOfWorldReached endOfWorldReached;
0317 
0318     /// Volume constraint aborter
0319     VolumeConstraintAborter volumeConstraintAborter;
0320 
0321     /// The logger instance
0322     const Logger* actorLogger{nullptr};
0323 
0324     /// Logger helper
0325     const Logger& logger() const { return *actorLogger; }
0326 
0327     /// @brief Kalman actor operation
0328     ///
0329     /// @tparam propagator_state_t is the type of Propagator state
0330     /// @tparam stepper_t Type of the stepper
0331     /// @tparam navigator_t Type of the navigator
0332     ///
0333     /// @param state is the mutable propagator state object
0334     /// @param stepper The stepper in use
0335     /// @param navigator The navigator in use
0336     /// @param result is the mutable result state object
0337     template <typename propagator_state_t, typename stepper_t,
0338               typename navigator_t>
0339     Result<void> act(propagator_state_t& state, const stepper_t& stepper,
0340                      const navigator_t& navigator, result_type& result,
0341                      const Logger& /*logger*/) const {
0342       assert(result.fittedStates && "No MultiTrajectory set");
0343 
0344       if (result.finished) {
0345         return Result<void>::success();
0346       }
0347 
0348       ACTS_VERBOSE("KalmanFitter step at pos: "
0349                    << stepper.position(state.stepping).transpose()
0350                    << " dir: " << stepper.direction(state.stepping).transpose()
0351                    << " momentum: "
0352                    << stepper.absoluteMomentum(state.stepping));
0353 
0354       // Initialize path limit reached aborter
0355       if (result.pathLimitReached.internalLimit ==
0356           std::numeric_limits<double>::max()) {
0357         detail::setupLoopProtection(state, stepper, result.pathLimitReached,
0358                                     true, logger());
0359       }
0360 
0361       // Update:
0362       // - Waiting for a current surface
0363       const Surface* surface = navigator.currentSurface(state.navigation);
0364       if (surface != nullptr) {
0365         // Check if the surface is in the measurement map
0366         // -> Get the measurement / calibrate
0367         // -> Create the predicted state
0368         // -> Check outlier behavior, if non-outlier:
0369         // -> Perform the kalman update
0370         // -> Fill track state information & update stepper information
0371 
0372         ACTS_VERBOSE("Perform " << state.options.direction << " filter step");
0373         auto res = filter(*surface, state, stepper, navigator, result);
0374         if (!res.ok()) {
0375           ACTS_DEBUG("Error in " << state.options.direction
0376                                  << " filter: " << res.error());
0377           return res.error();
0378         }
0379       }
0380 
0381       // Finalization:
0382       // when all track states have been handled or an aborter is triggered
0383       const bool isTrackComplete =
0384           result.measurementStates == inputMeasurements.size();
0385       const bool isEndOfWorldReached =
0386           endOfWorldReached.checkAbort(state, stepper, navigator, logger());
0387       const bool isVolumeConstraintReached = volumeConstraintAborter.checkAbort(
0388           state, stepper, navigator, logger());
0389       const bool isPathLimitReached = result.pathLimitReached.checkAbort(
0390           state, stepper, navigator, logger());
0391       const bool isTargetReached =
0392           targetReached.checkAbort(state, stepper, navigator, logger());
0393       if (isTrackComplete || isEndOfWorldReached || isVolumeConstraintReached ||
0394           isPathLimitReached || isTargetReached) {
0395         ACTS_VERBOSE(
0396             "Finalizing Kalman fit: "
0397             << (isTrackComplete ? "track complete; " : "")
0398             << (isEndOfWorldReached ? "end of world reached; " : "")
0399             << (isVolumeConstraintReached ? "volume constraint reached; " : "")
0400             << (isPathLimitReached ? "path limit reached; " : "")
0401             << (isTargetReached ? "target surface reached; " : ""));
0402 
0403         if (isTargetReached) {
0404           ACTS_VERBOSE("Setting fitted parameters at target surface");
0405 
0406           // Bind the parameter to the target surface
0407           auto res = stepper.boundState(state.stepping, *targetReached.surface);
0408           if (!res.ok()) {
0409             ACTS_DEBUG("Error while acquiring bound state for target surface: "
0410                        << res.error() << " " << res.error().message());
0411             return res.error();
0412           } else {
0413             const auto& [boundParams, jacobian, pathLength] = *res;
0414             result.fittedParameters = boundParams;
0415           }
0416         }
0417 
0418         result.finished = true;
0419       }
0420 
0421       return Result<void>::success();
0422     }
0423 
0424     template <typename propagator_state_t, typename stepper_t,
0425               typename navigator_t>
0426     bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0427                     const navigator_t& /*navigator*/, const result_type& result,
0428                     const Logger& /*logger*/) const {
0429       return result.finished;
0430     }
0431 
0432     /// @brief Kalman actor operation: update
0433     ///
0434     /// @tparam propagator_state_t is the type of Propagator state
0435     /// @tparam stepper_t Type of the stepper
0436     /// @tparam navigator_t Type of the navigator
0437     ///
0438     /// @param surface The surface where the update happens
0439     /// @param state The mutable propagator state object
0440     /// @param stepper The stepper in use
0441     /// @param navigator The navigator in use
0442     /// @param result The mutable result state object
0443     template <typename propagator_state_t, typename stepper_t,
0444               typename navigator_t>
0445     Result<void> filter(const Surface& surface, propagator_state_t& state,
0446                         const stepper_t& stepper, const navigator_t& navigator,
0447                         result_type& result) const {
0448       const bool precedingMeasurementExists = result.measurementStates > 0;
0449       const bool surfaceIsSensitive = surface.isSensitive();
0450       const bool surfaceHasMaterial = surface.hasMaterial();
0451 
0452       // Try to find the surface in the measurement surfaces
0453       const auto sourceLinkIt = inputMeasurements.find(&surface);
0454       if (sourceLinkIt != inputMeasurements.end()) {
0455         // Screen output message
0456         ACTS_VERBOSE("Measurement surface " << surface.geometryId()
0457                                             << " detected.");
0458         // Transport the covariance to the surface
0459         stepper.transportCovarianceToBound(state.stepping, surface,
0460                                            freeToBoundCorrection);
0461 
0462         // Update state and stepper with pre material effects
0463         detail::performMaterialInteraction(
0464             state, stepper, surface,
0465             detail::determineMaterialUpdateMode(state, navigator,
0466                                                 MaterialUpdateMode::PreUpdate),
0467             NoiseUpdateMode::addNoise, multipleScattering, energyLoss,
0468             logger());
0469 
0470         // Create a track state with the desired components
0471         TrackStatePropMask mask =
0472             TrackStatePropMask::Predicted | TrackStatePropMask::Filtered |
0473             TrackStatePropMask::Jacobian | TrackStatePropMask::Calibrated;
0474         typename traj_t::TrackStateProxy trackStateProxy =
0475             result.fittedStates->makeTrackState(mask, result.lastTrackIndex);
0476 
0477         typename traj_t::ConstTrackStateProxy trackStateProxyConst{
0478             trackStateProxy};
0479 
0480         // Set the trackStateProxy components with the state from the ongoing
0481         // propagation
0482         trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0483         // Bind the transported state to the current surface
0484         auto res = stepper.boundState(state.stepping, surface, false,
0485                                       freeToBoundCorrection);
0486         if (!res.ok()) {
0487           ACTS_DEBUG("Propagate to surface " << surface.geometryId()
0488                                              << " failed: " << res.error());
0489           return res.error();
0490         }
0491         const auto& [boundParams, jacobian, pathLength] = *res;
0492 
0493         // Fill the track state
0494         trackStateProxy.predicted() = boundParams.parameters();
0495         trackStateProxy.predictedCovariance() = state.stepping.cov;
0496 
0497         trackStateProxy.jacobian() = jacobian;
0498         trackStateProxy.pathLength() = pathLength;
0499 
0500         // We have predicted parameters, so calibrate the uncalibrated input
0501         // measurement
0502         extensions.calibrator(state.geoContext, *calibrationContext,
0503                               sourceLinkIt->second, trackStateProxy);
0504 
0505         // Get and set the type flags
0506         auto typeFlags = trackStateProxy.typeFlags();
0507         typeFlags.setHasParameters();
0508         if (surface.hasMaterial()) {
0509           typeFlags.setHasMaterial();
0510         }
0511 
0512         // Check if the state is an outlier.
0513         // If not:
0514         // - run Kalman update
0515         // - tag it as a measurement
0516         // - update the stepping state.
0517         // Else, just tag it as an outlier
0518         if (!extensions.outlierFinder(trackStateProxyConst)) {
0519           // Run Kalman update
0520           auto updateRes =
0521               extensions.updater(state.geoContext, trackStateProxy, logger());
0522           if (!updateRes.ok()) {
0523             ACTS_DEBUG("Update step failed: " << updateRes.error());
0524             return updateRes.error();
0525           }
0526           // Set the measurement type flag
0527           typeFlags.setIsMeasurement();
0528         } else {
0529           ACTS_VERBOSE(
0530               "Filtering step successful. But measurement is determined "
0531               "to be an outlier. Stepping state is not updated.");
0532           // Set the outlier type flag
0533           typeFlags.setIsOutlier();
0534           trackStateProxy.shareFrom(trackStateProxy,
0535                                     TrackStatePropMask::Predicted,
0536                                     TrackStatePropMask::Filtered);
0537         }
0538 
0539         result.lastTrackIndex = trackStateProxy.index();
0540 
0541         // Update the stepper if it is not an outlier
0542         if (trackStateProxy.typeFlags().isMeasurement()) {
0543           // Update the stepping state with filtered parameters
0544           ACTS_VERBOSE("Filtering step successful, updated parameters are:\n"
0545                        << trackStateProxy.filtered().transpose());
0546           // update stepping state using filtered parameters after kalman
0547           stepper.update(state.stepping,
0548                          MultiTrajectoryHelpers::freeFiltered(
0549                              state.options.geoContext, trackStateProxy),
0550                          trackStateProxy.filtered(),
0551                          trackStateProxy.filteredCovariance(), surface);
0552           // We count the state with measurement
0553           ++result.measurementStates;
0554         }
0555 
0556         // Update state and stepper with post material effects
0557         detail::performMaterialInteraction(
0558             state, stepper, surface,
0559             detail::determineMaterialUpdateMode(state, navigator,
0560                                                 MaterialUpdateMode::PostUpdate),
0561             NoiseUpdateMode::addNoise, multipleScattering, energyLoss,
0562             logger());
0563         // We count the processed state
0564         ++result.processedStates;
0565         // Update the number of holes count only when encountering a
0566         // measurement
0567         result.measurementHoles = result.missedActiveSurfaces.size();
0568         // Since we encountered a measurement update the lastMeasurementIndex to
0569         // the lastTrackIndex.
0570         result.lastMeasurementIndex = result.lastTrackIndex;
0571 
0572       } else if ((precedingMeasurementExists && surfaceIsSensitive) ||
0573                  surfaceHasMaterial) {
0574         // We only create track states here if there is already measurement
0575         // detected or if the surface has material (no holes before the first
0576         // measurement)
0577 
0578         // Create a track state with the desired components
0579         TrackStatePropMask mask =
0580             TrackStatePropMask::Predicted | TrackStatePropMask::Jacobian;
0581         typename traj_t::TrackStateProxy trackStateProxy =
0582             result.fittedStates->makeTrackState(mask, result.lastTrackIndex);
0583 
0584         // Set the trackStateProxy components with the state from the ongoing
0585         // propagation
0586         trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0587         // Bind the transported state to the current surface
0588         auto res = stepper.boundState(state.stepping, surface, true,
0589                                       freeToBoundCorrection);
0590         if (!res.ok()) {
0591           return res.error();
0592         }
0593         const auto& [boundParams, jacobian, pathLength] = *res;
0594 
0595         // Fill the track state
0596         trackStateProxy.predicted() = boundParams.parameters();
0597         trackStateProxy.predictedCovariance() = state.stepping.cov;
0598 
0599         trackStateProxy.jacobian() = jacobian;
0600         trackStateProxy.pathLength() = pathLength;
0601 
0602         // Set the filtered parameter index to be the same with predicted
0603         // parameter
0604         trackStateProxy.shareFrom(trackStateProxy,
0605                                   TrackStatePropMask::Predicted,
0606                                   TrackStatePropMask::Filtered);
0607 
0608         // Set the track state flags
0609         auto typeFlags = trackStateProxy.typeFlags();
0610         typeFlags.setHasParameters();
0611 
0612         if (surfaceHasMaterial) {
0613           typeFlags.setHasMaterial();
0614         }
0615 
0616         if (surfaceIsSensitive && precedingMeasurementExists) {
0617           ACTS_VERBOSE("Detected hole on " << surface.geometryId());
0618           // If the surface is sensitive, set the hole type flag
0619           typeFlags.setIsHole();
0620         } else if (surfaceIsSensitive) {
0621           ACTS_VERBOSE("Skip hole (no preceding measurements) on surface "
0622                        << surface.geometryId());
0623         } else if (surfaceHasMaterial) {
0624           ACTS_VERBOSE("Detected in-sensitive surface "
0625                        << surface.geometryId());
0626         }
0627 
0628         result.lastTrackIndex = trackStateProxy.index();
0629 
0630         if (trackStateProxy.typeFlags().isHole()) {
0631           // Count the missed surface
0632           result.missedActiveSurfaces.push_back(&surface);
0633         }
0634 
0635         ++result.processedStates;
0636 
0637         // Update state and stepper with (possible) material effects
0638         detail::performMaterialInteraction(
0639             state, stepper, surface,
0640             detail::determineMaterialUpdateMode(state, navigator,
0641                                                 MaterialUpdateMode::FullUpdate),
0642             NoiseUpdateMode::addNoise, multipleScattering, energyLoss,
0643             logger());
0644       }
0645 
0646       return Result<void>::success();
0647     }
0648   };
0649 
0650  public:
0651   /// Fit implementation of the forward filter, calls the
0652   /// the filter and smoother/reversed filter
0653   ///
0654   /// @tparam source_link_iterator_t Iterator type used to pass source links
0655   /// @tparam track_container_t Type of the track container
0656   ///
0657   /// @param it Begin iterator for the fittable uncalibrated measurements
0658   /// @param end End iterator for the fittable uncalibrated measurements
0659   /// @param sParameters The initial track parameters
0660   /// @param kfOptions KalmanOptions steering the fit
0661   /// @param trackContainer Input track container storage to append into
0662   /// @note The input measurements are given in the form of @c SourceLink s.
0663   /// It's the calibrators job to turn them into calibrated measurements used in
0664   /// the fit.
0665   ///
0666   /// @return the output as an output track
0667   template <typename source_link_iterator_t,
0668             TrackContainerFrontend track_container_t>
0669   Result<typename track_container_t::TrackProxy> fit(
0670       source_link_iterator_t it, source_link_iterator_t end,
0671       const BoundTrackParameters& sParameters,
0672       const KalmanFitterOptions<traj_t>& kfOptions,
0673       track_container_t& trackContainer) const {
0674     return fit_impl(it, end, sParameters, kfOptions, nullptr, trackContainer);
0675   }
0676 
0677   /// Fit implementation of the forward filter, calls the
0678   /// the filter and smoother/reversed filter
0679   ///
0680   /// @tparam source_link_iterator_t Iterator type used to pass source links
0681   /// @tparam track_container_t Type of the track container
0682   ///
0683   /// @param it Begin iterator for the fittable uncalibrated measurements
0684   /// @param end End iterator for the fittable uncalibrated measurements
0685   /// @param sParameters The initial track parameters
0686   /// @param kfOptions KalmanOptions steering the fit
0687   /// @param sSequence surface sequence used to initialize a DirectNavigator
0688   /// @param trackContainer Input track container storage to append into
0689   /// @note The input measurements are given in the form of @c SourceLinks.
0690   /// It's
0691   /// @c calibrator_t's job to turn them into calibrated measurements used in
0692   /// the fit.
0693   ///
0694   /// @return the output as an output track
0695   template <typename source_link_iterator_t,
0696             TrackContainerFrontend track_container_t>
0697   Result<typename track_container_t::TrackProxy> fit(
0698       source_link_iterator_t it, source_link_iterator_t end,
0699       const BoundTrackParameters& sParameters,
0700       const KalmanFitterOptions<traj_t>& kfOptions,
0701       const std::vector<const Surface*>& sSequence,
0702       track_container_t& trackContainer) const
0703     requires(isDirectNavigator)
0704   {
0705     return fit_impl(it, end, sParameters, kfOptions, &sSequence,
0706                     trackContainer);
0707   }
0708 
0709  private:
0710   template <typename source_link_iterator_t>
0711   auto make_propagator_options(source_link_iterator_t it,
0712                                source_link_iterator_t end,
0713                                const KalmanFitterOptions<traj_t>& kfOptions,
0714                                const std::vector<const Surface*>* sSequence,
0715                                const Surface* targetSurface,
0716                                bool reverseDirection) const {
0717     using KalmanActor = Actor;
0718     using Actors = ActorList<KalmanActor>;
0719     using PropagatorOptions = typename propagator_t::template Options<Actors>;
0720 
0721     const std::size_t nMeasurements = std::distance(it, end);
0722 
0723     // To be able to find measurements later, we put them into a map
0724     // We need to copy input SourceLinks anyway, so the map can own them.
0725     ACTS_VERBOSE("Preparing " << nMeasurements << " input measurements");
0726     std::unordered_map<const Surface*, SourceLink> inputMeasurements;
0727     for (; it != end; ++it) {
0728       SourceLink sl = *it;
0729       const Surface* surface = kfOptions.extensions.surfaceAccessor(sl);
0730       inputMeasurements.try_emplace(surface, std::move(sl));
0731     }
0732 
0733     // Create relevant options for the propagation options
0734     PropagatorOptions propagatorOptions(kfOptions.geoContext,
0735                                         kfOptions.magFieldContext);
0736 
0737     // Set the trivial propagator options
0738     propagatorOptions.setPlainOptions(kfOptions.propagatorPlainOptions);
0739 
0740     if (reverseDirection) {
0741       propagatorOptions.direction = propagatorOptions.direction.invert();
0742     }
0743 
0744     if constexpr (!isDirectNavigator) {
0745       // Add the measurement surface as external surface to navigator.
0746       // We will try to hit those surface by ignoring boundary checks.
0747       for (const auto& [surface, _] : inputMeasurements) {
0748         propagatorOptions.navigation.appendExternalSurface(*surface);
0749       }
0750     } else {
0751       assert(sSequence != nullptr &&
0752              "DirectNavigator requires a surface sequence for KalmanFitter");
0753       // Set the surface sequence
0754       propagatorOptions.navigation.externalSurfaces = *sSequence;
0755     }
0756 
0757     // Catch the actor and set the measurements
0758     auto& kalmanActor = propagatorOptions.actorList.template get<KalmanActor>();
0759     kalmanActor.inputMeasurements = std::move(inputMeasurements);
0760     kalmanActor.targetReached.surface = targetSurface;
0761     kalmanActor.multipleScattering = kfOptions.multipleScattering;
0762     kalmanActor.energyLoss = kfOptions.energyLoss;
0763     kalmanActor.freeToBoundCorrection = kfOptions.freeToBoundCorrection;
0764     kalmanActor.calibrationContext = &kfOptions.calibrationContext.get();
0765     kalmanActor.extensions = kfOptions.extensions;
0766     kalmanActor.actorLogger = m_actorLogger.get();
0767 
0768     return propagatorOptions;
0769   }
0770 
0771   template <typename propagator_options_t,
0772             TrackContainerFrontend track_container_t>
0773   auto filter_impl(const BoundTrackParameters& sParameters,
0774                    const propagator_options_t& propagatorOptions,
0775                    track_container_t& trackContainer) const
0776       -> Result<typename track_container_t::TrackProxy> {
0777     auto propagatorState = m_propagator.makeState(propagatorOptions);
0778 
0779     auto propagatorInitResult =
0780         m_propagator.initialize(propagatorState, sParameters);
0781     if (!propagatorInitResult.ok()) {
0782       ACTS_DEBUG("Propagation initialization failed: "
0783                  << propagatorInitResult.error());
0784       return propagatorInitResult.error();
0785     }
0786 
0787     auto& kalmanResult =
0788         propagatorState.template get<KalmanFitterResult<traj_t>>();
0789     kalmanResult.fittedStates = &trackContainer.trackStateContainer();
0790 
0791     // Run the fitter
0792     auto result = m_propagator.propagate(propagatorState);
0793 
0794     if (!result.ok()) {
0795       ACTS_DEBUG("Propagation failed: " << result.error());
0796       return result.error();
0797     }
0798 
0799     /// It could happen that the fit ends in zero measurement states.
0800     /// The result gets meaningless so such case is regarded as fit failure.
0801     if (!kalmanResult.measurementStates) {
0802       ACTS_DEBUG("KalmanFilter failed: No measurement states found");
0803       return KalmanFitterError::NoMeasurementFound;
0804     }
0805 
0806     auto track = trackContainer.makeTrack();
0807     track.tipIndex() = kalmanResult.lastMeasurementIndex;
0808     if (kalmanResult.fittedParameters) {
0809       const auto& params = kalmanResult.fittedParameters.value();
0810       track.parameters() = params.parameters();
0811       track.covariance() = params.covariance().value();
0812       track.setReferenceSurface(params.referenceSurface().getSharedPtr());
0813     }
0814 
0815     calculateTrackQuantities(track);
0816 
0817     return track;
0818   }
0819 
0820   /// Common fit implementation
0821   ///
0822   /// @tparam source_link_iterator_t Iterator type used to pass source links
0823   /// @tparam track_container_t Type of the track container
0824   ///
0825   /// @param it Begin iterator for the fittable uncalibrated measurements
0826   /// @param end End iterator for the fittable uncalibrated measurements
0827   /// @param sParameters The initial track parameters
0828   /// @param kfOptions KalmanOptions steering the fit
0829   /// @param sSequence surface sequence used to initialize a DirectNavigator
0830   /// @param trackContainer Input track container storage to append into
0831   ///
0832   /// @return the output as an output track
0833   template <typename source_link_iterator_t,
0834             TrackContainerFrontend track_container_t>
0835   auto fit_impl(source_link_iterator_t it, source_link_iterator_t end,
0836                 const BoundTrackParameters& sParameters,
0837                 const KalmanFitterOptions<traj_t>& kfOptions,
0838                 const std::vector<const Surface*>* sSequence,
0839                 track_container_t& trackContainer) const
0840       -> Result<typename track_container_t::TrackProxy> {
0841     using TrackProxy = typename track_container_t::TrackProxy;
0842     using TrackStateProxy = typename track_container_t::TrackStateProxy;
0843 
0844     auto forwardPropagatorOptions =
0845         make_propagator_options(it, end, kfOptions, sSequence, nullptr, false);
0846 
0847     auto forwardFilterResult =
0848         filter_impl(sParameters, forwardPropagatorOptions, trackContainer);
0849 
0850     if (!forwardFilterResult.ok()) {
0851       ACTS_DEBUG("KalmanFilter failed: "
0852                  << forwardFilterResult.error() << ", "
0853                  << forwardFilterResult.error().message());
0854       return forwardFilterResult.error();
0855     }
0856 
0857     TrackProxy forwardTrack = forwardFilterResult.value();
0858 
0859     TrackStateProxy firstMeasurementState =
0860         trackContainer.trackStateContainer().getTrackState(
0861             findFirstMeasurementState(forwardTrack).value().index());
0862     TrackStateProxy lastMeasurementState =
0863         trackContainer.trackStateContainer().getTrackState(
0864             findLastMeasurementState(forwardTrack).value().index());
0865     lastMeasurementState.shareFrom(lastMeasurementState,
0866                                    TrackStatePropMask::Filtered,
0867                                    TrackStatePropMask::Smoothed);
0868 
0869     TrackProxy track = forwardTrack;
0870 
0871     const bool doReverseFilter =
0872         kfOptions.reverseFiltering ||
0873         kfOptions.extensions.reverseFilteringLogic(
0874             typename traj_t::ConstTrackStateProxy(lastMeasurementState));
0875     if (doReverseFilter) {
0876       ACTS_VERBOSE("Smooth track by reversed filtering");
0877 
0878       auto reverseStartParameters = forwardTrack.createParametersFromState(
0879           typename traj_t::ConstTrackStateProxy(lastMeasurementState));
0880       reverseStartParameters.covariance().value() *=
0881           kfOptions.reverseFilteringCovarianceScaling;
0882       auto reversePropagatorOptions = make_propagator_options(
0883           it, end, kfOptions, sSequence, kfOptions.referenceSurface, true);
0884       auto reverseFilterResult = filter_impl(
0885           reverseStartParameters, reversePropagatorOptions, trackContainer);
0886 
0887       if (!reverseFilterResult.ok()) {
0888         ACTS_DEBUG("Reversed KalmanFilter failed: "
0889                    << reverseFilterResult.error() << ", "
0890                    << reverseFilterResult.error().message());
0891         return reverseFilterResult.error();
0892       }
0893 
0894       TrackProxy reverseTrack = reverseFilterResult.value();
0895 
0896       TrackStateProxy reverseLastMeasurementState =
0897           trackContainer.trackStateContainer().getTrackState(
0898               findLastMeasurementState(reverseTrack).value().index());
0899 
0900       if (&firstMeasurementState.referenceSurface() !=
0901           &reverseLastMeasurementState.referenceSurface()) {
0902         ACTS_DEBUG(
0903             "Inconsistent reference surfaces between forward and "
0904             "reversed filtered tracks");
0905         return Result<TrackProxy>::failure(
0906             KalmanFitterError::InconsistentTrackStates);
0907       }
0908       firstMeasurementState.shareFrom(reverseLastMeasurementState,
0909                                       TrackStatePropMask::Filtered,
0910                                       TrackStatePropMask::Smoothed);
0911 
0912       if (reverseTrack.hasReferenceSurface()) {
0913         track.parameters() = reverseTrack.parameters();
0914         track.covariance() = reverseTrack.covariance();
0915         track.setReferenceSurface(
0916             reverseTrack.referenceSurface().getSharedPtr());
0917       }
0918 
0919       trackContainer.removeTrack(reverseTrack.index());
0920     } else {
0921       ACTS_VERBOSE("Smooth track directly without reversed filtering");
0922 
0923       auto smoothRes = kfOptions.extensions.smoother(
0924           kfOptions.geoContext, trackContainer.trackStateContainer(),
0925           forwardTrack.tipIndex(), logger());
0926       if (!smoothRes.ok()) {
0927         ACTS_DEBUG("Smoothing step failed: " << smoothRes.error() << ", "
0928                                              << smoothRes.error().message());
0929         return smoothRes.error();
0930       }
0931     }
0932 
0933     if (!track.hasReferenceSurface() && kfOptions.referenceSurface != nullptr) {
0934       typename propagator_t::template Options<> extrapolationOptions(
0935           kfOptions.geoContext, kfOptions.magFieldContext);
0936       auto extrapolationResult = extrapolateTrackToReferenceSurface(
0937           track, *kfOptions.referenceSurface, m_propagator,
0938           extrapolationOptions, kfOptions.referenceSurfaceStrategy, logger());
0939 
0940       if (!extrapolationResult.ok()) {
0941         ACTS_DEBUG("Extrapolation to reference surface failed: "
0942                    << extrapolationResult.error() << ", "
0943                    << extrapolationResult.error().message());
0944         return extrapolationResult.error();
0945       }
0946     }
0947 
0948     if (trackContainer.hasColumn(hashString("smoothed"))) {
0949       track.template component<bool, hashString("smoothed")>() =
0950           !doReverseFilter;
0951     }
0952     if (trackContainer.hasColumn(hashString("reversed"))) {
0953       track.template component<bool, hashString("reversed")>() =
0954           doReverseFilter;
0955     }
0956 
0957     return track;
0958   }
0959 };
0960 
0961 /// @}
0962 
0963 }  // namespace Acts