Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-17 08:21:05

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/Definitions/TrackParametrization.hpp"
0013 #include "Acts/EventData/MultiTrajectory.hpp"
0014 #include "Acts/EventData/Types.hpp"
0015 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0016 #include "Acts/Surfaces/Surface.hpp"
0017 #include "Acts/TrackFitting/BetheHeitlerApprox.hpp"
0018 #include "Acts/TrackFitting/GsfComponent.hpp"
0019 #include "Acts/TrackFitting/GsfOptions.hpp"
0020 #include "Acts/TrackFitting/detail/GsfComponentMerging.hpp"
0021 #include "Acts/TrackFitting/detail/GsfUtils.hpp"
0022 #include "Acts/Utilities/Helpers.hpp"
0023 
0024 #include <map>
0025 
0026 namespace Acts::detail::Gsf {
0027 
0028 template <typename traj_t>
0029 struct GsfResult {
0030   /// The multi-trajectory which stores the graph of components
0031   traj_t* fittedStates{nullptr};
0032 
0033   /// The current top index of the MultiTrajectory
0034   TrackIndexType currentTip = kTrackIndexInvalid;
0035 
0036   /// The last tip referring to a measurement state in the MultiTrajectory
0037   TrackIndexType lastMeasurementTip = kTrackIndexInvalid;
0038 
0039   /// The last multi-component measurement state. Used to initialize the
0040   /// backward pass.
0041   std::vector<std::tuple<double, BoundVector, BoundMatrix>>
0042       lastMeasurementComponents;
0043 
0044   /// The last measurement surface. Used to initialize the backward pass.
0045   const Acts::Surface* lastMeasurementSurface = nullptr;
0046 
0047   /// Some counting
0048   std::size_t measurementStates = 0;
0049   std::size_t measurementHoles = 0;
0050   std::size_t processedStates = 0;
0051 
0052   std::vector<const Surface*> visitedSurfaces;
0053   std::vector<const Surface*> surfacesVisitedBwdAgain;
0054 
0055   /// Statistics about material encounterings
0056   Updatable<std::size_t> nInvalidBetheHeitler;
0057   Updatable<double> maxPathXOverX0;
0058   Updatable<double> sumPathXOverX0;
0059 
0060   // Internal: bethe heitler approximation component cache
0061   std::vector<BetheHeitlerApprox::Component> betheHeitlerCache;
0062 
0063   // Internal: component cache to avoid reallocation
0064   std::vector<GsfComponent> componentCache;
0065 };
0066 
0067 /// The actor carrying out the GSF algorithm
0068 template <typename traj_t>
0069 struct GsfActor {
0070   /// Enforce default construction
0071   GsfActor() = default;
0072 
0073   /// Broadcast the result_type
0074   using result_type = GsfResult<traj_t>;
0075 
0076   // Actor configuration
0077   struct Config {
0078     /// Maximum number of components which the GSF should handle
0079     std::size_t maxComponents = 16;
0080 
0081     /// Input measurements
0082     const std::map<GeometryIdentifier, SourceLink>* inputMeasurements = nullptr;
0083 
0084     /// Bethe Heitler Approximator pointer. The fitter holds the approximator
0085     /// instance TODO if we somehow could initialize a reference here...
0086     const BetheHeitlerApprox* bethe_heitler_approx = nullptr;
0087 
0088     /// Whether to consider multiple scattering.
0089     bool multipleScattering = true;
0090 
0091     /// When to discard components
0092     double weightCutoff = 1.0e-4;
0093 
0094     /// When this option is enabled, material information on all surfaces is
0095     /// ignored. This disables the component convolution as well as the handling
0096     /// of energy. This may be useful for debugging.
0097     bool disableAllMaterialHandling = false;
0098 
0099     /// Whether to abort immediately when an error occurs
0100     bool abortOnError = false;
0101 
0102     /// We can stop the propagation if we reach this number of measurement
0103     /// states
0104     std::optional<std::size_t> numberMeasurements;
0105 
0106     /// The extensions
0107     GsfExtensions<traj_t> extensions;
0108 
0109     /// Whether we are in the reverse pass or not. This is more reliable than
0110     /// checking the navigation direction, because in principle the fitter can
0111     /// be started backwards in the first pass
0112     bool inReversePass = false;
0113 
0114     /// How to reduce the states that are stored in the multi trajectory
0115     ComponentMergeMethod mergeMethod = ComponentMergeMethod::eMaxWeight;
0116 
0117     const Logger* logger{nullptr};
0118 
0119     /// Calibration context for the fit
0120     const CalibrationContext* calibrationContext{nullptr};
0121 
0122   } m_cfg;
0123 
0124   const Logger& logger() const { return *m_cfg.logger; }
0125 
0126   using TemporaryStates = detail::Gsf::TemporaryStates<traj_t>;
0127 
0128   using FiltProjector = MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0129 
0130   /// @brief GSF actor operation
0131   ///
0132   /// @tparam propagator_state_t is the type of Propagator state
0133   /// @tparam stepper_t Type of the stepper
0134   /// @tparam navigator_t Type of the navigator
0135   ///
0136   /// @param state is the mutable propagator state object
0137   /// @param stepper The stepper in use
0138   /// @param result is the mutable result state object
0139   template <typename propagator_state_t, typename stepper_t,
0140             typename navigator_t>
0141   Result<void> act(propagator_state_t& state, const stepper_t& stepper,
0142                    const navigator_t& navigator, result_type& result,
0143                    const Logger& /*logger*/) const {
0144     assert(result.fittedStates && "No MultiTrajectory set");
0145 
0146     // Prints some VERBOSE things and performs some asserts. Can be removed
0147     // without change of behaviour
0148     const ScopedGsfInfoPrinterAndChecker printer(state, stepper, navigator,
0149                                                  logger());
0150 
0151     // We only need to do something if we are on a surface
0152     if (navigator.currentSurface(state.navigation) == nullptr) {
0153       return Result<void>::success();
0154     }
0155 
0156     const auto& surface = *navigator.currentSurface(state.navigation);
0157     ACTS_VERBOSE("Step is at surface " << surface.geometryId());
0158 
0159     // All components must be normalized at the beginning here, otherwise the
0160     // stepper misbehaves
0161     [[maybe_unused]] auto stepperComponents =
0162         stepper.constComponentIterable(state.stepping);
0163     assert(weightsAreNormalized(stepperComponents,
0164                                 [](const auto& cmp) { return cmp.weight(); }));
0165 
0166     // All components must have status "on surface". It is however possible,
0167     // that currentSurface is nullptr and all components are "on surface" (e.g.,
0168     // for surfaces excluded from the navigation)
0169     using Status [[maybe_unused]] = IntersectionStatus;
0170     assert(std::all_of(
0171         stepperComponents.begin(), stepperComponents.end(),
0172         [](const auto& cmp) { return cmp.status() == Status::onSurface; }));
0173 
0174     // Early return if we already were on this surface TODO why is this
0175     // necessary
0176     const bool visited = rangeContainsValue(result.visitedSurfaces, &surface);
0177 
0178     if (visited) {
0179       ACTS_VERBOSE("Already visited surface, return");
0180       return Result<void>::success();
0181     }
0182 
0183     result.visitedSurfaces.push_back(&surface);
0184 
0185     // Check what we have on this surface
0186     const auto foundSourceLink =
0187         m_cfg.inputMeasurements->find(surface.geometryId());
0188     const bool haveMaterial =
0189         surface.hasMaterial() && !m_cfg.disableAllMaterialHandling;
0190     const bool haveMeasurement =
0191         foundSourceLink != m_cfg.inputMeasurements->end();
0192 
0193     ACTS_VERBOSE(std::boolalpha << "haveMaterial " << haveMaterial
0194                                 << ", haveMeasurement: " << haveMeasurement);
0195 
0196     ////////////////////////
0197     // The Core Algorithm
0198     ////////////////////////
0199 
0200     // Early return if nothing happens
0201     if (!haveMaterial && !haveMeasurement) {
0202       // No hole before first measurement
0203       if (result.processedStates > 0 && surface.isSensitive()) {
0204         TemporaryStates tmpStates;
0205         Result<void> res = noMeasurementUpdate(state, stepper, surface, result,
0206                                                tmpStates, true);
0207         if (!res.ok()) {
0208           if (m_cfg.abortOnError) {
0209             std::abort();
0210           }
0211           return res.error();
0212         }
0213       }
0214       return Result<void>::success();
0215     }
0216 
0217     // Update the counters. Note that this should be done before potential
0218     // material interactions, because if this is our last measurement this would
0219     // not influence the fit anymore.
0220     if (haveMeasurement) {
0221       result.maxPathXOverX0.update();
0222       result.sumPathXOverX0.update();
0223       result.nInvalidBetheHeitler.update();
0224     }
0225 
0226     for (auto cmp : stepper.componentIterable(state.stepping)) {
0227       Result<void> transportRes =
0228           cmp.singleStepper(stepper).transportCovarianceToBound(cmp.state(),
0229                                                                 surface);
0230       if (!transportRes.ok()) {
0231         if (m_cfg.abortOnError) {
0232           std::abort();
0233         }
0234         return transportRes.error();
0235       }
0236     }
0237 
0238     if (m_cfg.multipleScattering && haveMaterial) {
0239       if (haveMeasurement) {
0240         const Result<void> materialInteractionRes = applyMultipleScattering(
0241             state, stepper, surface,
0242             determineMaterialUpdateMode(state, navigator,
0243                                         MaterialUpdateMode::PreUpdate),
0244             logger());
0245         if (!materialInteractionRes.ok()) {
0246           return materialInteractionRes.error();
0247         }
0248       } else {
0249         const Result<void> materialInteractionRes = applyMultipleScattering(
0250             state, stepper, surface,
0251             determineMaterialUpdateMode(state, navigator,
0252                                         MaterialUpdateMode::FullUpdate),
0253             logger());
0254         if (!materialInteractionRes.ok()) {
0255           return materialInteractionRes.error();
0256         }
0257       }
0258     }
0259 
0260     // We do not need the component cache here, we can just update our stepper
0261     // state with the filtered components.
0262     // NOTE because of early return before we know that we have a measurement
0263     if (!haveMaterial) {
0264       TemporaryStates tmpStates;
0265 
0266       auto res = kalmanUpdate(state, stepper, surface, result, tmpStates,
0267                               foundSourceLink->second);
0268 
0269       if (!res.ok()) {
0270         if (m_cfg.abortOnError) {
0271           std::abort();
0272         }
0273         return res.error();
0274       }
0275 
0276       updateStepper(state, stepper, tmpStates, m_cfg.weightCutoff);
0277     }
0278     // We have material, we thus need a component cache since we will
0279     // convolute the components and later reduce them again before updating
0280     // the stepper
0281     else {
0282       TemporaryStates tmpStates;
0283       Result<void> res;
0284 
0285       if (haveMeasurement) {
0286         res = kalmanUpdate(state, stepper, surface, result, tmpStates,
0287                            foundSourceLink->second);
0288       } else {
0289         res = noMeasurementUpdate(state, stepper, surface, result, tmpStates,
0290                                   false);
0291       }
0292 
0293       if (!res.ok()) {
0294         if (m_cfg.abortOnError) {
0295           std::abort();
0296         }
0297         return res.error();
0298       }
0299 
0300       // Reuse memory over all calls to the Actor in a single propagation
0301       std::vector<GsfComponent>& componentCache = result.componentCache;
0302       componentCache.clear();
0303 
0304       double pathXOverX0 = 0.0;
0305       for (const TrackIndexType idx : tmpStates.tips) {
0306         auto proxy = tmpStates.traj.getTrackState(idx);
0307 
0308         const BoundTrackParameters bound(
0309             surface.getSharedPtr(), proxy.filtered(),
0310             proxy.filteredCovariance(),
0311             stepper.particleHypothesis(state.stepping));
0312 
0313         pathXOverX0 += applyBetheHeitler(
0314             state.options.geoContext, surface, state.options.direction, bound,
0315             tmpStates.weights.at(idx), *m_cfg.bethe_heitler_approx,
0316             result.betheHeitlerCache, m_cfg.weightCutoff, componentCache,
0317             result.nInvalidBetheHeitler.tmp(), result.maxPathXOverX0.tmp(),
0318             logger());
0319       }
0320       // Store average material seen by the components
0321       // Should not be too broadly distributed
0322       result.sumPathXOverX0.tmp() += pathXOverX0 / tmpStates.tips.size();
0323 
0324       if (componentCache.empty()) {
0325         ACTS_WARNING(
0326             "No components left after applying energy loss. "
0327             "Is the weight cutoff "
0328             << m_cfg.weightCutoff << " too high?");
0329         ACTS_WARNING("Return to propagator without applying energy loss");
0330         return Result<void>::success();
0331       }
0332 
0333       // reduce component number
0334       const auto finalCmpNumber = std::min(
0335           static_cast<std::size_t>(stepper.maxComponents), m_cfg.maxComponents);
0336       m_cfg.extensions.mixtureReducer(componentCache, finalCmpNumber, surface);
0337 
0338       removeLowWeightComponents(componentCache, m_cfg.weightCutoff);
0339 
0340       updateStepper(state, stepper, surface, componentCache);
0341     }
0342 
0343     // If we have only done preUpdate before, now do postUpdate
0344     if (m_cfg.multipleScattering && haveMaterial && haveMeasurement) {
0345       const Result<void> materialInteractionRes = applyMultipleScattering(
0346           state, stepper, surface,
0347           determineMaterialUpdateMode(state, navigator,
0348                                       MaterialUpdateMode::PostUpdate),
0349           logger());
0350       if (!materialInteractionRes.ok()) {
0351         return materialInteractionRes.error();
0352       }
0353     }
0354 
0355     return Result<void>::success();
0356   }
0357 
0358   template <typename propagator_state_t, typename stepper_t,
0359             typename navigator_t>
0360   bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0361                   const navigator_t& /*navigator*/, const result_type& result,
0362                   const Logger& /*logger*/) const {
0363     if (m_cfg.numberMeasurements &&
0364         result.measurementStates == m_cfg.numberMeasurements) {
0365       ACTS_VERBOSE("Stop navigation because all measurements are found");
0366       return true;
0367     }
0368 
0369     return false;
0370   }
0371 
0372   /// This function performs the kalman update, computes the new posterior
0373   /// weights, renormalizes all components, and does some statistics.
0374   template <typename propagator_state_t, typename stepper_t>
0375   Result<void> kalmanUpdate(propagator_state_t& state, const stepper_t& stepper,
0376                             const Surface& surface, result_type& result,
0377                             TemporaryStates& tmpStates,
0378                             const SourceLink& sourceLink) const {
0379     // Keep track of all created components for outlier handling
0380     std::vector<TrackIndexType> allTips;
0381     allTips.reserve(stepper.numberComponents(state.stepping));
0382 
0383     for (auto cmp : stepper.componentIterable(state.stepping)) {
0384       auto singleState = cmp.singleState(state);
0385       const auto& singleStepper = cmp.singleStepper(stepper);
0386 
0387       // Add a <mask> TrackState entry multi trajectory. This allocates storage
0388       // for all components, which we will set later. The filtered parameters
0389       // are deliberately not allocated here: they are only added once the
0390       // Kalman update is about to write them, so that the calibrator and the
0391       // outlier finder cannot observe allocated but uninitialized filtered
0392       // parameters via `parameters()`.
0393       TrackStatePropMask mask = TrackStatePropMask::Predicted |
0394                                 TrackStatePropMask::Jacobian |
0395                                 TrackStatePropMask::Calibrated;
0396       typename traj_t::TrackStateProxy trackStateProxy =
0397           tmpStates.traj.makeTrackState(mask, kTrackIndexInvalid);
0398       typename traj_t::ConstTrackStateProxy trackStateProxyConst{
0399           trackStateProxy};
0400 
0401       // Set the trackStateProxy components with the state from the ongoing
0402       // propagation
0403       {
0404         trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0405         // Bind the transported state to the current surface
0406         auto res =
0407             singleStepper.boundState(singleState.stepping, surface, false);
0408         if (!res.ok()) {
0409           ACTS_DEBUG("Propagate to surface " << surface.geometryId()
0410                                              << " failed: " << res.error());
0411           return res.error();
0412         }
0413         const auto& [boundParams, jacobian, pathLength] = *res;
0414 
0415         // Fill the track state
0416         trackStateProxy.predicted() = boundParams.parameters();
0417         trackStateProxy.predictedCovariance() = singleState.stepping.cov;
0418 
0419         trackStateProxy.jacobian() = jacobian;
0420         trackStateProxy.pathLength() = pathLength;
0421       }
0422 
0423       // We have predicted parameters, so calibrate the uncalibrated input
0424       // measurement
0425       m_cfg.extensions.calibrator(state.geoContext, *m_cfg.calibrationContext,
0426                                   sourceLink, trackStateProxy);
0427 
0428       if (!m_cfg.extensions.outlierFinder(trackStateProxyConst)) {
0429         // Allocate the filtered parameters right before they are written
0430         trackStateProxy.addComponents(TrackStatePropMask::Filtered);
0431         // Run Kalman update
0432         auto updateRes = m_cfg.extensions.updater(state.geoContext,
0433                                                   trackStateProxy, logger());
0434         if (!updateRes.ok()) {
0435           ACTS_DEBUG("Update step failed: " << updateRes.error());
0436           return updateRes.error();
0437         }
0438 
0439         tmpStates.tips.push_back(trackStateProxy.index());
0440         tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0441       }
0442 
0443       allTips.push_back(trackStateProxy.index());
0444     }
0445 
0446     const bool isOutlier = tmpStates.tips.empty();
0447 
0448     if (!isOutlier) {
0449       computePosteriorWeights(tmpStates.traj, tmpStates.tips,
0450                               tmpStates.weights);
0451       normalizeWeights(tmpStates.tips, [&](auto idx) -> double& {
0452         return tmpStates.weights.at(idx);
0453       });
0454     } else {
0455       auto cmps = stepper.componentIterable(state.stepping);
0456       for (const auto [cmp, idx] : zip(cmps, allTips)) {
0457         typename traj_t::TrackStateProxy trackStateProxy =
0458             tmpStates.traj.getTrackState(idx);
0459 
0460         // Set the filtered parameter index to be the same with predicted
0461         // parameter
0462         trackStateProxy.shareFrom(trackStateProxy,
0463                                   TrackStatePropMask::Predicted,
0464                                   TrackStatePropMask::Filtered);
0465 
0466         tmpStates.tips.push_back(trackStateProxy.index());
0467         tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0468       }
0469     }
0470 
0471     // Do the statistics
0472     ++result.processedStates;
0473     if (!isOutlier) {
0474       ++result.measurementStates;
0475     }
0476 
0477     updateMultiTrajectory(result, tmpStates, surface,
0478                           TrackStateType()
0479                               .setHasParameters()
0480                               .setHasMaterial(surface.hasMaterial())
0481                               .setHasMeasurement()
0482                               .setIsOutlier(isOutlier));
0483 
0484     result.lastMeasurementTip = result.currentTip;
0485     result.lastMeasurementSurface = &surface;
0486 
0487     // Note, that we do not normalize the components here.
0488     // This must be done before initializing the backward pass.
0489     result.lastMeasurementComponents.clear();
0490 
0491     FiltProjector proj{tmpStates.traj, tmpStates.weights};
0492     for (const auto& idx : tmpStates.tips) {
0493       const auto& [w, p, c] = proj(idx);
0494       // TODO check why zero weight can occur
0495       if (w > 0.0) {
0496         result.lastMeasurementComponents.push_back({w, p, c});
0497       }
0498     }
0499 
0500     // Return success
0501     return Result<void>::success();
0502   }
0503 
0504   template <typename propagator_state_t, typename stepper_t>
0505   Result<void> noMeasurementUpdate(propagator_state_t& state,
0506                                    const stepper_t& stepper,
0507                                    const Surface& surface, result_type& result,
0508                                    TemporaryStates& tmpStates,
0509                                    bool doCovTransport) const {
0510     for (auto cmp : stepper.componentIterable(state.stepping)) {
0511       auto& singleState = cmp.state();
0512       const auto& singleStepper = cmp.singleStepper(stepper);
0513 
0514       // Add a <mask> TrackState entry multi trajectory. This allocates storage
0515       // for all components, which we will set later.
0516       TrackStatePropMask mask =
0517           TrackStatePropMask::Predicted | TrackStatePropMask::Jacobian;
0518       typename traj_t::TrackStateProxy trackStateProxy =
0519           tmpStates.traj.makeTrackState(mask, kTrackIndexInvalid);
0520 
0521       // Set the trackStateProxy components with the state from the ongoing
0522       // propagation
0523       {
0524         trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0525         // Bind the transported state to the current surface
0526         auto res =
0527             singleStepper.boundState(singleState, surface, doCovTransport);
0528         if (!res.ok()) {
0529           return res.error();
0530         }
0531         const auto& [boundParams, jacobian, pathLength] = *res;
0532 
0533         // Fill the track state
0534         trackStateProxy.predicted() = boundParams.parameters();
0535         trackStateProxy.predictedCovariance() = singleState.cov;
0536 
0537         trackStateProxy.jacobian() = jacobian;
0538         trackStateProxy.pathLength() = pathLength;
0539 
0540         // Set the filtered parameter index to be the same with predicted
0541         // parameter
0542         trackStateProxy.shareFrom(trackStateProxy,
0543                                   TrackStatePropMask::Predicted,
0544                                   TrackStatePropMask::Filtered);
0545       }
0546 
0547       tmpStates.tips.push_back(trackStateProxy.index());
0548       tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0549     }
0550 
0551     const bool precedingMeasurementExists = result.processedStates > 0;
0552     const bool isHole = surface.isSensitive();
0553 
0554     // Do the statistics
0555     ++result.processedStates;
0556     if (precedingMeasurementExists && isHole) {
0557       ++result.measurementHoles;
0558     }
0559 
0560     updateMultiTrajectory(result, tmpStates, surface,
0561                           TrackStateType()
0562                               .setHasParameters()
0563                               .setHasMaterial(surface.hasMaterial())
0564                               .setIsHole(isHole));
0565 
0566     return Result<void>::success();
0567   }
0568 
0569   void updateMultiTrajectory(result_type& result,
0570                              const TemporaryStates& tmpStates,
0571                              const Surface& surface,
0572                              TrackStateType type) const {
0573     using PrtProjector =
0574         MultiTrajectoryProjector<StatesType::ePredicted, traj_t>;
0575     using FltProjector =
0576         MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0577 
0578     if (!m_cfg.inReversePass) {
0579       assert(!tmpStates.tips.empty() &&
0580              "No components to update multi-trajectory");
0581 
0582       const auto firstCmpProxy =
0583           tmpStates.traj.getTrackState(tmpStates.tips.front());
0584 
0585       // Smoothed parameters are not allocated here but in the backward pass
0586       // that computes them, so they are never left uninitialized
0587       auto combinedStateMask = TrackStatePropMask::Predicted;
0588       if (type.isMeasurement()) {
0589         combinedStateMask |=
0590             TrackStatePropMask::Calibrated | TrackStatePropMask::Filtered;
0591       } else if (type.isOutlier()) {
0592         combinedStateMask |= TrackStatePropMask::Calibrated;
0593       }
0594       auto combinedState = result.fittedStates->makeTrackState(
0595           combinedStateMask, result.currentTip);
0596       result.currentTip = combinedState.index();
0597 
0598       // copy chi2, path length, surface
0599       auto copyMask = TrackStatePropMask::None;
0600       if (ACTS_CHECK_BIT(combinedStateMask, TrackStatePropMask::Calibrated)) {
0601         // also copy source link, calibrated measurement, and subspace
0602         copyMask |= TrackStatePropMask::Calibrated;
0603       }
0604       combinedState.copyFrom(firstCmpProxy, copyMask);
0605       combinedState.typeFlags() = type;
0606 
0607       auto [prtMean, prtCov] = mergeGaussianMixture(
0608           tmpStates.tips, PrtProjector{tmpStates.traj, tmpStates.weights},
0609           surface, m_cfg.mergeMethod);
0610       combinedState.predicted() = prtMean;
0611       combinedState.predictedCovariance() = prtCov;
0612 
0613       if (type.isMeasurement()) {
0614         auto [fltMean, fltCov] = mergeGaussianMixture(
0615             tmpStates.tips, FltProjector{tmpStates.traj, tmpStates.weights},
0616             surface, m_cfg.mergeMethod);
0617         combinedState.filtered() = fltMean;
0618         combinedState.filteredCovariance() = fltCov;
0619       } else {
0620         combinedState.shareFrom(TrackStatePropMask::Predicted,
0621                                 TrackStatePropMask::Filtered);
0622       }
0623 
0624     } else {
0625       assert((result.currentTip != kTrackIndexInvalid && "tip not valid"));
0626 
0627       result.fittedStates->applyBackwards(
0628           result.currentTip, [&](auto trackState) {
0629             if (&trackState.referenceSurface() != &surface) {
0630               return true;
0631             }
0632 
0633             result.surfacesVisitedBwdAgain.push_back(&surface);
0634 
0635             // The last forward measurement state already shares smoothed with
0636             // filtered and is skipped as an already visited surface
0637             if (trackState.typeFlags().isMeasurement()) {
0638               const auto [smtMean, smtCov] = mergeGaussianMixture(
0639                   tmpStates.tips,
0640                   FltProjector{tmpStates.traj, tmpStates.weights}, surface,
0641                   m_cfg.mergeMethod);
0642 
0643               trackState.addComponents(TrackStatePropMask::Smoothed);
0644               trackState.smoothed() = smtMean;
0645               trackState.smoothedCovariance() = smtCov;
0646             }
0647 
0648             return false;
0649           });
0650     }
0651   }
0652 
0653   /// Set the relevant options that can be set from the Options struct all in
0654   /// one place
0655   void setOptions(const GsfOptions<traj_t>& options) {
0656     m_cfg.maxComponents = options.maxComponents;
0657     m_cfg.extensions = options.extensions;
0658     m_cfg.abortOnError = options.abortOnError;
0659     m_cfg.disableAllMaterialHandling = options.disableAllMaterialHandling;
0660     m_cfg.weightCutoff = options.weightCutoff;
0661     m_cfg.mergeMethod = options.componentMergeMethod;
0662     m_cfg.calibrationContext = &options.calibrationContext.get();
0663   }
0664 };
0665 
0666 }  // namespace Acts::detail::Gsf