Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-19 09:23:37

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