Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-19 07:35:31

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.
0382       TrackStatePropMask mask =
0383           TrackStatePropMask::Predicted | TrackStatePropMask::Filtered |
0384           TrackStatePropMask::Jacobian | TrackStatePropMask::Calibrated;
0385       typename traj_t::TrackStateProxy trackStateProxy =
0386           tmpStates.traj.makeTrackState(mask, kTrackIndexInvalid);
0387       typename traj_t::ConstTrackStateProxy trackStateProxyConst{
0388           trackStateProxy};
0389 
0390       // Set the trackStateProxy components with the state from the ongoing
0391       // propagation
0392       {
0393         trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0394         // Bind the transported state to the current surface
0395         auto res =
0396             singleStepper.boundState(singleState.stepping, surface, false);
0397         if (!res.ok()) {
0398           ACTS_DEBUG("Propagate to surface " << surface.geometryId()
0399                                              << " failed: " << res.error());
0400           return res.error();
0401         }
0402         const auto& [boundParams, jacobian, pathLength] = *res;
0403 
0404         // Fill the track state
0405         trackStateProxy.predicted() = boundParams.parameters();
0406         trackStateProxy.predictedCovariance() = singleState.stepping.cov;
0407 
0408         trackStateProxy.jacobian() = jacobian;
0409         trackStateProxy.pathLength() = pathLength;
0410       }
0411 
0412       // We have predicted parameters, so calibrate the uncalibrated input
0413       // measurement
0414       m_cfg.extensions.calibrator(state.geoContext, *m_cfg.calibrationContext,
0415                                   sourceLink, trackStateProxy);
0416 
0417       if (!m_cfg.extensions.outlierFinder(trackStateProxyConst)) {
0418         // Run Kalman update
0419         auto updateRes = m_cfg.extensions.updater(state.geoContext,
0420                                                   trackStateProxy, logger());
0421         if (!updateRes.ok()) {
0422           ACTS_DEBUG("Update step failed: " << updateRes.error());
0423           return updateRes.error();
0424         }
0425 
0426         tmpStates.tips.push_back(trackStateProxy.index());
0427         tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0428       }
0429 
0430       allTips.push_back(trackStateProxy.index());
0431     }
0432 
0433     const bool isOutlier = tmpStates.tips.empty();
0434 
0435     if (!isOutlier) {
0436       computePosteriorWeights(tmpStates.traj, tmpStates.tips,
0437                               tmpStates.weights);
0438       normalizeWeights(tmpStates.tips, [&](auto idx) -> double& {
0439         return tmpStates.weights.at(idx);
0440       });
0441     } else {
0442       auto cmps = stepper.componentIterable(state.stepping);
0443       for (const auto [cmp, idx] : zip(cmps, allTips)) {
0444         typename traj_t::TrackStateProxy trackStateProxy =
0445             tmpStates.traj.getTrackState(idx);
0446 
0447         // Set the filtered parameter index to be the same with predicted
0448         // parameter
0449         trackStateProxy.shareFrom(trackStateProxy,
0450                                   TrackStatePropMask::Predicted,
0451                                   TrackStatePropMask::Filtered);
0452 
0453         tmpStates.tips.push_back(trackStateProxy.index());
0454         tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0455       }
0456     }
0457 
0458     // Do the statistics
0459     ++result.processedStates;
0460     if (!isOutlier) {
0461       ++result.measurementStates;
0462     }
0463 
0464     updateMultiTrajectory(result, tmpStates, surface,
0465                           TrackStateType()
0466                               .setHasParameters()
0467                               .setHasMaterial(surface.hasMaterial())
0468                               .setHasMeasurement()
0469                               .setIsOutlier(isOutlier));
0470 
0471     result.lastMeasurementTip = result.currentTip;
0472     result.lastMeasurementSurface = &surface;
0473 
0474     // Note, that we do not normalize the components here.
0475     // This must be done before initializing the backward pass.
0476     result.lastMeasurementComponents.clear();
0477 
0478     FiltProjector proj{tmpStates.traj, tmpStates.weights};
0479     for (const auto& idx : tmpStates.tips) {
0480       const auto& [w, p, c] = proj(idx);
0481       // TODO check why zero weight can occur
0482       if (w > 0.0) {
0483         result.lastMeasurementComponents.push_back({w, p, c});
0484       }
0485     }
0486 
0487     // Return success
0488     return Result<void>::success();
0489   }
0490 
0491   template <typename propagator_state_t, typename stepper_t>
0492   Result<void> noMeasurementUpdate(propagator_state_t& state,
0493                                    const stepper_t& stepper,
0494                                    const Surface& surface, result_type& result,
0495                                    TemporaryStates& tmpStates,
0496                                    bool doCovTransport) const {
0497     for (auto cmp : stepper.componentIterable(state.stepping)) {
0498       auto& singleState = cmp.state();
0499       const auto& singleStepper = cmp.singleStepper(stepper);
0500 
0501       // Add a <mask> TrackState entry multi trajectory. This allocates storage
0502       // for all components, which we will set later.
0503       TrackStatePropMask mask =
0504           TrackStatePropMask::Predicted | TrackStatePropMask::Jacobian;
0505       typename traj_t::TrackStateProxy trackStateProxy =
0506           tmpStates.traj.makeTrackState(mask, kTrackIndexInvalid);
0507 
0508       // Set the trackStateProxy components with the state from the ongoing
0509       // propagation
0510       {
0511         trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0512         // Bind the transported state to the current surface
0513         auto res =
0514             singleStepper.boundState(singleState, surface, doCovTransport);
0515         if (!res.ok()) {
0516           return res.error();
0517         }
0518         const auto& [boundParams, jacobian, pathLength] = *res;
0519 
0520         // Fill the track state
0521         trackStateProxy.predicted() = boundParams.parameters();
0522         trackStateProxy.predictedCovariance() = singleState.cov;
0523 
0524         trackStateProxy.jacobian() = jacobian;
0525         trackStateProxy.pathLength() = pathLength;
0526 
0527         // Set the filtered parameter index to be the same with predicted
0528         // parameter
0529         trackStateProxy.shareFrom(trackStateProxy,
0530                                   TrackStatePropMask::Predicted,
0531                                   TrackStatePropMask::Filtered);
0532       }
0533 
0534       tmpStates.tips.push_back(trackStateProxy.index());
0535       tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0536     }
0537 
0538     const bool precedingMeasurementExists = result.processedStates > 0;
0539     const bool isHole = surface.isSensitive();
0540 
0541     // Do the statistics
0542     ++result.processedStates;
0543     if (precedingMeasurementExists && isHole) {
0544       ++result.measurementHoles;
0545     }
0546 
0547     updateMultiTrajectory(result, tmpStates, surface,
0548                           TrackStateType()
0549                               .setHasParameters()
0550                               .setHasMaterial(surface.hasMaterial())
0551                               .setIsHole(isHole));
0552 
0553     return Result<void>::success();
0554   }
0555 
0556   void updateMultiTrajectory(result_type& result,
0557                              const TemporaryStates& tmpStates,
0558                              const Surface& surface,
0559                              TrackStateType type) const {
0560     using PrtProjector =
0561         MultiTrajectoryProjector<StatesType::ePredicted, traj_t>;
0562     using FltProjector =
0563         MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0564 
0565     if (!m_cfg.inReversePass) {
0566       assert(!tmpStates.tips.empty() &&
0567              "No components to update multi-trajectory");
0568 
0569       const auto firstCmpProxy =
0570           tmpStates.traj.getTrackState(tmpStates.tips.front());
0571 
0572       auto combinedStateMask = TrackStatePropMask::Predicted;
0573       if (type.isMeasurement()) {
0574         combinedStateMask |= TrackStatePropMask::Calibrated |
0575                              TrackStatePropMask::Filtered |
0576                              TrackStatePropMask::Smoothed;
0577       } else if (type.isOutlier()) {
0578         combinedStateMask |= TrackStatePropMask::Calibrated;
0579       }
0580       auto combinedState = result.fittedStates->makeTrackState(
0581           combinedStateMask, result.currentTip);
0582       result.currentTip = combinedState.index();
0583 
0584       // copy chi2, path length, surface
0585       auto copyMask = TrackStatePropMask::None;
0586       if (ACTS_CHECK_BIT(combinedStateMask, TrackStatePropMask::Calibrated)) {
0587         // also copy source link, calibrated measurement, and subspace
0588         copyMask |= TrackStatePropMask::Calibrated;
0589       }
0590       combinedState.copyFrom(firstCmpProxy, copyMask);
0591       combinedState.typeFlags() = type;
0592 
0593       auto [prtMean, prtCov] = mergeGaussianMixture(
0594           tmpStates.tips, PrtProjector{tmpStates.traj, tmpStates.weights},
0595           surface, m_cfg.mergeMethod);
0596       combinedState.predicted() = prtMean;
0597       combinedState.predictedCovariance() = prtCov;
0598 
0599       if (type.isMeasurement()) {
0600         auto [fltMean, fltCov] = mergeGaussianMixture(
0601             tmpStates.tips, FltProjector{tmpStates.traj, tmpStates.weights},
0602             surface, m_cfg.mergeMethod);
0603         combinedState.filtered() = fltMean;
0604         combinedState.filteredCovariance() = fltCov;
0605 
0606         // place sentinel values for smoothed parameters for now. they will be
0607         // filled in the backward pass
0608         combinedState.smoothed() = BoundVector::Constant(-2);
0609         combinedState.smoothedCovariance() = BoundMatrix::Constant(-2);
0610       } else {
0611         combinedState.shareFrom(TrackStatePropMask::Predicted,
0612                                 TrackStatePropMask::Filtered);
0613       }
0614 
0615     } else {
0616       assert((result.currentTip != kTrackIndexInvalid && "tip not valid"));
0617 
0618       result.fittedStates->applyBackwards(
0619           result.currentTip, [&](auto trackState) {
0620             if (&trackState.referenceSurface() != &surface) {
0621               return true;
0622             }
0623 
0624             result.surfacesVisitedBwdAgain.push_back(&surface);
0625 
0626             if (trackState.hasSmoothed()) {
0627               const auto [smtMean, smtCov] = mergeGaussianMixture(
0628                   tmpStates.tips,
0629                   FltProjector{tmpStates.traj, tmpStates.weights}, surface,
0630                   m_cfg.mergeMethod);
0631 
0632               trackState.smoothed() = smtMean;
0633               trackState.smoothedCovariance() = smtCov;
0634             }
0635 
0636             return false;
0637           });
0638     }
0639   }
0640 
0641   /// Set the relevant options that can be set from the Options struct all in
0642   /// one place
0643   void setOptions(const GsfOptions<traj_t>& options) {
0644     m_cfg.maxComponents = options.maxComponents;
0645     m_cfg.extensions = options.extensions;
0646     m_cfg.abortOnError = options.abortOnError;
0647     m_cfg.disableAllMaterialHandling = options.disableAllMaterialHandling;
0648     m_cfg.weightCutoff = options.weightCutoff;
0649     m_cfg.mergeMethod = options.componentMergeMethod;
0650     m_cfg.calibrationContext = &options.calibrationContext.get();
0651   }
0652 };
0653 
0654 }  // namespace Acts::detail::Gsf