@@ -49,6 +49,11 @@ struct TrackCovI {
4949 static constexpr float XRegErrFactor = 10 .f;
5050 static constexpr float XRegNone = -1 .f;
5151
52+ // Legacy (mOldMode) factor for the conversion of the track covYY to a dummy covXX: instead of
53+ // deriving the X information from the track slopes, the old code assigned sigma_x^2 = 5*Cyy and
54+ // left the XY,XZ information terms at 0, see DCAFitterN::mOldMode.
55+ static constexpr float XerrFactorOld = 5 .f;
56+
5257 GPUdDefault () TrackCovI() = default ;
5358
5459 GPUd () bool set (const o2::track::TrackParCov& trc, float xRegErrFactor = XRegErrFactor)
@@ -66,6 +71,11 @@ struct TrackCovI {
6671 syy = czz * detYZI;
6772 syz = -cyz * detYZI;
6873 szz = cyy * detYZI;
74+ if (mOldMode ) { // dummy X error, no slope-driven X information (xRegErrFactor is ignored)
75+ sxy = sxz = 0 .f ;
76+ sxx = 1 .f / (cyy * XerrFactorOld);
77+ return res;
78+ }
6979 const float cspI = 1 .f / trc.getCsp ();
7080 const float dydx = trc.getSnp () * cspI;
7181 const float dzdx = trc.getTgl () * cspI;
@@ -177,6 +187,10 @@ class DCAFitterN
177187 static_assert (N >= NMin && N <= NMax, " N prongs outside of allowed range" );
178188 }
179189
190+ // Setters and getters for the temporary mOldMode flag, which controls the behavior of the covariance matrix calculation.
191+ bool isOldMode () const { return mOldMode ; }
192+ void setOldMode (bool v) { mOldMode = v; }
193+
180194 // =========================================================================
181195 // /< return PCA candidate, by default best on is provided (no check for the index validity)
182196 GPUd () const Vec3D& getPCACandidate (int cand = 0 ) const { return mPCA [mOrder [cand]]; }
@@ -346,6 +360,29 @@ class DCAFitterN
346360 arrmat[ZZ ] += tcov.szz ;
347361 }
348362
363+ // /< generate 3D matrix for track rotation to global frame (mOldMode calcPCACovMatrix only)
364+ GPUd () MatStd3D getTrackRotMatrix (int i) const
365+ {
366+ MatStd3D mat;
367+ mat (2 , 2 ) = 1 ;
368+ mat (0 , 0 ) = mat (1 , 1 ) = mTrAux [i].c ;
369+ mat (0 , 1 ) = -mTrAux [i].s ;
370+ mat (1 , 0 ) = mTrAux [i].s ;
371+ return mat;
372+ }
373+
374+ // /< generate covariance matrix of track position, adding fake X error (mOldMode calcPCACovMatrix only)
375+ GPUd () MatSym3D getTrackCovMatrix (int i, int cand = 0 ) const
376+ {
377+ const auto & trc = mCandTr [mOrder [cand]][i];
378+ MatSym3D mat;
379+ mat (0 , 0 ) = trc.getSigmaY2 () * TrackCovI::XerrFactorOld;
380+ mat (1 , 1 ) = trc.getSigmaY2 ();
381+ mat (2 , 2 ) = trc.getSigmaZ2 ();
382+ mat (2 , 1 ) = trc.getSigmaZY ();
383+ return mat;
384+ }
385+
349386 GPUd () void assign (int ) {}
350387 template <class T , class ... Tr>
351388 GPUd () void assign (int i, const T& t, const Tr&... args)
@@ -444,7 +481,18 @@ class DCAFitterN
444481 float mMaxStep = 2.0 ; // Max step for propagation with Propagator
445482 int mFitterID = 0 ; // locat fitter ID (mostly for debugging)
446483 size_t mCallID = 0 ;
447- ClassDefNV (DCAFitterN, 3 );
484+
485+ // /< Temporary:
486+ // /< Reproduce exactly the behaviour preceding the x-axis error treatment fix (PR15610, commit
487+ // /< 775528b421ce6b9ec381a759c664cd5a2ab76fe6): the track information matrix gets a dummy X
488+ // /< variance TrackCovI::XerrFactorOld*Cyy with no XY/XZ terms (hence the fitted PCA and chi2 use
489+ // /< the old, artificial longitudinal error), the PCA covariance is obtained by inverting the sum
490+ // /< of the inverses of the rotated dummy track covariances, the chi2 Hessian curvature term is
491+ // /< accumulated as before and the Newton step updates only mTrPos by a Taylor expansion, leaving
492+ // /< the candidate tracks (and thus the derivatives) at the seed X. For validation/comparison only.
493+ bool mOldMode = false ;
494+
495+ ClassDefNV (DCAFitterN, 4 );
448496};
449497
450498// /_________________________________________________________________________
@@ -696,10 +744,13 @@ GPUd() void DCAFitterN<N, Args...>::calcChi2Derivatives()
696744 const auto & dr1j = mDResidDx [k][j]; // vector of k-th residuals 1st derivative over X param of track j
697745 const auto & cidrkj = covIDrDx[i][k]; // vector covI_k * dres_k/dx_i
698746 dchi2 += o2::math_utils::Dot (dr1j, cidrkj);
699- if (i == j) {
700- const auto & res = mTrRes [mCurHyp ][k]; // vector of residuals of track k
701- const auto & covI = mTrcEInv [mCurHyp ][k]; // inverse cov matrix of track k
702- const auto & dr2ij = mD2ResidDx2 [k][i]; // vector of k-th residuals 2nd derivative over X param i
747+ // A trajectory has a second derivative only with respect to its own X parameter, hence the
748+ // curvature term contributes only to the diagonal H_ii. The mOldMode variant instead added
749+ // it wherever k == j, i.e. also to the off-diagonal elements of the column j.
750+ if (mOldMode ? (k == j) : (i == j)) {
751+ const auto & res = mTrRes [mCurHyp ][k]; // vector of residuals of track k
752+ const auto & covI = mTrcEInv [mCurHyp ][k]; // inverse cov matrix of track k
753+ const auto & dr2ij = mD2ResidDx2 [k][mOldMode ? j : i]; // vector of k-th residuals 2nd derivative over X param
703754 dchi2 += res[0 ] * (covI.sxx * dr2ij[0 ] + covI.sxy * dr2ij[1 ] + covI.sxz * dr2ij[2 ]) +
704755 res[1 ] * (covI.sxy * dr2ij[0 ] + covI.syy * dr2ij[1 ] + covI.syz * dr2ij[2 ]) +
705756 res[2 ] * (covI.sxz * dr2ij[0 ] + covI.syz * dr2ij[1 ] + covI.szz * dr2ij[2 ]);
@@ -726,13 +777,14 @@ GPUd() void DCAFitterN<N, Args...>::calcChi2DerivativesNoErr()
726777 for (int i = N; i--;) {
727778 for (int j = i + 1 ; j--;) {
728779 auto & dchi2 = mD2Chi2Dx2 [i][j];
729- dchi2 = 0 .;
780+ // A trajectory has a second derivative only with respect to its own X parameter, hence the
781+ // curvature term contributes only to H_ii. The mOldMode variant instead added the single
782+ // res_i * D2res_i/Dx_i/Dx_j term to every element with i >= j.
783+ dchi2 = mOldMode ? o2::math_utils::Dot (mTrRes [mCurHyp ][i], mD2ResidDx2 [i][j]) : 0 .;
730784 for (int k = N; k--;) {
731785 // Gauss-Newton term, present for diagonal and mixed elements.
732786 dchi2 += o2::math_utils::Dot (mDResidDx [k][i], mDResidDx [k][j]);
733- // A trajectory has a second derivative only with respect to its own
734- // X parameter, hence the curvature term contributes only to H_ii.
735- if (i == j) {
787+ if (!mOldMode && i == j) {
736788 dchi2 += o2::math_utils::Dot (mTrRes [mCurHyp ][k], mD2ResidDx2 [k][i]);
737789 }
738790 }
@@ -763,7 +815,7 @@ GPUd() bool DCAFitterN<N, Args...>::recalculatePCAWithErrors(int cand)
763815 mCurHyp = mOrder [cand];
764816 if (mUseAbsDCA ) {
765817 for (int i = N; i--;) {
766- if (!mTrcEInv [mCurHyp ][i].set (mCandTr [mCurHyp ][i])) { // prepare inverse cov.matrices at starting point
818+ if (!mTrcEInv [mCurHyp ][i].set (mCandTr [mCurHyp ][i], TrackCovI::XRegErrFactor, mOldMode )) { // prepare inverse cov.matrices at starting point
767819 if (mLoggerBadCov .needToLog ()) {
768820#ifndef GPUCA_GPUCODE
769821 printf (" fitter %d: error (%ld muted): overrode invalid track covariance from %s\n " ,
@@ -872,6 +924,27 @@ GPUd() o2::math_utils::SMatrix<double, 3, 3, o2::math_utils::MatRepSym<double, 3
872924 // the minimization (TrackCovI::XRegNone), otherwise the vertex error along the
873925 // weakly constrained direction would be defined by that dummy term.
874926 // A singular/ill-conditioned sum is caught below and replaced by a loose dummy.
927+ if (mOldMode ) { // sum the inverses of the rotated dummy-X track covariances and invert the sum
928+ MatSym3D covm;
929+ int nAdded = 0 ;
930+ for (int i = N; i--;) { // calculate sum of inverses
931+ // RS by using Similarity(mTrCFVT[mOrder[cand]][i], getTrackCovMatrix(i, cand)) we underestimate the error, use simple rotation
932+ MatSym3D covTr = o2::math_utils::Similarity (getTrackRotMatrix (i), getTrackCovMatrix (i, cand));
933+ if (covTr.Invert ()) {
934+ covm += covTr;
935+ nAdded++;
936+ }
937+ }
938+ if (nAdded && covm.Invert ()) {
939+ return covm;
940+ }
941+ // correct way has failed, use simple sum
942+ MatSym3D covmSum;
943+ for (int i = N; i--;) {
944+ covmSum += o2::math_utils::Similarity (getTrackRotMatrix (i), getTrackCovMatrix (i, cand));
945+ }
946+ return covmSum;
947+ }
875948 MatSym3D info;
876949 auto * arrmat = info.Array ();
877950 memset (arrmat, 0 , sizeof (info));
@@ -977,6 +1050,16 @@ GPUd() bool DCAFitterN<N, Args...>::correctTracks(const VecND& corrX)
9771050 // Propagator and material corrections): the Newton corrections are small, but the track state must
9781051 // stay synchronized with mTrPos for the next derivative update. The final propagation to the PCA
9791052 // (propagateTracksToVertex) refetches the original tracks and does use the full transport.
1053+ if (mOldMode ) { // update mTrPos only, by the Taylor expansion, leaving mCandTr at the previous X
1054+ for (int i = N; i--;) {
1055+ const auto & trDer = mTrDer [mCurHyp ][i];
1056+ auto dx2h = 0.5 * corrX[i] * corrX[i];
1057+ mTrPos [mCurHyp ][i][0 ] -= corrX[i];
1058+ mTrPos [mCurHyp ][i][1 ] -= trDer.dydx * corrX[i] - dx2h * trDer.d2ydx2 ;
1059+ mTrPos [mCurHyp ][i][2 ] -= trDer.dzdx * corrX[i] - dx2h * trDer.d2zdx2 ;
1060+ }
1061+ return true ;
1062+ }
9801063 for (int i = N; i--;) {
9811064 auto & trc = mCandTr [mCurHyp ][i];
9821065 const float x = static_cast <float >(mTrPos [mCurHyp ][i][0 ] - corrX[i]);
@@ -1080,7 +1163,7 @@ GPUd() bool DCAFitterN<N, Args...>::minimizeChi2()
10801163 return false ;
10811164 }
10821165 setTrackPos (mTrPos [mCurHyp ][i], mCandTr [mCurHyp ][i]); // prepare positions
1083- if (!mTrcEInv [mCurHyp ][i].set (mCandTr [mCurHyp ][i])) { // prepare inverse cov.matrices at starting point
1166+ if (!mTrcEInv [mCurHyp ][i].set (mCandTr [mCurHyp ][i], TrackCovI::XRegErrFactor, mOldMode )) { // prepare inverse cov.matrices at starting point
10841167 if (mLoggerBadCov .needToLog ()) {
10851168#ifndef GPUCA_GPUCODE
10861169 printf (" fitter %d: error (%ld muted): overrode invalid track covariance from %s\n " ,
0 commit comments