2525#include " Framework/AnalysisDataModel.h"
2626#include " Framework/AnalysisTask.h"
2727#include " Framework/runDataProcessing.h"
28+ #include " Common/Core/fwdtrackUtilities.h"
29+ #include " CommonConstants/GeomConstants.h"
2830#include " GlobalTracking/MatchGlobalFwd.h"
2931#include " MCHTracking/TrackExtrap.h"
3032#include " ReconstructionDataFormats/TrackFwd.h"
@@ -71,6 +73,13 @@ struct UpcCandProducerGlobalMuon {
7173 Configurable<float > fMaxEtaMFT {" fMaxEtaMFT" , -2.5 , " Maximum eta for MFT acceptance" };
7274 Configurable<bool > fSaveMFTClusters {" fSaveMFTClusters" , true , " Save MFT cluster information" };
7375
76+ // Ambiguous track propagation configurables
77+ Configurable<bool > fApplyZShiftFromCCDB {" fApplyZShiftFromCCDB" , false , " Apply z-shift from CCDB for global muon propagation" };
78+ Configurable<std::string> fZShiftPath {" fZShiftPath" , " Users/m/mcoquet/ZShift" , " CCDB path for z-shift" };
79+ Configurable<float > fManualZShift {" fManualZShift" , 0 .0f , " Manual z-shift for global muon propagation to PV" };
80+ Configurable<float > fMaxDCAxy {" fMaxDCAxy" , 999 .f , " Maximum DCAxy for global muon track selection (cm)" };
81+ Configurable<int > fBcWindowCollision {" fBcWindowCollision" , 4 , " BC window for collision search for DCA-based vertex assignment" };
82+
7483 using ForwardTracks = o2::soa::Join<o2::aod::FwdTracks, o2::aod::FwdTracksCov>;
7584
7685 HistogramRegistry histRegistry{" HistRegistry" , {}, OutputObjHandlingPolicy::AnalysisObject};
@@ -80,10 +89,15 @@ struct UpcCandProducerGlobalMuon {
8089 o2::ccdb::CcdbApi fCCDBApi ;
8190 o2::globaltracking::MatchGlobalFwd fMatching ;
8291
92+ // Ambiguous track propagation members
93+ float fBz {0 }; // Magnetic field at MFT center
94+ static constexpr double fCcenterMFT [3 ] = {0 , 0 , -61.4 }; // Field evaluation point at center of MFT
95+ float fZShift {0 }; // z-vertex shift for forward track propagation
96+
8397 void init (InitContext&)
8498 {
8599 fUpcCuts = (UPCCutparHolder)fUpcCutsConf ;
86- if (fUpcCuts .getUseFwdCuts ()) {
100+ if (fUpcCuts .getUseFwdCuts () || fEnableMFT ) {
87101 fCCDB ->setURL (" http://alice-ccdb.cern.ch" );
88102 fCCDB ->setCaching (true );
89103 fCCDB ->setLocalObjectValidityChecking ();
@@ -110,6 +124,12 @@ struct UpcCandProducerGlobalMuon {
110124 const AxisSpec axisEta{100 , -4.0 , -2.0 , " #eta" };
111125 histRegistry.add (" hEtaMFT" , " MFT track eta" , kTH1F , {axisEta});
112126 histRegistry.add (" hEtaGlobal" , " Global track eta" , kTH1F , {axisEta});
127+
128+ const AxisSpec axisDCAxy{200 , 0 ., 10 ., " DCA_{xy} (cm)" };
129+ const AxisSpec axisDCAz{200 , -10 ., 10 ., " DCA_{z} (cm)" };
130+ histRegistry.add (" hDCAxyGlobal" , " DCAxy of global tracks to best collision" , kTH1F , {axisDCAxy});
131+ histRegistry.add (" hDCAzGlobal" , " DCAz of global tracks to best collision" , kTH1F , {axisDCAz});
132+ histRegistry.add (" hNCompatColls" , " Number of compatible collisions per global track" , kTH1F , {{21 , -0.5 , 20.5 }});
113133 }
114134 }
115135
@@ -436,6 +456,19 @@ struct UpcCandProducerGlobalMuon {
436456 return (eta > fMinEtaMFT && eta < fMaxEtaMFT );
437457 }
438458
459+ // Propagate global muon track to collision vertex using helix propagation
460+ // and compute DCA (adapted from ambiguousTrackPropagation)
461+ // Returns {DCAxy, DCAz, DCAx, DCAy}
462+ std::array<double , 4 > propagateGlobalToDCA (ForwardTracks::iterator const & track,
463+ double colX, double colY, double colZ)
464+ {
465+ o2::track::TrackParCovFwd trackPar = o2::aod::fwdtrackutils::getTrackParCovFwdShift (track, fZShift );
466+ std::array<double , 3 > dcaOrig;
467+ trackPar.propagateToDCAhelix (fBz , {colX, colY, colZ}, dcaOrig);
468+ double dcaXY = std::sqrt (dcaOrig[0 ] * dcaOrig[0 ] + dcaOrig[1 ] * dcaOrig[1 ]);
469+ return {dcaXY, dcaOrig[2 ], dcaOrig[0 ], dcaOrig[1 ]};
470+ }
471+
439472 void createCandidates (ForwardTracks const & fwdTracks,
440473 o2::aod::FwdTrkCls const & fwdTrkCls,
441474 o2::aod::AmbiguousFwdTracks const & ambFwdTracks,
@@ -451,7 +484,7 @@ struct UpcCandProducerGlobalMuon {
451484 using o2::aod::fwdtrack::ForwardTrackTypeEnum::MuonStandaloneTrack;
452485
453486 int32_t runNumber = bcs.iteratorAt (0 ).runNumber ();
454- if (fUpcCuts .getUseFwdCuts ()) {
487+ if (fUpcCuts .getUseFwdCuts () || fEnableMFT ) {
455488 if (runNumber != fRun ) {
456489 fRun = runNumber;
457490 std::map<std::string, std::string> metadata;
@@ -461,7 +494,28 @@ struct UpcCandProducerGlobalMuon {
461494 o2::base::Propagator::initFieldFromGRP (grpmag);
462495 if (!o2::base::GeometryManager::isGeometryLoaded ())
463496 fCCDB ->get <TGeoManager>(" GLO/Config/GeometryAligned" );
464- o2::mch::TrackExtrap::setField ();
497+ if (fUpcCuts .getUseFwdCuts ()) {
498+ o2::mch::TrackExtrap::setField ();
499+ }
500+ // Initialize MFT magnetic field and z-shift for ambiguous track propagation
501+ if (fEnableMFT ) {
502+ o2::field::MagneticField* field = static_cast <o2::field::MagneticField*>(TGeoGlobalMagField::Instance ()->GetField ());
503+ fBz = field->getBz (fCcenterMFT );
504+ LOG (info) << " Magnetic field at MFT center: bZ = " << fBz ;
505+ if (fApplyZShiftFromCCDB ) {
506+ auto * zShift = fCCDB ->getForTimeStamp <std::vector<float >>(fZShiftPath , ts);
507+ if (zShift != nullptr && !zShift->empty ()) {
508+ fZShift = (*zShift)[0 ];
509+ LOGF (info, " z-shift from CCDB: %f cm" , fZShift );
510+ } else {
511+ fZShift = 0 ;
512+ LOGF (info, " z-shift not found in CCDB path %s, set to 0 cm" , fZShiftPath .value );
513+ }
514+ } else {
515+ fZShift = fManualZShift ;
516+ LOGF (info, " z-shift manually set to %f cm" , fZShift );
517+ }
518+ }
465519 }
466520 }
467521
@@ -533,6 +587,15 @@ struct UpcCandProducerGlobalMuon {
533587 }
534588 }
535589
590+ // Map global BC to collisions for DCA-based vertex assignment
591+ std::map<uint64_t , std::vector<int64_t >> mapGlobalBCtoCollisions;
592+ if (fEnableMFT ) {
593+ for (const auto & col : collisions) {
594+ uint64_t gbc = vGlobalBCs[col.bcId ()];
595+ mapGlobalBCtoCollisions[gbc].push_back (col.globalIndex ());
596+ }
597+ }
598+
536599 std::vector<int64_t > selTrackIds{}; // NEW: For cluster saving
537600
538601 int32_t candId = 0 ;
@@ -552,34 +615,77 @@ struct UpcCandProducerGlobalMuon {
552615 continue ;
553616 }
554617
555- uint16_t numContrib = 0 ;
556618 auto & vGlobalIds = gbc_globalids.second ;
557619
558- // Check if we have global tracks (with MFT)
620+ // Step 1: Find best collision vertex using DCA-based propagation
621+ // (adapted from ambiguousTrackPropagation processMFTReassoc3D)
622+ float bestVtxX = 0 ., bestVtxY = 0 ., bestVtxZ = 0 .;
623+ double bestAvgDCA = 999 .;
624+ bool hasVertex = false ;
625+ int nCompatColls = 0 ;
626+
627+ for (int dbc = -static_cast <int >(fBcWindowCollision ); dbc <= static_cast <int >(fBcWindowCollision ); dbc++) {
628+ uint64_t searchBC = globalBcGlobal + dbc;
629+ auto itCol = mapGlobalBCtoCollisions.find (searchBC);
630+ if (itCol == mapGlobalBCtoCollisions.end ())
631+ continue ;
632+ for (auto colIdx : itCol->second ) {
633+ nCompatColls++;
634+ const auto & col = collisions.iteratorAt (colIdx);
635+ double sumDCAxy = 0 .;
636+ int nTracks = 0 ;
637+ for (const auto & iglobal : vGlobalIds) {
638+ const auto & trk = fwdTracks.iteratorAt (iglobal);
639+ auto dca = propagateGlobalToDCA (trk, col.posX (), col.posY (), col.posZ ());
640+ sumDCAxy += dca[0 ];
641+ nTracks++;
642+ }
643+ double avgDCA = nTracks > 0 ? sumDCAxy / nTracks : 999 .;
644+ if (!hasVertex || avgDCA < bestAvgDCA) {
645+ bestAvgDCA = avgDCA;
646+ bestVtxX = col.posX ();
647+ bestVtxY = col.posY ();
648+ bestVtxZ = col.posZ ();
649+ hasVertex = true ;
650+ }
651+ }
652+ }
653+
654+ histRegistry.fill (HIST (" hNCompatColls" ), nCompatColls);
655+
656+ // Step 2: Select tracks with DCA quality cut
559657 std::vector<int64_t > tracksToSave;
560658 for (const auto & iglobal : vGlobalIds) {
561659 const auto & trk = fwdTracks.iteratorAt (iglobal);
562660
661+ // Apply DCA cut using best collision vertex
662+ if (hasVertex) {
663+ auto dca = propagateGlobalToDCA (trk, bestVtxX, bestVtxY, bestVtxZ);
664+ histRegistry.fill (HIST (" hDCAxyGlobal" ), dca[0 ]);
665+ histRegistry.fill (HIST (" hDCAzGlobal" ), dca[1 ]);
666+ if (dca[0 ] > static_cast <double >(fMaxDCAxy ))
667+ continue ;
668+ }
669+
563670 // Check MFT acceptance and decide which track to use
564671 if (isInMFTAcceptance (trk.eta ())) {
565672 // Inside MFT acceptance - use global track
566673 tracksToSave.push_back (iglobal);
567674 histRegistry.fill (HIST (" hEtaMFT" ), trk.eta ());
568675 } else {
569676 // Outside MFT acceptance - look for MCH-MID counterpart
570- // Find the corresponding MCH-MID track at the same BC
571677 auto itMid = mapGlobalBcsWithMCHMIDTrackIds.find (globalBcGlobal);
572678 if (itMid != mapGlobalBcsWithMCHMIDTrackIds.end ()) {
573- // Use MCH-MID track instead
574679 if (!itMid->second .empty ()) {
575680 tracksToSave.push_back (itMid->second [0 ]);
576- itMid->second .erase (itMid->second .begin ()); // Remove used track
681+ itMid->second .erase (itMid->second .begin ());
577682 }
578683 }
579684 }
580685 }
581686
582- // Write selected tracks
687+ // Step 3: Write tracks and event candidate with actual vertex position
688+ uint16_t numContrib = 0 ;
583689 for (const auto & trkId : tracksToSave) {
584690 if (!addToFwdTable (candId, trkId, globalBcGlobal, 0 ., fwdTracks, mcFwdTrackLabels))
585691 continue ;
@@ -590,7 +696,7 @@ struct UpcCandProducerGlobalMuon {
590696 if (numContrib < 1 )
591697 continue ;
592698
593- eventCandidates (globalBcGlobal, runNumber, 0 ., 0 ., 0 . , 0 , numContrib, 0 , 0 );
699+ eventCandidates (globalBcGlobal, runNumber, bestVtxX, bestVtxY, bestVtxZ , 0 , numContrib, 0 , 0 );
594700 std::vector<float > amplitudesV0A{};
595701 std::vector<int8_t > relBCsV0A{};
596702 std::vector<float > amplitudesT0A{};
@@ -683,8 +789,9 @@ struct UpcCandProducerGlobalMuon {
683789 vAmbFwdTrackIndexBCs.clear ();
684790 mapGlobalBcsWithMCHMIDTrackIds.clear ();
685791 mapGlobalBcsWithMCHTrackIds.clear ();
686- mapGlobalBcsWithGlobalTrackIds.clear (); // NEW
687- selTrackIds.clear (); // NEW
792+ mapGlobalBcsWithGlobalTrackIds.clear ();
793+ mapGlobalBCtoCollisions.clear ();
794+ selTrackIds.clear ();
688795 }
689796
690797 void processFwd (ForwardTracks const & fwdTracks,
0 commit comments