Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:11:05

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Definitions/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::optional<MultiComponentBoundTrackParameters> lastMeasurementState;
0044 
0045   /// Some counting
0046   std::size_t measurementStates = 0;
0047   std::size_t measurementHoles = 0;
0048   std::size_t processedStates = 0;
0049 
0050   std::vector<const Surface*> visitedSurfaces;
0051   std::vector<const Surface*> surfacesVisitedBwdAgain;
0052 
0053   /// Statistics about material encounterings
0054   Updatable<std::size_t> nInvalidBetheHeitler;
0055   Updatable<double> maxPathXOverX0;
0056   Updatable<double> sumPathXOverX0;
0057 
0058   // Propagate potential errors to the outside
0059   Result<void> result{Result<void>::success()};
0060 
0061   // Internal: component cache to avoid reallocation
0062   std::vector<GsfComponent> componentCache;
0063 };
0064 
0065 /// The actor carrying out the GSF algorithm
0066 template <typename bethe_heitler_approx_t, typename traj_t>
0067 struct GsfActor {
0068   /// Enforce default construction
0069   GsfActor() = default;
0070 
0071   using ComponentCache = GsfComponent;
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 bethe_heitler_approx_t* 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   struct TemporaryStates {
0127     traj_t traj;
0128     std::vector<MultiTrajectoryTraits::IndexType> tips;
0129     std::map<MultiTrajectoryTraits::IndexType, double> weights;
0130   };
0131 
0132   /// @brief GSF actor operation
0133   ///
0134   /// @tparam propagator_state_t is the type of Propagator state
0135   /// @tparam stepper_t Type of the stepper
0136   /// @tparam navigator_t Type of the navigator
0137   ///
0138   /// @param state is the mutable propagator state object
0139   /// @param stepper The stepper in use
0140   /// @param result is the mutable result state object
0141   template <typename propagator_state_t, typename stepper_t,
0142             typename navigator_t>
0143   void act(propagator_state_t& state, const stepper_t& stepper,
0144            const navigator_t& navigator, result_type& result,
0145            const Logger& /*logger*/) const {
0146     assert(result.fittedStates && "No MultiTrajectory set");
0147 
0148     // Return is we found an error earlier
0149     if (!result.result.ok()) {
0150       ACTS_WARNING("result.result not ok, return!");
0151       return;
0152     }
0153 
0154     // Set error or abort utility
0155     auto setErrorOrAbort = [&](auto error) {
0156       if (m_cfg.abortOnError) {
0157         std::abort();
0158       } else {
0159         result.result = error;
0160       }
0161     };
0162 
0163     // Prints some VERBOSE things and performs some asserts. Can be removed
0164     // without change of behaviour
0165     const detail::ScopedGsfInfoPrinterAndChecker printer(state, stepper,
0166                                                          navigator, logger());
0167 
0168     // We only need to do something if we are on a surface
0169     if (!navigator.currentSurface(state.navigation)) {
0170       return;
0171     }
0172 
0173     const auto& surface = *navigator.currentSurface(state.navigation);
0174     ACTS_VERBOSE("Step is at surface " << surface.geometryId());
0175 
0176     // All components must be normalized at the beginning here, otherwise the
0177     // stepper misbehaves
0178     [[maybe_unused]] auto stepperComponents =
0179         stepper.constComponentIterable(state.stepping);
0180     assert(detail::weightsAreNormalized(
0181         stepperComponents, [](const auto& cmp) { return cmp.weight(); }));
0182 
0183     // All components must have status "on surface". It is however possible,
0184     // that currentSurface is nullptr and all components are "on surface" (e.g.,
0185     // for surfaces excluded from the navigation)
0186     using Status [[maybe_unused]] = IntersectionStatus;
0187     assert(std::all_of(
0188         stepperComponents.begin(), stepperComponents.end(),
0189         [](const auto& cmp) { return cmp.status() == Status::onSurface; }));
0190 
0191     // Early return if we already were on this surface TODO why is this
0192     // necessary
0193     const bool visited = rangeContainsValue(result.visitedSurfaces, &surface);
0194 
0195     if (visited) {
0196       ACTS_VERBOSE("Already visited surface, return");
0197       return;
0198     }
0199 
0200     result.visitedSurfaces.push_back(&surface);
0201 
0202     // Check what we have on this surface
0203     const auto foundSourceLink =
0204         m_cfg.inputMeasurements->find(surface.geometryId());
0205     const bool haveMaterial =
0206         navigator.currentSurface(state.navigation)->surfaceMaterial() &&
0207         !m_cfg.disableAllMaterialHandling;
0208     const bool haveMeasurement =
0209         foundSourceLink != m_cfg.inputMeasurements->end();
0210 
0211     ACTS_VERBOSE(std::boolalpha << "haveMaterial " << haveMaterial
0212                                 << ", haveMeasurement: " << haveMeasurement);
0213 
0214     ////////////////////////
0215     // The Core Algorithm
0216     ////////////////////////
0217 
0218     // Early return if nothing happens
0219     if (!haveMaterial && !haveMeasurement) {
0220       // No hole before first measurement
0221       if (result.processedStates > 0 && surface.associatedDetectorElement()) {
0222         TemporaryStates tmpStates;
0223         noMeasurementUpdate(state, stepper, navigator, result, tmpStates, true);
0224       }
0225       return;
0226     }
0227 
0228     // Update the counters. Note that this should be done before potential
0229     // material interactions, because if this is our last measurement this would
0230     // not influence the fit anymore.
0231     if (haveMeasurement) {
0232       result.maxPathXOverX0.update();
0233       result.sumPathXOverX0.update();
0234       result.nInvalidBetheHeitler.update();
0235     }
0236 
0237     for (auto cmp : stepper.componentIterable(state.stepping)) {
0238       auto singleState = cmp.singleState(state);
0239       cmp.singleStepper(stepper).transportCovarianceToBound(
0240           singleState.stepping, surface);
0241     }
0242 
0243     if (haveMaterial) {
0244       if (haveMeasurement) {
0245         applyMultipleScattering(state, stepper, navigator,
0246                                 MaterialUpdateStage::PreUpdate);
0247       } else {
0248         applyMultipleScattering(state, stepper, navigator,
0249                                 MaterialUpdateStage::FullUpdate);
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, navigator, result, tmpStates,
0260                               foundSourceLink->second);
0261 
0262       if (!res.ok()) {
0263         setErrorOrAbort(res.error());
0264         return;
0265       }
0266 
0267       updateStepper(state, stepper, tmpStates);
0268     }
0269     // We have material, we thus need a component cache since we will
0270     // convolute the components and later reduce them again before updating
0271     // the stepper
0272     else {
0273       TemporaryStates tmpStates;
0274       Result<void> res;
0275 
0276       if (haveMeasurement) {
0277         res = kalmanUpdate(state, stepper, navigator, result, tmpStates,
0278                            foundSourceLink->second);
0279       } else {
0280         res = noMeasurementUpdate(state, stepper, navigator, result, tmpStates,
0281                                   false);
0282       }
0283 
0284       if (!res.ok()) {
0285         setErrorOrAbort(res.error());
0286         return;
0287       }
0288 
0289       // Reuse memory over all calls to the Actor in a single propagation
0290       std::vector<ComponentCache>& componentCache = result.componentCache;
0291       componentCache.clear();
0292 
0293       convoluteComponents(state, stepper, navigator, tmpStates, componentCache,
0294                           result);
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;
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);
0311 
0312       updateStepper(state, stepper, navigator, componentCache);
0313     }
0314 
0315     // If we have only done preUpdate before, now do postUpdate
0316     if (haveMaterial && haveMeasurement) {
0317       applyMultipleScattering(state, stepper, navigator,
0318                               MaterialUpdateStage::PostUpdate);
0319     }
0320   }
0321 
0322   template <typename propagator_state_t, typename stepper_t,
0323             typename navigator_t>
0324   bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0325                   const navigator_t& /*navigator*/, const result_type& result,
0326                   const Logger& /*logger*/) const {
0327     if (m_cfg.numberMeasurements &&
0328         result.measurementStates == m_cfg.numberMeasurements) {
0329       ACTS_VERBOSE("Stop navigation because all measurements are found");
0330       return true;
0331     }
0332 
0333     return false;
0334   }
0335 
0336   template <typename propagator_state_t, typename stepper_t,
0337             typename navigator_t>
0338   void convoluteComponents(propagator_state_t& state, const stepper_t& stepper,
0339                            const navigator_t& navigator,
0340                            const TemporaryStates& tmpStates,
0341                            std::vector<ComponentCache>& componentCache,
0342                            result_type& result) const {
0343     auto cmps = stepper.componentIterable(state.stepping);
0344     double pathXOverX0 = 0.0;
0345     for (auto [idx, cmp] : zip(tmpStates.tips, cmps)) {
0346       auto proxy = tmpStates.traj.getTrackState(idx);
0347 
0348       BoundTrackParameters bound(proxy.referenceSurface().getSharedPtr(),
0349                                  proxy.filtered(), proxy.filteredCovariance(),
0350                                  stepper.particleHypothesis(state.stepping));
0351 
0352       pathXOverX0 +=
0353           applyBetheHeitler(state, navigator, bound, tmpStates.weights.at(idx),
0354                             componentCache, result);
0355     }
0356 
0357     // Store average material seen by the components
0358     // Should not be too broadly distributed
0359     result.sumPathXOverX0.tmp() += pathXOverX0 / tmpStates.tips.size();
0360   }
0361 
0362   template <typename propagator_state_t, typename navigator_t>
0363   double applyBetheHeitler(const propagator_state_t& state,
0364                            const navigator_t& navigator,
0365                            const BoundTrackParameters& old_bound,
0366                            const double old_weight,
0367                            std::vector<ComponentCache>& componentCaches,
0368                            result_type& result) const {
0369     const auto& surface = *navigator.currentSurface(state.navigation);
0370     const auto p_prev = old_bound.absoluteMomentum();
0371     const auto& particleHypothesis = old_bound.particleHypothesis();
0372 
0373     // Evaluate material slab
0374     auto slab = surface.surfaceMaterial()->materialSlab(
0375         old_bound.position(state.geoContext), state.options.direction,
0376         MaterialUpdateStage::FullUpdate);
0377 
0378     const auto pathCorrection = surface.pathCorrection(
0379         state.geoContext, old_bound.position(state.geoContext),
0380         old_bound.direction());
0381     slab.scaleThickness(pathCorrection);
0382 
0383     const double pathXOverX0 = slab.thicknessInX0();
0384     result.maxPathXOverX0.tmp() =
0385         std::max(result.maxPathXOverX0.tmp(), pathXOverX0);
0386 
0387     // Emit a warning if the approximation is not valid for this x/x0
0388     if (!m_cfg.bethe_heitler_approx->validXOverX0(pathXOverX0)) {
0389       ++result.nInvalidBetheHeitler.tmp();
0390       ACTS_DEBUG(
0391           "Bethe-Heitler approximation encountered invalid value for x/x0="
0392           << pathXOverX0 << " at surface " << surface.geometryId());
0393     }
0394 
0395     // Get the mixture
0396     const auto mixture = m_cfg.bethe_heitler_approx->mixture(pathXOverX0);
0397 
0398     // Create all possible new components
0399     for (const auto& gaussian : mixture) {
0400       // Here we combine the new child weight with the parent weight.
0401       // However, this must be later re-adjusted
0402       const auto new_weight = gaussian.weight * old_weight;
0403 
0404       if (new_weight < m_cfg.weightCutoff) {
0405         ACTS_VERBOSE("Skip component with weight " << new_weight);
0406         continue;
0407       }
0408 
0409       if (gaussian.mean < 1.e-8) {
0410         ACTS_WARNING("Skip component with gaussian " << gaussian.mean << " +- "
0411                                                      << gaussian.var);
0412         continue;
0413       }
0414 
0415       // compute delta p from mixture and update parameters
0416       auto new_pars = old_bound.parameters();
0417 
0418       const auto delta_p = [&]() {
0419         if (state.options.direction == Direction::Forward()) {
0420           return p_prev * (gaussian.mean - 1.);
0421         } else {
0422           return p_prev * (1. / gaussian.mean - 1.);
0423         }
0424       }();
0425 
0426       assert(p_prev + delta_p > 0. && "new momentum must be > 0");
0427       new_pars[eBoundQOverP] =
0428           particleHypothesis.qOverP(p_prev + delta_p, old_bound.charge());
0429 
0430       // compute inverse variance of p from mixture and update covariance
0431       auto new_cov = old_bound.covariance().value();
0432 
0433       const auto varInvP = [&]() {
0434         if (state.options.direction == Direction::Forward()) {
0435           const auto f = 1. / (p_prev * gaussian.mean);
0436           return f * f * gaussian.var;
0437         } else {
0438           return gaussian.var / (p_prev * p_prev);
0439         }
0440       }();
0441 
0442       new_cov(eBoundQOverP, eBoundQOverP) += varInvP;
0443       assert(std::isfinite(new_cov(eBoundQOverP, eBoundQOverP)) &&
0444              "new cov not finite");
0445 
0446       // Set the remaining things and push to vector
0447       componentCaches.push_back({new_weight, new_pars, new_cov});
0448     }
0449 
0450     return pathXOverX0;
0451   }
0452 
0453   /// Remove components with low weights and renormalize from the component
0454   /// cache
0455   /// TODO This function does not expect normalized components, but this
0456   /// could be redundant work...
0457   void removeLowWeightComponents(std::vector<ComponentCache>& cmps) const {
0458     auto proj = [](auto& cmp) -> double& { return cmp.weight; };
0459 
0460     detail::normalizeWeights(cmps, proj);
0461 
0462     auto new_end = std::remove_if(cmps.begin(), cmps.end(), [&](auto& cmp) {
0463       return proj(cmp) < m_cfg.weightCutoff;
0464     });
0465 
0466     // In case we would remove all components, keep only the largest
0467     if (std::distance(cmps.begin(), new_end) == 0) {
0468       cmps = {*std::max_element(
0469           cmps.begin(), cmps.end(),
0470           [&](auto& a, auto& b) { return proj(a) < proj(b); })};
0471       cmps.front().weight = 1.0;
0472     } else {
0473       cmps.erase(new_end, cmps.end());
0474       detail::normalizeWeights(cmps, proj);
0475     }
0476   }
0477 
0478   /// Function that updates the stepper from the MultiTrajectory
0479   template <typename propagator_state_t, typename stepper_t>
0480   void updateStepper(propagator_state_t& state, const stepper_t& stepper,
0481                      const TemporaryStates& tmpStates) const {
0482     auto cmps = stepper.componentIterable(state.stepping);
0483 
0484     for (auto [idx, cmp] : zip(tmpStates.tips, cmps)) {
0485       // we set ignored components to missed, so we can remove them after
0486       // the loop
0487       if (tmpStates.weights.at(idx) < m_cfg.weightCutoff) {
0488         cmp.status() = IntersectionStatus::unreachable;
0489         continue;
0490       }
0491 
0492       auto proxy = tmpStates.traj.getTrackState(idx);
0493 
0494       cmp.pars() =
0495           MultiTrajectoryHelpers::freeFiltered(state.geoContext, proxy);
0496       cmp.cov() = proxy.filteredCovariance();
0497       cmp.weight() = tmpStates.weights.at(idx);
0498     }
0499 
0500     stepper.removeMissedComponents(state.stepping);
0501 
0502     // TODO we have two normalization passes here now, this can probably be
0503     // optimized
0504     detail::normalizeWeights(cmps,
0505                              [&](auto cmp) -> double& { return cmp.weight(); });
0506   }
0507 
0508   /// Function that updates the stepper from the ComponentCache
0509   template <typename propagator_state_t, typename stepper_t,
0510             typename navigator_t>
0511   void updateStepper(propagator_state_t& state, const stepper_t& stepper,
0512                      const navigator_t& navigator,
0513                      const std::vector<ComponentCache>& componentCache) const {
0514     const auto& surface = *navigator.currentSurface(state.navigation);
0515 
0516     // Clear components before adding new ones
0517     stepper.clearComponents(state.stepping);
0518 
0519     // Finally loop over components
0520     for (const auto& [weight, pars, cov] : componentCache) {
0521       // Add the component to the stepper
0522       BoundTrackParameters bound(surface.getSharedPtr(), pars, cov,
0523                                  stepper.particleHypothesis(state.stepping));
0524 
0525       auto res = stepper.addComponent(state.stepping, std::move(bound), weight);
0526 
0527       if (!res.ok()) {
0528         ACTS_ERROR("Error adding component to MultiStepper");
0529         continue;
0530       }
0531 
0532       auto& cmp = *res;
0533       auto freeParams = cmp.pars();
0534       cmp.jacToGlobal() = surface.boundToFreeJacobian(
0535           state.geoContext, freeParams.template segment<3>(eFreePos0),
0536           freeParams.template segment<3>(eFreeDir0));
0537       cmp.pathAccumulated() = state.stepping.pathAccumulated;
0538       cmp.jacobian() = BoundMatrix::Identity();
0539       cmp.derivative() = FreeVector::Zero();
0540       cmp.jacTransport() = FreeMatrix::Identity();
0541     }
0542   }
0543 
0544   /// This function performs the kalman update, computes the new posterior
0545   /// weights, renormalizes all components, and does some statistics.
0546   template <typename propagator_state_t, typename stepper_t,
0547             typename navigator_t>
0548   Result<void> kalmanUpdate(propagator_state_t& state, const stepper_t& stepper,
0549                             const navigator_t& navigator, result_type& result,
0550                             TemporaryStates& tmpStates,
0551                             const SourceLink& sourceLink) const {
0552     const auto& surface = *navigator.currentSurface(state.navigation);
0553 
0554     // Boolean flag, to distinguish measurement and outlier states. This flag
0555     // is only modified by the valid-measurement-branch, so only if there
0556     // isn't any valid measurement state, the flag stays false and the state
0557     // is thus counted as an outlier
0558     bool is_valid_measurement = false;
0559 
0560     auto cmps = stepper.componentIterable(state.stepping);
0561     for (auto cmp : cmps) {
0562       auto singleState = cmp.singleState(state);
0563       const auto& singleStepper = cmp.singleStepper(stepper);
0564 
0565       auto trackStateProxyRes = detail::kalmanHandleMeasurement(
0566           *m_cfg.calibrationContext, singleState, singleStepper,
0567           m_cfg.extensions, surface, sourceLink, tmpStates.traj,
0568           MultiTrajectoryTraits::kInvalid, false, logger());
0569 
0570       if (!trackStateProxyRes.ok()) {
0571         return trackStateProxyRes.error();
0572       }
0573 
0574       const auto& trackStateProxy = *trackStateProxyRes;
0575 
0576       // If at least one component is no outlier, we consider the whole thing
0577       // as a measurementState
0578       if (trackStateProxy.typeFlags().test(TrackStateFlag::MeasurementFlag)) {
0579         is_valid_measurement = true;
0580       }
0581 
0582       tmpStates.tips.push_back(trackStateProxy.index());
0583       tmpStates.weights[tmpStates.tips.back()] = cmp.weight();
0584     }
0585 
0586     computePosteriorWeights(tmpStates.traj, tmpStates.tips, tmpStates.weights);
0587 
0588     detail::normalizeWeights(tmpStates.tips, [&](auto idx) -> double& {
0589       return tmpStates.weights.at(idx);
0590     });
0591 
0592     // Do the statistics
0593     ++result.processedStates;
0594 
0595     // TODO should outlier states also be counted here?
0596     if (is_valid_measurement) {
0597       ++result.measurementStates;
0598     }
0599 
0600     addCombinedState(result, tmpStates, surface);
0601     result.lastMeasurementTip = result.currentTip;
0602 
0603     using FiltProjector =
0604         MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0605     FiltProjector proj{tmpStates.traj, tmpStates.weights};
0606 
0607     std::vector<std::tuple<double, BoundVector, BoundMatrix>> v;
0608 
0609     // TODO Check why can zero weights can occur
0610     for (const auto& idx : tmpStates.tips) {
0611       const auto [w, p, c] = proj(idx);
0612       if (w > 0.0) {
0613         v.push_back({w, p, c});
0614       }
0615     }
0616 
0617     normalizeWeights(v, [](auto& c) -> double& { return std::get<double>(c); });
0618 
0619     result.lastMeasurementState = MultiComponentBoundTrackParameters(
0620         surface.getSharedPtr(), std::move(v),
0621         stepper.particleHypothesis(state.stepping));
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     auto cmps = stepper.componentIterable(state.stepping);
0644     for (auto cmp : cmps) {
0645       auto singleState = cmp.singleState(state);
0646       const auto& singleStepper = cmp.singleStepper(stepper);
0647 
0648       // There is some redundant checking inside this function, but do this for
0649       // now until we measure this is significant
0650       auto trackStateProxyRes = detail::kalmanHandleNoMeasurement(
0651           singleState, singleStepper, surface, tmpStates.traj,
0652           MultiTrajectoryTraits::kInvalid, doCovTransport, logger(),
0653           precedingMeasurementExists);
0654 
0655       if (!trackStateProxyRes.ok()) {
0656         return trackStateProxyRes.error();
0657       }
0658 
0659       const auto& trackStateProxy = *trackStateProxyRes;
0660 
0661       if (!trackStateProxy.typeFlags().test(TrackStateFlag::HoleFlag)) {
0662         isHole = false;
0663       }
0664 
0665       tmpStates.tips.push_back(trackStateProxy.index());
0666       tmpStates.weights[tmpStates.tips.back()] = cmp.weight();
0667     }
0668 
0669     // These things should only be done once for all components
0670     if (isHole) {
0671       ++result.measurementHoles;
0672     }
0673 
0674     ++result.processedStates;
0675 
0676     addCombinedState(result, tmpStates, surface);
0677 
0678     return Result<void>::success();
0679   }
0680 
0681   /// Apply the multiple scattering to the state
0682   template <typename propagator_state_t, typename stepper_t,
0683             typename navigator_t>
0684   void applyMultipleScattering(propagator_state_t& state,
0685                                const stepper_t& stepper,
0686                                const navigator_t& navigator,
0687                                const MaterialUpdateStage& updateStage =
0688                                    MaterialUpdateStage::FullUpdate) const {
0689     const auto& surface = *navigator.currentSurface(state.navigation);
0690 
0691     for (auto cmp : stepper.componentIterable(state.stepping)) {
0692       auto singleState = cmp.singleState(state);
0693       const auto& singleStepper = cmp.singleStepper(stepper);
0694 
0695       detail::PointwiseMaterialInteraction interaction(&surface, singleState,
0696                                                        singleStepper);
0697       if (interaction.evaluateMaterialSlab(singleState, navigator,
0698                                            updateStage)) {
0699         // In the Gsf we only need to handle the multiple scattering
0700         interaction.evaluatePointwiseMaterialInteraction(
0701             m_cfg.multipleScattering, false);
0702 
0703         // Screen out material effects info
0704         ACTS_VERBOSE("Material effects on surface: "
0705                      << surface.geometryId()
0706                      << " at update stage: " << updateStage << " are :");
0707         ACTS_VERBOSE("eLoss = "
0708                      << interaction.Eloss << ", "
0709                      << "variancePhi = " << interaction.variancePhi << ", "
0710                      << "varianceTheta = " << interaction.varianceTheta << ", "
0711                      << "varianceQoverP = " << interaction.varianceQoverP);
0712 
0713         // Update the state and stepper with material effects
0714         interaction.updateState(singleState, singleStepper, addNoise);
0715 
0716         assert(singleState.stepping.cov.array().isFinite().all() &&
0717                "covariance not finite after multi scattering");
0718       }
0719     }
0720   }
0721 
0722   void addCombinedState(result_type& result, 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