Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-10 09:46:44

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       cmp.singleStepper(stepper).transportCovarianceToBound(cmp.state(),
0228                                                             surface);
0229     }
0230 
0231     if (m_cfg.multipleScattering && haveMaterial) {
0232       if (haveMeasurement) {
0233         const Result<void> materialInteractionRes = applyMultipleScattering(
0234             state, stepper, surface,
0235             determineMaterialUpdateMode(state, navigator,
0236                                         MaterialUpdateMode::PreUpdate),
0237             logger());
0238         if (!materialInteractionRes.ok()) {
0239           return materialInteractionRes.error();
0240         }
0241       } else {
0242         const Result<void> materialInteractionRes = applyMultipleScattering(
0243             state, stepper, surface,
0244             determineMaterialUpdateMode(state, navigator,
0245                                         MaterialUpdateMode::FullUpdate),
0246             logger());
0247         if (!materialInteractionRes.ok()) {
0248           return materialInteractionRes.error();
0249         }
0250       }
0251     }
0252 
0253     // We do not need the component cache here, we can just update our stepper
0254     // state with the filtered components.
0255     // NOTE because of early return before we know that we have a measurement
0256     if (!haveMaterial) {
0257       TemporaryStates tmpStates;
0258 
0259       auto res = kalmanUpdate(state, stepper, surface, result, tmpStates,
0260                               foundSourceLink->second);
0261 
0262       if (!res.ok()) {
0263         if (m_cfg.abortOnError) {
0264           std::abort();
0265         }
0266         return res.error();
0267       }
0268 
0269       updateStepper(state, stepper, tmpStates, m_cfg.weightCutoff);
0270     }
0271     // We have material, we thus need a component cache since we will
0272     // convolute the components and later reduce them again before updating
0273     // the stepper
0274     else {
0275       TemporaryStates tmpStates;
0276       Result<void> res;
0277 
0278       if (haveMeasurement) {
0279         res = kalmanUpdate(state, stepper, surface, result, tmpStates,
0280                            foundSourceLink->second);
0281       } else {
0282         res = noMeasurementUpdate(state, stepper, surface, result, tmpStates,
0283                                   false);
0284       }
0285 
0286       if (!res.ok()) {
0287         if (m_cfg.abortOnError) {
0288           std::abort();
0289         }
0290         return res.error();
0291       }
0292 
0293       // Reuse memory over all calls to the Actor in a single propagation
0294       std::vector<GsfComponent>& componentCache = result.componentCache;
0295       componentCache.clear();
0296 
0297       double pathXOverX0 = 0.0;
0298       for (const TrackIndexType idx : tmpStates.tips) {
0299         auto proxy = tmpStates.traj.getTrackState(idx);
0300 
0301         const BoundTrackParameters bound(
0302             surface.getSharedPtr(), proxy.filtered(),
0303             proxy.filteredCovariance(),
0304             stepper.particleHypothesis(state.stepping));
0305 
0306         pathXOverX0 += applyBetheHeitler(
0307             state.options.geoContext, surface, state.options.direction, bound,
0308             tmpStates.weights.at(idx), *m_cfg.bethe_heitler_approx,
0309             result.betheHeitlerCache, m_cfg.weightCutoff, componentCache,
0310             result.nInvalidBetheHeitler.tmp(), result.maxPathXOverX0.tmp(),
0311             logger());
0312       }
0313       // Store average material seen by the components
0314       // Should not be too broadly distributed
0315       result.sumPathXOverX0.tmp() += pathXOverX0 / tmpStates.tips.size();
0316 
0317       if (componentCache.empty()) {
0318         ACTS_WARNING(
0319             "No components left after applying energy loss. "
0320             "Is the weight cutoff "
0321             << m_cfg.weightCutoff << " too high?");
0322         ACTS_WARNING("Return to propagator without applying energy loss");
0323         return Result<void>::success();
0324       }
0325 
0326       // reduce component number
0327       const auto finalCmpNumber = std::min(
0328           static_cast<std::size_t>(stepper.maxComponents), m_cfg.maxComponents);
0329       m_cfg.extensions.mixtureReducer(componentCache, finalCmpNumber, surface);
0330 
0331       removeLowWeightComponents(componentCache, m_cfg.weightCutoff);
0332 
0333       updateStepper(state, stepper, surface, componentCache);
0334     }
0335 
0336     // If we have only done preUpdate before, now do postUpdate
0337     if (m_cfg.multipleScattering && haveMaterial && haveMeasurement) {
0338       const Result<void> materialInteractionRes = applyMultipleScattering(
0339           state, stepper, surface,
0340           determineMaterialUpdateMode(state, navigator,
0341                                       MaterialUpdateMode::PostUpdate),
0342           logger());
0343       if (!materialInteractionRes.ok()) {
0344         return materialInteractionRes.error();
0345       }
0346     }
0347 
0348     return Result<void>::success();
0349   }
0350 
0351   template <typename propagator_state_t, typename stepper_t,
0352             typename navigator_t>
0353   bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0354                   const navigator_t& /*navigator*/, const result_type& result,
0355                   const Logger& /*logger*/) const {
0356     if (m_cfg.numberMeasurements &&
0357         result.measurementStates == m_cfg.numberMeasurements) {
0358       ACTS_VERBOSE("Stop navigation because all measurements are found");
0359       return true;
0360     }
0361 
0362     return false;
0363   }
0364 
0365   /// This function performs the kalman update, computes the new posterior
0366   /// weights, renormalizes all components, and does some statistics.
0367   template <typename propagator_state_t, typename stepper_t>
0368   Result<void> kalmanUpdate(propagator_state_t& state, const stepper_t& stepper,
0369                             const Surface& surface, result_type& result,
0370                             TemporaryStates& tmpStates,
0371                             const SourceLink& sourceLink) const {
0372     // Keep track of all created components for outlier handling
0373     std::vector<TrackIndexType> allTips;
0374     allTips.reserve(stepper.numberComponents(state.stepping));
0375 
0376     for (auto cmp : stepper.componentIterable(state.stepping)) {
0377       auto singleState = cmp.singleState(state);
0378       const auto& singleStepper = cmp.singleStepper(stepper);
0379 
0380       // Add a <mask> TrackState entry multi trajectory. This allocates storage
0381       // for all components, which we will set later. The filtered parameters
0382       // are deliberately not allocated here: they are only added once the
0383       // Kalman update is about to write them, so that the calibrator and the
0384       // outlier finder cannot observe allocated but uninitialized filtered
0385       // parameters via `parameters()`.
0386       TrackStatePropMask mask = TrackStatePropMask::Predicted |
0387                                 TrackStatePropMask::Jacobian |
0388                                 TrackStatePropMask::Calibrated;
0389       typename traj_t::TrackStateProxy trackStateProxy =
0390           tmpStates.traj.makeTrackState(mask, kTrackIndexInvalid);
0391       typename traj_t::ConstTrackStateProxy trackStateProxyConst{
0392           trackStateProxy};
0393 
0394       // Set the trackStateProxy components with the state from the ongoing
0395       // propagation
0396       {
0397         trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0398         // Bind the transported state to the current surface
0399         auto res =
0400             singleStepper.boundState(singleState.stepping, surface, false);
0401         if (!res.ok()) {
0402           ACTS_DEBUG("Propagate to surface " << surface.geometryId()
0403                                              << " failed: " << res.error());
0404           return res.error();
0405         }
0406         const auto& [boundParams, jacobian, pathLength] = *res;
0407 
0408         // Fill the track state
0409         trackStateProxy.predicted() = boundParams.parameters();
0410         trackStateProxy.predictedCovariance() = singleState.stepping.cov;
0411 
0412         trackStateProxy.jacobian() = jacobian;
0413         trackStateProxy.pathLength() = pathLength;
0414       }
0415 
0416       // We have predicted parameters, so calibrate the uncalibrated input
0417       // measurement
0418       m_cfg.extensions.calibrator(state.geoContext, *m_cfg.calibrationContext,
0419                                   sourceLink, trackStateProxy);
0420 
0421       if (!m_cfg.extensions.outlierFinder(trackStateProxyConst)) {
0422         // Allocate the filtered parameters right before they are written
0423         trackStateProxy.addComponents(TrackStatePropMask::Filtered);
0424         // Run Kalman update
0425         auto updateRes = m_cfg.extensions.updater(state.geoContext,
0426                                                   trackStateProxy, logger());
0427         if (!updateRes.ok()) {
0428           ACTS_DEBUG("Update step failed: " << updateRes.error());
0429           return updateRes.error();
0430         }
0431 
0432         tmpStates.tips.push_back(trackStateProxy.index());
0433         tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0434       }
0435 
0436       allTips.push_back(trackStateProxy.index());
0437     }
0438 
0439     const bool isOutlier = tmpStates.tips.empty();
0440 
0441     if (!isOutlier) {
0442       computePosteriorWeights(tmpStates.traj, tmpStates.tips,
0443                               tmpStates.weights);
0444       normalizeWeights(tmpStates.tips, [&](auto idx) -> double& {
0445         return tmpStates.weights.at(idx);
0446       });
0447     } else {
0448       auto cmps = stepper.componentIterable(state.stepping);
0449       for (const auto [cmp, idx] : zip(cmps, allTips)) {
0450         typename traj_t::TrackStateProxy trackStateProxy =
0451             tmpStates.traj.getTrackState(idx);
0452 
0453         // Set the filtered parameter index to be the same with predicted
0454         // parameter
0455         trackStateProxy.shareFrom(trackStateProxy,
0456                                   TrackStatePropMask::Predicted,
0457                                   TrackStatePropMask::Filtered);
0458 
0459         tmpStates.tips.push_back(trackStateProxy.index());
0460         tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0461       }
0462     }
0463 
0464     // Do the statistics
0465     ++result.processedStates;
0466     if (!isOutlier) {
0467       ++result.measurementStates;
0468     }
0469 
0470     updateMultiTrajectory(result, tmpStates, surface,
0471                           TrackStateType()
0472                               .setHasParameters()
0473                               .setHasMaterial(surface.hasMaterial())
0474                               .setHasMeasurement()
0475                               .setIsOutlier(isOutlier));
0476 
0477     result.lastMeasurementTip = result.currentTip;
0478     result.lastMeasurementSurface = &surface;
0479 
0480     // Note, that we do not normalize the components here.
0481     // This must be done before initializing the backward pass.
0482     result.lastMeasurementComponents.clear();
0483 
0484     FiltProjector proj{tmpStates.traj, tmpStates.weights};
0485     for (const auto& idx : tmpStates.tips) {
0486       const auto& [w, p, c] = proj(idx);
0487       // TODO check why zero weight can occur
0488       if (w > 0.0) {
0489         result.lastMeasurementComponents.push_back({w, p, c});
0490       }
0491     }
0492 
0493     // Return success
0494     return Result<void>::success();
0495   }
0496 
0497   template <typename propagator_state_t, typename stepper_t>
0498   Result<void> noMeasurementUpdate(propagator_state_t& state,
0499                                    const stepper_t& stepper,
0500                                    const Surface& surface, result_type& result,
0501                                    TemporaryStates& tmpStates,
0502                                    bool doCovTransport) const {
0503     for (auto cmp : stepper.componentIterable(state.stepping)) {
0504       auto& singleState = cmp.state();
0505       const auto& singleStepper = cmp.singleStepper(stepper);
0506 
0507       // Add a <mask> TrackState entry multi trajectory. This allocates storage
0508       // for all components, which we will set later.
0509       TrackStatePropMask mask =
0510           TrackStatePropMask::Predicted | TrackStatePropMask::Jacobian;
0511       typename traj_t::TrackStateProxy trackStateProxy =
0512           tmpStates.traj.makeTrackState(mask, kTrackIndexInvalid);
0513 
0514       // Set the trackStateProxy components with the state from the ongoing
0515       // propagation
0516       {
0517         trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0518         // Bind the transported state to the current surface
0519         auto res =
0520             singleStepper.boundState(singleState, surface, doCovTransport);
0521         if (!res.ok()) {
0522           return res.error();
0523         }
0524         const auto& [boundParams, jacobian, pathLength] = *res;
0525 
0526         // Fill the track state
0527         trackStateProxy.predicted() = boundParams.parameters();
0528         trackStateProxy.predictedCovariance() = singleState.cov;
0529 
0530         trackStateProxy.jacobian() = jacobian;
0531         trackStateProxy.pathLength() = pathLength;
0532 
0533         // Set the filtered parameter index to be the same with predicted
0534         // parameter
0535         trackStateProxy.shareFrom(trackStateProxy,
0536                                   TrackStatePropMask::Predicted,
0537                                   TrackStatePropMask::Filtered);
0538       }
0539 
0540       tmpStates.tips.push_back(trackStateProxy.index());
0541       tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0542     }
0543 
0544     const bool precedingMeasurementExists = result.processedStates > 0;
0545     const bool isHole = surface.isSensitive();
0546 
0547     // Do the statistics
0548     ++result.processedStates;
0549     if (precedingMeasurementExists && isHole) {
0550       ++result.measurementHoles;
0551     }
0552 
0553     updateMultiTrajectory(result, tmpStates, surface,
0554                           TrackStateType()
0555                               .setHasParameters()
0556                               .setHasMaterial(surface.hasMaterial())
0557                               .setIsHole(isHole));
0558 
0559     return Result<void>::success();
0560   }
0561 
0562   void updateMultiTrajectory(result_type& result,
0563                              const TemporaryStates& tmpStates,
0564                              const Surface& surface,
0565                              TrackStateType type) const {
0566     using PrtProjector =
0567         MultiTrajectoryProjector<StatesType::ePredicted, traj_t>;
0568     using FltProjector =
0569         MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0570 
0571     if (!m_cfg.inReversePass) {
0572       assert(!tmpStates.tips.empty() &&
0573              "No components to update multi-trajectory");
0574 
0575       const auto firstCmpProxy =
0576           tmpStates.traj.getTrackState(tmpStates.tips.front());
0577 
0578       // Smoothed parameters are not allocated here but in the backward pass
0579       // that computes them, so they are never left uninitialized
0580       auto combinedStateMask = TrackStatePropMask::Predicted;
0581       if (type.isMeasurement()) {
0582         combinedStateMask |=
0583             TrackStatePropMask::Calibrated | TrackStatePropMask::Filtered;
0584       } else if (type.isOutlier()) {
0585         combinedStateMask |= TrackStatePropMask::Calibrated;
0586       }
0587       auto combinedState = result.fittedStates->makeTrackState(
0588           combinedStateMask, result.currentTip);
0589       result.currentTip = combinedState.index();
0590 
0591       // copy chi2, path length, surface
0592       auto copyMask = TrackStatePropMask::None;
0593       if (ACTS_CHECK_BIT(combinedStateMask, TrackStatePropMask::Calibrated)) {
0594         // also copy source link, calibrated measurement, and subspace
0595         copyMask |= TrackStatePropMask::Calibrated;
0596       }
0597       combinedState.copyFrom(firstCmpProxy, copyMask);
0598       combinedState.typeFlags() = type;
0599 
0600       auto [prtMean, prtCov] = mergeGaussianMixture(
0601           tmpStates.tips, PrtProjector{tmpStates.traj, tmpStates.weights},
0602           surface, m_cfg.mergeMethod);
0603       combinedState.predicted() = prtMean;
0604       combinedState.predictedCovariance() = prtCov;
0605 
0606       if (type.isMeasurement()) {
0607         auto [fltMean, fltCov] = mergeGaussianMixture(
0608             tmpStates.tips, FltProjector{tmpStates.traj, tmpStates.weights},
0609             surface, m_cfg.mergeMethod);
0610         combinedState.filtered() = fltMean;
0611         combinedState.filteredCovariance() = fltCov;
0612       } else {
0613         combinedState.shareFrom(TrackStatePropMask::Predicted,
0614                                 TrackStatePropMask::Filtered);
0615       }
0616 
0617     } else {
0618       assert((result.currentTip != kTrackIndexInvalid && "tip not valid"));
0619 
0620       result.fittedStates->applyBackwards(
0621           result.currentTip, [&](auto trackState) {
0622             if (&trackState.referenceSurface() != &surface) {
0623               return true;
0624             }
0625 
0626             result.surfacesVisitedBwdAgain.push_back(&surface);
0627 
0628             // The last forward measurement state already shares smoothed with
0629             // filtered and is skipped as an already visited surface
0630             if (trackState.typeFlags().isMeasurement()) {
0631               const auto [smtMean, smtCov] = mergeGaussianMixture(
0632                   tmpStates.tips,
0633                   FltProjector{tmpStates.traj, tmpStates.weights}, surface,
0634                   m_cfg.mergeMethod);
0635 
0636               trackState.addComponents(TrackStatePropMask::Smoothed);
0637               trackState.smoothed() = smtMean;
0638               trackState.smoothedCovariance() = smtCov;
0639             }
0640 
0641             return false;
0642           });
0643     }
0644   }
0645 
0646   /// Set the relevant options that can be set from the Options struct all in
0647   /// one place
0648   void setOptions(const GsfOptions<traj_t>& options) {
0649     m_cfg.maxComponents = options.maxComponents;
0650     m_cfg.extensions = options.extensions;
0651     m_cfg.abortOnError = options.abortOnError;
0652     m_cfg.disableAllMaterialHandling = options.disableAllMaterialHandling;
0653     m_cfg.weightCutoff = options.weightCutoff;
0654     m_cfg.mergeMethod = options.componentMergeMethod;
0655     m_cfg.calibrationContext = &options.calibrationContext.get();
0656   }
0657 };
0658 
0659 }  // namespace Acts::detail::Gsf