2323#include < CCDB/BasicCCDBManager.h>
2424#include < CCDB/CcdbApi.h>
2525#include < CommonConstants/LHCConstants.h>
26+ #include < CommonConstants/PhysicsConstants.h>
2627#include < DataFormatsParameters/GRPMagField.h>
2728#include < DetectorsBase/GeometryManager.h>
2829#include < DetectorsBase/Propagator.h>
@@ -87,8 +88,6 @@ struct UpcCandProducerGlobalMuon {
8788
8889 // NEW: MFT/Global track support configurables
8990 Configurable<bool > fEnableMFT {" fEnableMFT" , true , " Enable MFT/global track processing" };
90- Configurable<float > fMinEtaMFT {" fMinEtaMFT" , -3.6 , " Minimum eta for MFT acceptance" };
91- Configurable<float > fMaxEtaMFT {" fMaxEtaMFT" , -2.5 , " Maximum eta for MFT acceptance" };
9291 Configurable<bool > fSaveMFTClusters {" fSaveMFTClusters" , true , " Save MFT cluster information" };
9392
9493 // Ambiguous track propagation configurables
@@ -97,6 +96,8 @@ struct UpcCandProducerGlobalMuon {
9796 Configurable<float > fManualZShift {" fManualZShift" , 0 .0f , " Manual z-shift for global muon propagation to PV" };
9897 Configurable<float > fMaxDCAxy {" fMaxDCAxy" , 999 .f , " Maximum DCAxy for global muon track selection (cm)" };
9998 Configurable<int > fBcWindowCollision {" fBcWindowCollision" , 4 , " BC window for collision search for DCA-based vertex assignment" };
99+ Configurable<float > fMaxChi2MatchMCHMFT {" fMaxChi2MatchMCHMFT" , 4 .f , " Maximum chi2 for MCH-MFT matching quality filter" };
100+ Configurable<int > fBcWindowMCHMFT {" fBcWindowMCHMFT" , 20 , " BC window for searching MCH-MFT tracks around MCH-MID-MFT anchors" };
100101
101102 using ForwardTracks = o2::soa::Join<o2::aod::FwdTracks, o2::aod::FwdTracksCov>;
102103
@@ -140,14 +141,20 @@ struct UpcCandProducerGlobalMuon {
140141 histRegistry.get <TH1>(HIST (" hTrackTypes" ))->GetXaxis ()->SetBinLabel (4 , " GlobalFwd" );
141142
142143 const AxisSpec axisEta{100 , -4.0 , -2.0 , " #eta" };
143- histRegistry.add (" hEtaMFT" , " MFT track eta" , kTH1F , {axisEta});
144144 histRegistry.add (" hEtaGlobal" , " Global track eta" , kTH1F , {axisEta});
145145
146146 const AxisSpec axisDCAxy{200 , 0 ., 10 ., " DCA_{xy} (cm)" };
147147 const AxisSpec axisDCAz{200 , -10 ., 10 ., " DCA_{z} (cm)" };
148148 histRegistry.add (" hDCAxyGlobal" , " DCAxy of global tracks to best collision" , kTH1F , {axisDCAxy});
149149 histRegistry.add (" hDCAzGlobal" , " DCAz of global tracks to best collision" , kTH1F , {axisDCAz});
150150 histRegistry.add (" hNCompatColls" , " Number of compatible collisions per global track" , kTH1F , {{21 , -0.5 , 20.5 }});
151+
152+ const AxisSpec axisChi2Match{200 , 0 ., 100 ., " #chi^{2}_{MCH-MFT}" };
153+ histRegistry.add (" hChi2MatchMCHMFT" , " Chi2 of MCH-MFT matching (before cut)" , kTH1F , {axisChi2Match});
154+
155+ const AxisSpec axisMass{500 , 0 ., 10 ., " m_{inv} (GeV/c^{2})" };
156+ histRegistry.add (" hMassGlobalMuon" , " Invariant mass from MCH-MID-MFT tracks only" , kTH1F , {axisMass});
157+ histRegistry.add (" hMassGlobalMuonWithMCHMFT" , " Invariant mass from MCH-MID-MFT + MCH-MFT tracks" , kTH1F , {axisMass});
151158 }
152159 }
153160
@@ -482,12 +489,6 @@ struct UpcCandProducerGlobalMuon {
482489 }
483490 }
484491
485- // NEW: Check if track is in MFT acceptance
486- bool isInMFTAcceptance (float eta)
487- {
488- return (eta > fMinEtaMFT && eta < fMaxEtaMFT );
489- }
490-
491492 // Propagate global muon track to collision vertex using helix propagation
492493 // and compute DCA (adapted from ambiguousTrackPropagation)
493494 // Returns {DCAxy, DCAz, DCAx, DCAy}
@@ -594,7 +595,8 @@ struct UpcCandProducerGlobalMuon {
594595
595596 std::map<uint64_t , std::vector<int64_t >> mapGlobalBcsWithMCHMIDTrackIds;
596597 std::map<uint64_t , std::vector<int64_t >> mapGlobalBcsWithMCHTrackIds;
597- std::map<uint64_t , std::vector<int64_t >> mapGlobalBcsWithGlobalTrackIds; // NEW: For global tracks
598+ std::map<uint64_t , std::vector<int64_t >> mapGlobalBcsWithGlobalMuonTrackIds; // MCH-MID-MFT (good timing from MID)
599+ std::map<uint64_t , std::vector<int64_t >> mapGlobalBcsWithMCHMFTTrackIds; // MCH-MFT only (poor timing)
598600
599601 for (const auto & fwdTrack : fwdTracks) {
600602 auto trackType = fwdTrack.trackType ();
@@ -614,8 +616,16 @@ struct UpcCandProducerGlobalMuon {
614616 mapGlobalBcsWithMCHMIDTrackIds[globalBC].push_back (trackId);
615617 } else if (trackType == MCHStandaloneTrack) { // MCH-only
616618 mapGlobalBcsWithMCHTrackIds[globalBC].push_back (trackId);
617- } else if (fEnableMFT && (trackType == GlobalMuonTrack || trackType == GlobalForwardTrack)) { // NEW: Global tracks
618- mapGlobalBcsWithGlobalTrackIds[globalBC].push_back (trackId);
619+ } else if (fEnableMFT && trackType == GlobalMuonTrack) { // MCH-MID-MFT: good timing, used as anchor
620+ histRegistry.fill (HIST (" hChi2MatchMCHMFT" ), fwdTrack.chi2MatchMCHMFT ());
621+ if (fwdTrack.chi2MatchMCHMFT () > 0 && fwdTrack.chi2MatchMCHMFT () < fMaxChi2MatchMCHMFT ) {
622+ mapGlobalBcsWithGlobalMuonTrackIds[globalBC].push_back (trackId);
623+ }
624+ } else if (fEnableMFT && trackType == GlobalForwardTrack) { // MCH-MFT: poor timing, matched to anchors
625+ histRegistry.fill (HIST (" hChi2MatchMCHMFT" ), fwdTrack.chi2MatchMCHMFT ());
626+ if (fwdTrack.chi2MatchMCHMFT () > 0 && fwdTrack.chi2MatchMCHMFT () < fMaxChi2MatchMCHMFT ) {
627+ mapGlobalBcsWithMCHMFTTrackIds[globalBC].push_back (trackId);
628+ }
619629 }
620630 }
621631
@@ -632,11 +642,13 @@ struct UpcCandProducerGlobalMuon {
632642
633643 int32_t candId = 0 ;
634644
635- // NEW: Process global tracks if MFT is enabled
636- if (fEnableMFT && !mapGlobalBcsWithGlobalTrackIds.empty ()) {
637- for (const auto & gbc_globalids : mapGlobalBcsWithGlobalTrackIds) {
638- uint64_t globalBcGlobal = gbc_globalids.first ;
639- auto itFv0Id = mapGlobalBcWithV0A.find (globalBcGlobal);
645+ // Process global tracks: MCH-MID-MFT anchors + MCH-MFT in BC window
646+ // MCH-MID-MFT tracks have good timing (from MID) and serve as anchors.
647+ // MCH-MFT tracks have poor timing and are searched in a BC window around anchors.
648+ if (fEnableMFT && !mapGlobalBcsWithGlobalMuonTrackIds.empty ()) {
649+ for (const auto & gbc_anchorids : mapGlobalBcsWithGlobalMuonTrackIds) {
650+ uint64_t globalBcAnchor = gbc_anchorids.first ;
651+ auto itFv0Id = mapGlobalBcWithV0A.find (globalBcAnchor);
640652 if (itFv0Id != mapGlobalBcWithV0A.end ()) {
641653 auto fv0Id = itFv0Id->second ;
642654 const auto & fv0 = fv0s.iteratorAt (fv0Id);
@@ -647,17 +659,28 @@ struct UpcCandProducerGlobalMuon {
647659 continue ;
648660 }
649661
650- auto & vGlobalIds = gbc_globalids.second ;
662+ auto & vAnchorIds = gbc_anchorids.second ; // MCH-MID-MFT tracks at this BC
663+
664+ // Search MCH-MFT tracks in BC window around anchor (analogous to MCH-only matching)
665+ std::map<int64_t , uint64_t > mapMchMftIdBc{};
666+ getMchTrackIds (globalBcAnchor, mapGlobalBcsWithMCHMFTTrackIds, fBcWindowMCHMFT , mapMchMftIdBc);
667+
668+ // Collect all track IDs for vertex finding (anchors + matched MCH-MFT)
669+ std::vector<int64_t > allTrackIds;
670+ allTrackIds.reserve (vAnchorIds.size () + mapMchMftIdBc.size ());
671+ for (const auto & id : vAnchorIds)
672+ allTrackIds.push_back (id);
673+ for (const auto & [id, gbc] : mapMchMftIdBc)
674+ allTrackIds.push_back (id);
651675
652676 // Step 1: Find best collision vertex using DCA-based propagation
653- // (adapted from ambiguousTrackPropagation processMFTReassoc3D)
654677 float bestVtxX = 0 ., bestVtxY = 0 ., bestVtxZ = 0 .;
655678 double bestAvgDCA = 999 .;
656679 bool hasVertex = false ;
657680 int nCompatColls = 0 ;
658681
659682 for (int dbc = -static_cast <int >(fBcWindowCollision ); dbc <= static_cast <int >(fBcWindowCollision ); dbc++) {
660- uint64_t searchBC = globalBcGlobal + dbc;
683+ uint64_t searchBC = globalBcAnchor + dbc;
661684 auto itCol = mapGlobalBCtoCollisions.find (searchBC);
662685 if (itCol == mapGlobalBCtoCollisions.end ())
663686 continue ;
@@ -666,7 +689,7 @@ struct UpcCandProducerGlobalMuon {
666689 const auto & col = collisions.iteratorAt (colIdx);
667690 double sumDCAxy = 0 .;
668691 int nTracks = 0 ;
669- for (const auto & iglobal : vGlobalIds ) {
692+ for (const auto & iglobal : allTrackIds ) {
670693 const auto & trk = fwdTracks.iteratorAt (iglobal);
671694 auto dca = propagateGlobalToDCA (trk, col.posX (), col.posY (), col.posZ ());
672695 sumDCAxy += dca[0 ];
@@ -685,60 +708,80 @@ struct UpcCandProducerGlobalMuon {
685708
686709 histRegistry.fill (HIST (" hNCompatColls" ), nCompatColls);
687710
688- // Step 2: Select tracks with DCA quality cut
689- std::vector<int64_t > tracksToSave;
690- for (const auto & iglobal : vGlobalIds) {
691- const auto & trk = fwdTracks.iteratorAt (iglobal);
692-
693- // Apply DCA cut using best collision vertex
711+ // Step 2: Write anchor tracks (MCH-MID-MFT) with DCA quality cut
712+ constexpr double kMuonMass = o2::constants::physics::MassMuon;
713+ uint16_t numContrib = 0 ;
714+ double sumPx = 0 ., sumPy = 0 ., sumPz = 0 ., sumE = 0 .;
715+ for (const auto & ianchor : vAnchorIds) {
694716 if (hasVertex) {
717+ const auto & trk = fwdTracks.iteratorAt (ianchor);
695718 auto dca = propagateGlobalToDCA (trk, bestVtxX, bestVtxY, bestVtxZ);
696719 histRegistry.fill (HIST (" hDCAxyGlobal" ), dca[0 ]);
697720 histRegistry.fill (HIST (" hDCAzGlobal" ), dca[1 ]);
698721 if (dca[0 ] > static_cast <double >(fMaxDCAxy ))
699722 continue ;
700723 }
724+ if (!addToFwdTable (candId, ianchor, globalBcAnchor, 0 ., fwdTracks, mcFwdTrackLabels))
725+ continue ;
726+ const auto & trk = fwdTracks.iteratorAt (ianchor);
727+ double p2 = trk.px () * trk.px () + trk.py () * trk.py () + trk.pz () * trk.pz ();
728+ sumPx += trk.px ();
729+ sumPy += trk.py ();
730+ sumPz += trk.pz ();
731+ sumE += std::sqrt (p2 + kMuonMass * kMuonMass );
732+ numContrib++;
733+ selTrackIds.push_back (ianchor);
734+ }
701735
702- // Check MFT acceptance and decide which track to use
703- if (isInMFTAcceptance (trk.eta ())) {
704- // Inside MFT acceptance - use global track
705- tracksToSave.push_back (iglobal);
706- histRegistry.fill (HIST (" hEtaMFT" ), trk.eta ());
707- } else {
708- // Outside MFT acceptance - look for MCH-MID counterpart
709- auto itMid = mapGlobalBcsWithMCHMIDTrackIds.find (globalBcGlobal);
710- if (itMid != mapGlobalBcsWithMCHMIDTrackIds.end ()) {
711- if (!itMid->second .empty ()) {
712- tracksToSave.push_back (itMid->second [0 ]);
713- itMid->second .erase (itMid->second .begin ());
714- }
715- }
716- }
736+ // Fill invariant mass from MCH-MID-MFT anchors only
737+ uint16_t numContribAnch = numContrib;
738+ if (numContribAnch >= 2 ) {
739+ double mass2 = sumE * sumE - sumPx * sumPx - sumPy * sumPy - sumPz * sumPz;
740+ histRegistry.fill (HIST (" hMassGlobalMuon" ), mass2 > 0 . ? std::sqrt (mass2) : 0 .);
717741 }
718742
719- // Step 3: Write tracks and event candidate with actual vertex position
720- uint16_t numContrib = 0 ;
721- for (const auto & trkId : tracksToSave) {
722- if (!addToFwdTable (candId, trkId, globalBcGlobal, 0 ., fwdTracks, mcFwdTrackLabels))
743+ // Step 3: Write matched MCH-MFT tracks with DCA quality cut and adjusted track time
744+ for (const auto & [imchMft, gbc] : mapMchMftIdBc) {
745+ if (hasVertex) {
746+ const auto & trk = fwdTracks.iteratorAt (imchMft);
747+ auto dca = propagateGlobalToDCA (trk, bestVtxX, bestVtxY, bestVtxZ);
748+ histRegistry.fill (HIST (" hDCAxyGlobal" ), dca[0 ]);
749+ histRegistry.fill (HIST (" hDCAzGlobal" ), dca[1 ]);
750+ if (dca[0 ] > static_cast <double >(fMaxDCAxy ))
751+ continue ;
752+ }
753+ if (!addToFwdTable (candId, imchMft, gbc, (gbc - globalBcAnchor) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mcFwdTrackLabels))
723754 continue ;
755+ const auto & trk = fwdTracks.iteratorAt (imchMft);
756+ double p2 = trk.px () * trk.px () + trk.py () * trk.py () + trk.pz () * trk.pz ();
757+ sumPx += trk.px ();
758+ sumPy += trk.py ();
759+ sumPz += trk.pz ();
760+ sumE += std::sqrt (p2 + kMuonMass * kMuonMass );
724761 numContrib++;
725- selTrackIds.push_back (trkId);
762+ selTrackIds.push_back (imchMft);
763+ }
764+
765+ // Fill invariant mass including MCH-MFT tracks (only if MCH-MFT tracks were added)
766+ if (numContrib > numContribAnch && numContrib >= 2 ) {
767+ double mass2 = sumE * sumE - sumPx * sumPx - sumPy * sumPy - sumPz * sumPz;
768+ histRegistry.fill (HIST (" hMassGlobalMuonWithMCHMFT" ), mass2 > 0 . ? std::sqrt (mass2) : 0 .);
726769 }
727770
728771 if (numContrib < 1 )
729772 continue ;
730773
731- eventCandidates (globalBcGlobal , runNumber, bestVtxX, bestVtxY, bestVtxZ, 0 , numContrib, 0 , 0 );
774+ eventCandidates (globalBcAnchor , runNumber, bestVtxX, bestVtxY, bestVtxZ, 0 , numContrib, 0 , 0 );
732775 std::vector<float > amplitudesV0A{};
733776 std::vector<int8_t > relBCsV0A{};
734777 std::vector<float > amplitudesT0A{};
735778 std::vector<int8_t > relBCsT0A{};
736779 if (nFV0s > 0 ) {
737- getFV0Amplitudes (globalBcGlobal , fv0s, fBcWindowFITAmps , mapGlobalBcWithV0A, amplitudesV0A, relBCsV0A);
780+ getFV0Amplitudes (globalBcAnchor , fv0s, fBcWindowFITAmps , mapGlobalBcWithV0A, amplitudesV0A, relBCsV0A);
738781 }
739782 eventCandidatesSelsFwd (0 ., 0 ., amplitudesT0A, relBCsT0A, amplitudesV0A, relBCsV0A);
740783 if (nZdcs > 0 ) {
741- auto itZDC = mapGlobalBcWithZdc.find (globalBcGlobal );
784+ auto itZDC = mapGlobalBcWithZdc.find (globalBcAnchor );
742785 if (itZDC != mapGlobalBcWithZdc.end ()) {
743786 const auto & zdc = zdcs.iteratorAt (itZDC->second );
744787 float timeZNA = zdc.timeZNA ();
@@ -821,7 +864,8 @@ struct UpcCandProducerGlobalMuon {
821864 vAmbFwdTrackIndexBCs.clear ();
822865 mapGlobalBcsWithMCHMIDTrackIds.clear ();
823866 mapGlobalBcsWithMCHTrackIds.clear ();
824- mapGlobalBcsWithGlobalTrackIds.clear ();
867+ mapGlobalBcsWithGlobalMuonTrackIds.clear ();
868+ mapGlobalBcsWithMCHMFTTrackIds.clear ();
825869 mapGlobalBCtoCollisions.clear ();
826870 selTrackIds.clear ();
827871 }
0 commit comments