Back to home page

EIC code displayed by LXR

 
 

    


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

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 // Workaround for building on clang+libstdc++
0012 #include "Acts/Utilities/detail/ReferenceWrapperAnyCompat.hpp"
0013 
0014 #include "Acts/Definitions/Algebra.hpp"
0015 #include "Acts/EventData/MeasurementHelpers.hpp"
0016 #include "Acts/EventData/MultiTrajectory.hpp"
0017 #include "Acts/EventData/MultiTrajectoryHelpers.hpp"
0018 #include "Acts/EventData/SourceLink.hpp"
0019 #include "Acts/EventData/TrackContainerFrontendConcept.hpp"
0020 #include "Acts/EventData/TrackParameters.hpp"
0021 #include "Acts/EventData/TrackProxyConcept.hpp"
0022 #include "Acts/EventData/VectorMultiTrajectory.hpp"
0023 #include "Acts/EventData/VectorTrackContainer.hpp"
0024 #include "Acts/Geometry/GeometryContext.hpp"
0025 #include "Acts/Geometry/TrackingVolume.hpp"
0026 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0027 #include "Acts/Material/Interactions.hpp"
0028 #include "Acts/Material/MaterialSlab.hpp"
0029 #include "Acts/Propagator/ActorList.hpp"
0030 #include "Acts/Propagator/ConstrainedStep.hpp"
0031 #include "Acts/Propagator/DirectNavigator.hpp"
0032 #include "Acts/Propagator/Navigator.hpp"
0033 #include "Acts/Propagator/Propagator.hpp"
0034 #include "Acts/Propagator/StandardAborters.hpp"
0035 #include "Acts/Propagator/StraightLineStepper.hpp"
0036 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0037 #include "Acts/TrackFitting/GlobalChiSquareFitterError.hpp"
0038 #include "Acts/TrackFitting/detail/VoidFitterComponents.hpp"
0039 #include "Acts/Utilities/CalibrationContext.hpp"
0040 #include "Acts/Utilities/Delegate.hpp"
0041 #include "Acts/Utilities/Logger.hpp"
0042 #include "Acts/Utilities/Result.hpp"
0043 #include "Acts/Utilities/TrackHelpers.hpp"
0044 
0045 #include <functional>
0046 #include <limits>
0047 #include <map>
0048 #include <memory>
0049 #include <type_traits>
0050 #include <unordered_map>
0051 
0052 namespace Acts::Experimental {
0053 
0054 namespace Gx2fConstants {
0055 constexpr std::string_view gx2fnUpdateColumn = "Gx2fnUpdateColumn";
0056 
0057 // Mask for the track states. We don't need Predicted and Filtered
0058 constexpr TrackStatePropMask trackStateMask = TrackStatePropMask::Smoothed |
0059                                               TrackStatePropMask::Jacobian |
0060                                               TrackStatePropMask::Calibrated;
0061 
0062 // A projector used for scattering. By using Jacobian * phiThetaProjector one
0063 // gets only the derivatives for the variables phi and theta.
0064 const Eigen::Matrix<double, eBoundSize, 2> phiThetaProjector = [] {
0065   Eigen::Matrix<double, eBoundSize, 2> m =
0066       Eigen::Matrix<double, eBoundSize, 2>::Zero();
0067   m(eBoundPhi, 0) = 1.0;
0068   m(eBoundTheta, 1) = 1.0;
0069   return m;
0070 }();
0071 }  // namespace Gx2fConstants
0072 
0073 /// Extension struct which holds delegates to customise the GX2F behaviour
0074 template <typename traj_t>
0075 struct Gx2FitterExtensions {
0076   using TrackStateProxy = typename MultiTrajectory<traj_t>::TrackStateProxy;
0077   using ConstTrackStateProxy =
0078       typename MultiTrajectory<traj_t>::ConstTrackStateProxy;
0079   using Parameters = typename TrackStateProxy::Parameters;
0080 
0081   using Calibrator =
0082       Delegate<void(const GeometryContext&, const CalibrationContext&,
0083                     const SourceLink&, TrackStateProxy)>;
0084 
0085   using Updater = Delegate<Result<void>(const GeometryContext&, TrackStateProxy,
0086                                         const Logger&)>;
0087 
0088   using OutlierFinder = Delegate<bool(ConstTrackStateProxy)>;
0089 
0090   /// The Calibrator is a dedicated calibration algorithm that allows
0091   /// to calibrate measurements using track information, this could be
0092   /// e.g. sagging for wires, module deformations, etc.
0093   Calibrator calibrator;
0094 
0095   /// The updater incorporates measurement information into the track parameters
0096   Updater updater;
0097 
0098   /// Determines whether a measurement is supposed to be considered as an
0099   /// outlier
0100   OutlierFinder outlierFinder;
0101 
0102   /// Retrieves the associated surface from a source link
0103   SourceLinkSurfaceAccessor surfaceAccessor;
0104 
0105   /// Default constructor which connects the default void components
0106   Gx2FitterExtensions() {
0107     calibrator.template connect<&detail::voidFitterCalibrator<traj_t>>();
0108     updater.template connect<&detail::voidFitterUpdater<traj_t>>();
0109     outlierFinder.template connect<&detail::voidOutlierFinder<traj_t>>();
0110     surfaceAccessor.connect<&detail::voidSurfaceAccessor>();
0111   }
0112 };
0113 
0114 /// Combined options for the Global-Chi-Square fitter.
0115 ///
0116 /// @tparam traj_t The trajectory type
0117 template <typename traj_t>
0118 struct Gx2FitterOptions {
0119   /// PropagatorOptions with context.
0120   ///
0121   /// @param gctx The geometry context for this fit
0122   /// @param mctx The magnetic context for this fit
0123   /// @param cctx The calibration context for this fit
0124   /// @param extensions_ The KF extensions
0125   /// @param pOptions The plain propagator options
0126   /// @param rSurface The reference surface for the fit to be expressed at
0127   /// @param mScattering Whether to include multiple scattering
0128   /// @param eLoss Whether to include energy loss
0129   /// @param freeToBoundCorrection_ Correction for non-linearity effect during transform from free to bound
0130   /// @param nUpdateMax_ Max number of iterations for updating the parameters
0131   /// @param relChi2changeCutOff_ Check for convergence (abort condition). Set to 0 to skip.
0132   Gx2FitterOptions(const GeometryContext& gctx,
0133                    const MagneticFieldContext& mctx,
0134                    std::reference_wrapper<const CalibrationContext> cctx,
0135                    Gx2FitterExtensions<traj_t> extensions_,
0136                    const PropagatorPlainOptions& pOptions,
0137                    const Surface* rSurface = nullptr, bool mScattering = false,
0138                    bool eLoss = false,
0139                    const FreeToBoundCorrection& freeToBoundCorrection_ =
0140                        FreeToBoundCorrection(false),
0141                    const std::size_t nUpdateMax_ = 5,
0142                    double relChi2changeCutOff_ = 1e-5)
0143       : geoContext(gctx),
0144         magFieldContext(mctx),
0145         calibrationContext(cctx),
0146         extensions(extensions_),
0147         propagatorPlainOptions(pOptions),
0148         referenceSurface(rSurface),
0149         multipleScattering(mScattering),
0150         energyLoss(eLoss),
0151         freeToBoundCorrection(freeToBoundCorrection_),
0152         nUpdateMax(nUpdateMax_),
0153         relChi2changeCutOff(relChi2changeCutOff_) {}
0154 
0155   /// Contexts are required and the options must not be default-constructible.
0156   Gx2FitterOptions() = delete;
0157 
0158   /// Context object for the geometry
0159   std::reference_wrapper<const GeometryContext> geoContext;
0160   /// Context object for the magnetic field
0161   std::reference_wrapper<const MagneticFieldContext> magFieldContext;
0162   /// context object for the calibration
0163   std::reference_wrapper<const CalibrationContext> calibrationContext;
0164 
0165   Gx2FitterExtensions<traj_t> extensions;
0166 
0167   /// The trivial propagator options
0168   PropagatorPlainOptions propagatorPlainOptions;
0169 
0170   /// The reference Surface
0171   const Surface* referenceSurface = nullptr;
0172 
0173   /// Whether to consider multiple scattering
0174   bool multipleScattering = false;
0175 
0176   /// Whether to consider energy loss
0177   bool energyLoss = false;
0178 
0179   /// Whether to include non-linear correction during global to local
0180   /// transformation
0181   FreeToBoundCorrection freeToBoundCorrection;
0182 
0183   /// Max number of iterations during the fit (abort condition)
0184   std::size_t nUpdateMax = 5;
0185 
0186   /// Check for convergence (abort condition). Set to 0 to skip.
0187   double relChi2changeCutOff = 1e-7;
0188 };
0189 
0190 template <typename traj_t>
0191 struct Gx2FitterResult {
0192   // Fitted states that the actor has handled.
0193   traj_t* fittedStates{nullptr};
0194 
0195   // This is the index of the 'tip' of the track stored in multitrajectory.
0196   // This corresponds to the last measurement state in the multitrajectory.
0197   // Since this KF only stores one trajectory, it is unambiguous.
0198   // Acts::MultiTrajectoryTraits::kInvalid is the start of a trajectory.
0199   std::size_t lastMeasurementIndex = Acts::MultiTrajectoryTraits::kInvalid;
0200 
0201   // This is the index of the 'tip' of the states stored in multitrajectory.
0202   // This corresponds to the last state in the multitrajectory.
0203   // Since this KF only stores one trajectory, it is unambiguous.
0204   // Acts::MultiTrajectoryTraits::kInvalid is the start of a trajectory.
0205   std::size_t lastTrackIndex = Acts::MultiTrajectoryTraits::kInvalid;
0206 
0207   // The optional Parameters at the provided surface
0208   std::optional<BoundTrackParameters> fittedParameters;
0209 
0210   // Counter for states with non-outlier measurements
0211   std::size_t measurementStates = 0;
0212 
0213   // Counter for measurements holes
0214   // A hole correspond to a surface with an associated detector element with no
0215   // associated measurement. Holes are only taken into account if they are
0216   // between the first and last measurements.
0217   std::size_t measurementHoles = 0;
0218 
0219   // Counter for handled states
0220   std::size_t processedStates = 0;
0221 
0222   // Counter for handled measurements
0223   std::size_t processedMeasurements = 0;
0224 
0225   // Indicator if track fitting has been done
0226   bool finished = false;
0227 
0228   // Measurement surfaces without hits
0229   std::vector<const Surface*> missedActiveSurfaces;
0230 
0231   // Measurement surfaces handled in both forward and
0232   // backward filtering
0233   std::vector<const Surface*> passedAgainSurfaces;
0234 
0235   Result<void> result{Result<void>::success()};
0236 
0237   // Count how many surfaces have been hit
0238   std::size_t surfaceCount = 0;
0239 };
0240 
0241 /// @brief A container to store scattering properties for each material surface
0242 ///
0243 /// This struct holds the scattering angles, the inverse covariance of the
0244 /// material, and a validity flag indicating whether the material is valid for
0245 /// the scattering process.
0246 struct ScatteringProperties {
0247  public:
0248   /// @brief Constructor to initialize scattering properties.
0249   ///
0250   /// @param scatteringAngles_ The vector of scattering angles.
0251   /// @param invCovarianceMaterial_ The inverse covariance of the material.
0252   /// @param materialIsValid_ A boolean flag indicating whether the material is valid.
0253   ScatteringProperties(const BoundVector& scatteringAngles_,
0254                        const double invCovarianceMaterial_,
0255                        const bool materialIsValid_)
0256       : m_scatteringAngles(scatteringAngles_),
0257         m_invCovarianceMaterial(invCovarianceMaterial_),
0258         m_materialIsValid(materialIsValid_) {}
0259 
0260   // Accessor for the scattering angles.
0261   const BoundVector& scatteringAngles() const { return m_scatteringAngles; }
0262 
0263   // Accessor for a modifiable reference to the scattering angles
0264   BoundVector& scatteringAngles() { return m_scatteringAngles; }
0265 
0266   // Accessor for the inverse covariance of the material.
0267   double invCovarianceMaterial() const { return m_invCovarianceMaterial; }
0268 
0269   // Accessor for the material validity flag.
0270   bool materialIsValid() const { return m_materialIsValid; }
0271 
0272  private:
0273   /// Vector of scattering angles. The vector is usually all zeros except for
0274   /// eBoundPhi and eBoundTheta.
0275   BoundVector m_scatteringAngles;
0276 
0277   /// Inverse covariance of the material. Compute with e.g. the Highland
0278   /// formula.
0279   double m_invCovarianceMaterial;
0280 
0281   /// Flag indicating whether the material is valid. Commonly vacuum and zero
0282   /// thickness material will be ignored.
0283   bool m_materialIsValid;
0284 };
0285 
0286 /// @brief A container to manage all properties of a gx2f system
0287 ///
0288 /// This struct manages the mathematical infrastructure for the gx2f. It
0289 /// initializes and maintains the extended aMatrix and extended bVector.
0290 struct Gx2fSystem {
0291  public:
0292   /// @brief Constructor to initialize matrices and vectors to zero based on specified dimensions.
0293   ///
0294   /// @param nDims Number of dimensions for the extended matrix and vector.
0295   explicit Gx2fSystem(std::size_t nDims)
0296       : m_nDims{nDims},
0297         m_aMatrix{Eigen::MatrixXd::Zero(nDims, nDims)},
0298         m_bVector{Eigen::VectorXd::Zero(nDims)} {}
0299 
0300   // Accessor for nDims (const reference).
0301   std::size_t nDims() const { return m_nDims; }
0302 
0303   // Accessor for chi2
0304   double chi2() const { return m_chi2; }
0305 
0306   // Modifier for chi2
0307   double& chi2() { return m_chi2; }
0308 
0309   // Accessor for the matrix.
0310   const Eigen::MatrixXd& aMatrix() const { return m_aMatrix; }
0311 
0312   // Accessor for a modifiable reference to the matrix.
0313   Eigen::MatrixXd& aMatrix() { return m_aMatrix; }
0314 
0315   // Accessor for the vector.
0316   const Eigen::VectorXd& bVector() const { return m_bVector; }
0317 
0318   // Accessor for a modifiable reference to the vector.
0319   Eigen::VectorXd& bVector() { return m_bVector; }
0320 
0321   // Accessor for NDF
0322   std::size_t ndf() const { return m_ndf; }
0323 
0324   // Modifier for NDF
0325   std::size_t& ndf() { return m_ndf; }
0326 
0327   //  It automatically deduces if we want to fit e.g. q/p and adjusts itself
0328   //  later. We have only 3 cases, because we always have l0, l1, phi, theta:
0329   // - 4: no magnetic field -> q/p is empty
0330   // - 5: no time measurement -> time is not fittable
0331   // - 6: full fit
0332   std::size_t findRequiredNdf() {
0333     std::size_t ndfSystem = 0;
0334     if (m_aMatrix(4, 4) == 0) {
0335       ndfSystem = 4;
0336     } else if (m_aMatrix(5, 5) == 0) {
0337       ndfSystem = 5;
0338     } else {
0339       ndfSystem = 6;
0340     }
0341 
0342     return ndfSystem;
0343   }
0344 
0345   bool isWellDefined() { return m_ndf > findRequiredNdf(); }
0346 
0347  private:
0348   /// Number of dimensions of the (extended) system
0349   std::size_t m_nDims;
0350 
0351   /// Sum of chi-squared values.
0352   double m_chi2 = 0.;
0353 
0354   /// Extended matrix for accumulation.
0355   Eigen::MatrixXd m_aMatrix;
0356 
0357   /// Extended vector for accumulation.
0358   Eigen::VectorXd m_bVector;
0359 
0360   /// Number of degrees of freedom of the system
0361   std::size_t m_ndf = 0u;
0362 };
0363 
0364 /// @brief Adds a measurement to the GX2F equation system in a modular backend function.
0365 ///
0366 /// This function processes measurement data and integrates it into the GX2F
0367 /// system.
0368 ///
0369 /// @param extendedSystem All parameters of the current equation system to update.
0370 /// @param jacobianFromStart The Jacobian matrix from the start to the current state.
0371 /// @param covarianceMeasurement The covariance matrix of the measurement.
0372 /// @param predicted The predicted state vector based on the track state.
0373 /// @param measurement The measurement vector.
0374 /// @param projector The projection matrix.
0375 /// @param logger A logger instance.
0376 ///
0377 /// @note The dynamic Eigen matrices are suboptimal. We could think of
0378 /// templating again in the future on kMeasDims. We currently use dynamic
0379 /// matrices to reduce the memory during compile time.
0380 void addMeasurementToGx2fSumsBackend(
0381     Gx2fSystem& extendedSystem,
0382     const std::vector<BoundMatrix>& jacobianFromStart,
0383     const Eigen::MatrixXd& covarianceMeasurement, const BoundVector& predicted,
0384     const Eigen::VectorXd& measurement, const Eigen::MatrixXd& projector,
0385     const Logger& logger);
0386 
0387 /// @brief Process measurements and fill the aMatrix and bVector
0388 ///
0389 /// The function processes each measurement for the GX2F Actor fitting process.
0390 /// It extracts the information from the track state and adds it to aMatrix,
0391 /// bVector, and chi2sum.
0392 ///
0393 /// @tparam kMeasDim Number of dimensions of the measurement
0394 /// @tparam track_state_t The type of the track state
0395 ///
0396 /// @param extendedSystem All parameters of the current equation system to update
0397 /// @param jacobianFromStart The Jacobian matrix from start to the current state
0398 /// @param trackState The track state to analyse
0399 /// @param logger A logger instance
0400 template <std::size_t kMeasDim, typename track_state_t>
0401 void addMeasurementToGx2fSums(Gx2fSystem& extendedSystem,
0402                               const std::vector<BoundMatrix>& jacobianFromStart,
0403                               const track_state_t& trackState,
0404                               const Logger& logger) {
0405   const ActsSquareMatrix<kMeasDim> covarianceMeasurement =
0406       trackState.template calibratedCovariance<kMeasDim>();
0407 
0408   const BoundVector predicted = trackState.smoothed();
0409 
0410   const ActsVector<kMeasDim> measurement =
0411       trackState.template calibrated<kMeasDim>();
0412 
0413   const ActsMatrix<kMeasDim, eBoundSize> projector =
0414       trackState.template projectorSubspaceHelper<kMeasDim>().projector();
0415 
0416   addMeasurementToGx2fSumsBackend(extendedSystem, jacobianFromStart,
0417                                   covarianceMeasurement, predicted, measurement,
0418                                   projector, logger);
0419 }
0420 
0421 /// @brief Process material and fill the aMatrix and bVector
0422 ///
0423 /// The function processes each material for the GX2F Actor fitting process.
0424 /// It extracts the information from the track state and adds it to aMatrix,
0425 /// bVector, and chi2sum.
0426 ///
0427 /// @tparam track_state_t The type of the track state
0428 ///
0429 /// @param extendedSystem All parameters of the current equation system
0430 /// @param nMaterialsHandled How many materials we already handled. Used for the offset.
0431 /// @param scatteringMap The scattering map, containing all scattering angles and covariances
0432 /// @param trackState The track state to analyse
0433 /// @param logger A logger instance
0434 template <typename track_state_t>
0435 void addMaterialToGx2fSums(
0436     Gx2fSystem& extendedSystem, const std::size_t nMaterialsHandled,
0437     const std::unordered_map<GeometryIdentifier, ScatteringProperties>&
0438         scatteringMap,
0439     const track_state_t& trackState, const Logger& logger) {
0440   // Get and store geoId for the current material surface
0441   const GeometryIdentifier geoId = trackState.referenceSurface().geometryId();
0442   const auto scatteringMapId = scatteringMap.find(geoId);
0443   if (scatteringMapId == scatteringMap.end()) {
0444     ACTS_ERROR("No scattering angles found for material surface " << geoId);
0445     throw std::runtime_error(
0446         "No scattering angles found for material surface.");
0447   }
0448 
0449   const double sinThetaLoc = std::sin(trackState.smoothed()[eBoundTheta]);
0450 
0451   // The position, where we need to insert the values in aMatrix and bVector
0452   const std::size_t deltaPosition = eBoundSize + 2 * nMaterialsHandled;
0453 
0454   const BoundVector& scatteringAngles =
0455       scatteringMapId->second.scatteringAngles();
0456 
0457   const double invCov = scatteringMapId->second.invCovarianceMaterial();
0458 
0459   // Phi contribution
0460   extendedSystem.aMatrix()(deltaPosition, deltaPosition) +=
0461       invCov * sinThetaLoc * sinThetaLoc;
0462   extendedSystem.bVector()(deltaPosition, 0) -=
0463       invCov * scatteringAngles[eBoundPhi] * sinThetaLoc;
0464   extendedSystem.chi2() += invCov * scatteringAngles[eBoundPhi] * sinThetaLoc *
0465                            scatteringAngles[eBoundPhi] * sinThetaLoc;
0466 
0467   // Theta Contribution
0468   extendedSystem.aMatrix()(deltaPosition + 1, deltaPosition + 1) += invCov;
0469   extendedSystem.bVector()(deltaPosition + 1, 0) -=
0470       invCov * scatteringAngles[eBoundTheta];
0471   extendedSystem.chi2() +=
0472       invCov * scatteringAngles[eBoundTheta] * scatteringAngles[eBoundTheta];
0473 
0474   ACTS_VERBOSE(
0475       "Contributions in addMaterialToGx2fSums:\n"
0476       << "    invCov:        " << invCov << "\n"
0477       << "    sinThetaLoc:   " << sinThetaLoc << "\n"
0478       << "    deltaPosition: " << deltaPosition << "\n"
0479       << "    Phi:\n"
0480       << "        scattering angle:     " << scatteringAngles[eBoundPhi] << "\n"
0481       << "        aMatrix contribution: " << invCov * sinThetaLoc * sinThetaLoc
0482       << "\n"
0483       << "        bVector contribution: "
0484       << invCov * scatteringAngles[eBoundPhi] * sinThetaLoc << "\n"
0485       << "        chi2sum contribution: "
0486       << invCov * scatteringAngles[eBoundPhi] * sinThetaLoc *
0487              scatteringAngles[eBoundPhi] * sinThetaLoc
0488       << "\n"
0489       << "    Theta:\n"
0490       << "        scattering angle:     " << scatteringAngles[eBoundTheta]
0491       << "\n"
0492       << "        aMatrix contribution: " << invCov << "\n"
0493       << "        bVector contribution: "
0494       << invCov * scatteringAngles[eBoundTheta] << "\n"
0495       << "        chi2sum contribution: "
0496       << invCov * scatteringAngles[eBoundTheta] * scatteringAngles[eBoundTheta]
0497       << "\n");
0498 
0499   return;
0500 }
0501 
0502 /// @brief Fill the GX2F system with data from a track
0503 ///
0504 /// This function processes a track proxy and updates the aMatrix, bVector, and
0505 /// chi2 values for the GX2F fitting system. It considers material only if
0506 /// multiple scattering is enabled.
0507 ///
0508 /// @tparam track_proxy_t The type of the track proxy
0509 ///
0510 /// @param track A constant track proxy to inspect
0511 /// @param extendedSystem All parameters of the current equation system
0512 /// @param multipleScattering Flag to consider multiple scattering in the calculation
0513 /// @param scatteringMap Map of geometry identifiers to scattering properties,
0514 ///        containing scattering angles and validation status
0515 /// @param geoIdVector A vector to store geometry identifiers for tracking processed elements
0516 /// @param logger A logger instance
0517 template <TrackProxyConcept track_proxy_t>
0518 void fillGx2fSystem(
0519     const track_proxy_t track, Gx2fSystem& extendedSystem,
0520     const bool multipleScattering,
0521     const std::unordered_map<GeometryIdentifier, ScatteringProperties>&
0522         scatteringMap,
0523     std::vector<GeometryIdentifier>& geoIdVector, const Logger& logger) {
0524   std::vector<BoundMatrix> jacobianFromStart;
0525   jacobianFromStart.emplace_back(BoundMatrix::Identity());
0526 
0527   for (const auto& trackState : track.trackStates()) {
0528     // Get and store geoId for the current surface
0529     const GeometryIdentifier geoId = trackState.referenceSurface().geometryId();
0530     ACTS_DEBUG("Start to investigate trackState on surface " << geoId);
0531     const auto typeFlags = trackState.typeFlags();
0532     const bool stateHasMeasurement =
0533         typeFlags.test(TrackStateFlag::MeasurementFlag);
0534     const bool stateHasMaterial = typeFlags.test(TrackStateFlag::MaterialFlag);
0535 
0536     // First we figure out, if we would need to look into material
0537     // surfaces at all. Later, we also check, if the material slab is
0538     // valid, otherwise we modify this flag to ignore the material
0539     // completely.
0540     bool doMaterial = multipleScattering && stateHasMaterial;
0541     if (doMaterial) {
0542       const auto scatteringMapId = scatteringMap.find(geoId);
0543       assert(scatteringMapId != scatteringMap.end() &&
0544              "No scattering angles found for material surface.");
0545       doMaterial = doMaterial && scatteringMapId->second.materialIsValid();
0546     }
0547 
0548     // We only consider states with a measurement (and/or material)
0549     if (!stateHasMeasurement && !doMaterial) {
0550       ACTS_DEBUG("    Skip state.");
0551       continue;
0552     }
0553 
0554     // update all Jacobians from start
0555     for (auto& jac : jacobianFromStart) {
0556       jac = trackState.jacobian() * jac;
0557     }
0558 
0559     // Handle measurement
0560     if (stateHasMeasurement) {
0561       ACTS_DEBUG("    Handle measurement.");
0562 
0563       const auto measDim = trackState.calibratedSize();
0564 
0565       if (measDim < 1 || 6 < measDim) {
0566         ACTS_ERROR("Can not process state with measurement with "
0567                    << measDim << " dimensions.");
0568         throw std::domain_error(
0569             "Found measurement with less than 1 or more than 6 dimension(s).");
0570       }
0571 
0572       extendedSystem.ndf() += measDim;
0573 
0574       visit_measurement(measDim, [&](auto N) {
0575         addMeasurementToGx2fSums<N>(extendedSystem, jacobianFromStart,
0576                                     trackState, logger);
0577       });
0578     }
0579 
0580     // Handle material
0581     if (doMaterial) {
0582       ACTS_DEBUG("    Handle material");
0583       // Add for this material a new Jacobian, starting from this surface.
0584       jacobianFromStart.emplace_back(BoundMatrix::Identity());
0585 
0586       // Add the material contribution to the system
0587       addMaterialToGx2fSums(extendedSystem, geoIdVector.size(), scatteringMap,
0588                             trackState, logger);
0589 
0590       geoIdVector.emplace_back(geoId);
0591     }
0592   }
0593 }
0594 
0595 /// @brief Count the valid material states in a track for scattering calculations.
0596 ///
0597 /// This function counts the valid material surfaces encountered in a track
0598 /// by examining each track state. The count is based on the presence of
0599 /// material flags and the availability of scattering information for each
0600 /// surface.
0601 ///
0602 /// @tparam track_proxy_t The type of the track proxy
0603 ///
0604 /// @param track A constant track proxy to inspect
0605 /// @param scatteringMap Map of geometry identifiers to scattering properties,
0606 ///        containing scattering angles and validation status
0607 /// @param logger A logger instance
0608 template <TrackProxyConcept track_proxy_t>
0609 std::size_t countMaterialStates(
0610     const track_proxy_t track,
0611     const std::unordered_map<GeometryIdentifier, ScatteringProperties>&
0612         scatteringMap,
0613     const Logger& logger) {
0614   std::size_t nMaterialSurfaces = 0;
0615   ACTS_DEBUG("Count the valid material surfaces.");
0616   for (const auto& trackState : track.trackStates()) {
0617     const auto typeFlags = trackState.typeFlags();
0618     const bool stateHasMaterial = typeFlags.test(TrackStateFlag::MaterialFlag);
0619 
0620     if (!stateHasMaterial) {
0621       continue;
0622     }
0623 
0624     // Get and store geoId for the current material surface
0625     const GeometryIdentifier geoId = trackState.referenceSurface().geometryId();
0626 
0627     const auto scatteringMapId = scatteringMap.find(geoId);
0628     assert(scatteringMapId != scatteringMap.end() &&
0629            "No scattering angles found for material surface.");
0630     if (!scatteringMapId->second.materialIsValid()) {
0631       continue;
0632     }
0633 
0634     nMaterialSurfaces++;
0635   }
0636 
0637   return nMaterialSurfaces;
0638 }
0639 
0640 /// @brief Solve the gx2f system to get the delta parameters for the update
0641 ///
0642 /// This function computes the delta parameters for the GX2F Actor fitting
0643 /// process by solving the linear equation system [a] * delta = b. It uses the
0644 /// column-pivoting Householder QR decomposition for numerical stability.
0645 ///
0646 /// @param extendedSystem All parameters of the current equation system
0647 Eigen::VectorXd computeGx2fDeltaParams(const Gx2fSystem& extendedSystem);
0648 
0649 /// @brief Update parameters (and scattering angles if applicable)
0650 ///
0651 /// @param params Parameters to be updated
0652 /// @param deltaParamsExtended Delta parameters for bound parameter and scattering angles
0653 /// @param nMaterialSurfaces Number of material surfaces in the track
0654 /// @param scatteringMap Map of geometry identifiers to scattering properties,
0655 ///        containing all scattering angles and covariances
0656 /// @param geoIdVector Vector of geometry identifiers corresponding to material surfaces
0657 void updateGx2fParams(
0658     BoundTrackParameters& params, const Eigen::VectorXd& deltaParamsExtended,
0659     const std::size_t nMaterialSurfaces,
0660     std::unordered_map<GeometryIdentifier, ScatteringProperties>& scatteringMap,
0661     const std::vector<GeometryIdentifier>& geoIdVector);
0662 
0663 /// @brief Calculate and update the covariance of the fitted parameters
0664 ///
0665 /// This function calculates the covariance of the fitted parameters using
0666 /// cov = inv([a])
0667 /// It then updates the first square block of size ndfSystem. This ensures,
0668 /// that we only update the covariance for fitted parameters. (In case of
0669 /// no qop/time fit)
0670 ///
0671 /// @param fullCovariancePredicted The covariance matrix to update
0672 /// @param extendedSystem All parameters of the current equation system
0673 void updateGx2fCovarianceParams(BoundMatrix& fullCovariancePredicted,
0674                                 Gx2fSystem& extendedSystem);
0675 
0676 /// Global Chi Square fitter (GX2F) implementation.
0677 ///
0678 /// @tparam propagator_t Type of the propagation class
0679 ///
0680 /// TODO Write description
0681 template <typename propagator_t, typename traj_t>
0682 class Gx2Fitter {
0683   /// The navigator type
0684   using Gx2fNavigator = typename propagator_t::Navigator;
0685 
0686   /// The navigator has DirectNavigator type or not
0687   static constexpr bool isDirectNavigator =
0688       std::is_same_v<Gx2fNavigator, DirectNavigator>;
0689 
0690  public:
0691   Gx2Fitter(propagator_t pPropagator,
0692             std::unique_ptr<const Logger> _logger =
0693                 getDefaultLogger("Gx2Fitter", Logging::INFO))
0694       : m_propagator(std::move(pPropagator)),
0695         m_logger{std::move(_logger)},
0696         m_actorLogger{m_logger->cloneWithSuffix("Actor")},
0697         m_addToSumLogger{m_logger->cloneWithSuffix("AddToSum")} {}
0698 
0699  private:
0700   /// The propagator for the transport and material update
0701   propagator_t m_propagator;
0702 
0703   /// The logger instance
0704   std::unique_ptr<const Logger> m_logger;
0705   std::unique_ptr<const Logger> m_actorLogger;
0706   std::unique_ptr<const Logger> m_addToSumLogger;
0707 
0708   const Logger& logger() const { return *m_logger; }
0709 
0710   /// @brief Propagator Actor plugin for the GX2F
0711   ///
0712   /// @tparam parameters_t The type of parameters used for "local" parameters.
0713   /// @tparam calibrator_t The type of calibrator
0714   /// @tparam outlier_finder_t Type of the outlier finder class
0715   ///
0716   /// The GX2F Actor does not rely on the measurements to be sorted along the
0717   /// track.
0718   template <typename parameters_t>
0719   class Actor {
0720    public:
0721     /// Broadcast the result_type
0722     using result_type = Gx2FitterResult<traj_t>;
0723 
0724     /// The target surface
0725     const Surface* targetSurface = nullptr;
0726 
0727     /// Allows retrieving measurements for a surface
0728     const std::map<GeometryIdentifier, SourceLink>* inputMeasurements = nullptr;
0729 
0730     /// Whether to consider multiple scattering.
0731     bool multipleScattering = false;
0732 
0733     /// Whether to consider energy loss.
0734     bool energyLoss = false;  /// TODO implement later
0735 
0736     /// Whether to include non-linear correction during global to local
0737     /// transformation
0738     FreeToBoundCorrection freeToBoundCorrection;
0739 
0740     /// Input MultiTrajectory
0741     std::shared_ptr<MultiTrajectory<traj_t>> outputStates;
0742 
0743     /// The logger instance
0744     const Logger* actorLogger{nullptr};
0745 
0746     /// Logger helper
0747     const Logger& logger() const { return *actorLogger; }
0748 
0749     Gx2FitterExtensions<traj_t> extensions;
0750 
0751     /// The Surface being
0752     SurfaceReached targetReached;
0753 
0754     /// Calibration context for the fit
0755     const CalibrationContext* calibrationContext{nullptr};
0756 
0757     /// The particle hypothesis is needed for estimating scattering angles
0758     const parameters_t* parametersWithHypothesis = nullptr;
0759 
0760     /// The scatteringMap stores for each visited surface their scattering
0761     /// properties
0762     std::unordered_map<GeometryIdentifier, ScatteringProperties>*
0763         scatteringMap = nullptr;
0764 
0765     /// @brief Gx2f actor operation
0766     ///
0767     /// @tparam propagator_state_t is the type of Propagator state
0768     /// @tparam stepper_t Type of the stepper
0769     /// @tparam navigator_t Type of the navigator
0770     ///
0771     /// @param state is the mutable propagator state object
0772     /// @param stepper The stepper in use
0773     /// @param navigator The navigator in use
0774     /// @param result is the mutable result state object
0775     template <typename propagator_state_t, typename stepper_t,
0776               typename navigator_t>
0777     void act(propagator_state_t& state, const stepper_t& stepper,
0778              const navigator_t& navigator, result_type& result,
0779              const Logger& /*logger*/) const {
0780       assert(result.fittedStates && "No MultiTrajectory set");
0781 
0782       // Check if we can stop to propagate
0783       if (result.measurementStates == inputMeasurements->size()) {
0784         ACTS_DEBUG("Actor: finish: All measurements have been found.");
0785         result.finished = true;
0786       } else if (state.navigation.navigationBreak) {
0787         ACTS_DEBUG("Actor: finish: navigationBreak.");
0788         result.finished = true;
0789       }
0790 
0791       // End the propagation and return to the fitter
0792       if (result.finished || !result.result.ok()) {
0793         // Remove the missing surfaces that occur after the last measurement
0794         if (result.measurementStates > 0) {
0795           result.missedActiveSurfaces.resize(result.measurementHoles);
0796         }
0797 
0798         return;
0799       }
0800 
0801       // We are only interested in surfaces. If we are not on a surface, we
0802       // continue the navigation
0803       auto surface = navigator.currentSurface(state.navigation);
0804       if (surface == nullptr) {
0805         return;
0806       }
0807 
0808       ++result.surfaceCount;
0809       const GeometryIdentifier geoId = surface->geometryId();
0810       ACTS_DEBUG("Surface " << geoId << " detected.");
0811 
0812       const bool surfaceIsSensitive =
0813           (surface->associatedDetectorElement() != nullptr);
0814       const bool surfaceHasMaterial = (surface->surfaceMaterial() != nullptr);
0815       // First we figure out, if we would need to look into material surfaces at
0816       // all. Later, we also check, if the material slab is valid, otherwise we
0817       // modify this flag to ignore the material completely.
0818       bool doMaterial = multipleScattering && surfaceHasMaterial;
0819 
0820       // Found material - add a scatteringAngles entry if not done yet.
0821       // Handling will happen later
0822       if (doMaterial) {
0823         ACTS_DEBUG("    The surface contains material, ...");
0824 
0825         auto scatteringMapId = scatteringMap->find(geoId);
0826         if (scatteringMapId == scatteringMap->end()) {
0827           ACTS_DEBUG("    ... create entry in scattering map.");
0828 
0829           detail::PointwiseMaterialInteraction interaction(surface, state,
0830                                                            stepper);
0831           // We need to evaluate the material to create the correct slab
0832           const bool slabIsValid = interaction.evaluateMaterialSlab(
0833               state, navigator, MaterialUpdateStage::FullUpdate);
0834           double invSigma2 = 0.;
0835           if (slabIsValid) {
0836             const auto& particle =
0837                 parametersWithHypothesis->particleHypothesis();
0838 
0839             const double sigma =
0840                 static_cast<double>(Acts::computeMultipleScatteringTheta0(
0841                     interaction.slab, particle.absolutePdg(), particle.mass(),
0842                     static_cast<float>(
0843                         parametersWithHypothesis->parameters()[eBoundQOverP]),
0844                     particle.absoluteCharge()));
0845             ACTS_VERBOSE(
0846                 "        The Highland formula gives sigma = " << sigma);
0847             invSigma2 = 1. / std::pow(sigma, 2);
0848           } else {
0849             ACTS_VERBOSE("        Material slab is not valid.");
0850           }
0851 
0852           scatteringMap->emplace(
0853               geoId, ScatteringProperties{BoundVector::Zero(), invSigma2,
0854                                           slabIsValid});
0855           scatteringMapId = scatteringMap->find(geoId);
0856         } else {
0857           ACTS_DEBUG("    ... found entry in scattering map.");
0858         }
0859 
0860         doMaterial = doMaterial && scatteringMapId->second.materialIsValid();
0861       }
0862 
0863       // Here we handle all measurements
0864       if (auto sourceLinkIt = inputMeasurements->find(geoId);
0865           sourceLinkIt != inputMeasurements->end()) {
0866         ACTS_DEBUG("    The surface contains a measurement.");
0867 
0868         // Transport the covariance to the surface
0869         stepper.transportCovarianceToBound(state.stepping, *surface,
0870                                            freeToBoundCorrection);
0871 
0872         // TODO generalize the update of the currentTrackIndex
0873         auto& fittedStates = *result.fittedStates;
0874 
0875         // Add a <trackStateMask> TrackState entry multi trajectory. This
0876         // allocates storage for all components, which we will set later.
0877         typename traj_t::TrackStateProxy trackStateProxy =
0878             fittedStates.makeTrackState(Gx2fConstants::trackStateMask,
0879                                         result.lastTrackIndex);
0880         const std::size_t currentTrackIndex = trackStateProxy.index();
0881 
0882         // Set the trackStateProxy components with the state from the ongoing
0883         // propagation
0884         {
0885           trackStateProxy.setReferenceSurface(surface->getSharedPtr());
0886           // Bind the transported state to the current surface
0887           auto res = stepper.boundState(state.stepping, *surface, false,
0888                                         freeToBoundCorrection);
0889           if (!res.ok()) {
0890             result.result = res.error();
0891             return;
0892           }
0893           // Not const since, we might need to update with scattering angles
0894           auto& [boundParams, jacobian, pathLength] = *res;
0895 
0896           // For material surfaces, we also update the angles with the
0897           // available scattering information
0898           if (doMaterial) {
0899             ACTS_DEBUG("    Update parameters with scattering angles.");
0900             const auto scatteringMapId = scatteringMap->find(geoId);
0901             ACTS_VERBOSE(
0902                 "        scatteringAngles: "
0903                 << scatteringMapId->second.scatteringAngles().transpose());
0904             ACTS_VERBOSE("        boundParams before the update: "
0905                          << boundParams.parameters().transpose());
0906             boundParams.parameters() +=
0907                 scatteringMapId->second.scatteringAngles();
0908             ACTS_VERBOSE("        boundParams after the update: "
0909                          << boundParams.parameters().transpose());
0910           }
0911 
0912           // Fill the track state
0913           trackStateProxy.smoothed() = boundParams.parameters();
0914           trackStateProxy.smoothedCovariance() = state.stepping.cov;
0915 
0916           trackStateProxy.jacobian() = jacobian;
0917           trackStateProxy.pathLength() = pathLength;
0918 
0919           if (doMaterial) {
0920             stepper.update(state.stepping,
0921                            transformBoundToFreeParameters(
0922                                trackStateProxy.referenceSurface(),
0923                                state.geoContext, trackStateProxy.smoothed()),
0924                            trackStateProxy.smoothed(),
0925                            trackStateProxy.smoothedCovariance(), *surface);
0926           }
0927         }
0928 
0929         // We have smoothed parameters, so calibrate the uncalibrated input
0930         // measurement
0931         extensions.calibrator(state.geoContext, *calibrationContext,
0932                               sourceLinkIt->second, trackStateProxy);
0933 
0934         // Get and set the type flags
0935         auto typeFlags = trackStateProxy.typeFlags();
0936         typeFlags.set(TrackStateFlag::ParameterFlag);
0937         if (surfaceHasMaterial) {
0938           typeFlags.set(TrackStateFlag::MaterialFlag);
0939         }
0940 
0941         // Set the measurement type flag
0942         typeFlags.set(TrackStateFlag::MeasurementFlag);
0943         // We count the processed measurement
0944         ++result.processedMeasurements;
0945 
0946         result.lastMeasurementIndex = currentTrackIndex;
0947         result.lastTrackIndex = currentTrackIndex;
0948 
0949         // TODO check for outlier first
0950         // We count the state with measurement
0951         ++result.measurementStates;
0952 
0953         // We count the processed state
0954         ++result.processedStates;
0955 
0956         // Update the number of holes count only when encountering a
0957         // measurement
0958         result.measurementHoles = result.missedActiveSurfaces.size();
0959 
0960         return;
0961       }
0962 
0963       if (doMaterial) {
0964         // Here we handle material for multipleScattering. If holes exist, we
0965         // also handle them already. We create a full trackstate (unlike for
0966         // simple holes), since we need to evaluate the material later
0967         ACTS_DEBUG(
0968             "    The surface contains no measurement, but material and maybe "
0969             "a hole.");
0970 
0971         // Transport the covariance to the surface
0972         stepper.transportCovarianceToBound(state.stepping, *surface,
0973                                            freeToBoundCorrection);
0974 
0975         // TODO generalize the update of the currentTrackIndex
0976         auto& fittedStates = *result.fittedStates;
0977 
0978         // Add a <trackStateMask> TrackState entry multi trajectory. This
0979         // allocates storage for all components, which we will set later.
0980         typename traj_t::TrackStateProxy trackStateProxy =
0981             fittedStates.makeTrackState(Gx2fConstants::trackStateMask,
0982                                         result.lastTrackIndex);
0983         const std::size_t currentTrackIndex = trackStateProxy.index();
0984 
0985         // Set the trackStateProxy components with the state from the ongoing
0986         // propagation
0987         {
0988           trackStateProxy.setReferenceSurface(surface->getSharedPtr());
0989           // Bind the transported state to the current surface
0990           auto res = stepper.boundState(state.stepping, *surface, false,
0991                                         freeToBoundCorrection);
0992           if (!res.ok()) {
0993             result.result = res.error();
0994             return;
0995           }
0996           // Not const since, we might need to update with scattering angles
0997           auto& [boundParams, jacobian, pathLength] = *res;
0998 
0999           // For material surfaces, we also update the angles with the
1000           // available scattering information
1001           // We can skip the if here, since we already know, that we do
1002           // multipleScattering and have material
1003           ACTS_DEBUG("    Update parameters with scattering angles.");
1004           const auto scatteringMapId = scatteringMap->find(geoId);
1005           ACTS_VERBOSE(
1006               "        scatteringAngles: "
1007               << scatteringMapId->second.scatteringAngles().transpose());
1008           ACTS_VERBOSE("        boundParams before the update: "
1009                        << boundParams.parameters().transpose());
1010           boundParams.parameters() +=
1011               scatteringMapId->second.scatteringAngles();
1012           ACTS_VERBOSE("        boundParams after the update: "
1013                        << boundParams.parameters().transpose());
1014 
1015           // Fill the track state
1016           trackStateProxy.smoothed() = boundParams.parameters();
1017           trackStateProxy.smoothedCovariance() = state.stepping.cov;
1018 
1019           trackStateProxy.jacobian() = jacobian;
1020           trackStateProxy.pathLength() = pathLength;
1021 
1022           stepper.update(state.stepping,
1023                          transformBoundToFreeParameters(
1024                              trackStateProxy.referenceSurface(),
1025                              state.geoContext, trackStateProxy.smoothed()),
1026                          trackStateProxy.smoothed(),
1027                          trackStateProxy.smoothedCovariance(), *surface);
1028         }
1029 
1030         // Get and set the type flags
1031         auto typeFlags = trackStateProxy.typeFlags();
1032         typeFlags.set(TrackStateFlag::ParameterFlag);
1033         typeFlags.set(TrackStateFlag::MaterialFlag);
1034 
1035         // Set hole only, if we are on a sensitive surface and had
1036         // measurements before (no holes before the first measurement)
1037         const bool precedingMeasurementExists = (result.measurementStates > 0);
1038         if (surfaceIsSensitive && precedingMeasurementExists) {
1039           ACTS_DEBUG("    Surface is also sensitive. Marked as hole.");
1040           typeFlags.set(TrackStateFlag::HoleFlag);
1041 
1042           // Count the missed surface
1043           result.missedActiveSurfaces.push_back(surface);
1044         }
1045 
1046         result.lastTrackIndex = currentTrackIndex;
1047 
1048         ++result.processedStates;
1049 
1050         return;
1051       }
1052 
1053       if (surfaceIsSensitive || surfaceHasMaterial) {
1054         // Here we handle holes. If material hasn't been handled before
1055         // (because multipleScattering is turned off), we will also handle it
1056         // here
1057         if (multipleScattering) {
1058           ACTS_DEBUG(
1059               "    The surface contains no measurement, but maybe a hole.");
1060         } else {
1061           ACTS_DEBUG(
1062               "    The surface contains no measurement, but maybe a hole "
1063               "and/or material.");
1064         }
1065 
1066         // We only create track states here if there is already a measurement
1067         // detected (no holes before the first measurement) or if we encounter
1068         // material
1069         const bool precedingMeasurementExists = (result.measurementStates > 0);
1070         if (!precedingMeasurementExists && !surfaceHasMaterial) {
1071           ACTS_DEBUG(
1072               "    Ignoring hole, because there are no preceding "
1073               "measurements.");
1074           return;
1075         }
1076 
1077         auto& fittedStates = *result.fittedStates;
1078 
1079         // Add a <trackStateMask> TrackState entry multi trajectory. This
1080         // allocates storage for all components, which we will set later.
1081         typename traj_t::TrackStateProxy trackStateProxy =
1082             fittedStates.makeTrackState(Gx2fConstants::trackStateMask,
1083                                         result.lastTrackIndex);
1084         const std::size_t currentTrackIndex = trackStateProxy.index();
1085 
1086         // Set the trackStateProxy components with the state from the
1087         // ongoing propagation
1088         {
1089           trackStateProxy.setReferenceSurface(surface->getSharedPtr());
1090           // Bind the transported state to the current surface
1091           auto res = stepper.boundState(state.stepping, *surface, false,
1092                                         freeToBoundCorrection);
1093           if (!res.ok()) {
1094             result.result = res.error();
1095             return;
1096           }
1097           const auto& [boundParams, jacobian, pathLength] = *res;
1098 
1099           // Fill the track state
1100           trackStateProxy.smoothed() = boundParams.parameters();
1101           trackStateProxy.smoothedCovariance() = state.stepping.cov;
1102 
1103           trackStateProxy.jacobian() = jacobian;
1104           trackStateProxy.pathLength() = pathLength;
1105         }
1106 
1107         // Get and set the type flags
1108         auto typeFlags = trackStateProxy.typeFlags();
1109         typeFlags.set(TrackStateFlag::ParameterFlag);
1110         if (surfaceHasMaterial) {
1111           ACTS_DEBUG("    It is material.");
1112           typeFlags.set(TrackStateFlag::MaterialFlag);
1113         }
1114 
1115         // Set hole only, if we are on a sensitive surface
1116         if (surfaceIsSensitive && precedingMeasurementExists) {
1117           ACTS_DEBUG("    It is a hole.");
1118           typeFlags.set(TrackStateFlag::HoleFlag);
1119           // Count the missed surface
1120           result.missedActiveSurfaces.push_back(surface);
1121         }
1122 
1123         result.lastTrackIndex = currentTrackIndex;
1124 
1125         ++result.processedStates;
1126 
1127         return;
1128       }
1129 
1130       ACTS_DEBUG("    The surface contains no measurement/material/hole.");
1131       return;
1132     }
1133 
1134     template <typename propagator_state_t, typename stepper_t,
1135               typename navigator_t, typename result_t>
1136     bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
1137                     const navigator_t& /*navigator*/, const result_t& result,
1138                     const Logger& /*logger*/) const {
1139       if (!result.result.ok() || result.finished) {
1140         return true;
1141       }
1142       return false;
1143     }
1144   };
1145 
1146  public:
1147   /// Fit implementation
1148   ///
1149   /// @tparam source_link_iterator_t Iterator type used to pass source links
1150   /// @tparam start_parameters_t Type of the initial parameters
1151   /// @tparam parameters_t Type of parameters used for local parameters
1152   /// @tparam track_container_t Type of the track container backend
1153   /// @tparam holder_t Type defining track container backend ownership
1154   ///
1155   /// @param it Begin iterator for the fittable uncalibrated measurements
1156   /// @param end End iterator for the fittable uncalibrated measurements
1157   /// @param sParameters The initial track parameters
1158   /// @param gx2fOptions Gx2FitterOptions steering the fit
1159   /// @param trackContainer Input track container storage to append into
1160   /// @note The input measurements are given in the form of @c SourceLink s.
1161   /// It's the calibrators job to turn them into calibrated measurements used in
1162   /// the fit.
1163   ///
1164   /// @return the output as an output track
1165   template <typename source_link_iterator_t, typename start_parameters_t,
1166             typename parameters_t = BoundTrackParameters,
1167             TrackContainerFrontend track_container_t>
1168   Result<typename track_container_t::TrackProxy> fit(
1169       source_link_iterator_t it, source_link_iterator_t end,
1170       const start_parameters_t& sParameters,
1171       const Gx2FitterOptions<traj_t>& gx2fOptions,
1172       track_container_t& trackContainer) const
1173     requires(!isDirectNavigator)
1174   {
1175     // Preprocess Measurements (SourceLinks -> map)
1176     // To be able to find measurements later, we put them into a map.
1177     // We need to copy input SourceLinks anyway, so the map can own them.
1178     ACTS_VERBOSE("Preparing " << std::distance(it, end)
1179                               << " input measurements");
1180     std::map<GeometryIdentifier, SourceLink> inputMeasurements;
1181 
1182     for (; it != end; ++it) {
1183       SourceLink sl = *it;
1184       auto geoId = gx2fOptions.extensions.surfaceAccessor(sl)->geometryId();
1185       inputMeasurements.emplace(geoId, std::move(sl));
1186     }
1187 
1188     // Store, if we want to do multiple scattering. We still need to pass this
1189     // option to the Actor.
1190     const bool multipleScattering = gx2fOptions.multipleScattering;
1191 
1192     // Create the ActorList
1193     using GX2FActor = Actor<parameters_t>;
1194 
1195     using GX2FResult = typename GX2FActor::result_type;
1196     using Actors = Acts::ActorList<GX2FActor>;
1197 
1198     using PropagatorOptions = typename propagator_t::template Options<Actors>;
1199 
1200     start_parameters_t params = sParameters;
1201     double chi2sum = 0;
1202     double oldChi2sum = std::numeric_limits<double>::max();
1203 
1204     // We need to create a temporary track container. We create several times a
1205     // new track and delete it after updating the parameters. However, if we
1206     // would work on the externally provided track container, it would be
1207     // difficult to remove the correct track, if it contains more than one.
1208     typename track_container_t::TrackContainerBackend trackContainerTempBackend;
1209     traj_t trajectoryTempBackend;
1210     TrackContainer trackContainerTemp{trackContainerTempBackend,
1211                                       trajectoryTempBackend};
1212 
1213     // Create an index of the 'tip' of the track stored in multitrajectory. It
1214     // is needed outside the update loop. It will be updated with each iteration
1215     // and used for the final track
1216     std::size_t tipIndex = Acts::MultiTrajectoryTraits::kInvalid;
1217 
1218     // The scatteringMap stores for each visited surface their scattering
1219     // properties
1220     std::unordered_map<GeometryIdentifier, ScatteringProperties> scatteringMap;
1221 
1222     // This will be filled during the updates with the final covariance of the
1223     // track parameters.
1224     BoundMatrix fullCovariancePredicted = BoundMatrix::Identity();
1225 
1226     ACTS_VERBOSE("Initial parameters: " << params.parameters().transpose());
1227 
1228     /// Actual Fitting /////////////////////////////////////////////////////////
1229     ACTS_DEBUG("Start to iterate");
1230 
1231     // Iterate the fit and improve result. Abort after n steps or after
1232     // convergence.
1233     // nUpdate is initialized outside to save its state for the track.
1234     std::size_t nUpdate = 0;
1235     for (nUpdate = 0; nUpdate < gx2fOptions.nUpdateMax; nUpdate++) {
1236       ACTS_DEBUG("nUpdate = " << nUpdate + 1 << "/" << gx2fOptions.nUpdateMax);
1237 
1238       // set up propagator and co
1239       Acts::GeometryContext geoCtx = gx2fOptions.geoContext;
1240       Acts::MagneticFieldContext magCtx = gx2fOptions.magFieldContext;
1241       // Set options for propagator
1242       PropagatorOptions propagatorOptions(geoCtx, magCtx);
1243 
1244       // Add the measurement surface as external surface to the navigator.
1245       // We will try to hit those surface by ignoring boundary checks.
1246       for (const auto& [surfaceId, _] : inputMeasurements) {
1247         propagatorOptions.navigation.insertExternalSurface(surfaceId);
1248       }
1249 
1250       auto& gx2fActor = propagatorOptions.actorList.template get<GX2FActor>();
1251       gx2fActor.inputMeasurements = &inputMeasurements;
1252       gx2fActor.multipleScattering = false;
1253       gx2fActor.extensions = gx2fOptions.extensions;
1254       gx2fActor.calibrationContext = &gx2fOptions.calibrationContext.get();
1255       gx2fActor.actorLogger = m_actorLogger.get();
1256       gx2fActor.scatteringMap = &scatteringMap;
1257       gx2fActor.parametersWithHypothesis = &params;
1258 
1259       auto propagatorState = m_propagator.makeState(params, propagatorOptions);
1260 
1261       auto& r = propagatorState.template get<Gx2FitterResult<traj_t>>();
1262       r.fittedStates = &trajectoryTempBackend;
1263 
1264       // Clear the track container. It could be more performant to update the
1265       // existing states, but this needs some more thinking.
1266       trackContainerTemp.clear();
1267 
1268       auto propagationResult = m_propagator.propagate(propagatorState);
1269 
1270       // Run the fitter
1271       auto result =
1272           m_propagator.makeResult(std::move(propagatorState), propagationResult,
1273                                   propagatorOptions, false);
1274 
1275       if (!result.ok()) {
1276         ACTS_ERROR("Propagation failed: " << result.error());
1277         return result.error();
1278       }
1279 
1280       // TODO Improve Propagator + Actor [allocate before loop], rewrite
1281       // makeMeasurements
1282       auto& propRes = *result;
1283       GX2FResult gx2fResult = std::move(propRes.template get<GX2FResult>());
1284 
1285       if (!gx2fResult.result.ok()) {
1286         ACTS_INFO("GlobalChiSquareFitter failed in actor: "
1287                   << gx2fResult.result.error() << ", "
1288                   << gx2fResult.result.error().message());
1289         return gx2fResult.result.error();
1290       }
1291 
1292       auto track = trackContainerTemp.makeTrack();
1293       tipIndex = gx2fResult.lastMeasurementIndex;
1294 
1295       // It could happen, that no measurements were found. Then the track would
1296       // be empty and the following operations would be invalid. Usually, this
1297       // only happens during the first iteration, due to bad initial parameters.
1298       if (tipIndex == Acts::MultiTrajectoryTraits::kInvalid) {
1299         ACTS_INFO("Did not find any measurements in nUpdate "
1300                   << nUpdate + 1 << "/" << gx2fOptions.nUpdateMax);
1301         return Experimental::GlobalChiSquareFitterError::NotEnoughMeasurements;
1302       }
1303 
1304       track.tipIndex() = tipIndex;
1305       track.linkForward();
1306 
1307       // Count the material surfaces, to set up the system. In the multiple
1308       // scattering case, we need to extend our system.
1309       const std::size_t nMaterialSurfaces = 0u;
1310 
1311       // We need 6 dimensions for the bound parameters and 2 * nMaterialSurfaces
1312       // dimensions for the scattering angles.
1313       const std::size_t dimsExtendedParams = eBoundSize + 2 * nMaterialSurfaces;
1314 
1315       // System that we fill with the information gathered by the actor and
1316       // evaluate later
1317       Gx2fSystem extendedSystem{dimsExtendedParams};
1318 
1319       // This vector stores the IDs for each visited material in order. We use
1320       // it later for updating the scattering angles. We cannot use
1321       // scatteringMap directly, since we cannot guarantee, that we will visit
1322       // all stored material in each propagation.
1323       std::vector<GeometryIdentifier> geoIdVector;
1324 
1325       fillGx2fSystem(track, extendedSystem, false, scatteringMap, geoIdVector,
1326                      *m_addToSumLogger);
1327 
1328       chi2sum = extendedSystem.chi2();
1329 
1330       // This check takes into account the evaluated dimensions of the
1331       // measurements. To fit, we need at least NDF+1 measurements. However, we
1332       // count n-dimensional measurements for n measurements, reducing the
1333       // effective number of needed measurements. We might encounter the case,
1334       // where we cannot use some (parts of a) measurements, maybe if we do not
1335       // support that kind of measurement. This is also taken into account here.
1336       // We skip the check during the first iteration, since we cannot guarantee
1337       // to hit all/enough measurement surfaces with the initial parameter
1338       // guess.
1339       if ((nUpdate > 0) && !extendedSystem.isWellDefined()) {
1340         ACTS_INFO("Not enough measurements. Require "
1341                   << extendedSystem.findRequiredNdf() + 1 << ", but only "
1342                   << extendedSystem.ndf() << " could be used.");
1343         return Experimental::GlobalChiSquareFitterError::NotEnoughMeasurements;
1344       }
1345 
1346       Eigen::VectorXd deltaParamsExtended =
1347           computeGx2fDeltaParams(extendedSystem);
1348 
1349       ACTS_VERBOSE("aMatrix:\n"
1350                    << extendedSystem.aMatrix() << "\n"
1351                    << "bVector:\n"
1352                    << extendedSystem.bVector() << "\n"
1353                    << "deltaParamsExtended:\n"
1354                    << deltaParamsExtended << "\n"
1355                    << "oldChi2sum = " << oldChi2sum << "\n"
1356                    << "chi2sum = " << extendedSystem.chi2());
1357 
1358       if ((gx2fOptions.relChi2changeCutOff != 0) && (nUpdate > 0) &&
1359           (std::abs(extendedSystem.chi2() / oldChi2sum - 1) <
1360            gx2fOptions.relChi2changeCutOff)) {
1361         ACTS_DEBUG("Abort with relChi2changeCutOff after "
1362                    << nUpdate + 1 << "/" << gx2fOptions.nUpdateMax
1363                    << " iterations.");
1364         updateGx2fCovarianceParams(fullCovariancePredicted, extendedSystem);
1365         break;
1366       }
1367 
1368       if (extendedSystem.chi2() > oldChi2sum + 1e-5) {
1369         ACTS_DEBUG("chi2 not converging monotonically in update " << nUpdate);
1370       }
1371 
1372       // If this is the final iteration, update the covariance and break.
1373       // Otherwise, we would update the scattering angles too much.
1374       if (nUpdate == gx2fOptions.nUpdateMax - 1) {
1375         // Since currently most of our tracks converge in 4-5 updates, we want
1376         // to set nUpdateMax higher than that to guarantee convergence for most
1377         // tracks. In cases, where we set a smaller nUpdateMax, it's because we
1378         // want to investigate the behaviour of the fitter before it converges,
1379         // like in some unit-tests.
1380         if (gx2fOptions.nUpdateMax > 5) {
1381           ACTS_INFO("Did not converge in " << gx2fOptions.nUpdateMax
1382                                            << " updates.");
1383           return Experimental::GlobalChiSquareFitterError::DidNotConverge;
1384         }
1385 
1386         updateGx2fCovarianceParams(fullCovariancePredicted, extendedSystem);
1387         break;
1388       }
1389 
1390       updateGx2fParams(params, deltaParamsExtended, nMaterialSurfaces,
1391                        scatteringMap, geoIdVector);
1392       ACTS_VERBOSE("Updated parameters: " << params.parameters().transpose());
1393 
1394       oldChi2sum = extendedSystem.chi2();
1395     }
1396     ACTS_DEBUG("Finished to iterate");
1397     ACTS_VERBOSE("Final parameters: " << params.parameters().transpose());
1398     /// Finish Fitting /////////////////////////////////////////////////////////
1399 
1400     /// Actual MATERIAL Fitting ////////////////////////////////////////////////
1401     ACTS_DEBUG("Start to evaluate material");
1402     if (multipleScattering) {
1403       // set up propagator and co
1404       Acts::GeometryContext geoCtx = gx2fOptions.geoContext;
1405       Acts::MagneticFieldContext magCtx = gx2fOptions.magFieldContext;
1406       // Set options for propagator
1407       PropagatorOptions propagatorOptions(geoCtx, magCtx);
1408 
1409       // Add the measurement surface as external surface to the navigator.
1410       // We will try to hit those surface by ignoring boundary checks.
1411       for (const auto& [surfaceId, _] : inputMeasurements) {
1412         propagatorOptions.navigation.insertExternalSurface(surfaceId);
1413       }
1414 
1415       auto& gx2fActor = propagatorOptions.actorList.template get<GX2FActor>();
1416       gx2fActor.inputMeasurements = &inputMeasurements;
1417       gx2fActor.multipleScattering = true;
1418       gx2fActor.extensions = gx2fOptions.extensions;
1419       gx2fActor.calibrationContext = &gx2fOptions.calibrationContext.get();
1420       gx2fActor.actorLogger = m_actorLogger.get();
1421       gx2fActor.scatteringMap = &scatteringMap;
1422       gx2fActor.parametersWithHypothesis = &params;
1423 
1424       auto propagatorState = m_propagator.makeState(params, propagatorOptions);
1425 
1426       auto& r = propagatorState.template get<Gx2FitterResult<traj_t>>();
1427       r.fittedStates = &trajectoryTempBackend;
1428 
1429       // Clear the track container. It could be more performant to update the
1430       // existing states, but this needs some more thinking.
1431       trackContainerTemp.clear();
1432 
1433       auto propagationResult = m_propagator.propagate(propagatorState);
1434 
1435       // Run the fitter
1436       auto result =
1437           m_propagator.makeResult(std::move(propagatorState), propagationResult,
1438                                   propagatorOptions, false);
1439 
1440       if (!result.ok()) {
1441         ACTS_ERROR("Propagation failed: " << result.error());
1442         return result.error();
1443       }
1444 
1445       // TODO Improve Propagator + Actor [allocate before loop], rewrite
1446       // makeMeasurements
1447       auto& propRes = *result;
1448       GX2FResult gx2fResult = std::move(propRes.template get<GX2FResult>());
1449 
1450       if (!gx2fResult.result.ok()) {
1451         ACTS_INFO("GlobalChiSquareFitter failed in actor: "
1452                   << gx2fResult.result.error() << ", "
1453                   << gx2fResult.result.error().message());
1454         return gx2fResult.result.error();
1455       }
1456 
1457       auto track = trackContainerTemp.makeTrack();
1458       tipIndex = gx2fResult.lastMeasurementIndex;
1459 
1460       // It could happen, that no measurements were found. Then the track would
1461       // be empty and the following operations would be invalid. Usually, this
1462       // only happens during the first iteration, due to bad initial parameters.
1463       if (tipIndex == Acts::MultiTrajectoryTraits::kInvalid) {
1464         ACTS_INFO("Did not find any measurements in material fit.");
1465         return Experimental::GlobalChiSquareFitterError::NotEnoughMeasurements;
1466       }
1467 
1468       track.tipIndex() = tipIndex;
1469       track.linkForward();
1470 
1471       // Count the material surfaces, to set up the system. In the multiple
1472       // scattering case, we need to extend our system.
1473       const std::size_t nMaterialSurfaces =
1474           countMaterialStates(track, scatteringMap, *m_addToSumLogger);
1475 
1476       // We need 6 dimensions for the bound parameters and 2 * nMaterialSurfaces
1477       // dimensions for the scattering angles.
1478       const std::size_t dimsExtendedParams = eBoundSize + 2 * nMaterialSurfaces;
1479 
1480       // System that we fill with the information gathered by the actor and
1481       // evaluate later
1482       Gx2fSystem extendedSystem{dimsExtendedParams};
1483 
1484       // This vector stores the IDs for each visited material in order. We use
1485       // it later for updating the scattering angles. We cannot use
1486       // scatteringMap directly, since we cannot guarantee, that we will visit
1487       // all stored material in each propagation.
1488       std::vector<GeometryIdentifier> geoIdVector;
1489 
1490       fillGx2fSystem(track, extendedSystem, true, scatteringMap, geoIdVector,
1491                      *m_addToSumLogger);
1492 
1493       chi2sum = extendedSystem.chi2();
1494 
1495       // This check takes into account the evaluated dimensions of the
1496       // measurements. To fit, we need at least NDF+1 measurements. However, we
1497       // count n-dimensional measurements for n measurements, reducing the
1498       // effective number of needed measurements. We might encounter the case,
1499       // where we cannot use some (parts of a) measurements, maybe if we do not
1500       // support that kind of measurement. This is also taken into account here.
1501       // We skip the check during the first iteration, since we cannot guarantee
1502       // to hit all/enough measurement surfaces with the initial parameter
1503       // guess.
1504       if ((nUpdate > 0) && !extendedSystem.isWellDefined()) {
1505         ACTS_INFO("Not enough measurements. Require "
1506                   << extendedSystem.findRequiredNdf() + 1 << ", but only "
1507                   << extendedSystem.ndf() << " could be used.");
1508         return Experimental::GlobalChiSquareFitterError::NotEnoughMeasurements;
1509       }
1510 
1511       Eigen::VectorXd deltaParamsExtended =
1512           computeGx2fDeltaParams(extendedSystem);
1513 
1514       ACTS_VERBOSE("aMatrix:\n"
1515                    << extendedSystem.aMatrix() << "\n"
1516                    << "bVector:\n"
1517                    << extendedSystem.bVector() << "\n"
1518                    << "deltaParamsExtended:\n"
1519                    << deltaParamsExtended << "\n"
1520                    << "oldChi2sum = " << oldChi2sum << "\n"
1521                    << "chi2sum = " << extendedSystem.chi2());
1522 
1523       chi2sum = extendedSystem.chi2();
1524 
1525       updateGx2fParams(params, deltaParamsExtended, nMaterialSurfaces,
1526                        scatteringMap, geoIdVector);
1527       ACTS_VERBOSE("Updated parameters: " << params.parameters().transpose());
1528 
1529       updateGx2fCovarianceParams(fullCovariancePredicted, extendedSystem);
1530     }
1531     ACTS_DEBUG("Finished to evaluate material");
1532     ACTS_VERBOSE(
1533         "Final parameters after material: " << params.parameters().transpose());
1534     /// Finish MATERIAL Fitting ////////////////////////////////////////////////
1535 
1536     ACTS_VERBOSE("Final scattering angles:");
1537     for (const auto& [key, value] : scatteringMap) {
1538       if (!value.materialIsValid()) {
1539         continue;
1540       }
1541       const auto& angles = value.scatteringAngles();
1542       ACTS_VERBOSE("    ( " << angles[eBoundTheta] << " | " << angles[eBoundPhi]
1543                             << " )");
1544     }
1545 
1546     ACTS_VERBOSE("Final covariance:\n" << fullCovariancePredicted);
1547 
1548     // Propagate again with the final covariance matrix. This is necessary to
1549     // obtain the propagated covariance for each state.
1550     // We also need to recheck the result and find the tipIndex, because at this
1551     // step, we will not ignore the boundary checks for measurement surfaces. We
1552     // want to create trackstates only on surfaces, that we actually hit.
1553     if (gx2fOptions.nUpdateMax > 0) {
1554       ACTS_VERBOSE("Propagate with the final covariance.");
1555       // update covariance
1556       params.covariance() = fullCovariancePredicted;
1557 
1558       // set up propagator and co
1559       Acts::GeometryContext geoCtx = gx2fOptions.geoContext;
1560       Acts::MagneticFieldContext magCtx = gx2fOptions.magFieldContext;
1561       // Set options for propagator
1562       PropagatorOptions propagatorOptions(geoCtx, magCtx);
1563       auto& gx2fActor = propagatorOptions.actorList.template get<GX2FActor>();
1564       gx2fActor.inputMeasurements = &inputMeasurements;
1565       gx2fActor.multipleScattering = multipleScattering;
1566       gx2fActor.extensions = gx2fOptions.extensions;
1567       gx2fActor.calibrationContext = &gx2fOptions.calibrationContext.get();
1568       gx2fActor.actorLogger = m_actorLogger.get();
1569       gx2fActor.scatteringMap = &scatteringMap;
1570       gx2fActor.parametersWithHypothesis = &params;
1571 
1572       auto propagatorState = m_propagator.makeState(params, propagatorOptions);
1573 
1574       auto& r = propagatorState.template get<Gx2FitterResult<traj_t>>();
1575       r.fittedStates = &trackContainer.trackStateContainer();
1576 
1577       auto propagationResult = m_propagator.propagate(propagatorState);
1578 
1579       // Run the fitter
1580       auto result =
1581           m_propagator.makeResult(std::move(propagatorState), propagationResult,
1582                                   propagatorOptions, false);
1583 
1584       if (!result.ok()) {
1585         ACTS_ERROR("Propagation failed: " << result.error());
1586         return result.error();
1587       }
1588 
1589       auto& propRes = *result;
1590       GX2FResult gx2fResult = std::move(propRes.template get<GX2FResult>());
1591 
1592       if (!gx2fResult.result.ok()) {
1593         ACTS_INFO("GlobalChiSquareFitter failed in actor: "
1594                   << gx2fResult.result.error() << ", "
1595                   << gx2fResult.result.error().message());
1596         return gx2fResult.result.error();
1597       }
1598 
1599       if (tipIndex != gx2fResult.lastMeasurementIndex) {
1600         ACTS_INFO("Final fit used unreachable measurements.");
1601         return Experimental::GlobalChiSquareFitterError::
1602             UsedUnreachableMeasurements;
1603       }
1604     }
1605 
1606     if (!trackContainer.hasColumn(
1607             Acts::hashString(Gx2fConstants::gx2fnUpdateColumn))) {
1608       trackContainer.template addColumn<std::uint32_t>("Gx2fnUpdateColumn");
1609     }
1610 
1611     // Prepare track for return
1612     auto track = trackContainer.makeTrack();
1613     track.tipIndex() = tipIndex;
1614     track.parameters() = params.parameters();
1615     track.covariance() = fullCovariancePredicted;
1616     track.setReferenceSurface(params.referenceSurface().getSharedPtr());
1617 
1618     if (trackContainer.hasColumn(
1619             Acts::hashString(Gx2fConstants::gx2fnUpdateColumn))) {
1620       ACTS_DEBUG("Add nUpdate to track");
1621       track.template component<std::uint32_t>("Gx2fnUpdateColumn") =
1622           static_cast<std::uint32_t>(nUpdate);
1623     }
1624 
1625     // TODO write test for calculateTrackQuantities
1626     calculateTrackQuantities(track);
1627 
1628     // Set the chi2sum for the track summary manually, since we don't calculate
1629     // it for each state
1630     track.chi2() = chi2sum;
1631 
1632     // Return the converted Track
1633     return track;
1634   }
1635 };
1636 
1637 }  // namespace Acts::Experimental