1717#include " PWGUD/Core/UPCCutparHolder.h"
1818#include " PWGUD/Core/UPCHelpers.h"
1919
20+ #include " Common/Core/fwdtrackUtilities.h"
21+
2022#include " CCDB/BasicCCDBManager.h"
23+ #include " CommonConstants/GeomConstants.h"
2124#include " CommonConstants/LHCConstants.h"
2225#include " DataFormatsParameters/GRPMagField.h"
2326#include " DetectorsBase/Propagator.h"
@@ -71,6 +74,13 @@ struct UpcCandProducerGlobalMuon {
7174 Configurable<float > fMaxEtaMFT {" fMaxEtaMFT" , -2.5 , " Maximum eta for MFT acceptance" };
7275 Configurable<bool > fSaveMFTClusters {" fSaveMFTClusters" , true , " Save MFT cluster information" };
7376
77+ // Ambiguous track propagation configurables
78+ Configurable<bool > fApplyZShiftFromCCDB {" fApplyZShiftFromCCDB" , false , " Apply z-shift from CCDB for global muon propagation" };
79+ Configurable<std::string> fZShiftPath {" fZShiftPath" , " Users/m/mcoquet/ZShift" , " CCDB path for z-shift" };
80+ Configurable<float > fManualZShift {" fManualZShift" , 0 .0f , " Manual z-shift for global muon propagation to PV" };
81+ Configurable<float > fMaxDCAxy {" fMaxDCAxy" , 999 .f , " Maximum DCAxy for global muon track selection (cm)" };
82+ Configurable<int > fBcWindowCollision {" fBcWindowCollision" , 4 , " BC window for collision search for DCA-based vertex assignment" };
83+
7484 using ForwardTracks = o2::soa::Join<o2::aod::FwdTracks, o2::aod::FwdTracksCov>;
7585
7686 HistogramRegistry histRegistry{" HistRegistry" , {}, OutputObjHandlingPolicy::AnalysisObject};
@@ -80,10 +90,15 @@ struct UpcCandProducerGlobalMuon {
8090 o2::ccdb::CcdbApi fCCDBApi ;
8191 o2::globaltracking::MatchGlobalFwd fMatching ;
8292
93+ // Ambiguous track propagation members
94+ float fBz {0 }; // Magnetic field at MFT center
95+ static constexpr double fCcenterMFT [3 ] = {0 , 0 , -61.4 }; // Field evaluation point at center of MFT
96+ float fZShift {0 }; // z-vertex shift for forward track propagation
97+
8398 void init (InitContext&)
8499 {
85100 fUpcCuts = (UPCCutparHolder)fUpcCutsConf ;
86- if (fUpcCuts .getUseFwdCuts ()) {
101+ if (fUpcCuts .getUseFwdCuts () || fEnableMFT ) {
87102 fCCDB ->setURL (" http://alice-ccdb.cern.ch" );
88103 fCCDB ->setCaching (true );
89104 fCCDB ->setLocalObjectValidityChecking ();
@@ -110,6 +125,12 @@ struct UpcCandProducerGlobalMuon {
110125 const AxisSpec axisEta{100 , -4.0 , -2.0 , " #eta" };
111126 histRegistry.add (" hEtaMFT" , " MFT track eta" , kTH1F , {axisEta});
112127 histRegistry.add (" hEtaGlobal" , " Global track eta" , kTH1F , {axisEta});
128+
129+ const AxisSpec axisDCAxy{200 , 0 ., 10 ., " DCA_{xy} (cm)" };
130+ const AxisSpec axisDCAz{200 , -10 ., 10 ., " DCA_{z} (cm)" };
131+ histRegistry.add (" hDCAxyGlobal" , " DCAxy of global tracks to best collision" , kTH1F , {axisDCAxy});
132+ histRegistry.add (" hDCAzGlobal" , " DCAz of global tracks to best collision" , kTH1F , {axisDCAz});
133+ histRegistry.add (" hNCompatColls" , " Number of compatible collisions per global track" , kTH1F , {{21 , -0.5 , 20.5 }});
113134 }
114135 }
115136
@@ -436,6 +457,19 @@ struct UpcCandProducerGlobalMuon {
436457 return (eta > fMinEtaMFT && eta < fMaxEtaMFT );
437458 }
438459
460+ // Propagate global muon track to collision vertex using helix propagation
461+ // and compute DCA (adapted from ambiguousTrackPropagation)
462+ // Returns {DCAxy, DCAz, DCAx, DCAy}
463+ std::array<double , 4 > propagateGlobalToDCA (ForwardTracks::iterator const & track,
464+ double colX, double colY, double colZ)
465+ {
466+ o2::track::TrackParCovFwd trackPar = o2::aod::fwdtrackutils::getTrackParCovFwdShift (track, fZShift );
467+ std::array<double , 3 > dcaOrig;
468+ trackPar.propagateToDCAhelix (fBz , {colX, colY, colZ}, dcaOrig);
469+ double dcaXY = std::sqrt (dcaOrig[0 ] * dcaOrig[0 ] + dcaOrig[1 ] * dcaOrig[1 ]);
470+ return {dcaXY, dcaOrig[2 ], dcaOrig[0 ], dcaOrig[1 ]};
471+ }
472+
439473 void createCandidates (ForwardTracks const & fwdTracks,
440474 o2::aod::FwdTrkCls const & fwdTrkCls,
441475 o2::aod::AmbiguousFwdTracks const & ambFwdTracks,
@@ -451,7 +485,7 @@ struct UpcCandProducerGlobalMuon {
451485 using o2::aod::fwdtrack::ForwardTrackTypeEnum::MuonStandaloneTrack;
452486
453487 int32_t runNumber = bcs.iteratorAt (0 ).runNumber ();
454- if (fUpcCuts .getUseFwdCuts ()) {
488+ if (fUpcCuts .getUseFwdCuts () || fEnableMFT ) {
455489 if (runNumber != fRun ) {
456490 fRun = runNumber;
457491 std::map<std::string, std::string> metadata;
@@ -461,7 +495,28 @@ struct UpcCandProducerGlobalMuon {
461495 o2::base::Propagator::initFieldFromGRP (grpmag);
462496 if (!o2::base::GeometryManager::isGeometryLoaded ())
463497 fCCDB ->get <TGeoManager>(" GLO/Config/GeometryAligned" );
464- o2::mch::TrackExtrap::setField ();
498+ if (fUpcCuts .getUseFwdCuts ()) {
499+ o2::mch::TrackExtrap::setField ();
500+ }
501+ // Initialize MFT magnetic field and z-shift for ambiguous track propagation
502+ if (fEnableMFT ) {
503+ o2::field::MagneticField* field = static_cast <o2::field::MagneticField*>(TGeoGlobalMagField::Instance ()->GetField ());
504+ fBz = field->getBz (fCcenterMFT );
505+ LOG (info) << " Magnetic field at MFT center: bZ = " << fBz ;
506+ if (fApplyZShiftFromCCDB ) {
507+ auto * zShift = fCCDB ->getForTimeStamp <std::vector<float >>(fZShiftPath , ts);
508+ if (zShift != nullptr && !zShift->empty ()) {
509+ fZShift = (*zShift)[0 ];
510+ LOGF (info, " z-shift from CCDB: %f cm" , fZShift );
511+ } else {
512+ fZShift = 0 ;
513+ LOGF (info, " z-shift not found in CCDB path %s, set to 0 cm" , fZShiftPath .value );
514+ }
515+ } else {
516+ fZShift = fManualZShift ;
517+ LOGF (info, " z-shift manually set to %f cm" , fZShift );
518+ }
519+ }
465520 }
466521 }
467522
@@ -533,6 +588,15 @@ struct UpcCandProducerGlobalMuon {
533588 }
534589 }
535590
591+ // Map global BC to collisions for DCA-based vertex assignment
592+ std::map<uint64_t , std::vector<int64_t >> mapGlobalBCtoCollisions;
593+ if (fEnableMFT ) {
594+ for (const auto & col : collisions) {
595+ uint64_t gbc = vGlobalBCs[col.bcId ()];
596+ mapGlobalBCtoCollisions[gbc].push_back (col.globalIndex ());
597+ }
598+ }
599+
536600 std::vector<int64_t > selTrackIds{}; // NEW: For cluster saving
537601
538602 int32_t candId = 0 ;
@@ -552,34 +616,77 @@ struct UpcCandProducerGlobalMuon {
552616 continue ;
553617 }
554618
555- uint16_t numContrib = 0 ;
556619 auto & vGlobalIds = gbc_globalids.second ;
557620
558- // Check if we have global tracks (with MFT)
621+ // Step 1: Find best collision vertex using DCA-based propagation
622+ // (adapted from ambiguousTrackPropagation processMFTReassoc3D)
623+ float bestVtxX = 0 ., bestVtxY = 0 ., bestVtxZ = 0 .;
624+ double bestAvgDCA = 999 .;
625+ bool hasVertex = false ;
626+ int nCompatColls = 0 ;
627+
628+ for (int dbc = -static_cast <int >(fBcWindowCollision ); dbc <= static_cast <int >(fBcWindowCollision ); dbc++) {
629+ uint64_t searchBC = globalBcGlobal + dbc;
630+ auto itCol = mapGlobalBCtoCollisions.find (searchBC);
631+ if (itCol == mapGlobalBCtoCollisions.end ())
632+ continue ;
633+ for (auto colIdx : itCol->second ) {
634+ nCompatColls++;
635+ const auto & col = collisions.iteratorAt (colIdx);
636+ double sumDCAxy = 0 .;
637+ int nTracks = 0 ;
638+ for (const auto & iglobal : vGlobalIds) {
639+ const auto & trk = fwdTracks.iteratorAt (iglobal);
640+ auto dca = propagateGlobalToDCA (trk, col.posX (), col.posY (), col.posZ ());
641+ sumDCAxy += dca[0 ];
642+ nTracks++;
643+ }
644+ double avgDCA = nTracks > 0 ? sumDCAxy / nTracks : 999 .;
645+ if (!hasVertex || avgDCA < bestAvgDCA) {
646+ bestAvgDCA = avgDCA;
647+ bestVtxX = col.posX ();
648+ bestVtxY = col.posY ();
649+ bestVtxZ = col.posZ ();
650+ hasVertex = true ;
651+ }
652+ }
653+ }
654+
655+ histRegistry.fill (HIST (" hNCompatColls" ), nCompatColls);
656+
657+ // Step 2: Select tracks with DCA quality cut
559658 std::vector<int64_t > tracksToSave;
560659 for (const auto & iglobal : vGlobalIds) {
561660 const auto & trk = fwdTracks.iteratorAt (iglobal);
562661
662+ // Apply DCA cut using best collision vertex
663+ if (hasVertex) {
664+ auto dca = propagateGlobalToDCA (trk, bestVtxX, bestVtxY, bestVtxZ);
665+ histRegistry.fill (HIST (" hDCAxyGlobal" ), dca[0 ]);
666+ histRegistry.fill (HIST (" hDCAzGlobal" ), dca[1 ]);
667+ if (dca[0 ] > static_cast <double >(fMaxDCAxy ))
668+ continue ;
669+ }
670+
563671 // Check MFT acceptance and decide which track to use
564672 if (isInMFTAcceptance (trk.eta ())) {
565673 // Inside MFT acceptance - use global track
566674 tracksToSave.push_back (iglobal);
567675 histRegistry.fill (HIST (" hEtaMFT" ), trk.eta ());
568676 } else {
569677 // Outside MFT acceptance - look for MCH-MID counterpart
570- // Find the corresponding MCH-MID track at the same BC
571678 auto itMid = mapGlobalBcsWithMCHMIDTrackIds.find (globalBcGlobal);
572679 if (itMid != mapGlobalBcsWithMCHMIDTrackIds.end ()) {
573- // Use MCH-MID track instead
574680 if (!itMid->second .empty ()) {
575681 tracksToSave.push_back (itMid->second [0 ]);
576- itMid->second .erase (itMid->second .begin ()); // Remove used track
682+ itMid->second .erase (itMid->second .begin ());
577683 }
578684 }
579685 }
580686 }
581687
582- // Write selected tracks
688+ // Step 3: Write tracks and event candidate with actual vertex position
689+ uint16_t numContrib = 0 ;
583690 for (const auto & trkId : tracksToSave) {
584691 if (!addToFwdTable (candId, trkId, globalBcGlobal, 0 ., fwdTracks, mcFwdTrackLabels))
585692 continue ;
@@ -590,7 +697,7 @@ struct UpcCandProducerGlobalMuon {
590697 if (numContrib < 1 )
591698 continue ;
592699
593- eventCandidates (globalBcGlobal, runNumber, 0 ., 0 ., 0 . , 0 , numContrib, 0 , 0 );
700+ eventCandidates (globalBcGlobal, runNumber, bestVtxX, bestVtxY, bestVtxZ , 0 , numContrib, 0 , 0 );
594701 std::vector<float > amplitudesV0A{};
595702 std::vector<int8_t > relBCsV0A{};
596703 std::vector<float > amplitudesT0A{};
@@ -683,8 +790,9 @@ struct UpcCandProducerGlobalMuon {
683790 vAmbFwdTrackIndexBCs.clear ();
684791 mapGlobalBcsWithMCHMIDTrackIds.clear ();
685792 mapGlobalBcsWithMCHTrackIds.clear ();
686- mapGlobalBcsWithGlobalTrackIds.clear (); // NEW
687- selTrackIds.clear (); // NEW
793+ mapGlobalBcsWithGlobalTrackIds.clear ();
794+ mapGlobalBCtoCollisions.clear ();
795+ selTrackIds.clear ();
688796 }
689797
690798 void processFwd (ForwardTracks const & fwdTracks,
0 commit comments