Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-05 08:11:15

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/TrackParametrization.hpp"
0012 #include "Acts/EventData/MultiComponentTrackParameters.hpp"
0013 #include "Acts/EventData/MultiTrajectory.hpp"
0014 #include "Acts/EventData/MultiTrajectoryHelpers.hpp"
0015 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0016 #include "Acts/Surfaces/Surface.hpp"
0017 #include "Acts/TrackFitting/GsfOptions.hpp"
0018 #include "Acts/TrackFitting/detail/GsfComponentMerging.hpp"
0019 #include "Acts/TrackFitting/detail/GsfUtils.hpp"
0020 #include "Acts/TrackFitting/detail/KalmanUpdateHelpers.hpp"
0021 #include "Acts/Utilities/Helpers.hpp"
0022 #include "Acts/Utilities/Zip.hpp"
0023 
0024 #include <ios>
0025 #include <map>
0026 
0027 namespace Acts::detail {
0028 
0029 template <typename traj_t>
0030 struct GsfResult {
0031   /// The multi-trajectory which stores the graph of components
0032   traj_t* fittedStates{nullptr};
0033 
0034   /// The current top index of the MultiTrajectory
0035   MultiTrajectoryTraits::IndexType currentTip = MultiTrajectoryTraits::kInvalid;
0036 
0037   /// The last tip referring to a measurement state in the MultiTrajectory
0038   MultiTrajectoryTraits::IndexType lastMeasurementTip =
0039       MultiTrajectoryTraits::kInvalid;
0040 
0041   /// The last multi-component measurement state. Used to initialize the
0042   /// backward pass.
0043   std::vector<std::tuple<double, BoundVector, BoundMatrix>>
0044       lastMeasurementComponents;
0045 
0046   /// The last measurement surface. Used to initialize the backward pass.
0047   const Acts::Surface* lastMeasurementSurface = nullptr;
0048 
0049   /// Some counting
0050   std::size_t measurementStates = 0;
0051   std::size_t measurementHoles = 0;
0052   std::size_t processedStates = 0;
0053 
0054   std::vector<const Surface*> visitedSurfaces;
0055   std::vector<const Surface*> surfacesVisitedBwdAgain;
0056 
0057   /// Statistics about material encounterings
0058   Updatable<std::size_t> nInvalidBetheHeitler;
0059   Updatable<double> maxPathXOverX0;
0060   Updatable<double> sumPathXOverX0;
0061 
0062   // Propagate potential errors to the outside
0063   Result<void> result{Result<void>::success()};
0064 
0065   // Internal: component cache to avoid reallocation
0066   std::vector<GsfComponent> componentCache;
0067 };
0068 
0069 /// The actor carrying out the GSF algorithm
0070 template <typename bethe_heitler_approx_t, typename traj_t>
0071 struct GsfActor {
0072   /// Enforce default construction
0073   GsfActor() = default;
0074 
0075   using ComponentCache = GsfComponent;
0076 
0077   /// Broadcast the result_type
0078   using result_type = GsfResult<traj_t>;
0079 
0080   // Actor configuration
0081   struct Config {
0082     /// Maximum number of components which the GSF should handle
0083     std::size_t maxComponents = 16;
0084 
0085     /// Input measurements
0086     const std::map<GeometryIdentifier, SourceLink>* inputMeasurements = nullptr;
0087 
0088     /// Bethe Heitler Approximator pointer. The fitter holds the approximator
0089     /// instance TODO if we somehow could initialize a reference here...
0090     const bethe_heitler_approx_t* bethe_heitler_approx = nullptr;
0091 
0092     /// Whether to consider multiple scattering.
0093     bool multipleScattering = true;
0094 
0095     /// When to discard components
0096     double weightCutoff = 1.0e-4;
0097 
0098     /// When this option is enabled, material information on all surfaces is
0099     /// ignored. This disables the component convolution as well as the handling
0100     /// of energy. This may be useful for debugging.
0101     bool disableAllMaterialHandling = false;
0102 
0103     /// Whether to abort immediately when an error occurs
0104     bool abortOnError = false;
0105 
0106     /// We can stop the propagation if we reach this number of measurement
0107     /// states
0108     std::optional<std::size_t> numberMeasurements;
0109 
0110     /// The extensions
0111     GsfExtensions<traj_t> extensions;
0112 
0113     /// Whether we are in the reverse pass or not. This is more reliable than
0114     /// checking the navigation direction, because in principle the fitter can
0115     /// be started backwards in the first pass
0116     bool inReversePass = false;
0117 
0118     /// How to reduce the states that are stored in the multi trajectory
0119     ComponentMergeMethod mergeMethod = ComponentMergeMethod::eMaxWeight;
0120 
0121     const Logger* logger{nullptr};
0122 
0123     /// Calibration context for the fit
0124     const CalibrationContext* calibrationContext{nullptr};
0125 
0126   } m_cfg;
0127 
0128   const Logger& logger() const { return *m_cfg.logger; }
0129 
0130   struct TemporaryStates {
0131     traj_t traj;
0132     std::vector<MultiTrajectoryTraits::IndexType> tips;
0133     std::map<MultiTrajectoryTraits::IndexType, double> weights;
0134   };
0135 
0136   using FiltProjector = MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0137 
0138   /// @brief GSF actor operation
0139   ///
0140   /// @tparam propagator_state_t is the type of Propagator state
0141   /// @tparam stepper_t Type of the stepper
0142   /// @tparam navigator_t Type of the navigator
0143   ///
0144   /// @param state is the mutable propagator state object
0145   /// @param stepper The stepper in use
0146   /// @param result is the mutable result state object
0147   template <typename propagator_state_t, typename stepper_t,
0148             typename navigator_t>
0149   void act(propagator_state_t& state, const stepper_t& stepper,
0150            const navigator_t& navigator, result_type& result,
0151            const Logger& /*logger*/) const {
0152     assert(result.fittedStates && "No MultiTrajectory set");
0153 
0154     // Return is we found an error earlier
0155     if (!result.result.ok()) {
0156       ACTS_WARNING("result.result not ok, return!");
0157       return;
0158     }
0159 
0160     // Set error or abort utility
0161     auto setErrorOrAbort = [&](auto error) {
0162       if (m_cfg.abortOnError) {
0163         std::abort();
0164       } else {
0165         result.result = error;
0166       }
0167     };
0168 
0169     // Prints some VERBOSE things and performs some asserts. Can be removed
0170     // without change of behaviour
0171     const detail::ScopedGsfInfoPrinterAndChecker printer(state, stepper,
0172                                                          navigator, logger());
0173 
0174     // We only need to do something if we are on a surface
0175     if (!navigator.currentSurface(state.navigation)) {
0176       return;
0177     }
0178 
0179     const auto& surface = *navigator.currentSurface(state.navigation);
0180     ACTS_VERBOSE("Step is at surface " << surface.geometryId());
0181 
0182     // All components must be normalized at the beginning here, otherwise the
0183     // stepper misbehaves
0184     [[maybe_unused]] auto stepperComponents =
0185         stepper.constComponentIterable(state.stepping);
0186     assert(detail::weightsAreNormalized(
0187         stepperComponents, [](const auto& cmp) { return cmp.weight(); }));
0188 
0189     // All components must have status "on surface". It is however possible,
0190     // that currentSurface is nullptr and all components are "on surface" (e.g.,
0191     // for surfaces excluded from the navigation)
0192     using Status [[maybe_unused]] = IntersectionStatus;
0193     assert(std::all_of(
0194         stepperComponents.begin(), stepperComponents.end(),
0195         [](const auto& cmp) { return cmp.status() == Status::onSurface; }));
0196 
0197     // Early return if we already were on this surface TODO why is this
0198     // necessary
0199     const bool visited = rangeContainsValue(result.visitedSurfaces, &surface);
0200 
0201     if (visited) {
0202       ACTS_VERBOSE("Already visited surface, return");
0203       return;
0204     }
0205 
0206     result.visitedSurfaces.push_back(&surface);
0207 
0208     // Check what we have on this surface
0209     const auto foundSourceLink =
0210         m_cfg.inputMeasurements->find(surface.geometryId());
0211     const bool haveMaterial =
0212         navigator.currentSurface(state.navigation)->surfaceMaterial() &&
0213         !m_cfg.disableAllMaterialHandling;
0214     const bool haveMeasurement =
0215         foundSourceLink != m_cfg.inputMeasurements->end();
0216 
0217     ACTS_VERBOSE(std::boolalpha << "haveMaterial " << haveMaterial
0218                                 << ", haveMeasurement: " << haveMeasurement);
0219 
0220     ////////////////////////
0221     // The Core Algorithm
0222     ////////////////////////
0223 
0224     // Early return if nothing happens
0225     if (!haveMaterial && !haveMeasurement) {
0226       // No hole before first measurement
0227       if (result.processedStates > 0 && surface.associatedDetectorElement()) {
0228         TemporaryStates tmpStates;
0229         noMeasurementUpdate(state, stepper, navigator, result, tmpStates, true);
0230       }
0231       return;
0232     }
0233 
0234     // Update the counters. Note that this should be done before potential
0235     // material interactions, because if this is our last measurement this would
0236     // not influence the fit anymore.
0237     if (haveMeasurement) {
0238       result.maxPathXOverX0.update();
0239       result.sumPathXOverX0.update();
0240       result.nInvalidBetheHeitler.update();
0241     }
0242 
0243     for (auto cmp : stepper.componentIterable(state.stepping)) {
0244       cmp.singleStepper(stepper).transportCovarianceToBound(cmp.state(),
0245                                                             surface);
0246     }
0247 
0248     if (haveMaterial) {
0249       if (haveMeasurement) {
0250         applyMultipleScattering(state, stepper, navigator,
0251                                 MaterialUpdateStage::PreUpdate);
0252       } else {
0253         applyMultipleScattering(state, stepper, navigator,
0254                                 MaterialUpdateStage::FullUpdate);
0255       }
0256     }
0257 
0258     // We do not need the component cache here, we can just update our stepper
0259     // state with the filtered components.
0260     // NOTE because of early return before we know that we have a measurement
0261     if (!haveMaterial) {
0262       TemporaryStates tmpStates;
0263 
0264       auto res = kalmanUpdate(state, stepper, navigator, result, tmpStates,
0265                               foundSourceLink->second);
0266 
0267       if (!res.ok()) {
0268         setErrorOrAbort(res.error());
0269         return;
0270       }
0271 
0272       updateStepper(state, stepper, tmpStates);
0273     }
0274     // We have material, we thus need a component cache since we will
0275     // convolute the components and later reduce them again before updating
0276     // the stepper
0277     else {
0278       TemporaryStates tmpStates;
0279       Result<void> res;
0280 
0281       if (haveMeasurement) {
0282         res = kalmanUpdate(state, stepper, navigator, result, tmpStates,
0283                            foundSourceLink->second);
0284       } else {
0285         res = noMeasurementUpdate(state, stepper, navigator, result, tmpStates,
0286                                   false);
0287       }
0288 
0289       if (!res.ok()) {
0290         setErrorOrAbort(res.error());
0291         return;
0292       }
0293 
0294       // Reuse memory over all calls to the Actor in a single propagation
0295       std::vector<ComponentCache>& componentCache = result.componentCache;
0296       componentCache.clear();
0297 
0298       convoluteComponents(state, stepper, navigator, tmpStates, componentCache,
0299                           result);
0300 
0301       if (componentCache.empty()) {
0302         ACTS_WARNING(
0303             "No components left after applying energy loss. "
0304             "Is the weight cutoff "
0305             << m_cfg.weightCutoff << " too high?");
0306         ACTS_WARNING("Return to propagator without applying energy loss");
0307         return;
0308       }
0309 
0310       // reduce component number
0311       const auto finalCmpNumber = std::min(
0312           static_cast<std::size_t>(stepper.maxComponents), m_cfg.maxComponents);
0313       m_cfg.extensions.mixtureReducer(componentCache, finalCmpNumber, surface);
0314 
0315       removeLowWeightComponents(componentCache);
0316 
0317       updateStepper(state, stepper, navigator, componentCache);
0318     }
0319 
0320     // If we have only done preUpdate before, now do postUpdate
0321     if (haveMaterial && haveMeasurement) {
0322       applyMultipleScattering(state, stepper, navigator,
0323                               MaterialUpdateStage::PostUpdate);
0324     }
0325   }
0326 
0327   template <typename propagator_state_t, typename stepper_t,
0328             typename navigator_t>
0329   bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0330                   const navigator_t& /*navigator*/, const result_type& result,
0331                   const Logger& /*logger*/) const {
0332     if (m_cfg.numberMeasurements &&
0333         result.measurementStates == m_cfg.numberMeasurements) {
0334       ACTS_VERBOSE("Stop navigation because all measurements are found");
0335       return true;
0336     }
0337 
0338     return false;
0339   }
0340 
0341   template <typename propagator_state_t, typename stepper_t,
0342             typename navigator_t>
0343   void convoluteComponents(propagator_state_t& state, const stepper_t& stepper,
0344                            const navigator_t& navigator,
0345                            const TemporaryStates& tmpStates,
0346                            std::vector<ComponentCache>& componentCache,
0347                            result_type& result) const {
0348     auto cmps = stepper.componentIterable(state.stepping);
0349     double pathXOverX0 = 0.0;
0350     for (auto [idx, cmp] : zip(tmpStates.tips, cmps)) {
0351       auto proxy = tmpStates.traj.getTrackState(idx);
0352 
0353       BoundTrackParameters bound(proxy.referenceSurface().getSharedPtr(),
0354                                  proxy.filtered(), proxy.filteredCovariance(),
0355                                  stepper.particleHypothesis(state.stepping));
0356 
0357       pathXOverX0 +=
0358           applyBetheHeitler(state, navigator, bound, tmpStates.weights.at(idx),
0359                             componentCache, result);
0360     }
0361 
0362     // Store average material seen by the components
0363     // Should not be too broadly distributed
0364     result.sumPathXOverX0.tmp() += pathXOverX0 / tmpStates.tips.size();
0365   }
0366 
0367   template <typename propagator_state_t, typename navigator_t>
0368   double applyBetheHeitler(const propagator_state_t& state,
0369                            const navigator_t& navigator,
0370                            const BoundTrackParameters& old_bound,
0371                            const double old_weight,
0372                            std::vector<ComponentCache>& componentCaches,
0373                            result_type& result) const {
0374     const auto& surface = *navigator.currentSurface(state.navigation);
0375     const auto p_prev = old_bound.absoluteMomentum();
0376     const auto& particleHypothesis = old_bound.particleHypothesis();
0377 
0378     // Evaluate material slab
0379     auto slab = surface.surfaceMaterial()->materialSlab(
0380         old_bound.position(state.geoContext), state.options.direction,
0381         MaterialUpdateStage::FullUpdate);
0382 
0383     const auto pathCorrection = surface.pathCorrection(
0384         state.geoContext, old_bound.position(state.geoContext),
0385         old_bound.direction());
0386     slab.scaleThickness(pathCorrection);
0387 
0388     const double pathXOverX0 = slab.thicknessInX0();
0389     result.maxPathXOverX0.tmp() =
0390         std::max(result.maxPathXOverX0.tmp(), pathXOverX0);
0391 
0392     // Emit a warning if the approximation is not valid for this x/x0
0393     if (!m_cfg.bethe_heitler_approx->validXOverX0(pathXOverX0)) {
0394       ++result.nInvalidBetheHeitler.tmp();
0395       ACTS_DEBUG(
0396           "Bethe-Heitler approximation encountered invalid value for x/x0="
0397           << pathXOverX0 << " at surface " << surface.geometryId());
0398     }
0399 
0400     // Get the mixture
0401     const auto mixture = m_cfg.bethe_heitler_approx->mixture(pathXOverX0);
0402 
0403     // Create all possible new components
0404     for (const auto& gaussian : mixture) {
0405       // Here we combine the new child weight with the parent weight.
0406       // However, this must be later re-adjusted
0407       const auto new_weight = gaussian.weight * old_weight;
0408 
0409       if (new_weight < m_cfg.weightCutoff) {
0410         ACTS_VERBOSE("Skip component with weight " << new_weight);
0411         continue;
0412       }
0413 
0414       if (gaussian.mean < 1.e-8) {
0415         ACTS_WARNING("Skip component with gaussian " << gaussian.mean << " +- "
0416                                                      << gaussian.var);
0417         continue;
0418       }
0419 
0420       // compute delta p from mixture and update parameters
0421       auto new_pars = old_bound.parameters();
0422 
0423       const auto delta_p = [&]() {
0424         if (state.options.direction == Direction::Forward()) {
0425           return p_prev * (gaussian.mean - 1.);
0426         } else {
0427           return p_prev * (1. / gaussian.mean - 1.);
0428         }
0429       }();
0430 
0431       assert(p_prev + delta_p > 0. && "new momentum must be > 0");
0432       new_pars[eBoundQOverP] =
0433           particleHypothesis.qOverP(p_prev + delta_p, old_bound.charge());
0434 
0435       // compute inverse variance of p from mixture and update covariance
0436       auto new_cov = old_bound.covariance().value();
0437 
0438       const auto varInvP = [&]() {
0439         if (state.options.direction == Direction::Forward()) {
0440           const auto f = 1. / (p_prev * gaussian.mean);
0441           return f * f * gaussian.var;
0442         } else {
0443           return gaussian.var / (p_prev * p_prev);
0444         }
0445       }();
0446 
0447       new_cov(eBoundQOverP, eBoundQOverP) += varInvP;
0448       assert(std::isfinite(new_cov(eBoundQOverP, eBoundQOverP)) &&
0449              "new cov not finite");
0450 
0451       // Set the remaining things and push to vector
0452       componentCaches.push_back({new_weight, new_pars, new_cov});
0453     }
0454 
0455     return pathXOverX0;
0456   }
0457 
0458   /// Remove components with low weights and renormalize from the component
0459   /// cache
0460   /// TODO This function does not expect normalized components, but this
0461   /// could be redundant work...
0462   void removeLowWeightComponents(std::vector<ComponentCache>& cmps) const {
0463     auto proj = [](auto& cmp) -> double& { return cmp.weight; };
0464 
0465     detail::normalizeWeights(cmps, proj);
0466 
0467     auto new_end = std::remove_if(cmps.begin(), cmps.end(), [&](auto& cmp) {
0468       return proj(cmp) < m_cfg.weightCutoff;
0469     });
0470 
0471     // In case we would remove all components, keep only the largest
0472     if (std::distance(cmps.begin(), new_end) == 0) {
0473       cmps = {*std::max_element(
0474           cmps.begin(), cmps.end(),
0475           [&](auto& a, auto& b) { return proj(a) < proj(b); })};
0476       cmps.front().weight = 1.0;
0477     } else {
0478       cmps.erase(new_end, cmps.end());
0479       detail::normalizeWeights(cmps, proj);
0480     }
0481   }
0482 
0483   /// Function that updates the stepper from the MultiTrajectory
0484   template <typename propagator_state_t, typename stepper_t>
0485   void updateStepper(propagator_state_t& state, const stepper_t& stepper,
0486                      const TemporaryStates& tmpStates) const {
0487     auto cmps = stepper.componentIterable(state.stepping);
0488 
0489     for (auto [idx, cmp] : zip(tmpStates.tips, cmps)) {
0490       // we set ignored components to missed, so we can remove them after
0491       // the loop
0492       if (tmpStates.weights.at(idx) < m_cfg.weightCutoff) {
0493         cmp.status() = IntersectionStatus::unreachable;
0494         continue;
0495       }
0496 
0497       auto proxy = tmpStates.traj.getTrackState(idx);
0498 
0499       cmp.pars() =
0500           MultiTrajectoryHelpers::freeFiltered(state.geoContext, proxy);
0501       cmp.cov() = proxy.filteredCovariance();
0502       cmp.weight() = tmpStates.weights.at(idx);
0503     }
0504 
0505     stepper.removeMissedComponents(state.stepping);
0506 
0507     // TODO we have two normalization passes here now, this can probably be
0508     // optimized
0509     detail::normalizeWeights(cmps,
0510                              [&](auto cmp) -> double& { return cmp.weight(); });
0511   }
0512 
0513   /// Function that updates the stepper from the ComponentCache
0514   template <typename propagator_state_t, typename stepper_t,
0515             typename navigator_t>
0516   void updateStepper(propagator_state_t& state, const stepper_t& stepper,
0517                      const navigator_t& navigator,
0518                      const std::vector<ComponentCache>& componentCache) const {
0519     const auto& surface = *navigator.currentSurface(state.navigation);
0520 
0521     // Clear components before adding new ones
0522     stepper.clearComponents(state.stepping);
0523 
0524     // Finally loop over components
0525     for (const auto& [weight, pars, cov] : componentCache) {
0526       // Add the component to the stepper
0527       BoundTrackParameters bound(surface.getSharedPtr(), pars, cov,
0528                                  stepper.particleHypothesis(state.stepping));
0529 
0530       auto res = stepper.addComponent(state.stepping, std::move(bound), weight);
0531 
0532       if (!res.ok()) {
0533         ACTS_ERROR("Error adding component to MultiStepper");
0534         continue;
0535       }
0536 
0537       auto& cmp = *res;
0538       auto freeParams = cmp.pars();
0539       cmp.jacToGlobal() = surface.boundToFreeJacobian(
0540           state.geoContext, freeParams.template segment<3>(eFreePos0),
0541           freeParams.template segment<3>(eFreeDir0));
0542       cmp.pathAccumulated() = state.stepping.pathAccumulated;
0543       cmp.jacobian() = BoundMatrix::Identity();
0544       cmp.derivative() = FreeVector::Zero();
0545       cmp.jacTransport() = FreeMatrix::Identity();
0546     }
0547   }
0548 
0549   /// This function performs the kalman update, computes the new posterior
0550   /// weights, renormalizes all components, and does some statistics.
0551   template <typename propagator_state_t, typename stepper_t,
0552             typename navigator_t>
0553   Result<void> kalmanUpdate(propagator_state_t& state, const stepper_t& stepper,
0554                             const navigator_t& navigator, result_type& result,
0555                             TemporaryStates& tmpStates,
0556                             const SourceLink& sourceLink) const {
0557     const auto& surface = *navigator.currentSurface(state.navigation);
0558 
0559     // Boolean flag, to distinguish measurement and outlier states. This flag
0560     // is only modified by the valid-measurement-branch, so only if there
0561     // isn't any valid measurement state, the flag stays false and the state
0562     // is thus counted as an outlier
0563     bool is_valid_measurement = false;
0564 
0565     auto cmps = stepper.componentIterable(state.stepping);
0566     for (auto cmp : cmps) {
0567       auto singleState = cmp.singleState(state);
0568       const auto& singleStepper = cmp.singleStepper(stepper);
0569 
0570       auto trackStateProxyRes = detail::kalmanHandleMeasurement(
0571           *m_cfg.calibrationContext, singleState, singleStepper,
0572           m_cfg.extensions, surface, sourceLink, tmpStates.traj,
0573           MultiTrajectoryTraits::kInvalid, false, logger());
0574 
0575       if (!trackStateProxyRes.ok()) {
0576         return trackStateProxyRes.error();
0577       }
0578 
0579       const auto& trackStateProxy = *trackStateProxyRes;
0580 
0581       // If at least one component is no outlier, we consider the whole thing
0582       // as a measurementState
0583       if (trackStateProxy.typeFlags().test(TrackStateFlag::MeasurementFlag)) {
0584         is_valid_measurement = true;
0585       }
0586 
0587       tmpStates.tips.push_back(trackStateProxy.index());
0588       tmpStates.weights[tmpStates.tips.back()] = cmp.weight();
0589     }
0590 
0591     computePosteriorWeights(tmpStates.traj, tmpStates.tips, tmpStates.weights);
0592 
0593     detail::normalizeWeights(tmpStates.tips, [&](auto idx) -> double& {
0594       return tmpStates.weights.at(idx);
0595     });
0596 
0597     // Do the statistics
0598     ++result.processedStates;
0599 
0600     // TODO should outlier states also be counted here?
0601     if (is_valid_measurement) {
0602       ++result.measurementStates;
0603     }
0604 
0605     updateMultiTrajectory(result, tmpStates, surface);
0606 
0607     result.lastMeasurementTip = result.currentTip;
0608     result.lastMeasurementSurface = &surface;
0609 
0610     // Note, that we do not normalize the components here.
0611     // This must be done before initializing the backward pass.
0612     result.lastMeasurementComponents.clear();
0613 
0614     FiltProjector proj{tmpStates.traj, tmpStates.weights};
0615     for (const auto& idx : tmpStates.tips) {
0616       const auto& [w, p, c] = proj(idx);
0617       // TODO check why zero weight can occur
0618       if (w > 0.0) {
0619         result.lastMeasurementComponents.push_back({w, p, c});
0620       }
0621     }
0622 
0623     // Return success
0624     return Result<void>::success();
0625   }
0626 
0627   template <typename propagator_state_t, typename stepper_t,
0628             typename navigator_t>
0629   Result<void> noMeasurementUpdate(propagator_state_t& state,
0630                                    const stepper_t& stepper,
0631                                    const navigator_t& navigator,
0632                                    result_type& result,
0633                                    TemporaryStates& tmpStates,
0634                                    bool doCovTransport) const {
0635     const auto& surface = *navigator.currentSurface(state.navigation);
0636 
0637     const bool precedingMeasurementExists = result.processedStates > 0;
0638 
0639     // Initialize as true, so that any component can flip it. However, all
0640     // components should behave the same
0641     bool isHole = true;
0642 
0643     for (auto cmp : stepper.componentIterable(state.stepping)) {
0644       auto& singleState = cmp.state();
0645       const auto& singleStepper = cmp.singleStepper(stepper);
0646 
0647       // There is some redundant checking inside this function, but do this for
0648       // now until we measure this is significant
0649       auto trackStateProxyRes = detail::kalmanHandleNoMeasurement(
0650           singleState, singleStepper, surface, tmpStates.traj,
0651           MultiTrajectoryTraits::kInvalid, doCovTransport, logger(),
0652           precedingMeasurementExists);
0653 
0654       if (!trackStateProxyRes.ok()) {
0655         return trackStateProxyRes.error();
0656       }
0657 
0658       const auto& trackStateProxy = *trackStateProxyRes;
0659 
0660       if (!trackStateProxy.typeFlags().test(TrackStateFlag::HoleFlag)) {
0661         isHole = false;
0662       }
0663 
0664       tmpStates.tips.push_back(trackStateProxy.index());
0665       tmpStates.weights[tmpStates.tips.back()] = cmp.weight();
0666     }
0667 
0668     // These things should only be done once for all components
0669     if (isHole) {
0670       ++result.measurementHoles;
0671     }
0672 
0673     ++result.processedStates;
0674 
0675     updateMultiTrajectory(result, tmpStates, surface);
0676 
0677     return Result<void>::success();
0678   }
0679 
0680   /// Apply the multiple scattering to the state
0681   template <typename propagator_state_t, typename stepper_t,
0682             typename navigator_t>
0683   void applyMultipleScattering(propagator_state_t& state,
0684                                const stepper_t& stepper,
0685                                const navigator_t& navigator,
0686                                const MaterialUpdateStage& updateStage =
0687                                    MaterialUpdateStage::FullUpdate) const {
0688     const auto& surface = *navigator.currentSurface(state.navigation);
0689 
0690     for (auto cmp : stepper.componentIterable(state.stepping)) {
0691       auto singleState = cmp.singleState(state);
0692       const auto& singleStepper = cmp.singleStepper(stepper);
0693 
0694       detail::PointwiseMaterialInteraction interaction(&surface, singleState,
0695                                                        singleStepper);
0696       if (interaction.evaluateMaterialSlab(singleState, navigator,
0697                                            updateStage)) {
0698         // In the Gsf we only need to handle the multiple scattering
0699         interaction.evaluatePointwiseMaterialInteraction(
0700             m_cfg.multipleScattering, false);
0701 
0702         // Screen out material effects info
0703         ACTS_VERBOSE("Material effects on surface: "
0704                      << surface.geometryId()
0705                      << " at update stage: " << updateStage << " are :");
0706         ACTS_VERBOSE("eLoss = "
0707                      << interaction.Eloss << ", "
0708                      << "variancePhi = " << interaction.variancePhi << ", "
0709                      << "varianceTheta = " << interaction.varianceTheta << ", "
0710                      << "varianceQoverP = " << interaction.varianceQoverP);
0711 
0712         // Update the state and stepper with material effects
0713         interaction.updateState(singleState, singleStepper, addNoise);
0714 
0715         assert(singleState.stepping.cov.array().isFinite().all() &&
0716                "covariance not finite after multi scattering");
0717       }
0718     }
0719   }
0720 
0721   void updateMultiTrajectory(result_type& result,
0722                              const TemporaryStates& tmpStates,
0723                              const Surface& surface) const {
0724     using PrtProjector =
0725         MultiTrajectoryProjector<StatesType::ePredicted, traj_t>;
0726     using FltProjector =
0727         MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0728 
0729     if (!m_cfg.inReversePass) {
0730       const auto firstCmpProxy =
0731           tmpStates.traj.getTrackState(tmpStates.tips.front());
0732       const auto isMeasurement =
0733           firstCmpProxy.typeFlags().test(MeasurementFlag);
0734 
0735       const auto mask =
0736           isMeasurement
0737               ? TrackStatePropMask::Calibrated | TrackStatePropMask::Predicted |
0738                     TrackStatePropMask::Filtered | TrackStatePropMask::Smoothed
0739               : TrackStatePropMask::Calibrated | TrackStatePropMask::Predicted;
0740 
0741       auto proxy = result.fittedStates->makeTrackState(mask, result.currentTip);
0742       result.currentTip = proxy.index();
0743 
0744       proxy.setReferenceSurface(surface.getSharedPtr());
0745       proxy.copyFrom(firstCmpProxy, mask);
0746 
0747       auto [prtMean, prtCov] =
0748           mergeGaussianMixture(tmpStates.tips, surface, m_cfg.mergeMethod,
0749                                PrtProjector{tmpStates.traj, tmpStates.weights});
0750       proxy.predicted() = prtMean;
0751       proxy.predictedCovariance() = prtCov;
0752 
0753       if (isMeasurement) {
0754         auto [fltMean, fltCov] = mergeGaussianMixture(
0755             tmpStates.tips, surface, m_cfg.mergeMethod,
0756             FltProjector{tmpStates.traj, tmpStates.weights});
0757         proxy.filtered() = fltMean;
0758         proxy.filteredCovariance() = fltCov;
0759         proxy.smoothed() = BoundVector::Constant(-2);
0760         proxy.smoothedCovariance() = BoundSquareMatrix::Constant(-2);
0761       } else {
0762         proxy.shareFrom(TrackStatePropMask::Predicted,
0763                         TrackStatePropMask::Filtered);
0764       }
0765 
0766     } else {
0767       assert((result.currentTip != MultiTrajectoryTraits::kInvalid &&
0768               "tip not valid"));
0769 
0770       result.fittedStates->applyBackwards(
0771           result.currentTip, [&](auto trackState) {
0772             auto fSurface = &trackState.referenceSurface();
0773             if (fSurface == &surface) {
0774               result.surfacesVisitedBwdAgain.push_back(&surface);
0775 
0776               if (trackState.hasSmoothed()) {
0777                 const auto [smtMean, smtCov] = mergeGaussianMixture(
0778                     tmpStates.tips, surface, m_cfg.mergeMethod,
0779                     FltProjector{tmpStates.traj, tmpStates.weights});
0780 
0781                 trackState.smoothed() = smtMean;
0782                 trackState.smoothedCovariance() = smtCov;
0783               }
0784               return false;
0785             }
0786             return true;
0787           });
0788     }
0789   }
0790 
0791   /// Set the relevant options that can be set from the Options struct all in
0792   /// one place
0793   void setOptions(const GsfOptions<traj_t>& options) {
0794     m_cfg.maxComponents = options.maxComponents;
0795     m_cfg.extensions = options.extensions;
0796     m_cfg.abortOnError = options.abortOnError;
0797     m_cfg.disableAllMaterialHandling = options.disableAllMaterialHandling;
0798     m_cfg.weightCutoff = options.weightCutoff;
0799     m_cfg.mergeMethod = options.componentMergeMethod;
0800     m_cfg.calibrationContext = &options.calibrationContext.get();
0801   }
0802 };
0803 
0804 }  // namespace Acts::detail