Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-19 08:48:08

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/TrackStatePropMask.hpp"
0015 #include "Acts/EventData/Types.hpp"
0016 #include "Acts/Geometry/GeometryContext.hpp"
0017 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0018 #include "Acts/Propagator/ActorList.hpp"
0019 #include "Acts/Propagator/ConstrainedStep.hpp"
0020 #include "Acts/Propagator/PropagatorState.hpp"
0021 #include "Acts/Propagator/StandardAborters.hpp"
0022 #include "Acts/Propagator/detail/LoopProtection.hpp"
0023 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0024 #include "Acts/TrackFinding/CombinatorialKalmanFilterError.hpp"
0025 #include "Acts/TrackFinding/CombinatorialKalmanFilterExtensions.hpp"
0026 #include "Acts/Utilities/CalibrationContext.hpp"
0027 #include "Acts/Utilities/Logger.hpp"
0028 #include "Acts/Utilities/Result.hpp"
0029 
0030 #include <functional>
0031 #include <limits>
0032 #include <memory>
0033 #include <type_traits>
0034 
0035 namespace Acts {
0036 
0037 /// @addtogroup track_finding
0038 /// @{
0039 
0040 /// Combined options for the combinatorial Kalman filter.
0041 ///
0042 /// @tparam source_link_iterator_t Type of the source link iterator
0043 /// @tparam track_container_t Type of the track container
0044 template <typename track_container_t>
0045 struct CombinatorialKalmanFilterOptions {
0046   /// Type alias for track state container backend
0047   using TrackStateContainerBackend =
0048       typename track_container_t::TrackStateContainerBackend;
0049   /// Type alias for track state proxy from the container
0050   using TrackStateProxy = typename track_container_t::TrackStateProxy;
0051 
0052   /// PropagatorOptions with context
0053   ///
0054   /// @param gctx The geometry context for this track finding/fitting
0055   /// @param mctx The magnetic context for this track finding/fitting
0056   /// @param cctx The calibration context for this track finding/fitting
0057   /// @param extensions_ The extension struct
0058   /// @param pOptions The plain propagator options
0059   /// @param mScattering Whether to include multiple scattering
0060   /// @param eLoss Whether to include energy loss
0061   CombinatorialKalmanFilterOptions(
0062       const GeometryContext& gctx, const MagneticFieldContext& mctx,
0063       std::reference_wrapper<const CalibrationContext> cctx,
0064       CombinatorialKalmanFilterExtensions<track_container_t> extensions_,
0065       const PropagatorPlainOptions& pOptions, bool mScattering = true,
0066       bool eLoss = true)
0067       : geoContext(gctx),
0068         magFieldContext(mctx),
0069         calibrationContext(cctx),
0070         extensions(extensions_),
0071         propagatorPlainOptions(pOptions),
0072         multipleScattering(mScattering),
0073         energyLoss(eLoss) {}
0074 
0075   /// Contexts are required and the options must not be default-constructible.
0076   CombinatorialKalmanFilterOptions() = delete;
0077 
0078   /// Context object for the geometry
0079   std::reference_wrapper<const GeometryContext> geoContext;
0080   /// Context object for the magnetic field
0081   std::reference_wrapper<const MagneticFieldContext> magFieldContext;
0082   /// context object for the calibration
0083   std::reference_wrapper<const CalibrationContext> calibrationContext;
0084 
0085   /// The filter extensions
0086   CombinatorialKalmanFilterExtensions<track_container_t> extensions;
0087 
0088   /// The trivial propagator options
0089   PropagatorPlainOptions propagatorPlainOptions;
0090 
0091   /// The target surface
0092   /// @note This is useful if the filtering should be terminated at a
0093   ///       certain surface
0094   const Surface* targetSurface = nullptr;
0095 
0096   /// Whether to consider multiple scattering.
0097   bool multipleScattering = true;
0098 
0099   /// Whether to consider energy loss.
0100   bool energyLoss = true;
0101 
0102   /// Skip the pre propagation call. This effectively skips the first surface
0103   /// @note This is useful if the first surface should not be considered in a second reverse pass
0104   bool skipPrePropagationUpdate = false;
0105 };
0106 
0107 /// Result container for the combinatorial Kalman filter actor.
0108 ///
0109 /// @tparam track_container_t Type of the track container storing results
0110 template <typename track_container_t>
0111 struct CombinatorialKalmanFilterResult {
0112   /// Track state container backend type
0113   using TrackStateContainerBackend =
0114       typename track_container_t::TrackStateContainerBackend;
0115   /// Track proxy type
0116   using TrackProxy = typename track_container_t::TrackProxy;
0117   /// Track state proxy type
0118   using TrackStateProxy = typename track_container_t::TrackStateProxy;
0119 
0120   /// The track container to store the found tracks
0121   track_container_t* tracks{nullptr};
0122 
0123   /// Fitted states that the actor has handled.
0124   TrackStateContainerBackend* trackStates{nullptr};
0125 
0126   /// Indices into `tracks` which mark active branches
0127   std::vector<TrackProxy> activeBranches;
0128 
0129   /// Indices into `tracks` which mark active branches
0130   std::vector<TrackProxy> collectedTracks;
0131 
0132   /// Track state candidates buffer which can be used by the track state creator
0133   std::vector<TrackStateProxy> trackStateCandidates;
0134 
0135   /// Indicator if track finding has been done
0136   bool finished = false;
0137 
0138   /// Path limit aborter
0139   PathLimitReached pathLimitReached;
0140 };
0141 
0142 /// Combinatorial Kalman filter to find tracks.
0143 ///
0144 /// @tparam propagator_t Type of the propagator
0145 ///
0146 /// The CombinatorialKalmanFilter contains an Actor and a Sequencer sub-class.
0147 /// The Sequencer has to be part of the Navigator of the Propagator in order to
0148 /// initialize and provide the measurement surfaces.
0149 ///
0150 /// The Actor is part of the Propagation call and does the Kalman update.
0151 /// Updater and Calibrator are given to the Actor for further use:
0152 /// - The Updater is the implemented kalman updater formalism, it
0153 ///   runs via a visitor pattern through the measurements.
0154 ///
0155 /// Measurements are not required to be ordered for the
0156 /// CombinatorialKalmanFilter, measurement ordering needs to be figured out by
0157 /// the navigation of the propagator.
0158 ///
0159 /// The void components are provided mainly for unit testing.
0160 ///
0161 template <typename propagator_t, typename track_container_t>
0162 class CombinatorialKalmanFilter {
0163  public:
0164   /// Default constructor is deleted
0165   CombinatorialKalmanFilter() = delete;
0166 
0167   /// Constructor with propagator and logging level
0168   /// @param pPropagator The propagator used for the track finding
0169   /// @param _logger The logger for messages
0170   explicit CombinatorialKalmanFilter(propagator_t pPropagator,
0171                                      std::unique_ptr<const Logger> _logger =
0172                                          getDefaultLogger("CKF", Logging::INFO))
0173       : m_propagator(std::move(pPropagator)),
0174         m_logger(std::move(_logger)),
0175         m_actorLogger{m_logger->cloneWithSuffix("Actor")},
0176         m_updaterLogger{m_logger->cloneWithSuffix("Updater")} {}
0177 
0178  private:
0179   using BoundState = std::tuple<BoundTrackParameters, BoundMatrix, double>;
0180   using TrackStateContainerBackend =
0181       typename track_container_t::TrackStateContainerBackend;
0182   using TrackProxy = typename track_container_t::TrackProxy;
0183   using TrackStateProxy = typename track_container_t::TrackStateProxy;
0184 
0185   /// The propagator for the transport and material update
0186   propagator_t m_propagator;
0187 
0188   std::unique_ptr<const Logger> m_logger;
0189   std::shared_ptr<const Logger> m_actorLogger;
0190   std::shared_ptr<const Logger> m_updaterLogger;
0191 
0192   const Logger& logger() const { return *m_logger; }
0193 
0194   /// @brief Propagator Actor plugin for the CombinatorialKalmanFilter
0195   ///
0196   /// The CombinatorialKalmanFilter Actor does not rely on the measurements to
0197   /// be sorted along the track.
0198   class Actor {
0199    public:
0200     using BoundState = std::tuple<BoundTrackParameters, BoundMatrix, double>;
0201     /// Broadcast the result_type
0202     using result_type = CombinatorialKalmanFilterResult<track_container_t>;
0203 
0204     using BranchStopperResult = CombinatorialKalmanFilterBranchStopperResult;
0205 
0206     /// The target surface aborter
0207     SurfaceReached targetReached{std::numeric_limits<double>::lowest()};
0208 
0209     /// Whether to consider multiple scattering.
0210     bool multipleScattering = true;
0211 
0212     /// Whether to consider energy loss.
0213     bool energyLoss = true;
0214 
0215     /// Skip the pre propagation call. This effectively skips the first surface
0216     bool skipPrePropagationUpdate = false;
0217 
0218     /// Calibration context for the finding run
0219     const CalibrationContext* calibrationContextPtr{nullptr};
0220 
0221     CombinatorialKalmanFilterExtensions<track_container_t> extensions;
0222 
0223     /// End of world aborter
0224     EndOfWorldReached endOfWorldReached;
0225 
0226     /// Volume constraint aborter
0227     VolumeConstraintAborter volumeConstraintAborter;
0228 
0229     /// Actor logger instance
0230     const Logger* actorLogger{nullptr};
0231     /// Updater logger instance
0232     const Logger* updaterLogger{nullptr};
0233 
0234     const Logger& logger() const { return *actorLogger; }
0235 
0236     /// @brief CombinatorialKalmanFilter actor operation
0237     ///
0238     /// @tparam propagator_state_t Type of the Propagator state
0239     /// @tparam stepper_t Type of the stepper
0240     ///
0241     /// @param state is the mutable propagator state object
0242     /// @param stepper is the stepper in use
0243     /// @param navigator is the navigator in use
0244     /// @param result is the mutable result state object
0245     template <typename propagator_state_t, typename stepper_t,
0246               typename navigator_t>
0247     Result<void> act(propagator_state_t& state, const stepper_t& stepper,
0248                      const navigator_t& navigator, result_type& result,
0249                      const Logger& /*logger*/) const {
0250       ACTS_VERBOSE("CKF Actor called");
0251 
0252       assert(result.trackStates && "No MultiTrajectory set");
0253 
0254       if (state.stage == PropagatorStage::prePropagation &&
0255           skipPrePropagationUpdate) {
0256         ACTS_VERBOSE("Skip pre-propagation update (first surface)");
0257         return Result<void>::success();
0258       }
0259       if (state.stage == PropagatorStage::postPropagation) {
0260         ACTS_VERBOSE("Skip post-propagation action");
0261         return Result<void>::success();
0262       }
0263 
0264       ACTS_VERBOSE("CombinatorialKalmanFilter step");
0265 
0266       assert(!result.activeBranches.empty() && "No active branches");
0267       assert(!result.finished && "Should never reach this when finished");
0268 
0269       // Initialize path limit reached aborter
0270       if (result.pathLimitReached.internalLimit ==
0271           std::numeric_limits<double>::max()) {
0272         detail::setupLoopProtection(state, stepper, result.pathLimitReached,
0273                                     true, logger());
0274       }
0275 
0276       // Update:
0277       // - Waiting for a current surface
0278       if (const Surface* surface = navigator.currentSurface(state.navigation);
0279           surface != nullptr) {
0280         // There are three scenarios:
0281         // 1) The surface is in the measurement map
0282         // -> Select source links
0283         // -> Perform the kalman update for selected non-outlier source links
0284         // -> Add track states in multitrajectory. Multiple states mean branch
0285         // splitting.
0286         // -> Call branch stopper to justify each branch
0287         // -> If there is non-outlier state, update stepper information
0288         // 2) The surface is not in the measurement map but with material or is
0289         // an active surface
0290         // -> Add a hole or passive material state in multitrajectory
0291         // -> Call branch stopper to justify the branch
0292         // 3) The surface is neither in the measurement map nor with material
0293         // -> Do nothing
0294         ACTS_VERBOSE("Perform filter step");
0295         auto res = filter(*surface, state, stepper, navigator, result);
0296         if (!res.ok()) {
0297           ACTS_DEBUG("Error in filter: " << res.error().message());
0298           return res.error();
0299         }
0300 
0301         if (result.finished) {
0302           ACTS_VERBOSE("CKF Actor returns after filter step");
0303           return Result<void>::success();
0304         }
0305       }
0306 
0307       assert(!result.activeBranches.empty() && "No active branches");
0308 
0309       const bool isEndOfWorldReached =
0310           endOfWorldReached.checkAbort(state, stepper, navigator, logger());
0311       const bool isVolumeConstraintReached = volumeConstraintAborter.checkAbort(
0312           state, stepper, navigator, logger());
0313       const bool isPathLimitReached = result.pathLimitReached.checkAbort(
0314           state, stepper, navigator, logger());
0315       const bool isTargetReached =
0316           targetReached.checkAbort(state, stepper, navigator, logger());
0317       if (isEndOfWorldReached || isVolumeConstraintReached ||
0318           isPathLimitReached || isTargetReached) {
0319         if (isEndOfWorldReached) {
0320           ACTS_VERBOSE("End of world reached");
0321         } else if (isVolumeConstraintReached) {
0322           ACTS_VERBOSE("Volume constraint reached");
0323         } else if (isPathLimitReached) {
0324           ACTS_VERBOSE("Path limit reached");
0325         } else if (isTargetReached) {
0326           ACTS_VERBOSE("Target surface reached");
0327 
0328           // Bind the parameter to the target surface
0329           auto res = stepper.boundState(state.stepping, *targetReached.surface);
0330           if (!res.ok()) {
0331             ACTS_DEBUG("Error while acquiring bound state for target surface: "
0332                        << res.error() << " " << res.error().message());
0333             return res.error();
0334           }
0335 
0336           const auto& [boundParams, jacobian, pathLength] = *res;
0337           auto currentBranch = result.activeBranches.back();
0338           // Assign the fitted parameters
0339           currentBranch.parameters() = boundParams.parameters();
0340           currentBranch.covariance() = *boundParams.covariance();
0341           currentBranch.setReferenceSurface(
0342               boundParams.referenceSurface().getSharedPtr());
0343 
0344           stepper.releaseStepSize(state.stepping,
0345                                   ConstrainedStep::Type::Navigator);
0346         }
0347 
0348         // Record the active branch and remove it from the list
0349         storeLastActiveBranch(result);
0350         result.activeBranches.pop_back();
0351 
0352         // Reset propagation state to track state at next active branch
0353         auto resetRes = reset(state, stepper, navigator, result);
0354         if (!resetRes.ok()) {
0355           return resetRes.error();
0356         }
0357       }
0358 
0359       return Result<void>::success();
0360     }
0361 
0362     template <typename propagator_state_t, typename stepper_t,
0363               typename navigator_t>
0364     bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0365                     const navigator_t& /*navigator*/, const result_type& result,
0366                     const Logger& /*logger*/) const {
0367       return result.finished;
0368     }
0369 
0370     /// @brief CombinatorialKalmanFilter actor operation: reset propagation
0371     ///
0372     /// @tparam propagator_state_t Type of Propagator state
0373     /// @tparam stepper_t Type of the stepper
0374     /// @tparam navigator_t Type of the navigator
0375     ///
0376     /// @param state is the mutable propagator state object
0377     /// @param stepper is the stepper in use
0378     /// @param navigator is the navigator in use
0379     /// @param result is the mutable result state object
0380     template <typename propagator_state_t, typename stepper_t,
0381               typename navigator_t>
0382     Result<void> reset(propagator_state_t& state, const stepper_t& stepper,
0383                        const navigator_t& navigator,
0384                        result_type& result) const {
0385       if (result.activeBranches.empty()) {
0386         ACTS_VERBOSE("Stop CKF with " << result.collectedTracks.size()
0387                                       << " found tracks");
0388         result.finished = true;
0389 
0390         return Result<void>::success();
0391       }
0392 
0393       auto currentBranch = result.activeBranches.back();
0394       auto currentState = currentBranch.outermostTrackState();
0395 
0396       ACTS_VERBOSE("Propagation jumps to branch with tip = "
0397                    << currentBranch.tipIndex());
0398 
0399       // Reset the stepping state
0400       stepper.initialize(state.stepping, currentState.filtered(),
0401                          currentState.filteredCovariance(),
0402                          stepper.particleHypothesis(state.stepping),
0403                          currentState.referenceSurface());
0404 
0405       // Reset the navigation state
0406       // Set targetSurface to nullptr for forward filtering
0407       state.navigation.options.startSurface = &currentState.referenceSurface();
0408       state.navigation.options.targetSurface = nullptr;
0409       auto navInitRes = navigator.initialize(
0410           state.navigation, stepper.position(state.stepping),
0411           stepper.direction(state.stepping), state.options.direction);
0412       if (!navInitRes.ok()) {
0413         ACTS_DEBUG("Navigation initialization failed: " << navInitRes.error());
0414         return navInitRes.error();
0415       }
0416 
0417       // No Kalman filtering for the starting surface, but still need
0418       // to consider the material effects here
0419       const Result<detail::PointwiseMaterialEffects> materialInteractionRes =
0420           detail::performMaterialInteraction(
0421               state, stepper, currentState.referenceSurface(),
0422               detail::determineMaterialUpdateMode(
0423                   state, navigator, MaterialUpdateMode::PostUpdate),
0424               NoiseUpdateMode::addNoise, multipleScattering, energyLoss,
0425               logger());
0426       if (!materialInteractionRes.ok()) {
0427         ACTS_DEBUG("Material interaction failed during reset: "
0428                    << materialInteractionRes.error().message());
0429         return materialInteractionRes.error();
0430       }
0431 
0432       // Set path limit based on loop protection
0433       detail::setupLoopProtection(state, stepper, result.pathLimitReached, true,
0434                                   logger());
0435 
0436       // Set path limit based on target surface
0437       targetReached.checkAbort(state, stepper, navigator, logger());
0438 
0439       return Result<void>::success();
0440     }
0441 
0442     /// @brief CombinatorialKalmanFilter actor operation:
0443     /// - filtering for all measurement(s) on surface
0444     /// - store selected track states in multiTrajectory
0445     /// - update propagator state to the (last) selected track state
0446     ///
0447     /// @tparam propagator_state_t Type of the Propagator state
0448     /// @tparam stepper_t Type of the stepper
0449     /// @tparam navigator_t Type of the navigator
0450     ///
0451     /// @param surface The surface where the update happens
0452     /// @param state The mutable propagator state object
0453     /// @param stepper The stepper in use
0454     /// @param navigator The navigator in use
0455     /// @param result The mutable result state object
0456     template <typename propagator_state_t, typename stepper_t,
0457               typename navigator_t>
0458     Result<void> filter(const Surface& surface, propagator_state_t& state,
0459                         const stepper_t& stepper, const navigator_t& navigator,
0460                         result_type& result) const {
0461       using PM = TrackStatePropMask;
0462 
0463       bool isSensitive = surface.isSensitive();
0464       bool hasMaterial = surface.hasMaterial();
0465       bool isMaterialOnly = hasMaterial && !isSensitive;
0466       bool expectMeasurements = isSensitive;
0467 
0468       if (isSensitive) {
0469         ACTS_VERBOSE("Measurement surface " << surface.geometryId()
0470                                             << " detected.");
0471       } else if (isMaterialOnly) {
0472         ACTS_VERBOSE("Material surface " << surface.geometryId()
0473                                          << " detected.");
0474       } else {
0475         ACTS_VERBOSE("Passive surface " << surface.geometryId()
0476                                         << " detected.");
0477         return Result<void>::success();
0478       }
0479 
0480       // Transport the covariance to the surface
0481       if (isMaterialOnly) {
0482         stepper.transportCovarianceToCurvilinear(state.stepping);
0483       } else {
0484         stepper.transportCovarianceToBound(state.stepping, surface);
0485       }
0486 
0487       // Update state and stepper with pre material effects
0488       const Result<detail::PointwiseMaterialEffects> materialInteractionPreRes =
0489           detail::performMaterialInteraction(
0490               state, stepper, surface,
0491               detail::determineMaterialUpdateMode(
0492                   state, navigator, MaterialUpdateMode::PreUpdate),
0493               NoiseUpdateMode::addNoise, multipleScattering, energyLoss,
0494               logger());
0495       if (!materialInteractionPreRes.ok()) {
0496         ACTS_DEBUG("Material interaction failed during filter: "
0497                    << materialInteractionPreRes.error().message());
0498         return materialInteractionPreRes.error();
0499       }
0500 
0501       // Bind the transported state to the current surface
0502       auto boundStateRes = stepper.boundState(state.stepping, surface, false);
0503       if (!boundStateRes.ok()) {
0504         return boundStateRes.error();
0505       }
0506       auto& boundState = *boundStateRes;
0507       auto& [boundParams, jacobian, pathLength] = boundState;
0508       boundParams.covariance() = state.stepping.cov;
0509 
0510       auto currentBranch = result.activeBranches.back();
0511       TrackIndexType prevTip = currentBranch.tipIndex();
0512 
0513       using TrackStatesResult = Result<CkfTypes::BranchVector<TrackIndexType>>;
0514       TrackStatesResult tsRes = TrackStatesResult::success({});
0515       if (isSensitive) {
0516         // extend trajectory with measurements associated to the current surface
0517         // which may create extra trajectory branches if more than one
0518         // measurement is selected.
0519         tsRes = extensions.createTrackStates(
0520             state.geoContext, *calibrationContextPtr, surface, boundState,
0521             prevTip, result.trackStateCandidates, *result.trackStates,
0522             logger());
0523       }
0524 
0525       if (tsRes.ok() && !(*tsRes).empty()) {
0526         const CkfTypes::BranchVector<TrackIndexType>& newTrackStateList =
0527             *tsRes;
0528         Result<unsigned int> procRes =
0529             processNewTrackStates(state.geoContext, newTrackStateList, result);
0530         if (!procRes.ok()) {
0531           ACTS_DEBUG("Processing of selected track states failed: "
0532                      << procRes.error().message());
0533           return procRes.error();
0534         }
0535         unsigned int nBranchesOnSurface = *procRes;
0536 
0537         if (nBranchesOnSurface == 0) {
0538           ACTS_VERBOSE("All branches on surface " << surface.geometryId()
0539                                                   << " have been stopped");
0540 
0541           reset(state, stepper, navigator, result);
0542 
0543           return Result<void>::success();
0544         }
0545 
0546         // `currentBranch` is invalidated after `processNewTrackStates`
0547         currentBranch = result.activeBranches.back();
0548         prevTip = currentBranch.tipIndex();
0549       } else {
0550         if (!tsRes.ok()) {
0551           if (static_cast<CombinatorialKalmanFilterError>(
0552                   tsRes.error().value()) ==
0553               CombinatorialKalmanFilterError::NoMeasurementExpected) {
0554             // recoverable error returned by track state creator
0555             expectMeasurements = false;
0556           } else {
0557             ACTS_DEBUG("Track state creation failed on surface "
0558                        << surface.geometryId() << ": " << tsRes.error());
0559             return tsRes.error();
0560           }
0561         }
0562 
0563         if (expectMeasurements) {
0564           ACTS_VERBOSE("Detected hole after measurement selection on surface "
0565                        << surface.geometryId());
0566         }
0567 
0568         auto stateMask = PM::Predicted | PM::Jacobian;
0569 
0570         // Add a hole or material track state to the multitrajectory
0571         TrackIndexType currentTip =
0572             addNonSourcelinkState(stateMask, boundState, result, isSensitive,
0573                                   expectMeasurements, prevTip);
0574         currentBranch.tipIndex() = currentTip;
0575         auto currentState = currentBranch.outermostTrackState();
0576         if (expectMeasurements) {
0577           currentBranch.nHoles()++;
0578         }
0579 
0580         BranchStopperResult branchStopperResult =
0581             extensions.branchStopper(currentBranch, currentState);
0582 
0583         // Check the branch
0584         if (branchStopperResult == BranchStopperResult::Continue) {
0585           // Remembered the active branch and its state
0586         } else {
0587           // No branch on this surface
0588           if (branchStopperResult == BranchStopperResult::StopAndKeep) {
0589             storeLastActiveBranch(result);
0590           }
0591           // Remove the branch from list
0592           result.activeBranches.pop_back();
0593 
0594           // Branch on the surface has been stopped - reset
0595           ACTS_VERBOSE("Branch on surface " << surface.geometryId()
0596                                             << " has been stopped");
0597 
0598           reset(state, stepper, navigator, result);
0599 
0600           return Result<void>::success();
0601         }
0602       }
0603 
0604       auto currentState = currentBranch.outermostTrackState();
0605 
0606       if (currentState.typeFlags().isOutlier()) {
0607         // We don't need to update the stepper given an outlier state
0608         ACTS_VERBOSE("Outlier state detected on surface "
0609                      << surface.geometryId());
0610       } else if (currentState.typeFlags().isMeasurement()) {
0611         // If there are measurement track states on this surface
0612         // Update stepping state using filtered parameters of last track
0613         // state on this surface
0614         stepper.update(state.stepping,
0615                        MultiTrajectoryHelpers::freeFiltered(
0616                            state.options.geoContext, currentState),
0617                        currentState.filtered(),
0618                        currentState.filteredCovariance(), surface);
0619         ACTS_VERBOSE("Stepping state is updated with filtered parameter:");
0620         ACTS_VERBOSE("-> " << currentState.filtered().transpose()
0621                            << " of track state with tip = "
0622                            << currentState.index());
0623       }
0624 
0625       // Update state and stepper with post material effects
0626       const Result<detail::PointwiseMaterialEffects>
0627           materialInteractionPostRes = detail::performMaterialInteraction(
0628               state, stepper, surface,
0629               detail::determineMaterialUpdateMode(
0630                   state, navigator, MaterialUpdateMode::PostUpdate),
0631               NoiseUpdateMode::addNoise, multipleScattering, energyLoss,
0632               logger());
0633       if (!materialInteractionPostRes.ok()) {
0634         ACTS_DEBUG("Material interaction failed during filter: "
0635                    << materialInteractionPostRes.error().message());
0636         return materialInteractionPostRes.error();
0637       }
0638 
0639       return Result<void>::success();
0640     }
0641 
0642     /// Process new, incompomplete track states and set the filtered state
0643     ///
0644     /// @note will process the given list of new states, run the updater
0645     ///     or share the predicted state for states flagged as outliers
0646     ///     and add them to the list of active branches
0647     ///
0648     /// @param gctx The geometry context for this track finding/fitting
0649     /// @param newTrackStateList index list of new track states
0650     /// @param result which contains among others the new states, and the list of active branches
0651     /// @return the number of newly added branches or an error
0652     Result<unsigned int> processNewTrackStates(
0653         const GeometryContext& gctx,
0654         const CkfTypes::BranchVector<TrackIndexType>& newTrackStateList,
0655         result_type& result) const {
0656       using PM = TrackStatePropMask;
0657 
0658       unsigned int nBranchesOnSurface = 0;
0659 
0660       auto rootBranch = result.activeBranches.back();
0661 
0662       // Build the new branches by forking the root branch. Reverse the order
0663       // to process the best candidate first
0664       CkfTypes::BranchVector<TrackProxy> newBranches;
0665       for (auto it = newTrackStateList.rbegin(); it != newTrackStateList.rend();
0666            ++it) {
0667         // Keep the root branch as the first branch, make a copy for the
0668         // others
0669         auto shallowCopy = [&] {
0670           auto sc = rootBranch.container().makeTrack();
0671           sc.copyFromShallow(rootBranch);
0672           return sc;
0673         };
0674         auto newBranch =
0675             (it == newTrackStateList.rbegin()) ? rootBranch : shallowCopy();
0676         newBranch.tipIndex() = *it;
0677         newBranches.push_back(newBranch);
0678       }
0679 
0680       // Remove the root branch
0681       result.activeBranches.pop_back();
0682 
0683       // Update and select from the new branches
0684       for (TrackProxy newBranch : newBranches) {
0685         auto trackState = newBranch.outermostTrackState();
0686         TrackStateTypeMap typeFlags = trackState.typeFlags();
0687 
0688         if (typeFlags.isOutlier()) {
0689           // No Kalman update for outlier
0690           // Set the filtered parameter index to be the same with predicted
0691           // parameter
0692           trackState.shareFrom(PM::Predicted, PM::Filtered);
0693           // Increment number of outliers
0694           newBranch.nOutliers()++;
0695         } else if (typeFlags.isMeasurement()) {
0696           // Kalman update
0697           auto updateRes = extensions.updater(gctx, trackState, *updaterLogger);
0698           if (!updateRes.ok()) {
0699             ACTS_DEBUG("Update step failed: " << updateRes.error().message());
0700             return updateRes.error();
0701           }
0702           ACTS_VERBOSE("Appended measurement track state with tip = "
0703                        << newBranch.tipIndex());
0704           // Increment number of measurements
0705           newBranch.nMeasurements()++;
0706           newBranch.nDoF() += trackState.calibratedSize();
0707           newBranch.chi2() += trackState.chi2();
0708         } else {
0709           ACTS_WARNING("Cannot handle this track state flags");
0710           continue;
0711         }
0712 
0713         result.activeBranches.push_back(newBranch);
0714 
0715         BranchStopperResult branchStopperResult =
0716             extensions.branchStopper(newBranch, trackState);
0717 
0718         // Check if need to stop this branch
0719         if (branchStopperResult == BranchStopperResult::Continue) {
0720           // Record the number of branches on surface
0721           nBranchesOnSurface++;
0722         } else {
0723           // Record the number of stopped branches
0724           if (branchStopperResult == BranchStopperResult::StopAndKeep) {
0725             storeLastActiveBranch(result);
0726           }
0727           // Remove the branch from list
0728           result.activeBranches.pop_back();
0729         }
0730       }
0731 
0732       return nBranchesOnSurface;
0733     }
0734 
0735     /// @brief CombinatorialKalmanFilter actor operation: add a hole or material track state
0736     ///
0737     /// @param stateMask The bitmask that instructs which components to allocate
0738     /// @param boundState The bound state on current surface
0739     /// @param result is the mutable result state object and which to leave invalid
0740     /// @param isSensitive The surface is sensitive or passive
0741     /// @param expectMeasurements True if measurements where expected for this surface
0742     /// @param prevTip The index of the previous state
0743     ///
0744     /// @return The tip of added state
0745     TrackIndexType addNonSourcelinkState(TrackStatePropMask stateMask,
0746                                          const BoundState& boundState,
0747                                          result_type& result, bool isSensitive,
0748                                          bool expectMeasurements,
0749                                          TrackIndexType prevTip) const {
0750       using PM = TrackStatePropMask;
0751 
0752       // Add a track state
0753       auto trackStateProxy =
0754           result.trackStates->makeTrackState(stateMask, prevTip);
0755       ACTS_VERBOSE("Create "
0756                    << (isSensitive
0757                            ? (expectMeasurements ? "Hole"
0758                                                  : "noMeasurementExpected")
0759                            : "Material")
0760                    << " output track state #" << trackStateProxy.index()
0761                    << " with mask: " << stateMask);
0762 
0763       const auto& [boundParams, jacobian, pathLength] = boundState;
0764       // Fill the track state
0765       trackStateProxy.predicted() = boundParams.parameters();
0766       trackStateProxy.predictedCovariance() = boundParams.covariance().value();
0767       trackStateProxy.jacobian() = jacobian;
0768       trackStateProxy.pathLength() = pathLength;
0769       // Set the surface
0770       trackStateProxy.setReferenceSurface(
0771           boundParams.referenceSurface().getSharedPtr());
0772 
0773       // Set the track state flags
0774       auto typeFlags = trackStateProxy.typeFlags();
0775       if (trackStateProxy.referenceSurface().hasMaterial()) {
0776         typeFlags.setHasMaterial();
0777       }
0778       typeFlags.setHasParameters();
0779       if (isSensitive) {
0780         if (expectMeasurements) {
0781           typeFlags.setIsHole();
0782         } else {
0783           typeFlags.setHasNoExpectedHit();
0784         }
0785       }
0786 
0787       // Set the filtered parameter index to be the same with predicted
0788       // parameter
0789       trackStateProxy.shareFrom(PM::Predicted, PM::Filtered);
0790 
0791       return trackStateProxy.index();
0792     }
0793 
0794     void storeLastActiveBranch(result_type& result) const {
0795       auto currentBranch = result.activeBranches.back();
0796       TrackIndexType currentTip = currentBranch.tipIndex();
0797 
0798       ACTS_VERBOSE("Storing track "
0799                    << currentBranch.index() << " with tip index " << currentTip
0800                    << ". nMeasurements = " << currentBranch.nMeasurements()
0801                    << ", nOutliers = " << currentBranch.nOutliers()
0802                    << ", nHoles = " << currentBranch.nHoles());
0803 
0804       result.collectedTracks.push_back(currentBranch);
0805     }
0806   };
0807 
0808   /// Void path limit reached aborter to replace the default since the path
0809   /// limit is handled in the CKF actor internally.
0810   struct StubPathLimitReached {
0811     double internalLimit{};
0812 
0813     template <typename propagator_state_t, typename stepper_t,
0814               typename navigator_t>
0815     bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0816                     const navigator_t& /*navigator*/,
0817                     const Logger& /*logger*/) const {
0818       return false;
0819     }
0820   };
0821 
0822  public:
0823   /// Combinatorial Kalman Filter implementation, calls the Kalman filter
0824   ///
0825   /// @param initialParameters The initial track parameters
0826   /// @param tfOptions CombinatorialKalmanFilterOptions steering the track
0827   ///                  finding
0828   /// @param trackContainer Track container in which to store the results
0829   /// @param rootBranch The track to be used as the root branch
0830   ///
0831   /// @note The input measurements are given in the form of @c SourceLinks.
0832   ///       It's @c calibrator_t's job to turn them into calibrated measurements
0833   ///       used in the track finding.
0834   ///
0835   /// @return a container of track finding result for all the initial track
0836   /// parameters
0837   auto findTracks(
0838       const BoundTrackParameters& initialParameters,
0839       const CombinatorialKalmanFilterOptions<track_container_t>& tfOptions,
0840       track_container_t& trackContainer,
0841       typename track_container_t::TrackProxy rootBranch) const
0842       -> Result<std::vector<
0843           typename std::decay_t<decltype(trackContainer)>::TrackProxy>> {
0844     // Create the ActorList
0845     using CombinatorialKalmanFilterActor = Actor;
0846     using Actors = ActorList<CombinatorialKalmanFilterActor>;
0847 
0848     // Create relevant options for the propagation options
0849     using PropagatorOptions = typename propagator_t::template Options<Actors>;
0850     PropagatorOptions propOptions(tfOptions.geoContext,
0851                                   tfOptions.magFieldContext);
0852 
0853     // Set the trivial propagator options
0854     propOptions.setPlainOptions(tfOptions.propagatorPlainOptions);
0855 
0856     // Catch the actor
0857     auto& combKalmanActor =
0858         propOptions.actorList.template get<CombinatorialKalmanFilterActor>();
0859     combKalmanActor.targetReached.surface = tfOptions.targetSurface;
0860     combKalmanActor.multipleScattering = tfOptions.multipleScattering;
0861     combKalmanActor.energyLoss = tfOptions.energyLoss;
0862     combKalmanActor.skipPrePropagationUpdate =
0863         tfOptions.skipPrePropagationUpdate;
0864     combKalmanActor.actorLogger = m_actorLogger.get();
0865     combKalmanActor.updaterLogger = m_updaterLogger.get();
0866     combKalmanActor.calibrationContextPtr = &tfOptions.calibrationContext.get();
0867 
0868     // copy delegates to calibrator, updater, branch stopper
0869     combKalmanActor.extensions = tfOptions.extensions;
0870 
0871     auto propState =
0872         m_propagator
0873             .template makeState<PropagatorOptions, StubPathLimitReached>(
0874                 propOptions);
0875 
0876     auto initResult =
0877         m_propagator
0878             .template initialize<decltype(propState), StubPathLimitReached>(
0879                 propState, initialParameters);
0880     if (!initResult.ok()) {
0881       ACTS_DEBUG("Propagation initialization failed: " << initResult.error());
0882       return initResult.error();
0883     }
0884 
0885     auto& r =
0886         propState
0887             .template get<CombinatorialKalmanFilterResult<track_container_t>>();
0888     r.tracks = &trackContainer;
0889     r.trackStates = &trackContainer.trackStateContainer();
0890 
0891     // make sure the right particle hypothesis is set on the root branch
0892     rootBranch.setParticleHypothesis(initialParameters.particleHypothesis());
0893 
0894     r.activeBranches.push_back(rootBranch);
0895 
0896     auto propagationResult = m_propagator.propagate(propState);
0897 
0898     auto result = m_propagator.makeResult(
0899         std::move(propState), propagationResult, propOptions, false);
0900 
0901     if (!result.ok()) {
0902       ACTS_DEBUG("Propagation failed: " << result.error() << " "
0903                                         << result.error().message()
0904                                         << " with the initial parameters: \n"
0905                                         << initialParameters.parameters());
0906       return result.error();
0907     }
0908 
0909     auto& propRes = *result;
0910 
0911     // Get the result of the CombinatorialKalmanFilter
0912     auto combKalmanResult =
0913         std::move(propRes.template get<
0914                   CombinatorialKalmanFilterResult<track_container_t>>());
0915 
0916     // Check if track finding finished properly
0917     if (!combKalmanResult.finished) {
0918       ACTS_DEBUG("CombinatorialKalmanFilter failed: "
0919                  << "Propagation reached max steps "
0920                  << "with the initial parameters: "
0921                  << initialParameters.parameters().transpose());
0922       return CombinatorialKalmanFilterError::PropagationReachesMaxSteps;
0923     }
0924 
0925     return std::move(combKalmanResult.collectedTracks);
0926   }
0927 
0928   /// Combinatorial Kalman Filter implementation, calls the Kalman filter
0929   ///
0930   /// @param initialParameters The initial track parameters
0931   /// @param tfOptions CombinatorialKalmanFilterOptions steering the track
0932   ///                  finding
0933   /// @param trackContainer Track container in which to store the results
0934   /// @note The input measurements are given in the form of @c SourceLinks.
0935   ///       It's @c calibrator_t's job to turn them into calibrated measurements
0936   ///       used in the track finding.
0937   ///
0938   /// @return a container of track finding result for all the initial track
0939   /// parameters
0940   auto findTracks(
0941       const BoundTrackParameters& initialParameters,
0942       const CombinatorialKalmanFilterOptions<track_container_t>& tfOptions,
0943       track_container_t& trackContainer) const
0944       -> Result<std::vector<
0945           typename std::decay_t<decltype(trackContainer)>::TrackProxy>> {
0946     auto rootBranch = trackContainer.makeTrack();
0947     return findTracks(initialParameters, tfOptions, trackContainer, rootBranch);
0948   }
0949 };  // namespace Acts
0950 
0951 /// @}
0952 
0953 }  // namespace Acts