Back to home page

EIC code displayed by LXR

 
 

    


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

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 #include "Acts/Vertexing/FullBilloirVertexFitter.hpp"
0010 
0011 #include "Acts/Definitions/TrackParametrization.hpp"
0012 #include "Acts/MagneticField/ConstantBField.hpp"
0013 #include "Acts/Surfaces/PerigeeSurface.hpp"
0014 #include "Acts/Utilities/detail/periodic.hpp"
0015 #include "Acts/Vertexing/TrackAtVertex.hpp"
0016 #include "Acts/Vertexing/VertexingError.hpp"
0017 
0018 namespace {
0019 
0020 /// @struct BilloirTrack
0021 ///
0022 /// @brief Struct to cache track-specific matrix operations in Billoir fitter
0023 struct BilloirTrack {
0024   BilloirTrack(const Acts::InputTrack& params) : originalTrack(params) {}
0025 
0026   BilloirTrack(const BilloirTrack& arg) = default;
0027 
0028   Acts::InputTrack originalTrack;
0029   double chi2 = 0;
0030 
0031   // We drop the summation index i from Ref. (1) for better readability
0032   Acts::ActsMatrix<Acts::eBoundSize, Acts::eBoundSize> W;  // Wi weight matrix
0033   Acts::ActsMatrix<Acts::eBoundSize, 4> D;  // Di (position Jacobian)
0034   Acts::ActsMatrix<Acts::eBoundSize, 3> E;  // Ei (momentum Jacobian)
0035   Acts::ActsSquareMatrix<3> C;              //  = sum{Ei^T Wi * Ei}
0036   Acts::ActsMatrix<4, 3> B;                 //  = Di^T * Wi * Ei
0037   Acts::ActsSquareMatrix<3> Cinv;           //  = (Ei^T * Wi * Ei)^-1
0038   Acts::Vector3 U;                          //  = Ei^T * Wi * dqi
0039   Acts::ActsMatrix<4, 3> BCinv;             //  = Bi * Ci^-1
0040   Acts::BoundVector deltaQ;
0041 };
0042 
0043 /// @struct BilloirVertex
0044 ///
0045 /// @brief Struct to cache vertex-specific matrix operations in Billoir fitter
0046 struct BilloirVertex {
0047   // A  = sum{Di^T * Wi * Di}
0048   Acts::SquareMatrix4 A = Acts::SquareMatrix4::Zero();
0049   // T  = sum{Di^T * Wi * dqi}
0050   Acts::Vector4 T = Acts::Vector4::Zero();
0051   // sumBCinvBt = sum{Bi * Ci^-1 * Bi^T}
0052   Acts::SquareMatrix4 sumBCinvBt = Acts::SquareMatrix4::Zero();
0053   // sumBCinvU = sum{B * Ci^-1 * Ui}
0054   Acts::Vector4 sumBCinvU = Acts::Vector4::Zero();
0055 };
0056 
0057 }  // namespace
0058 
0059 Acts::Result<Acts::Vertex> Acts::FullBilloirVertexFitter::fit(
0060     const std::vector<InputTrack>& paramVector,
0061     const VertexingOptions& vertexingOptions,
0062     MagneticFieldProvider::Cache& fieldCache) const {
0063   unsigned int nTracks = paramVector.size();
0064   double chi2 = std::numeric_limits<double>::max();
0065 
0066   if (nTracks == 0) {
0067     return Vertex(Vector3(0., 0., 0.));
0068   }
0069 
0070   // Set number of degrees of freedom following Eq. 8.28 from Ref. (2):
0071   //
0072   // ndf = sum_i=1^nTracks rank(Wi^-1) - 3 * (nTracks + 1),
0073   //
0074   // where W_i denotes the weight matrix of the i-th track.
0075   // Assuming rank(W_i) = #(Perigee params) = 6 for all tracks, we have
0076   //
0077   // ndf = 3 * nTracks - 3.
0078   int ndf = 3 * nTracks - 3;
0079   // TODO: this seems strange - can we even do a vertex fit if we only have one
0080   // track?
0081   if (nTracks < 2) {
0082     ndf = 1;
0083   }
0084 
0085   // Since we add a term to the chi2 when adding a vertex constraint (see Ref.
0086   // (1)), the number of degrees of freedom increases
0087   if (vertexingOptions.useConstraintInFit) {
0088     ndf += 3;
0089   }
0090 
0091   std::vector<BilloirTrack> billoirTracks;
0092   std::vector<Vector3> trackMomenta;
0093   // Initial guess of the 4D vertex position
0094   Vector4 linPoint = vertexingOptions.constraint.fullPosition();
0095   Vertex fittedVertex;
0096 
0097   for (int nIter = 0; nIter < m_cfg.maxIterations; ++nIter) {
0098     billoirTracks.clear();
0099     double newChi2 = 0;
0100     BilloirVertex billoirVertex;
0101 
0102     Vector3 linPointPos = VectorHelpers::position(linPoint);
0103     // Make Perigee surface at linPointPos, transverse plane of Perigee
0104     // corresponds the global x-y plane
0105     const std::shared_ptr<PerigeeSurface> perigeeSurface =
0106         Surface::makeShared<PerigeeSurface>(linPointPos);
0107 
0108     // iterate over all tracks
0109     for (std::size_t iTrack = 0; iTrack < nTracks; ++iTrack) {
0110       const InputTrack& trackContainer = paramVector[iTrack];
0111 
0112       const auto& trackParams = m_cfg.extractParameters(trackContainer);
0113 
0114       auto result =
0115           m_cfg.trackLinearizer(trackParams, linPoint[3], *perigeeSurface,
0116                                 vertexingOptions.geoContext,
0117                                 vertexingOptions.magFieldContext, fieldCache);
0118       if (!result.ok()) {
0119         return result.error();
0120       }
0121 
0122       const auto& linTrack = *result;
0123       const auto& parametersAtPCA = linTrack.parametersAtPCA;
0124       double d0 = parametersAtPCA[BoundIndices::eBoundLoc0];
0125       double z0 = parametersAtPCA[BoundIndices::eBoundLoc1];
0126       double phi = parametersAtPCA[BoundIndices::eBoundPhi];
0127       double theta = parametersAtPCA[BoundIndices::eBoundTheta];
0128       double qOverP = parametersAtPCA[BoundIndices::eBoundQOverP];
0129       double t0 = parametersAtPCA[BoundIndices::eBoundTime];
0130 
0131       // Take the track momenta at the PCA as an initial estimate of the track
0132       // momenta at the vertex
0133       if (nIter == 0) {
0134         trackMomenta.push_back(Vector3(phi, theta, qOverP));
0135       }
0136 
0137       // Calculate F(V_0,p_0), i.e., the track parameters estimated from the
0138       // vertex position and the track momenta. fD0 = fZ0 = 0 because the track
0139       // originates at the vertex in the Billoir model.
0140       double fPhi = trackMomenta[iTrack][0];
0141       double fTheta = trackMomenta[iTrack][1];
0142       double fQOvP = trackMomenta[iTrack][2];
0143       double fTime = linPoint[FreeIndices::eFreeTime];
0144       BilloirTrack billoirTrack(trackContainer);
0145 
0146       billoirTrack.deltaQ << d0, z0, phi - fPhi, theta - fTheta, qOverP - fQOvP,
0147           t0 - fTime;
0148 
0149       // position jacobian (D matrix)
0150       ActsMatrix<eBoundSize, 4> D = linTrack.positionJacobian;
0151 
0152       // momentum jacobian (E matrix)
0153       ActsMatrix<eBoundSize, 3> E = linTrack.momentumJacobian;
0154 
0155       // cache some matrix multiplications
0156       BoundSquareMatrix W = linTrack.weightAtPCA;
0157       ActsMatrix<4, eBoundSize> DtW = D.transpose() * W;
0158       ActsMatrix<3, eBoundSize> EtW = E.transpose() * W;
0159 
0160       // compute track quantities for Billoir fit
0161       billoirTrack.D = D;
0162       billoirTrack.E = E;
0163       billoirTrack.W = W;
0164       billoirTrack.C = EtW * E;
0165       billoirTrack.B = DtW * E;                        // Di^T * Wi * Ei
0166       billoirTrack.U = EtW * billoirTrack.deltaQ;      // Ei^T * Wi * dqi
0167       billoirTrack.Cinv = (billoirTrack.C).inverse();  // (Ei^T * Wi * Ei)^-1
0168       billoirTrack.BCinv =
0169           billoirTrack.B * billoirTrack.Cinv;  // BCinv = Bi * Ci^-1
0170 
0171       // compute vertex quantities for Billoir fit
0172       billoirVertex.T += DtW * billoirTrack.deltaQ;  // sum{Di^T * Wi * dqi}
0173       billoirVertex.A += DtW * D;                    // sum{Di^T * Wi * Di}
0174       billoirVertex.sumBCinvU +=
0175           billoirTrack.BCinv * billoirTrack.U;  // sum{Bi * Ci^-1 * Ui}
0176       billoirVertex.sumBCinvBt +=
0177           billoirTrack.BCinv *
0178           billoirTrack.B.transpose();  // sum{Bi * Ci^-1 * Bi^T}
0179 
0180       billoirTracks.push_back(billoirTrack);
0181     }  // end loop tracks
0182 
0183     // (Matrix-valued) factor contributing to the vertex estimate update (i.e.,
0184     // to deltaV).
0185     // deltaVFac = T-sum{Bi*Ci^-1*Ui}
0186     Vector4 deltaVFac = billoirVertex.T - billoirVertex.sumBCinvU;
0187 
0188     // Inverse of the covariance matrix of the 4D vertex position (note that
0189     // Cov(V) = Cov(deltaV)), see Ref. (1).
0190     // invCovV = A-sum{Bi*Ci^-1*Bi^T}
0191     SquareMatrix4 invCovV = billoirVertex.A - billoirVertex.sumBCinvBt;
0192     if (vertexingOptions.useConstraintInFit) {
0193       // Position of vertex constraint in Billoir frame (i.e., in coordinate
0194       // system with origin at linPoint). This will be 0 for first iteration but
0195       // != 0 from second on since our first guess for the vertex position is
0196       // the vertex constraint position.
0197       Vector4 posInBilloirFrame =
0198           vertexingOptions.constraint.fullPosition() - linPoint;
0199 
0200       // For vertex constraint: T -> T + Cb^-1 (b - V0) where Cb is the
0201       // covariance matrix of the constraint, b is the constraint position, and
0202       // V0 is the vertex estimate (see Ref. (1)).
0203       deltaVFac += vertexingOptions.constraint.fullCovariance().inverse() *
0204                    posInBilloirFrame;
0205       // For vertex constraint: A -> A + Cb^-1
0206       invCovV += vertexingOptions.constraint.fullCovariance().inverse();
0207     }
0208 
0209     // Covariance matrix of the 4D vertex position, see Ref. (3)
0210     SquareMatrix4 covV = invCovV.inverse();
0211     // Update of the vertex position
0212     Vector4 deltaV = covV * deltaVFac;
0213     //--------------------------------------------------------------------------------------
0214     // start momentum related calculations
0215 
0216     std::vector<std::optional<BoundSquareMatrix>> covDeltaP(nTracks);
0217 
0218     // Update track momenta and calculate the covariance of the track parameters
0219     // after the fit (TODO: parameters -> momenta).
0220     for (std::size_t iTrack = 0; iTrack < billoirTracks.size(); ++iTrack) {
0221       auto& billoirTrack = billoirTracks[iTrack];
0222 
0223       Vector3 deltaP = (billoirTrack.Cinv) *
0224                        (billoirTrack.U - billoirTrack.B.transpose() * deltaV);
0225 
0226       // update track momenta
0227       trackMomenta[iTrack] += deltaP;
0228 
0229       // correct for 2PI / PI periodicity
0230       const auto correctedPhiTheta = detail::normalizePhiTheta(
0231           trackMomenta[iTrack][0], trackMomenta[iTrack][1]);
0232       trackMomenta[iTrack][0] = correctedPhiTheta.first;
0233       trackMomenta[iTrack][1] = correctedPhiTheta.second;
0234 
0235       // Calculate chi2 of the track...
0236       Acts::BoundVector diffQ = billoirTrack.deltaQ - billoirTrack.D * deltaV -
0237                                 billoirTrack.E * deltaP;
0238       billoirTrack.chi2 = diffQ.transpose().dot(billoirTrack.W * diffQ);
0239       // ... and add it to the total chi2 value
0240       newChi2 += billoirTrack.chi2;
0241 
0242       // calculate the 6x6 cross-covariance matrix
0243       // TODO: replace fittedParams with fittedMomentum since d0 and z0 are
0244       // ill-defined. At the moment, only the submatrix of momentum covariances
0245       // is correct.
0246 
0247       // coordinate transformation matrix, i.e.,
0248       // d(d0,z0,phi,theta,qOverP,t)/d(x,y,z,t,phi,theta,qOverP)
0249       // TODO: This is not correct since the (x, y, z, t) in the derivatives in
0250       // the D matrix correspond to the global track position at the PCA rather
0251       // than the 4D vertex position.
0252       ActsMatrix<eBoundSize, 7> transMat;
0253       transMat.setZero();
0254       transMat.block<2, 2>(0, 0) = billoirTrack.D.template block<2, 2>(0, 0);
0255       transMat(1, 2) = 1.;
0256       transMat(2, 4) = 1.;
0257       transMat(3, 5) = 1.;
0258       transMat(4, 6) = 1.;
0259       transMat(5, 3) = 1.;
0260 
0261       // cov(V,P)
0262       ActsMatrix<4, 3> covVP = -covV * billoirTrack.BCinv;
0263 
0264       // cov(P,P), 3x3 matrix, see Ref. (3)
0265       ActsSquareMatrix<3> covP =
0266           billoirTrack.Cinv +
0267           billoirTrack.BCinv.transpose() * covV * billoirTrack.BCinv;
0268 
0269       ActsSquareMatrix<7> cov;
0270       cov.setZero();
0271       cov.block<4, 4>(0, 0) = covV;
0272       cov.block<4, 3>(0, 4) = covVP;
0273       cov.block<3, 4>(4, 0) = covVP.transpose();
0274       cov.block<3, 3>(4, 4) = covP;
0275 
0276       covDeltaP[iTrack] = transMat * cov * transMat.transpose();
0277     }
0278 
0279     // assign new linearization point (= new vertex position in global frame)
0280     linPoint += deltaV;
0281 
0282     // Add an additional term to chi2 if there is a vertex constraint (see Ref.
0283     // (1))
0284     if (vertexingOptions.useConstraintInFit) {
0285       // Position of vertex constraint in Billoir frame (i.e., in coordinate
0286       // system with origin at linPoint).
0287       Vector4 posInBilloirFrame =
0288           vertexingOptions.constraint.fullPosition() - linPoint;
0289 
0290       newChi2 +=
0291           (posInBilloirFrame.transpose())
0292               .dot(vertexingOptions.constraint.fullCovariance().inverse() *
0293                    posInBilloirFrame);
0294     }
0295 
0296     if (!std::isnormal(newChi2)) {
0297       ACTS_ERROR("Encountered non-normal chi2 value during the fit.");
0298       return VertexingError::NumericFailure;
0299     }
0300 
0301     if (newChi2 < chi2) {
0302       chi2 = newChi2;
0303 
0304       fittedVertex.setFullPosition(linPoint);
0305       fittedVertex.setFullCovariance(covV);
0306       fittedVertex.setFitQuality(chi2, ndf);
0307 
0308       std::vector<TrackAtVertex> tracksAtVertex;
0309 
0310       std::shared_ptr<PerigeeSurface> perigee =
0311           Surface::makeShared<PerigeeSurface>(
0312               VectorHelpers::position(linPoint));
0313 
0314       for (std::size_t iTrack = 0; iTrack < billoirTracks.size(); ++iTrack) {
0315         const auto& billoirTrack = billoirTracks[iTrack];
0316 
0317         // TODO: replace refittedParams with refittedMomenta since d0 and z0
0318         // after the vertex fit are ill-defined (FullBilloir only outputs the
0319         // updated track momenta)
0320 
0321         // new refitted trackparameters
0322         BoundVector paramVec = BoundVector::Zero();
0323         paramVec[eBoundPhi] = trackMomenta[iTrack](0);
0324         paramVec[eBoundTheta] = trackMomenta[iTrack](1);
0325         paramVec[eBoundQOverP] = trackMomenta[iTrack](2);
0326         paramVec[eBoundTime] = linPoint[FreeIndices::eFreeTime];
0327         BoundTrackParameters refittedParams(
0328             perigee, paramVec, covDeltaP[iTrack],
0329             m_cfg.extractParameters(billoirTrack.originalTrack)
0330                 .particleHypothesis());
0331         TrackAtVertex trackAtVertex(billoirTrack.chi2, refittedParams,
0332                                     billoirTrack.originalTrack);
0333         tracksAtVertex.push_back(std::move(trackAtVertex));
0334       }
0335 
0336       fittedVertex.setTracksAtVertex(std::move(tracksAtVertex));
0337     }
0338   }  // end loop iterations
0339 
0340   return fittedVertex;
0341 }