@@ -36,9 +36,22 @@ struct TrackCovI {
3636 // H = {{-dY/dX, 1, 0}, {-dZ/dX, 0, 1}}.
3737 float sxx, sxy, sxz, syy, syz, szz;
3838
39+ // H^T Cyz^{-1} H is singular by construction (rank 2): the chi2 is invariant
40+ // under sliding the reference point along the trajectory. A weak dummy X error
41+ // sigma_x^2 = XRegErrFactor * Cyy is added to the sxx element to regularize it.
42+ // This is needed ONLY to keep the Newton Hessian of the chi2 minimization
43+ // invertible for (nearly) collinear prongs.
44+ // It must NOT be used when the single track information matrices are summed to
45+ // obtain the PCA covariance (see calcPCACovMatrix): there the regularization
46+ // would define the longitudinal vertex error by this dummy term instead of by
47+ // the track slopes, i.e. reintroduce the very artifact it replaces. Pass
48+ // XRegNone in that case.
49+ static constexpr float XRegErrFactor = 10 .f;
50+ static constexpr float XRegNone = -1 .f;
51+
3952 GPUdDefault () TrackCovI() = default ;
4053
41- GPUd () bool set (const o2::track::TrackParCov& trc)
54+ GPUd () bool set (const o2::track::TrackParCov& trc, float xRegErrFactor = XRegErrFactor )
4255 {
4356 // Invert the 2D covariance of the measured track position (Y,Z).
4457 float cyy = trc.getSigmaY2 (), czz = trc.getSigmaZ2 (), cyz = trc.getSigmaZY ();
@@ -59,10 +72,9 @@ struct TrackCovI {
5972 sxy = -(syy * dydx + syz * dzdx);
6073 sxz = -(syz * dydx + szz * dzdx);
6174 sxx = dydx * dydx * syy + 2 .f * dydx * dzdx * syz + dzdx * dzdx * szz;
62- // The matrix is degenerate by construction, regularize sxx term to preserve the original YZ block exactly
63- constexpr float XRegErrFactor = 10 .f ;
64- const float sigmaX2 = cyy * XRegErrFactor;
65- sxx += 1 .f / sigmaX2;
75+ if (xRegErrFactor > 0 .f ) { // regularize the sxx term only, this preserves the YZ block exactly
76+ sxx += 1 .f / (cyy * xRegErrFactor);
77+ }
6678 return res;
6779 }
6880};
@@ -315,14 +327,23 @@ class DCAFitterN
315327 // /< track X-param at V0 candidate (no check for the candidate validity)
316328 GPUd () float getTrackX (int i, int cand = 0 ) const { return getTrackPos (i, cand)[0 ]; }
317329
318- GPUd () MatStd3D getTrackRotMatrix (int i) const // generate 3D matrix for track rotation to global frame
330+ // /< Accumulate the track information matrix rotated to the global frame, M*E*M^T, into the
331+ // /< flat MatRepSym-ordered {XX,XY,YY,XZ,YZ,ZZ} accumulator. Shared by calcInverseWeight()
332+ // /< (sum over prongs of a candidate) and calcPCACovMatrix() (same sum, unregularized).
333+ GPUd () static void addRotatedTrackInfo (double * arrmat, const TrackAuxPar& taux, const TrackCovI& tcov)
319334 {
320- MatStd3D mat;
321- mat (2 , 2 ) = 1 ;
322- mat (0 , 0 ) = mat (1 , 1 ) = mTrAux [i].c ;
323- mat (0 , 1 ) = -mTrAux [i].s ;
324- mat (1 , 0 ) = mTrAux [i].s ;
325- return mat;
335+ enum { XX ,
336+ XY ,
337+ YY ,
338+ XZ ,
339+ YZ ,
340+ ZZ };
341+ arrmat[XX ] += taux.cc * tcov.sxx - 2 . * taux.cs * tcov.sxy + taux.ss * tcov.syy ;
342+ arrmat[XY ] += taux.cs * (tcov.sxx - tcov.syy ) + (taux.cc - taux.ss ) * tcov.sxy ;
343+ arrmat[XZ ] += taux.c * tcov.sxz - taux.s * tcov.syz ;
344+ arrmat[YY ] += taux.ss * tcov.sxx + 2 . * taux.cs * tcov.sxy + taux.cc * tcov.syy ;
345+ arrmat[YZ ] += taux.s * tcov.sxz + taux.c * tcov.syz ;
346+ arrmat[ZZ ] += tcov.szz ;
326347 }
327348
328349 GPUd () void assign (int ) {}
@@ -525,21 +546,8 @@ GPUd() bool DCAFitterN<N, Args...>::calcInverseWeight()
525546 // < calculate [sum_{0<j<N} M_j*E_j*M_j^T]^-1 used for Ti matrices, see EQ.T
526547 auto * arrmat = mWeightInv .Array ();
527548 memset (arrmat, 0 , sizeof (mWeightInv ));
528- enum { XX ,
529- XY ,
530- YY ,
531- XZ ,
532- YZ ,
533- ZZ };
534- for (int i = N; i--;) {
535- const auto & taux = mTrAux [i];
536- const auto & tcov = mTrcEInv [mCurHyp ][i];
537- arrmat[XX ] += taux.cc * tcov.sxx - 2 . * taux.cs * tcov.sxy + taux.ss * tcov.syy ;
538- arrmat[XY ] += taux.cs * (tcov.sxx - tcov.syy ) + (taux.cc - taux.ss ) * tcov.sxy ;
539- arrmat[XZ ] += taux.c * tcov.sxz - taux.s * tcov.syz ;
540- arrmat[YY ] += taux.ss * tcov.sxx + 2 . * taux.cs * tcov.sxy + taux.cc * tcov.syy ;
541- arrmat[YZ ] += taux.s * tcov.sxz + taux.c * tcov.syz ;
542- arrmat[ZZ ] += tcov.szz ;
549+ for (int i = N; i--;) { // the mTrcEInv used here are regularized, see TrackCovI::XRegErrFactor
550+ addRotatedTrackInfo (arrmat, mTrAux [i], mTrcEInv [mCurHyp ][i]);
543551 }
544552 // invert 3x3 symmetrix matrix
545553 return mWeightInv .Invert ();
@@ -810,6 +818,8 @@ GPUd() void DCAFitterN<N, Args...>::calcPCANoErr()
810818template <int N, typename ... Args>
811819GPUd () double DCAFitterN<N, Args...>::calcCollinearInflation(int cand) const
812820{
821+ // Note: only std::array and o2::gpu::GPUCommonMath are used here, no host-only <algorithm>/<cmath>,
822+ // so that the method stays compilable for the device even though it is currently not called.
813823 std::array<std::array<double , 3 >, N> u{};
814824 int nu = 0 ;
815825
@@ -818,11 +828,11 @@ GPUd() double DCAFitterN<N, Args...>::calcCollinearInflation(int cand) const
818828 if (!getTrack (i, cand).getPxPyPzGlo (p)) {
819829 continue ;
820830 }
821- const double p2 = p[0 ] * p[0 ] + p[1 ] * p[1 ] + p[2 ] * p[2 ];
822- if (p2 <= 0 .) {
831+ const float p2 = p[0 ] * p[0 ] + p[1 ] * p[1 ] + p[2 ] * p[2 ]; // float: GPUCommonMath::Sqrt is float-only and p is float anyway
832+ if (p2 <= 0 .f ) {
823833 continue ;
824834 }
825- const double pI = 1 . / std::sqrt (p2);
835+ const double pI = 1 . / o2::gpu::GPUCommonMath::Sqrt (p2);
826836 u[nu++] = {p[0 ] * pI, p[1 ] * pI, p[2 ] * pI};
827837 }
828838
@@ -835,8 +845,8 @@ GPUd() double DCAFitterN<N, Args...>::calcCollinearInflation(int cand) const
835845 for (int i = 0 ; i < nu; ++i) {
836846 for (int j = i + 1 ; j < nu; ++j) {
837847 double cij = u[i][0 ] * u[j][0 ] + u[i][1 ] * u[j][1 ] + u[i][2 ] * u[j][2 ];
838- cij = std::clamp (cij, -1 ., 1 .);
839- sin2Mean += std::max (0 ., 1 . - cij * cij);
848+ cij = o2::gpu::GPUCommonMath::Clamp (cij, -1 ., 1 .);
849+ sin2Mean += o2::gpu::GPUCommonMath::Max (0 ., 1 . - cij * cij);
840850 ++npairs;
841851 }
842852 }
@@ -847,7 +857,7 @@ GPUd() double DCAFitterN<N, Args...>::calcCollinearInflation(int cand) const
847857 if (sin2Mean <= 0 .) {
848858 return MaxInflation;
849859 }
850- return sin2Mean < Sin2Ref ? std::min (MaxInflation, Sin2Ref / sin2Mean) : 1 .;
860+ return sin2Mean < Sin2Ref ? o2::gpu::GPUCommonMath::Min (MaxInflation, Sin2Ref / sin2Mean) : 1 .;
851861}
852862
853863// ___________________________________________________________________
@@ -857,48 +867,41 @@ GPUd() o2::math_utils::SMatrix<double, 3, 3, o2::math_utils::MatRepSym<double, 3
857867 // Each track measures Y and Z at the vertex X. With the local slopes
858868 // sy = dY/dX and sz = dZ/dX, its vertex measurement matrix is
859869 // H = {{-sy, 1, 0}, {-sz, 0, 1}}. The longitudinal information must come
860- // from the track geometry, not from a dummy X variance.
870+ // from the track geometry, not from a dummy X variance: hence the per-track
871+ // information matrices are built here WITHOUT the sxx regularization used by
872+ // the minimization (TrackCovI::XRegNone), otherwise the vertex error along the
873+ // weakly constrained direction would be defined by that dummy term.
874+ // A singular/ill-conditioned sum is caught below and replaced by a loose dummy.
861875 MatSym3D info;
862876 auto * arrmat = info.Array ();
863877 memset (arrmat, 0 , sizeof (info));
864- enum { XX ,
865- XY ,
866- YY ,
867- XZ ,
868- YZ ,
869- ZZ };
870878 const int ord = mOrder [cand];
871879 for (int i = N; i--;) {
872- const auto & taux = mTrAux [i];
873880 TrackCovI tcov;
874- tcov.set (mCandTr [ord][i]);
875- arrmat[XX ] += taux.cc * tcov.sxx - 2 . * taux.cs * tcov.sxy + taux.ss * tcov.syy ;
876- arrmat[XY ] += taux.cs * (tcov.sxx - tcov.syy ) + (taux.cc - taux.ss ) * tcov.sxy ;
877- arrmat[XZ ] += taux.c * tcov.sxz - taux.s * tcov.syz ;
878- arrmat[YY ] += taux.ss * tcov.sxx + 2 . * taux.cs * tcov.sxy + taux.cc * tcov.syy ;
879- arrmat[YZ ] += taux.s * tcov.sxz + taux.c * tcov.syz ;
880- arrmat[ZZ ] += tcov.szz ;
881+ tcov.set (mCandTr [ord][i], TrackCovI::XRegNone);
882+ addRotatedTrackInfo (arrmat, mTrAux [i], tcov);
881883 }
882884 const double maxDiag = o2::gpu::GPUCommonMath::Max (o2::gpu::GPUCommonMath::Max (info (0 , 0 ), info (1 , 1 )), info (2 , 2 ));
883885 const double det2 = info (0 , 0 ) * info (1 , 1 ) - info (1 , 0 ) * info (1 , 0 );
884886 const double det3 = info (0 , 0 ) * (info (1 , 1 ) * info (2 , 2 ) - info (2 , 1 ) * info (2 , 1 )) -
885887 info (1 , 0 ) * (info (1 , 0 ) * info (2 , 2 ) - info (2 , 1 ) * info (2 , 0 )) +
886888 info (2 , 0 ) * (info (1 , 0 ) * info (2 , 1 ) - info (1 , 1 ) * info (2 , 0 ));
887889 constexpr double MinRelDet = 1 .e -12 ;
888- constexpr double InflateRelDet = 1 .e -6 ;
889- constexpr double MaxInflation = 1 .e4 ;
890890 const bool isWellConditionedInfo = maxDiag > 0 . && info (0 , 0 ) > 0 . && det2 > 0 . && det3 > MinRelDet * maxDiag * maxDiag * maxDiag;
891891 if (isWellConditionedInfo) {
892892 auto cov = info;
893893 if (cov.Invert () && cov (0 , 0 ) > 0 . && cov (1 , 1 ) > 0 . && cov (2 , 2 ) > 0 .) {
894+ // TODO: for the collinear mode the covariance along the (badly defined) common direction
895+ // may need an extra inflation, calcCollinearInflation() provides a candidate scaling.
896+ // Kept disabled until validated on data.
894897 // if (mIsCollinear) {
895898 // cov *= calcCollinearInflation(cand);
896899 // }
897900 return cov;
898901 }
899902 }
900903 if (mLoggerBadPCACov .needToLog ()) {
901- printf (" fitter %d: error (%ld muted): override ill-conditioned PCACovMatrix by dummy matrix" , mFitterID , mLoggerBadPCACov .evCount );
904+ printf (" fitter %d: error (%ld muted): override ill-conditioned PCACovMatrix by dummy matrix\n " , mFitterID , mLoggerBadPCACov .evCount );
902905 }
903906 // Fall back on a deliberately loose vertex covariance. Returning a tight
904907 // identity covariance for a singular or ill-conditioned information matrix
@@ -966,23 +969,27 @@ GPUdi() double DCAFitterN<N, Args...>::calcChi2NoErr() const
966969template <int N, typename ... Args>
967970GPUd () bool DCAFitterN<N, Args...>::correctTracks(const VecND& corrX)
968971{
969- // Propagate the actual candidate tracks to the updated X. Use the analytic
970- // constant-Bz transport here: Newton corrections are small, but the track
971- // state must stay synchronized with mTrPos for the next derivative update.
972+ // Propagate the actual candidate tracks to the updated X. Updating only mTrPos by a Taylor
973+ // expansion (as was done before) leaves mCandTr at the previous X, hence calcTrackDerivatives()
974+ // (which reads mCandTr) stays insensitive to the update and the slopes/curvatures remain frozen
975+ // at the seed for all Newton iterations.
976+ // The analytic constant-Bz transport is used on purpose (rather than propagate{Param}ToX with the
977+ // Propagator and material corrections): the Newton corrections are small, but the track state must
978+ // stay synchronized with mTrPos for the next derivative update. The final propagation to the PCA
979+ // (propagateTracksToVertex) refetches the original tracks and does use the full transport.
972980 for (int i = N; i--;) {
973- /*
974- // Updating only mTrPos by Taylor expansion leaves mCandTr at the previous X,
975- // leaving calcTrackDerivatives() insensitive to the update. Use full fast propagation instead.
976- const auto& trDer = mTrDer[mCurHyp][i];
977- auto dx2h = 0.5 * corrX[i] * corrX[i];
978- mTrPos[mCurHyp][i][0] -= corrX[i];
979- mTrPos[mCurHyp][i][1] -= trDer.dydx * corrX[i] - dx2h * trDer.d2ydx2;
980- mTrPos[mCurHyp][i][2] -= trDer.dzdx * corrX[i] - dx2h * trDer.d2zdx2;
981- */
982981 auto & trc = mCandTr [mCurHyp ][i];
983982 const float x = static_cast <float >(mTrPos [mCurHyp ][i][0 ] - corrX[i]);
984983 const bool propagated = mUseAbsDCA ? trc.propagateParamTo (x, mBz ) : trc.propagateTo (x, mBz );
985- if (!propagated) {
984+ if (!propagated) { // flag and log as done by propagate{Param}ToX
985+ mPropFailed [mCurHyp ] = true ;
986+ if (mLoggerBadProp .needToLog ()) {
987+ #ifndef GPUCA_GPUCODE
988+ printf (" fitter %d: error (%ld muted): Newton step propagation to %.4f failed for %s\n " , mFitterID , mLoggerBadProp .evCount , x, trc.asString ().c_str ());
989+ #else
990+ printf (" fitter %d: error (%ld muted): Newton step propagation to %.4f failed\n " , mFitterID , mLoggerBadProp .evCount , x);
991+ #endif
992+ }
986993 return false ;
987994 }
988995 setTrackPos (mTrPos [mCurHyp ][i], trc);
@@ -1277,45 +1284,24 @@ GPUd() void DCAFitterN<N, Args...>::print() const
12771284template <int N, typename ... Args>
12781285GPUd () o2::track::TrackParCov DCAFitterN<N, Args...>::createParentTrackParCov(int cand, bool sectorAlpha) const
12791286{
1280- std::array<float , 21 > covV = {0 .};
1287+ std::array<float , o2::track:: kLabCovMatSize > covV = {0 .};
12811288 std::array<float , 3 > pvecV = {0 .};
12821289 int q = 0 ;
12831290 for (int it = 0 ; it < N; it++) {
12841291 const auto & trc = getTrack (it, cand);
12851292 std::array<float , 3 > pvecT = {0 .};
1286- const bool hasMomentum = trc.getPxPyPzGlo (pvecT);
1287-
1288- // Propagate only the native momentum-parameter covariance
1289- // (snp,tgl,q/pt) to the lab momentum covariance. The final constructor
1290- // below rotates the summed lab covariance to the parent alpha frame.
1291- if (hasMomentum) {
1292- const double snp = trc.getSnp ();
1293- const double csp = trc.getCsp ();
1294- const double pt = trc.getPt ();
1295- const double alpha = trc.getAlpha ();
1296- double sna = 0 ., csa = 0 .;
1297- o2::math_utils::detail::sincos (alpha, sna, csa);
1298- const double dPxdSnp = -pt * (snp * csa / csp + sna);
1299- const double dPydSnp = pt * (csa - snp * sna / csp);
1300- const double dPzdTgl = pt;
1301- const double q2ptI = 1 . / trc.getQ2Pt ();
1302- const double dPxdQ = -pvecT[0 ] * q2ptI;
1303- const double dPydQ = -pvecT[1 ] * q2ptI;
1304- const double dPzdQ = -pvecT[2 ] * q2ptI;
1305- const double cSnpSnp = trc.getSigmaSnp2 ();
1306- const double cTglSnp = trc.getSigmaTglSnp ();
1307- const double cTglTgl = trc.getSigmaTgl2 ();
1308- const double cQSnp = trc.getSigma1PtSnp ();
1309- const double cQTgl = trc.getSigma1PtTgl ();
1310- const double cQQ = trc.getSigma1Pt2 ();
1311- covV[9 ] += dPxdSnp * dPxdSnp * cSnpSnp + 2 . * dPxdSnp * dPxdQ * cQSnp + dPxdQ * dPxdQ * cQQ;
1312- covV[13 ] += dPydSnp * (dPxdSnp * cSnpSnp + dPxdQ * cQSnp) + dPydQ * (dPxdSnp * cQSnp + dPxdQ * cQQ);
1313- covV[14 ] += dPydSnp * dPydSnp * cSnpSnp + 2 . * dPydSnp * dPydQ * cQSnp + dPydQ * dPydQ * cQQ;
1314- covV[18 ] += dPzdTgl * (dPxdSnp * cTglSnp + dPxdQ * cQTgl) + dPzdQ * (dPxdSnp * cQSnp + dPxdQ * cQQ);
1315- covV[19 ] += dPzdTgl * (dPydSnp * cTglSnp + dPydQ * cQTgl) + dPzdQ * (dPydSnp * cQSnp + dPydQ * cQQ);
1316- covV[20 ] += dPzdTgl * dPzdTgl * cTglTgl + 2 . * dPzdTgl * dPzdQ * cQTgl + dPzdQ * dPzdQ * cQQ;
1293+ std::array<float , o2::track::kLabCovMatSize > covT = {0 .};
1294+ trc.getPxPyPzGlo (pvecT);
1295+ // The momentum block of getCovXYZPxPyPzGlo is already J*C*J^T for the native O2 momentum
1296+ // parameters (snp,tgl,q/pt), with the track-frame alpha rotation folded into J, so there is
1297+ // no need to re-derive it here (and both methods share the same |q/pt|/|snp| validity guard,
1298+ // zeroing the covariance if it fails). The daughter momentum covariances are summed in the
1299+ // lab px,py,pz frame; the TrackParCov constructor below rotates the sum to the parent frame.
1300+ trc.getCovXYZPxPyPzGlo (covT);
1301+ constexpr int MomInd[6 ] = {9 , 13 , 14 , 18 , 19 , 20 }; // cov matrix elements for momentum component
1302+ for (int i = 0 ; i < 6 ; i++) {
1303+ covV[MomInd[i]] += covT[MomInd[i]];
13171304 }
1318-
13191305 for (int i = 0 ; i < 3 ; i++) {
13201306 pvecV[i] += pvecT[i];
13211307 }
0 commit comments