Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-11 08:40:06

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