Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-06 08:10:14

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2023 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 http://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 // Workaround for building on clang+libstdc++
0012 #include "Acts/Utilities/detail/ReferenceWrapperAnyCompat.hpp"
0013 
0014 #include "Acts/Definitions/Algebra.hpp"
0015 #include "Acts/EventData/Measurement.hpp"
0016 #include "Acts/EventData/MeasurementHelpers.hpp"
0017 #include "Acts/EventData/MultiTrajectory.hpp"
0018 #include "Acts/EventData/MultiTrajectoryHelpers.hpp"
0019 #include "Acts/EventData/SourceLink.hpp"
0020 #include "Acts/EventData/TrackHelpers.hpp"
0021 #include "Acts/EventData/TrackParameters.hpp"
0022 #include "Acts/EventData/VectorMultiTrajectory.hpp"
0023 #include "Acts/Geometry/GeometryContext.hpp"
0024 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0025 #include "Acts/Material/MaterialSlab.hpp"
0026 #include "Acts/Propagator/AbortList.hpp"
0027 #include "Acts/Propagator/ActionList.hpp"
0028 #include "Acts/Propagator/ConstrainedStep.hpp"
0029 #include "Acts/Propagator/DirectNavigator.hpp"
0030 #include "Acts/Propagator/Navigator.hpp"
0031 #include "Acts/Propagator/Propagator.hpp"
0032 #include "Acts/Propagator/StandardAborters.hpp"
0033 #include "Acts/Propagator/StraightLineStepper.hpp"
0034 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0035 #include "Acts/TrackFitting/GlobalChiSquareFitterError.hpp"
0036 #include "Acts/TrackFitting/detail/VoidFitterComponents.hpp"
0037 #include "Acts/Utilities/CalibrationContext.hpp"
0038 #include "Acts/Utilities/Delegate.hpp"
0039 #include "Acts/Utilities/Logger.hpp"
0040 #include "Acts/Utilities/Result.hpp"
0041 
0042 #include <functional>
0043 #include <map>
0044 #include <memory>
0045 
0046 namespace Acts::Experimental {
0047 
0048 namespace Gx2fConstants {
0049 constexpr std::string_view gx2fnUpdateColumn = "Gx2fnUpdateColumn";
0050 }  // namespace Gx2fConstants
0051 
0052 /// Extension struct which holds delegates to customize the KF behavior
0053 template <typename traj_t>
0054 struct Gx2FitterExtensions {
0055   using TrackStateProxy = typename MultiTrajectory<traj_t>::TrackStateProxy;
0056   using ConstTrackStateProxy =
0057       typename MultiTrajectory<traj_t>::ConstTrackStateProxy;
0058   using Parameters = typename TrackStateProxy::Parameters;
0059 
0060   using Calibrator =
0061       Delegate<void(const GeometryContext&, const CalibrationContext&,
0062                     const SourceLink&, TrackStateProxy)>;
0063 
0064   using Updater = Delegate<Result<void>(const GeometryContext&, TrackStateProxy,
0065                                         Direction, const Logger&)>;
0066 
0067   using OutlierFinder = Delegate<bool(ConstTrackStateProxy)>;
0068 
0069   /// The Calibrator is a dedicated calibration algorithm that allows
0070   /// to calibrate measurements using track information, this could be
0071   /// e.g. sagging for wires, module deformations, etc.
0072   Calibrator calibrator;
0073 
0074   /// The updater incorporates measurement information into the track parameters
0075   Updater updater;
0076 
0077   /// Determines whether a measurement is supposed to be considered as an
0078   /// outlier
0079   OutlierFinder outlierFinder;
0080 
0081   /// Retrieves the associated surface from a source link
0082   SourceLinkSurfaceAccessor surfaceAccessor;
0083 
0084   /// Default constructor which connects the default void components
0085   Gx2FitterExtensions() {
0086     calibrator.template connect<&detail::voidFitterCalibrator<traj_t>>();
0087     updater.template connect<&detail::voidFitterUpdater<traj_t>>();
0088     outlierFinder.template connect<&detail::voidOutlierFinder<traj_t>>();
0089     surfaceAccessor.connect<&detail::voidSurfaceAccessor>();
0090   }
0091 };
0092 
0093 /// Combined options for the Global-Chi-Square fitter.
0094 ///
0095 /// @tparam traj_t The trajectory type
0096 template <typename traj_t>
0097 struct Gx2FitterOptions {
0098   /// PropagatorOptions with context.
0099   ///
0100   /// @param gctx The geometry context for this fit
0101   /// @param mctx The magnetic context for this fit
0102   /// @param cctx The calibration context for this fit
0103   /// @param extensions_ The KF extensions
0104   /// @param pOptions The plain propagator options
0105   /// @param rSurface The reference surface for the fit to be expressed at
0106   /// @param mScattering Whether to include multiple scattering
0107   /// @param eLoss Whether to include energy loss
0108   /// @param freeToBoundCorrection_ Correction for non-linearity effect during transform from free to bound
0109   /// @param nUpdateMax_ Max number of iterations for updating the parameters
0110   /// @param zeroField_ Disables the QoP fit in case of missing B-field
0111   /// @param relChi2changeCutOff_ Check for convergence (abort condition). Set to 0 to skip.
0112   Gx2FitterOptions(const GeometryContext& gctx,
0113                    const MagneticFieldContext& mctx,
0114                    std::reference_wrapper<const CalibrationContext> cctx,
0115                    Gx2FitterExtensions<traj_t> extensions_,
0116                    const PropagatorPlainOptions& pOptions,
0117                    const Surface* rSurface = nullptr, bool mScattering = false,
0118                    bool eLoss = false,
0119                    const FreeToBoundCorrection& freeToBoundCorrection_ =
0120                        FreeToBoundCorrection(false),
0121                    const std::size_t nUpdateMax_ = 5,
0122                    const bool zeroField_ = false,
0123                    double relChi2changeCutOff_ = 1e-5)
0124       : geoContext(gctx),
0125         magFieldContext(mctx),
0126         calibrationContext(cctx),
0127         extensions(extensions_),
0128         propagatorPlainOptions(pOptions),
0129         referenceSurface(rSurface),
0130         multipleScattering(mScattering),
0131         energyLoss(eLoss),
0132         freeToBoundCorrection(freeToBoundCorrection_),
0133         nUpdateMax(nUpdateMax_),
0134         zeroField(zeroField_),
0135         relChi2changeCutOff(relChi2changeCutOff_) {}
0136 
0137   /// Contexts are required and the options must not be default-constructible.
0138   Gx2FitterOptions() = delete;
0139 
0140   /// Context object for the geometry
0141   std::reference_wrapper<const GeometryContext> geoContext;
0142   /// Context object for the magnetic field
0143   std::reference_wrapper<const MagneticFieldContext> magFieldContext;
0144   /// context object for the calibration
0145   std::reference_wrapper<const CalibrationContext> calibrationContext;
0146 
0147   Gx2FitterExtensions<traj_t> extensions;
0148 
0149   /// The trivial propagator options
0150   PropagatorPlainOptions propagatorPlainOptions;
0151 
0152   /// The reference Surface
0153   const Surface* referenceSurface = nullptr;
0154 
0155   /// Whether to consider multiple scattering
0156   bool multipleScattering = false;
0157 
0158   /// Whether to consider energy loss
0159   bool energyLoss = false;
0160 
0161   /// Whether to include non-linear correction during global to local
0162   /// transformation
0163   FreeToBoundCorrection freeToBoundCorrection;
0164 
0165   /// Max number of iterations during the fit (abort condition)
0166   std::size_t nUpdateMax = 5;
0167 
0168   /// Disables the QoP fit in case of missing B-field
0169   bool zeroField = false;
0170 
0171   /// Check for convergence (abort condition). Set to 0 to skip.
0172   double relChi2changeCutOff = 1e-7;
0173 };
0174 
0175 template <typename traj_t>
0176 struct Gx2FitterResult {
0177   // Fitted states that the actor has handled.
0178   traj_t* fittedStates{nullptr};
0179 
0180   // This is the index of the 'tip' of the track stored in multitrajectory.
0181   // This corresponds to the last measurement state in the multitrajectory.
0182   // Since this KF only stores one trajectory, it is unambiguous.
0183   // SIZE_MAX is the start of a trajectory.
0184   std::size_t lastMeasurementIndex = Acts::MultiTrajectoryTraits::kInvalid;
0185 
0186   // This is the index of the 'tip' of the states stored in multitrajectory.
0187   // This corresponds to the last state in the multitrajectory.
0188   // Since this KF only stores one trajectory, it is unambiguous.
0189   // SIZE_MAX is the start of a trajectory.
0190   std::size_t lastTrackIndex = Acts::MultiTrajectoryTraits::kInvalid;
0191 
0192   // The optional Parameters at the provided surface
0193   std::optional<BoundTrackParameters> fittedParameters;
0194 
0195   // Counter for states with non-outlier measurements
0196   std::size_t measurementStates = 0;
0197 
0198   // Counter for measurements holes
0199   // A hole correspond to a surface with an associated detector element with no
0200   // associated measurement. Holes are only taken into account if they are
0201   // between the first and last measurements.
0202   std::size_t measurementHoles = 0;
0203 
0204   // Counter for handled states
0205   std::size_t processedStates = 0;
0206 
0207   // Counter for handled measurements
0208   std::size_t processedMeasurements = 0;
0209 
0210   // Indicator if track fitting has been done
0211   bool finished = false;
0212 
0213   // Measurement surfaces without hits
0214   std::vector<const Surface*> missedActiveSurfaces;
0215 
0216   // Measurement surfaces handled in both forward and
0217   // backward filtering
0218   std::vector<const Surface*> passedAgainSurfaces;
0219 
0220   Result<void> result{Result<void>::success()};
0221 
0222   // collectors
0223   std::vector<ActsScalar> collectorResiduals;
0224   std::vector<ActsScalar> collectorCovariances;
0225   std::vector<BoundVector> collectorProjectedJacobians;
0226 
0227   BoundMatrix jacobianFromStart = BoundMatrix::Identity();
0228 
0229   // Count how many surfaces have been hit
0230   std::size_t surfaceCount = 0;
0231 };
0232 
0233 /// Collector for the GX2F Actor
0234 /// The collector prepares each measurement for the actual fitting process. Each
0235 /// n-dimensional measurement is split into n 1-dimensional linearly independent
0236 /// measurements. Then the collector saves the following information:
0237 /// - Residual: Calculated from measurement and prediction
0238 /// - Covariance: The covariance of the measurement
0239 /// - Projected Jacobian: This implicitly contains the measurement type
0240 /// It also checks if the covariance is above a threshold, to detect and avoid
0241 /// too small covariances for a stable fit.
0242 ///
0243 /// @tparam measDim Number of dimensions of the measurement
0244 /// @tparam traj_t The trajectory type
0245 ///
0246 /// @param trackStateProxy is the track state proxy
0247 /// @param result is the mutable result/cache object
0248 /// @param logger a logger instance
0249 template <std::size_t measDim, typename traj_t>
0250 void collector(const typename traj_t::ConstTrackStateProxy& trackStateProxy,
0251                Gx2FitterResult<traj_t>& result, const Logger& logger) {
0252   auto predicted = trackStateProxy.predicted();
0253   auto measurement = trackStateProxy.template calibrated<measDim>();
0254   auto covarianceMeasurement =
0255       trackStateProxy.template calibratedCovariance<measDim>();
0256   // Project Jacobian and predicted measurements into the measurement dimensions
0257   auto projJacobian = (trackStateProxy.projector()
0258                            .template topLeftCorner<measDim, eBoundSize>() *
0259                        result.jacobianFromStart)
0260                           .eval();
0261   auto projPredicted = (trackStateProxy.projector()
0262                             .template topLeftCorner<measDim, eBoundSize>() *
0263                         predicted)
0264                            .eval();
0265 
0266   ACTS_VERBOSE("Processing and collecting measurements in Actor:"
0267                << "\n    Measurement:\t" << measurement.transpose()
0268                << "\n    Predicted:\t" << predicted.transpose()
0269                << "\n    Projector:\t" << trackStateProxy.effectiveProjector()
0270                << "\n    Projected Jacobian:\t" << projJacobian
0271                << "\n    Covariance Measurements:\t" << covarianceMeasurement);
0272 
0273   // Collect residuals, covariances, and projected jacobians
0274   for (std::size_t i = 0; i < measDim; i++) {
0275     if (covarianceMeasurement(i, i) < 1e-10) {
0276       ACTS_WARNING("Invalid covariance of measurement: cov(" << i << "," << i
0277                                                              << ") ~ 0")
0278       continue;
0279     }
0280 
0281     result.collectorResiduals.push_back(measurement[i] - projPredicted[i]);
0282     result.collectorCovariances.push_back(covarianceMeasurement(i, i));
0283     result.collectorProjectedJacobians.push_back(projJacobian.row(i));
0284 
0285     ACTS_VERBOSE("    Splitting the measurement:"
0286                  << "\n        Residual:\t" << measurement[i] - projPredicted[i]
0287                  << "\n        Covariance:\t" << covarianceMeasurement(i, i)
0288                  << "\n        Projected Jacobian:\t" << projJacobian.row(i));
0289   }
0290 }
0291 
0292 BoundVector calculateDeltaParams(bool zeroField, const BoundMatrix& aMatrix,
0293                                  const BoundVector& bVector);
0294 
0295 /// Global Chi Square fitter (GX2F) implementation.
0296 ///
0297 /// @tparam propagator_t Type of the propagation class
0298 ///
0299 /// TODO Write description
0300 template <typename propagator_t, typename traj_t>
0301 class Gx2Fitter {
0302   /// The navigator type
0303   using Gx2fNavigator = typename propagator_t::Navigator;
0304 
0305   /// The navigator has DirectNavigator type or not
0306   static constexpr bool isDirectNavigator =
0307       std::is_same<Gx2fNavigator, DirectNavigator>::value;
0308 
0309  public:
0310   Gx2Fitter(propagator_t pPropagator,
0311             std::unique_ptr<const Logger> _logger =
0312                 getDefaultLogger("Gx2Fitter", Logging::INFO))
0313       : m_propagator(std::move(pPropagator)),
0314         m_logger{std::move(_logger)},
0315         m_actorLogger{m_logger->cloneWithSuffix("Actor")} {}
0316 
0317  private:
0318   /// The propagator for the transport and material update
0319   propagator_t m_propagator;
0320 
0321   /// The logger instance
0322   std::unique_ptr<const Logger> m_logger;
0323   std::unique_ptr<const Logger> m_actorLogger;
0324 
0325   const Logger& logger() const { return *m_logger; }
0326 
0327   /// @brief Propagator Actor plugin for the GX2F
0328   ///
0329   /// @tparam parameters_t The type of parameters used for "local" parameters.
0330   /// @tparam calibrator_t The type of calibrator
0331   /// @tparam outlier_finder_t Type of the outlier finder class
0332   ///
0333   /// The GX2FnActor does not rely on the measurements to be
0334   /// sorted along the track. /// TODO is this true?
0335   template <typename parameters_t>
0336   class Actor {
0337    public:
0338     /// Broadcast the result_type
0339     using result_type = Gx2FitterResult<traj_t>;
0340 
0341     /// The target surface
0342     const Surface* targetSurface = nullptr;
0343 
0344     /// Allows retrieving measurements for a surface
0345     const std::map<GeometryIdentifier, SourceLink>* inputMeasurements = nullptr;
0346 
0347     /// Whether to consider multiple scattering.
0348     bool multipleScattering = false;  /// TODO implement later
0349 
0350     /// Whether to consider energy loss.
0351     bool energyLoss = false;  /// TODO implement later
0352 
0353     /// Whether to include non-linear correction during global to local
0354     /// transformation
0355     FreeToBoundCorrection freeToBoundCorrection;
0356 
0357     /// Input MultiTrajectory
0358     std::shared_ptr<MultiTrajectory<traj_t>> outputStates;
0359 
0360     /// The logger instance
0361     const Logger* actorLogger{nullptr};
0362 
0363     /// Logger helper
0364     const Logger& logger() const { return *actorLogger; }
0365 
0366     Gx2FitterExtensions<traj_t> extensions;
0367 
0368     /// The Surface being
0369     SurfaceReached targetReached;
0370 
0371     /// Calibration context for the fit
0372     const CalibrationContext* calibrationContext{nullptr};
0373 
0374     /// @brief Gx2f actor operation
0375     ///
0376     /// @tparam propagator_state_t is the type of Propagator state
0377     /// @tparam stepper_t Type of the stepper
0378     /// @tparam navigator_t Type of the navigator
0379     ///
0380     /// @param state is the mutable propagator state object
0381     /// @param stepper The stepper in use
0382     /// @param navigator The navigator in use
0383     /// @param result is the mutable result state object
0384     template <typename propagator_state_t, typename stepper_t,
0385               typename navigator_t>
0386     void operator()(propagator_state_t& state, const stepper_t& stepper,
0387                     const navigator_t& navigator, result_type& result,
0388                     const Logger& /*logger*/) const {
0389       assert(result.fittedStates && "No MultiTrajectory set");
0390 
0391       // Check if we can stop to propagate
0392       if (result.measurementStates == inputMeasurements->size()) {
0393         ACTS_INFO("Actor: finish: All measurements have been found.");
0394         result.finished = true;
0395       } else if (state.navigation.navigationBreak) {
0396         ACTS_INFO("Actor: finish: navigationBreak.");
0397         result.finished = true;
0398       }
0399 
0400       // End the propagation and return to the fitter
0401       if (result.finished) {
0402         // Remove the missing surfaces that occur after the last measurement
0403         if (result.measurementStates > 0) {
0404           result.missedActiveSurfaces.resize(result.measurementHoles);
0405         }
0406 
0407         return;
0408       }
0409 
0410       // Add the measurement surface as external surface to the navigator.
0411       // We will try to hit those surface by ignoring boundary checks.
0412       if (state.navigation.externalSurfaces.size() == 0) {
0413         for (auto measurementIt = inputMeasurements->begin();
0414              measurementIt != inputMeasurements->end(); measurementIt++) {
0415           navigator.insertExternalSurface(state.navigation,
0416                                           measurementIt->first);
0417         }
0418       }
0419 
0420       // Update:
0421       // - Waiting for a current surface
0422       auto surface = navigator.currentSurface(state.navigation);
0423       if (surface != nullptr) {
0424         ++result.surfaceCount;
0425         ACTS_VERBOSE("Surface " << surface->geometryId() << " detected.");
0426 
0427         // Check if we have a measurement surface
0428         if (auto sourcelink_it = inputMeasurements->find(surface->geometryId());
0429             sourcelink_it != inputMeasurements->end()) {
0430           ACTS_VERBOSE("Measurement surface " << surface->geometryId()
0431                                               << " detected.");
0432 
0433           // Transport the covariance to the surface
0434           stepper.transportCovarianceToBound(state.stepping, *surface,
0435                                              freeToBoundCorrection);
0436 
0437           ACTS_VERBOSE(
0438               "Actor - indices before processing:"
0439               << "\n    "
0440               << "result.lastMeasurementIndex: " << result.lastMeasurementIndex
0441               << "\n    "
0442               << "result.lastTrackIndex: " << result.lastTrackIndex << "\n    "
0443               << "result.fittedStates->size(): " << result.fittedStates->size())
0444 
0445           // TODO generalize the update of the currentTrackIndex
0446           auto& fittedStates = *result.fittedStates;
0447 
0448           // Mask for the track states. We don't need Smoothed and Filtered
0449           TrackStatePropMask mask = TrackStatePropMask::Predicted |
0450                                     TrackStatePropMask::Jacobian |
0451                                     TrackStatePropMask::Calibrated;
0452 
0453           ACTS_VERBOSE("    processSurface: addTrackState");
0454 
0455           // Add a <mask> TrackState entry multi trajectory. This allocates
0456           // storage for all components, which we will set later.
0457           typename traj_t::TrackStateProxy trackStateProxy =
0458               fittedStates.makeTrackState(mask, result.lastTrackIndex);
0459           std::size_t currentTrackIndex = trackStateProxy.index();
0460 
0461           // Set the trackStateProxy components with the state from the ongoing
0462           // propagation
0463           {
0464             trackStateProxy.setReferenceSurface(surface->getSharedPtr());
0465             // Bind the transported state to the current surface
0466             auto res = stepper.boundState(state.stepping, *surface, false,
0467                                           freeToBoundCorrection);
0468             if (!res.ok()) {
0469               result.result = res.error();
0470               return;
0471             }
0472             const auto& [boundParams, jacobian, pathLength] = *res;
0473 
0474             // Fill the track state
0475             trackStateProxy.predicted() = boundParams.parameters();
0476             trackStateProxy.predictedCovariance() = state.stepping.cov;
0477 
0478             trackStateProxy.jacobian() = jacobian;
0479             trackStateProxy.pathLength() = pathLength;
0480           }
0481 
0482           // We have predicted parameters, so calibrate the uncalibrated input
0483           // measurement
0484           extensions.calibrator(state.geoContext, *calibrationContext,
0485                                 sourcelink_it->second, trackStateProxy);
0486 
0487           // Get and set the type flags
0488           auto typeFlags = trackStateProxy.typeFlags();
0489           typeFlags.set(TrackStateFlag::ParameterFlag);
0490           if (surface->surfaceMaterial() != nullptr) {
0491             typeFlags.set(TrackStateFlag::MaterialFlag);
0492           }
0493 
0494           result.jacobianFromStart =
0495               trackStateProxy.jacobian() * result.jacobianFromStart;
0496 
0497           // Collect:
0498           // - Residuals
0499           // - Covariances
0500           // - ProjectedJacobians
0501           if (trackStateProxy.calibratedSize() == 1) {
0502             collector<1>(trackStateProxy, result, *actorLogger);
0503           } else if (trackStateProxy.calibratedSize() == 2) {
0504             collector<2>(trackStateProxy, result, *actorLogger);
0505           } else {
0506             ACTS_WARNING("Found measurement with "
0507                          << trackStateProxy.calibratedSize()
0508                          << " dimensions. Only measurements of 1 and 2 "
0509                             "dimensions are implemented yet.");
0510           }
0511 
0512           // Set the measurement type flag
0513           typeFlags.set(TrackStateFlag::MeasurementFlag);
0514           // We count the processed measurement
0515           ++result.processedMeasurements;
0516           ACTS_VERBOSE("Actor - indices after processing, before over writing:"
0517                        << "\n    "
0518                        << "result.lastMeasurementIndex: "
0519                        << result.lastMeasurementIndex << "\n    "
0520                        << "trackStateProxy.index(): " << trackStateProxy.index()
0521                        << "\n    "
0522                        << "result.lastTrackIndex: " << result.lastTrackIndex
0523                        << "\n    "
0524                        << "currentTrackIndex: " << currentTrackIndex)
0525           result.lastMeasurementIndex = currentTrackIndex;
0526           result.lastTrackIndex = currentTrackIndex;
0527 
0528           // TODO check for outlier first
0529           // We count the state with measurement
0530           ++result.measurementStates;
0531 
0532           // We count the processed state
0533           ++result.processedStates;
0534 
0535           // Update the number of holes count only when encountering a
0536           // measurement
0537           result.measurementHoles = result.missedActiveSurfaces.size();
0538         } else if (surface->associatedDetectorElement() != nullptr ||
0539                    surface->surfaceMaterial() != nullptr) {
0540           // Here we handle material and holes
0541           // TODO add material handling
0542           ACTS_VERBOSE("Non-Measurement surface " << surface->geometryId()
0543                                                   << " detected.");
0544 
0545           // We only create track states here if there is already a measurement
0546           // detected or if the surface has material (no holes before the first
0547           // measurement)
0548           if (result.measurementStates > 0
0549               // || surface->surfaceMaterial() != nullptr
0550           ) {
0551             ACTS_VERBOSE("Handle hole.");
0552 
0553             auto& fittedStates = *result.fittedStates;
0554 
0555             // Mask for the track states. We don't need Smoothed and Filtered
0556             TrackStatePropMask mask = TrackStatePropMask::Predicted |
0557                                       TrackStatePropMask::Jacobian |
0558                                       TrackStatePropMask::Calibrated;
0559 
0560             ACTS_VERBOSE("    processSurface: addTrackState");
0561 
0562             // Add a <mask> TrackState entry multi trajectory. This allocates
0563             // storage for all components, which we will set later.
0564             typename traj_t::TrackStateProxy trackStateProxy =
0565                 fittedStates.makeTrackState(mask, result.lastTrackIndex);
0566             std::size_t currentTrackIndex = trackStateProxy.index();
0567             {
0568               // Set the trackStateProxy components with the state from the
0569               // ongoing propagation
0570               {
0571                 trackStateProxy.setReferenceSurface(surface->getSharedPtr());
0572                 // Bind the transported state to the current surface
0573                 auto res = stepper.boundState(state.stepping, *surface, false,
0574                                               freeToBoundCorrection);
0575                 if (!res.ok()) {
0576                   result.result = res.error();
0577                   return;
0578                 }
0579                 const auto& [boundParams, jacobian, pathLength] = *res;
0580 
0581                 // Fill the track state
0582                 trackStateProxy.predicted() = boundParams.parameters();
0583                 trackStateProxy.predictedCovariance() = state.stepping.cov;
0584 
0585                 trackStateProxy.jacobian() = jacobian;
0586                 trackStateProxy.pathLength() = pathLength;
0587               }
0588 
0589               // Get and set the type flags
0590               auto typeFlags = trackStateProxy.typeFlags();
0591               typeFlags.set(TrackStateFlag::ParameterFlag);
0592               if (surface->surfaceMaterial() != nullptr) {
0593                 typeFlags.set(TrackStateFlag::MaterialFlag);
0594               }
0595 
0596               // Set hole only, if we are on a sensitive surface
0597               if (surface->associatedDetectorElement() != nullptr) {
0598                 ACTS_VERBOSE("Detected hole on " << surface->geometryId());
0599                 // If the surface is sensitive, set the hole type flag
0600                 typeFlags.set(TrackStateFlag::HoleFlag);
0601               } else {
0602                 ACTS_VERBOSE("Detected in-sensitive surface "
0603                              << surface->geometryId());
0604               }
0605             }
0606 
0607             ACTS_VERBOSE(
0608                 "Actor - indices after processing, before over writing:"
0609                 << "\n    "
0610                 << "result.lastMeasurementIndex: "
0611                 << result.lastMeasurementIndex << "\n    "
0612                 << "trackStateProxy.index(): " << trackStateProxy.index()
0613                 << "\n    "
0614                 << "result.lastTrackIndex: " << result.lastTrackIndex
0615                 << "\n    "
0616                 << "currentTrackIndex: " << currentTrackIndex)
0617             result.lastTrackIndex = currentTrackIndex;
0618 
0619             if (trackStateProxy.typeFlags().test(TrackStateFlag::HoleFlag)) {
0620               // Count the missed surface
0621               result.missedActiveSurfaces.push_back(surface);
0622             }
0623 
0624             ++result.processedStates;
0625           } else {
0626             ACTS_VERBOSE("Ignoring hole, because no preceding measurements.");
0627           }
0628 
0629           if (surface->surfaceMaterial() != nullptr) {
0630             // TODO write similar to KF?
0631             // Update state and stepper with material effects
0632             // materialInteractor(surface, state, stepper, navigator,
0633             // MaterialUpdateStage::FullUpdate);
0634           }
0635         } else {
0636           ACTS_INFO("Actor: This case is not implemented yet")
0637         }
0638       }
0639       ACTS_DEBUG("result.processedMeasurements: "
0640                  << result.processedMeasurements << "\n"
0641                  << "inputMeasurements.size()" << inputMeasurements->size())
0642       if (result.processedMeasurements >= inputMeasurements->size()) {
0643         ACTS_INFO("Actor: finish: all measurements found.");
0644         result.finished = true;
0645       }
0646 
0647       if (result.surfaceCount > 900) {
0648         ACTS_INFO("Actor: finish due to limit. Result might be garbage.");
0649         result.finished = true;
0650       }
0651     }
0652   };
0653 
0654   /// Aborter can stay like this probably
0655   template <typename parameters_t>
0656   class Aborter {
0657    public:
0658     /// Broadcast the result_type
0659     using action_type = Actor<parameters_t>;
0660 
0661     template <typename propagator_state_t, typename stepper_t,
0662               typename navigator_t, typename result_t>
0663     bool operator()(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0664                     const navigator_t& /*navigator*/, const result_t& result,
0665                     const Logger& /*logger*/) const {
0666       if (!result.result.ok() || result.finished) {
0667         return true;
0668       }
0669       return false;
0670     }
0671   };
0672 
0673  public:
0674   /// Fit implementation
0675   ///
0676   /// @tparam source_link_iterator_t Iterator type used to pass source links
0677   /// @tparam start_parameters_t Type of the initial parameters
0678   /// @tparam parameters_t Type of parameters used for local parameters
0679   /// @tparam track_container_t Type of the track container backend
0680   /// @tparam holder_t Type defining track container backend ownership
0681   ///
0682   /// @param it Begin iterator for the fittable uncalibrated measurements
0683   /// @param end End iterator for the fittable uncalibrated measurements
0684   /// @param sParameters The initial track parameters
0685   /// @param gx2fOptions Gx2FitterOptions steering the fit
0686   /// @param trackContainer Input track container storage to append into
0687   /// @note The input measurements are given in the form of @c SourceLink s.
0688   /// It's the calibrators job to turn them into calibrated measurements used in
0689   /// the fit.
0690   ///
0691   /// @return the output as an output track
0692   template <typename source_link_iterator_t, typename start_parameters_t,
0693             typename parameters_t = BoundTrackParameters,
0694             typename track_container_t, template <typename> class holder_t,
0695             bool _isdn = isDirectNavigator>
0696   auto fit(source_link_iterator_t it, source_link_iterator_t end,
0697            const start_parameters_t& sParameters,
0698            const Gx2FitterOptions<traj_t>& gx2fOptions,
0699            TrackContainer<track_container_t, traj_t, holder_t>& trackContainer)
0700       const -> std::enable_if_t<
0701           !_isdn, Result<typename TrackContainer<track_container_t, traj_t,
0702                                                  holder_t>::TrackProxy>> {
0703     // Preprocess Measurements (Sourcelinks -> map)
0704     // To be able to find measurements later, we put them into a map
0705     // We need to copy input SourceLinks anyway, so the map can own them.
0706     ACTS_VERBOSE("Preparing " << std::distance(it, end)
0707                               << " input measurements");
0708     std::map<GeometryIdentifier, SourceLink> inputMeasurements;
0709 
0710     for (; it != end; ++it) {
0711       SourceLink sl = *it;
0712       auto geoId = gx2fOptions.extensions.surfaceAccessor(sl)->geometryId();
0713       inputMeasurements.emplace(geoId, std::move(sl));
0714     }
0715     ACTS_VERBOSE("inputMeasurements.size() = " << inputMeasurements.size());
0716 
0717     /// Fully understand Aborter, Actor, Result later
0718     // Create the ActionList and AbortList
0719     using GX2FAborter = Aborter<parameters_t>;
0720     using GX2FActor = Actor<parameters_t>;
0721 
0722     using GX2FResult = typename GX2FActor::result_type;
0723     using Actors = Acts::ActionList<GX2FActor>;
0724     using Aborters = Acts::AbortList<GX2FAborter>;
0725 
0726     using PropagatorOptions = Acts::PropagatorOptions<Actors, Aborters>;
0727 
0728     start_parameters_t params = sParameters;
0729     BoundVector deltaParams = BoundVector::Zero();
0730     double chi2sum = 0;
0731     double oldChi2sum = std::numeric_limits<double>::max();
0732     BoundMatrix aMatrix = BoundMatrix::Zero();
0733     BoundVector bVector = BoundVector::Zero();
0734 
0735     // Create an index of the 'tip' of the track stored in multitrajectory. It
0736     // is needed outside the update loop. It will be updated with each iteration
0737     // and used for the final track
0738     std::size_t tipIndex = Acts::MultiTrajectoryTraits::kInvalid;
0739 
0740     ACTS_VERBOSE("params:\n" << params);
0741 
0742     /// Actual Fitting /////////////////////////////////////////////////////////
0743     ACTS_DEBUG("Start to iterate");
0744 
0745     // Iterate the fit and improve result. Abort after n steps or after
0746     // convergence
0747     // nUpdate is initialized outside to save its state for the track
0748     std::size_t nUpdate = 0;
0749     for (nUpdate = 0; nUpdate < gx2fOptions.nUpdateMax; nUpdate++) {
0750       ACTS_VERBOSE("nUpdate = " << nUpdate + 1 << "/"
0751                                 << gx2fOptions.nUpdateMax);
0752 
0753       // update params
0754       params.parameters() += deltaParams;
0755       ACTS_VERBOSE("updated params:\n" << params);
0756 
0757       // set up propagator and co
0758       Acts::GeometryContext geoCtx = gx2fOptions.geoContext;
0759       Acts::MagneticFieldContext magCtx = gx2fOptions.magFieldContext;
0760       // Set options for propagator
0761       PropagatorOptions propagatorOptions(geoCtx, magCtx);
0762       auto& gx2fActor = propagatorOptions.actionList.template get<GX2FActor>();
0763       gx2fActor.inputMeasurements = &inputMeasurements;
0764       gx2fActor.extensions = gx2fOptions.extensions;
0765       gx2fActor.calibrationContext = &gx2fOptions.calibrationContext.get();
0766       gx2fActor.actorLogger = m_actorLogger.get();
0767 
0768       auto propagatorState = m_propagator.makeState(params, propagatorOptions);
0769 
0770       auto& r = propagatorState.template get<Gx2FitterResult<traj_t>>();
0771       r.fittedStates = &trackContainer.trackStateContainer();
0772 
0773       // Clear the track container. It could be more performant to update the
0774       // existing states, but this needs some more thinking.
0775       trackContainer.clear();
0776 
0777       auto propagationResult = m_propagator.template propagate(propagatorState);
0778 
0779       auto result = m_propagator.template makeResult(std::move(propagatorState),
0780                                                      propagationResult,
0781                                                      propagatorOptions, false);
0782 
0783       // TODO Improve Propagator + Actor [allocate before loop], rewrite
0784       // makeMeasurements
0785       auto& propRes = *result;
0786       GX2FResult gx2fResult = std::move(propRes.template get<GX2FResult>());
0787 
0788       ACTS_VERBOSE("gx2fResult.collectorResiduals.size() = "
0789                    << gx2fResult.collectorResiduals.size());
0790       ACTS_VERBOSE("gx2fResult.collectorCovariances.size() = "
0791                    << gx2fResult.collectorCovariances.size());
0792       ACTS_VERBOSE("gx2fResult.collectorProjectedJacobians.size() = "
0793                    << gx2fResult.collectorProjectedJacobians.size());
0794 
0795       // This check takes into account the evaluated dimensions of the
0796       // measurements. To fit, we need at least NDF+1 measurements. However,
0797       // we count n-dimensional measurements for n measurements, reducing the
0798       // effective number of needed measurements.
0799       // We might encounter the case, where we cannot use some (parts of a)
0800       // measurements, maybe if we do not support that kind of measurement. This
0801       // is also taken into account here.
0802       // `ndf = 4` is chosen, since this a minimum that makes sense for us, but
0803       // a more general approach is desired.
0804       // We skip the check during the first iteration, since we cannot
0805       // guarantee to hit all/enough measurement surfaces with the initial
0806       // parameter guess.
0807       // TODO genernalize for n-dimensional fit
0808       constexpr std::size_t ndf = 4;
0809       if ((nUpdate > 0) && (ndf + 1 > gx2fResult.collectorResiduals.size())) {
0810         ACTS_INFO("Not enough measurements. Require "
0811                   << ndf + 1 << ", but only "
0812                   << gx2fResult.collectorResiduals.size() << " could be used.");
0813         return Experimental::GlobalChiSquareFitterError::NotEnoughMeasurements;
0814       }
0815 
0816       chi2sum = 0;
0817       aMatrix = BoundMatrix::Zero();
0818       bVector = BoundVector::Zero();
0819 
0820       // TODO generalize for non-2D measurements
0821       for (std::size_t iMeas = 0; iMeas < gx2fResult.collectorResiduals.size();
0822            iMeas++) {
0823         const auto ri = gx2fResult.collectorResiduals[iMeas];
0824         const auto covi = gx2fResult.collectorCovariances[iMeas];
0825         const auto projectedJacobian =
0826             gx2fResult.collectorProjectedJacobians[iMeas];
0827 
0828         const double chi2meas = ri / covi * ri;
0829         const BoundMatrix aMatrixMeas =
0830             projectedJacobian * projectedJacobian.transpose() / covi;
0831         const BoundVector bVectorMeas = projectedJacobian / covi * ri;
0832 
0833         chi2sum += chi2meas;
0834         aMatrix += aMatrixMeas;
0835         bVector += bVectorMeas;
0836       }
0837 
0838       // calculate delta params [a] * delta = b
0839       deltaParams =
0840           calculateDeltaParams(gx2fOptions.zeroField, aMatrix, bVector);
0841 
0842       ACTS_VERBOSE("aMatrix:\n"
0843                    << aMatrix << "\n"
0844                    << "bVector:\n"
0845                    << bVector << "\n"
0846                    << "deltaParams:\n"
0847                    << deltaParams << "\n"
0848                    << "oldChi2sum = " << oldChi2sum << "\n"
0849                    << "chi2sum = " << chi2sum);
0850 
0851       tipIndex = gx2fResult.lastMeasurementIndex;
0852 
0853       if ((gx2fOptions.relChi2changeCutOff != 0) && (nUpdate > 0) &&
0854           (std::abs(chi2sum / oldChi2sum - 1) <
0855            gx2fOptions.relChi2changeCutOff)) {
0856         ACTS_VERBOSE("Abort with relChi2changeCutOff after "
0857                      << nUpdate + 1 << "/" << gx2fOptions.nUpdateMax
0858                      << " iterations.");
0859         break;
0860       }
0861 
0862       // TODO investigate further
0863       if (chi2sum > oldChi2sum + 1e-5) {
0864         ACTS_DEBUG("chi2 not converging monotonically");
0865       }
0866 
0867       oldChi2sum = chi2sum;
0868     }
0869     ACTS_DEBUG("Finished to iterate");
0870     ACTS_VERBOSE("final params:\n" << params);
0871     /// Finish Fitting /////////////////////////////////////////////////////////
0872 
0873     // Since currently most of our tracks converge in 4-5 updates, we want to
0874     // set nUpdateMax higher than that to guarantee convergence for most tracks.
0875     // In cases, where we set a smaller nUpdateMax, it's because we want to
0876     // investigate the behaviour of the fitter before it converges, like in some
0877     // unit-tests.
0878     if (nUpdate == gx2fOptions.nUpdateMax && gx2fOptions.nUpdateMax > 5) {
0879       ACTS_INFO("Did not converge in " << gx2fOptions.nUpdateMax
0880                                        << " updates.");
0881       return Experimental::GlobalChiSquareFitterError::DidNotConverge;
0882     }
0883 
0884     // Calculate covariance of the fitted parameters with inverse of [a]
0885     BoundMatrix fullCovariancePredicted = BoundMatrix::Identity();
0886     bool aMatrixIsInvertible = false;
0887     if (gx2fOptions.zeroField) {
0888       constexpr std::size_t reducedMatrixSize = 4;
0889 
0890       auto safeReducedCovariance = safeInverse(
0891           aMatrix.topLeftCorner<reducedMatrixSize, reducedMatrixSize>().eval());
0892       if (safeReducedCovariance) {
0893         aMatrixIsInvertible = true;
0894         fullCovariancePredicted
0895             .topLeftCorner<reducedMatrixSize, reducedMatrixSize>() =
0896             *safeReducedCovariance;
0897       }
0898     } else {
0899       constexpr std::size_t reducedMatrixSize = 5;
0900 
0901       auto safeReducedCovariance = safeInverse(
0902           aMatrix.topLeftCorner<reducedMatrixSize, reducedMatrixSize>().eval());
0903       if (safeReducedCovariance) {
0904         aMatrixIsInvertible = true;
0905         fullCovariancePredicted
0906             .topLeftCorner<reducedMatrixSize, reducedMatrixSize>() =
0907             *safeReducedCovariance;
0908       }
0909     }
0910 
0911     if (!aMatrixIsInvertible && gx2fOptions.nUpdateMax > 0) {
0912       ACTS_ERROR("aMatrix is not invertible.");
0913       return Experimental::GlobalChiSquareFitterError::AIsNotInvertible;
0914     }
0915 
0916     ACTS_VERBOSE("final covariance:\n" << fullCovariancePredicted);
0917 
0918     // Propagate again with the final covariance matrix. This is necessary to
0919     // obtain the propagated covariance for each state.
0920     if (gx2fOptions.nUpdateMax > 0) {
0921       ACTS_VERBOSE("Propagate with the final covariance.");
0922       // update covariance
0923       ACTS_VERBOSE("finaldeltaParams:\n" << deltaParams);
0924       params.covariance() = fullCovariancePredicted;
0925 
0926       // set up propagator and co
0927       Acts::GeometryContext geoCtx = gx2fOptions.geoContext;
0928       Acts::MagneticFieldContext magCtx = gx2fOptions.magFieldContext;
0929       // Set options for propagator
0930       PropagatorOptions propagatorOptions(geoCtx, magCtx);
0931       auto& gx2fActor = propagatorOptions.actionList.template get<GX2FActor>();
0932       gx2fActor.inputMeasurements = &inputMeasurements;
0933       gx2fActor.extensions = gx2fOptions.extensions;
0934       gx2fActor.calibrationContext = &gx2fOptions.calibrationContext.get();
0935       gx2fActor.actorLogger = m_actorLogger.get();
0936 
0937       auto propagatorState = m_propagator.makeState(params, propagatorOptions);
0938 
0939       auto& r = propagatorState.template get<Gx2FitterResult<traj_t>>();
0940       r.fittedStates = &trackContainer.trackStateContainer();
0941 
0942       // Clear the track container. It could be more performant to update the
0943       // existing states, but this needs some more thinking.
0944       trackContainer.clear();
0945 
0946       m_propagator.template propagate(propagatorState);
0947     }
0948 
0949     if (!trackContainer.hasColumn(
0950             Acts::hashString(Gx2fConstants::gx2fnUpdateColumn))) {
0951       trackContainer.template addColumn<std::size_t>("Gx2fnUpdateColumn");
0952     }
0953 
0954     // Prepare track for return
0955     auto track = trackContainer.makeTrack();
0956     track.tipIndex() = tipIndex;
0957     track.parameters() = params.parameters();
0958     track.covariance() = fullCovariancePredicted;
0959     track.setReferenceSurface(params.referenceSurface().getSharedPtr());
0960 
0961     if (trackContainer.hasColumn(
0962             Acts::hashString(Gx2fConstants::gx2fnUpdateColumn))) {
0963       ACTS_DEBUG("Add nUpdate to track")
0964       track.template component<std::size_t>("Gx2fnUpdateColumn") = nUpdate;
0965     }
0966 
0967     // TODO write test for calculateTrackQuantities
0968     calculateTrackQuantities(track);
0969 
0970     // Set the chi2sum for the track summary manually, since we don't calculate
0971     // it for each state
0972     track.chi2() = chi2sum;
0973 
0974     // Return the converted Track
0975     return track;
0976   }
0977 };
0978 
0979 }  // namespace Acts::Experimental