1818#include " PWGUD/Core/UPCHelpers.h"
1919#include " PWGUD/DataModel/UDTables.h"
2020
21- #include " Common/Core/fwdtrackUtilities.h"
22-
2321#include < CCDB/BasicCCDBManager.h>
2422#include < CCDB/CcdbApi.h>
2523#include < CommonConstants/LHCConstants.h>
2624#include < CommonConstants/PhysicsConstants.h>
25+ #include < DCAFitter/FwdDCAFitterN.h>
2726#include < DataFormatsParameters/GRPMagField.h>
2827#include < DetectorsBase/GeometryManager.h>
2928#include < DetectorsBase/Propagator.h>
@@ -90,12 +89,12 @@ struct UpcCandProducerGlobalMuon {
9089 Configurable<bool > fEnableMFT {" fEnableMFT" , true , " Enable MFT/global track processing" };
9190 Configurable<bool > fSaveMFTClusters {" fSaveMFTClusters" , true , " Save MFT cluster information" };
9291
93- // Ambiguous track propagation configurables
94- Configurable<bool > fApplyZShiftFromCCDB { " fApplyZShiftFromCCDB " , false , " Apply z-shift from CCDB for global muon propagation " };
95- Configurable<std::string> fZShiftPath { " fZShiftPath " , " Users/m/mcoquet/ZShift " , " CCDB path for z-shift " };
96- Configurable<float > fManualZShift { " fManualZShift " , 0 . 0f , " Manual z-shift for global muon propagation to PV " };
97- Configurable<float > fMaxDCAxy { " fMaxDCAxy " , 999 . f , " Maximum DCAxy for global muon track selection (cm) " };
98- Configurable<int > fBcWindowCollision { " fBcWindowCollision " , 4 , " BC window for collision search for DCA-based vertex assignment " };
92+ // FwdDCAFitter configurables
93+ Configurable<bool > fPropagateToPCA { " fPropagateToPCA " , true , " Propagate tracks to PCA " };
94+ Configurable<float > fMaxR { " fMaxR " , 200 . f , " Maximum radius for FwdDCAFitter (cm) " };
95+ Configurable<float > fMinParamChange { " fMinParamChange " , 1.0e-3 , " Minimum parameter change for FwdDCAFitter convergence " };
96+ Configurable<float > fMinRelChi2Change { " fMinRelChi2Change " , 0.9 , " Minimum relative chi2 change for FwdDCAFitter convergence " };
97+ Configurable<bool > fUseAbsDCA { " fUseAbsDCA " , true , " Use absolute DCA in FwdDCAFitter " };
9998 Configurable<float > fMaxChi2MatchMCHMFT {" fMaxChi2MatchMCHMFT" , 4 .f , " Maximum chi2 for MCH-MFT matching quality filter" };
10099 Configurable<int > fBcWindowMCHMFT {" fBcWindowMCHMFT" , 20 , " BC window for searching MCH-MFT tracks around MCH-MID-MFT anchors" };
101100
@@ -108,13 +107,10 @@ struct UpcCandProducerGlobalMuon {
108107 o2::ccdb::CcdbApi fCCDBApi ;
109108 o2::globaltracking::MatchGlobalFwd fMatching ;
110109
111- // Ambiguous track propagation members
112- float fBz {0 }; // Magnetic field at MFT center
113- static constexpr double fCcenterMFT [3 ] = {0 , 0 , -61.4 }; // Field evaluation point at center of MFT
114- float fZShift {0 }; // z-vertex shift for forward track propagation
110+ // FwdDCAFitter member
111+ o2::vertexing::FwdDCAFitterN<2 > fFwdFitter ;
115112
116113 // Named constants (avoid magic numbers in expressions)
117- static constexpr double kInvalidDCA = 999 .; // Sentinel for "no valid DCA computed yet"
118114 static constexpr double kBcTimeRoundingOffset = 1 .; // Offset used when rounding trackTime to BC units
119115 static constexpr uint16_t kMinTracksForPair = 2 ; // Minimum tracks required to compute a pair invariant mass
120116 static constexpr uint16_t kMinTracksForCandidate = 1 ; // Minimum contributors required to save a candidate
@@ -148,11 +144,8 @@ struct UpcCandProducerGlobalMuon {
148144 const AxisSpec axisEta{100 , -4.0 , -2.0 , " #eta" };
149145 histRegistry.add (" hEtaGlobal" , " Global track eta" , kTH1F , {axisEta});
150146
151- const AxisSpec axisDCAxy{200 , 0 ., 10 ., " DCA_{xy} (cm)" };
152- const AxisSpec axisDCAz{200 , -10 ., 10 ., " DCA_{z} (cm)" };
153- histRegistry.add (" hDCAxyGlobal" , " DCAxy of global tracks to best collision" , kTH1F , {axisDCAxy});
154- histRegistry.add (" hDCAzGlobal" , " DCAz of global tracks to best collision" , kTH1F , {axisDCAz});
155- histRegistry.add (" hNCompatColls" , " Number of compatible collisions per global track" , kTH1F , {{21 , -0.5 , 20.5 }});
147+ const AxisSpec axisChi2PCA{200 , 0 ., 100 ., " #chi^{2}_{PCA}" };
148+ histRegistry.add (" hChi2PCA" , " Chi2 at PCA from FwdDCAFitter" , kTH1F , {axisChi2PCA});
156149
157150 const AxisSpec axisChi2Match{200 , 0 ., 100 ., " #chi^{2}_{MCH-MFT}" };
158151 histRegistry.add (" hChi2MatchMCHMFT" , " Chi2 of MCH-MFT matching (before cut)" , kTH1F , {axisChi2Match});
@@ -409,11 +402,9 @@ struct UpcCandProducerGlobalMuon {
409402 track.trackType () == o2::aod::fwdtrack::ForwardTrackTypeEnum::GlobalForwardTrack;
410403 o2::dataformats::GlobalFwdTrack pft;
411404 if (isGlobal && fEnableMFT ) {
412- // For global tracks, use MFT helix propagation to vertex instead of
413- // MCH extrapolation, which fails because the track z is upstream of
414- // the front absorber (z ~ -60 cm vs absorber at z ~ -90 cm)
415- o2::track::TrackParCovFwd trackPar = o2::aod::fwdtrackutils::getTrackParCovFwdShift (track, fZShift );
416- trackPar.propagateToZhelix (0 ., fBz );
405+ // For global tracks, use raw kinematics for cuts;
406+ // the FwdDCAFitter handles proper vertex propagation
407+ auto trackPar = fwdToTrackPar (track);
417408 pft.setParameters (trackPar.getParameters ());
418409 pft.setZ (trackPar.getZ ());
419410 pft.setCovariances (trackPar.getCovariances ());
@@ -491,34 +482,17 @@ struct UpcCandProducerGlobalMuon {
491482 }
492483 }
493484
494- // Propagate global muon track to collision vertex using helix propagation
495- // and compute DCA (adapted from ambiguousTrackPropagation)
496- // Returns {DCAxy, DCAz, DCAx, DCAy}
497- std::array<double , 4 > propagateGlobalToDCA (ForwardTracks::iterator const & track,
498- double colX, double colY, double colZ)
485+ // Convert forward track to TrackParCovFwd for the FwdDCAFitter
486+ o2::track::TrackParCovFwd fwdToTrackPar (ForwardTracks::iterator const & track)
499487 {
500- o2::track::TrackParCovFwd trackPar = o2::aod::fwdtrackutils::getTrackParCovFwdShift (track, fZShift );
501- std::array<double , 3 > dcaOrig;
502- trackPar.propagateToDCAhelix (fBz , {colX, colY, colZ}, dcaOrig);
503- double dcaXY = std::sqrt (dcaOrig[0 ] * dcaOrig[0 ] + dcaOrig[1 ] * dcaOrig[1 ]);
504- return {dcaXY, dcaOrig[2 ], dcaOrig[0 ], dcaOrig[1 ]};
505- }
506-
507- // Dispatch DCA computation: use MFT helix propagation when fEnableMFT is true,
508- // otherwise fall back to MCH extrapolation to (0,0,0) via propagateToZero.
509- // Returns {DCAxy, DCAz, DCAx, DCAy}
510- std::array<double , 4 > propagateFwdToDCA (ForwardTracks::iterator const & track,
511- double colX, double colY, double colZ)
512- {
513- if (fEnableMFT ) {
514- return propagateGlobalToDCA (track, colX, colY, colZ);
515- }
516- auto pft = propagateToZero (track);
517- double dcaX = pft.getX () - colX;
518- double dcaY = pft.getY () - colY;
519- double dcaXY = std::sqrt (dcaX * dcaX + dcaY * dcaY);
520- double dcaZ = pft.getZ () - colZ;
521- return {dcaXY, dcaZ, dcaX, dcaY};
488+ using SMatrix5 = ROOT::Math::SVector<double , 5 >;
489+ using SMatrix55 = ROOT::Math::SMatrix<double , 5 , 5 , ROOT::Math::MatRepSym<double , 5 >>;
490+ SMatrix5 tpars (track.x (), track.y (), track.phi (), track.tgl (), track.signed1Pt ());
491+ std::vector<double > v1{track.cXX (), track.cXY (), track.cYY (), track.cPhiX (), track.cPhiY (),
492+ track.cPhiPhi (), track.cTglX (), track.cTglY (), track.cTglPhi (), track.cTglTgl (),
493+ track.c1PtX (), track.c1PtY (), track.c1PtPhi (), track.c1PtTgl (), track.c1Pt21Pt2 ()};
494+ SMatrix55 tcovs (v1.begin (), v1.end ());
495+ return o2::track::TrackParCovFwd{track.z (), tpars, tcovs, track.chi2 ()};
522496 }
523497
524498 void createCandidates (ForwardTracks const & fwdTracks,
@@ -549,24 +523,18 @@ struct UpcCandProducerGlobalMuon {
549523 if (fUpcCuts .getUseFwdCuts ()) {
550524 o2::mch::TrackExtrap::setField ();
551525 }
552- // Initialize MFT magnetic field and z-shift for ambiguous track propagation
526+ // Initialize FwdDCAFitter with magnetic field
553527 if (fEnableMFT ) {
554528 o2::field::MagneticField* field = static_cast <o2::field::MagneticField*>(TGeoGlobalMagField::Instance ()->GetField ());
555- fBz = field->getBz (fCcenterMFT );
556- LOG (info) << " Magnetic field at MFT center: bZ = " << fBz ;
557- if (fApplyZShiftFromCCDB ) {
558- auto * zShift = fCCDB ->getForTimeStamp <std::vector<float >>(fZShiftPath , ts);
559- if (zShift != nullptr && !zShift->empty ()) {
560- fZShift = (*zShift)[0 ];
561- LOGF (info, " z-shift from CCDB: %f cm" , fZShift );
562- } else {
563- fZShift = 0 ;
564- LOGF (info, " z-shift not found in CCDB path %s, set to 0 cm" , fZShiftPath .value );
565- }
566- } else {
567- fZShift = fManualZShift ;
568- LOGF (info, " z-shift manually set to %f cm" , fZShift );
569- }
529+ static constexpr double kCenterMFT [3 ] = {0 , 0 , -61.4 };
530+ float bz = field->getBz (kCenterMFT );
531+ LOG (info) << " FwdDCAFitter magnetic field at MFT center: bZ = " << bz;
532+ fFwdFitter .setBz (bz);
533+ fFwdFitter .setPropagateToPCA (fPropagateToPCA );
534+ fFwdFitter .setMaxR (fMaxR );
535+ fFwdFitter .setMinParamChange (fMinParamChange );
536+ fFwdFitter .setMinRelChi2Change (fMinRelChi2Change );
537+ fFwdFitter .setUseAbsDCA (fUseAbsDCA );
570538 }
571539 }
572540 }
@@ -648,14 +616,7 @@ struct UpcCandProducerGlobalMuon {
648616 }
649617 }
650618
651- // Map global BC to collisions for DCA-based vertex assignment
652- std::map<uint64_t , std::vector<int64_t >> mapGlobalBCtoCollisions;
653- for (const auto & col : collisions) {
654- uint64_t gbc = vGlobalBCs[col.bcId ()];
655- mapGlobalBCtoCollisions[gbc].push_back (col.globalIndex ());
656- }
657-
658- std::vector<int64_t > selTrackIds{}; // NEW: For cluster saving
619+ std::vector<int64_t > selTrackIds{}; // For cluster saving
659620
660621 int32_t candId = 0 ;
661622
@@ -682,62 +643,38 @@ struct UpcCandProducerGlobalMuon {
682643 std::map<int64_t , uint64_t > mapMchMftIdBc{};
683644 getMchTrackIds (globalBcAnchor, mapGlobalBcsWithMCHMFTTrackIds, fBcWindowMCHMFT , mapMchMftIdBc);
684645
685- // Collect all track IDs for vertex finding (anchors + matched MCH-MFT)
646+ // Collect all track IDs (anchors + matched MCH-MFT)
686647 std::vector<int64_t > allTrackIds;
687648 allTrackIds.reserve (vAnchorIds.size () + mapMchMftIdBc.size ());
688649 for (const auto & id : vAnchorIds)
689650 allTrackIds.push_back (id);
690651 for (const auto & [id, gbc] : mapMchMftIdBc)
691652 allTrackIds.push_back (id);
692653
693- // Step 1: Find best collision vertex using DCA-based propagation
654+ // Step 1: Find secondary vertex using FwdDCAFitter on the first track pair
694655 float bestVtxX = 0 ., bestVtxY = 0 ., bestVtxZ = 0 .;
695- double bestAvgDCA = kInvalidDCA ;
696- bool hasVertex = false ;
697- int nCompatColls = 0 ;
698-
699- for (int dbc = -static_cast <int >(fBcWindowCollision ); dbc <= static_cast <int >(fBcWindowCollision ); dbc++) {
700- uint64_t searchBC = globalBcAnchor + dbc;
701- auto itCol = mapGlobalBCtoCollisions.find (searchBC);
702- if (itCol == mapGlobalBCtoCollisions.end ())
703- continue ;
704- for (const auto & colIdx : itCol->second ) {
705- nCompatColls++;
706- const auto & col = collisions.iteratorAt (colIdx);
707- double sumDCAxy = 0 .;
708- int nTracks = 0 ;
709- for (const auto & iglobal : allTrackIds) {
710- const auto & trk = fwdTracks.iteratorAt (iglobal);
711- auto dca = propagateFwdToDCA (trk, col.posX (), col.posY (), col.posZ ());
712- sumDCAxy += dca[0 ];
713- nTracks++;
714- }
715- double avgDCA = nTracks > 0 ? sumDCAxy / nTracks : kInvalidDCA ;
716- if (!hasVertex || avgDCA < bestAvgDCA) {
717- bestAvgDCA = avgDCA;
718- bestVtxX = col.posX ();
719- bestVtxY = col.posY ();
720- bestVtxZ = col.posZ ();
721- hasVertex = true ;
722- }
656+
657+ if (allTrackIds.size () >= kMinTracksForPair ) {
658+ const auto & trk1 = fwdTracks.iteratorAt (allTrackIds[0 ]);
659+ const auto & trk2 = fwdTracks.iteratorAt (allTrackIds[1 ]);
660+ auto pars1 = fwdToTrackPar (trk1);
661+ auto pars2 = fwdToTrackPar (trk2);
662+ int procCode = fFwdFitter .process (pars1, pars2);
663+ if (procCode != 0 ) {
664+ auto secondaryVertex = fFwdFitter .getPCACandidate ();
665+ auto chi2PCA = fFwdFitter .getChi2AtPCACandidate ();
666+ bestVtxX = secondaryVertex[0 ];
667+ bestVtxY = secondaryVertex[1 ];
668+ bestVtxZ = secondaryVertex[2 ];
669+ histRegistry.fill (HIST (" hChi2PCA" ), chi2PCA);
723670 }
724671 }
725672
726- histRegistry.fill (HIST (" hNCompatColls" ), nCompatColls);
727-
728- // Step 2: Write anchor tracks (MCH-MID-MFT) with DCA quality cut
673+ // Step 2: Write anchor tracks (MCH-MID-MFT)
729674 constexpr double kMuonMass = o2::constants::physics::MassMuon;
730675 uint16_t numContrib = 0 ;
731676 double sumPx = 0 ., sumPy = 0 ., sumPz = 0 ., sumE = 0 .;
732677 for (const auto & ianchor : vAnchorIds) {
733- if (hasVertex) {
734- const auto & trk = fwdTracks.iteratorAt (ianchor);
735- auto dca = propagateFwdToDCA (trk, bestVtxX, bestVtxY, bestVtxZ);
736- histRegistry.fill (HIST (" hDCAxyGlobal" ), dca[0 ]);
737- histRegistry.fill (HIST (" hDCAzGlobal" ), dca[1 ]);
738- if (dca[0 ] > static_cast <double >(fMaxDCAxy ))
739- continue ;
740- }
741678 if (!addToFwdTable (candId, ianchor, globalBcAnchor, 0 ., fwdTracks, mcFwdTrackLabels))
742679 continue ;
743680 const auto & trk = fwdTracks.iteratorAt (ianchor);
@@ -757,16 +694,8 @@ struct UpcCandProducerGlobalMuon {
757694 histRegistry.fill (HIST (" hMassGlobalMuon" ), mass2 > 0 . ? std::sqrt (mass2) : 0 .);
758695 }
759696
760- // Step 3: Write matched MCH-MFT tracks with DCA quality cut and adjusted track time
697+ // Step 3: Write matched MCH-MFT tracks with adjusted track time
761698 for (const auto & [imchMft, gbc] : mapMchMftIdBc) {
762- if (hasVertex) {
763- const auto & trk = fwdTracks.iteratorAt (imchMft);
764- auto dca = propagateFwdToDCA (trk, bestVtxX, bestVtxY, bestVtxZ);
765- histRegistry.fill (HIST (" hDCAxyGlobal" ), dca[0 ]);
766- histRegistry.fill (HIST (" hDCAzGlobal" ), dca[1 ]);
767- if (dca[0 ] > static_cast <double >(fMaxDCAxy ))
768- continue ;
769- }
770699 if (!addToFwdTable (candId, imchMft, gbc, (gbc - globalBcAnchor) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mcFwdTrackLabels))
771700 continue ;
772701 const auto & trk = fwdTracks.iteratorAt (imchMft);
@@ -883,7 +812,6 @@ struct UpcCandProducerGlobalMuon {
883812 mapGlobalBcsWithMCHTrackIds.clear ();
884813 mapGlobalBcsWithGlobalMuonTrackIds.clear ();
885814 mapGlobalBcsWithMCHMFTTrackIds.clear ();
886- mapGlobalBCtoCollisions.clear ();
887815 selTrackIds.clear ();
888816 }
889817
0 commit comments