1414// / \author Roman Lavicka, roman.lavicka@cern.ch
1515// / \since 11.02.2026
1616
17+ #include " Common/Core/fwdtrackUtilities.h"
1718#include " PWGUD/Core/UPCCutparHolder.h"
1819#include " PWGUD/Core/UPCHelpers.h"
1920#include " PWGUD/DataModel/UDTables.h"
5556#include < map>
5657#include < numeric>
5758#include < string>
59+ #include < unordered_map>
5860#include < vector>
5961
6062using namespace o2 ::framework;
@@ -64,6 +66,7 @@ struct UpcCandProducerGlobalMuon {
6466 bool fDoMC {false };
6567
6668 std::map<int32_t , int32_t > fNewPartIDs ;
69+ std::map<uint32_t , bool > fBestMuonMatch ;
6770
6871 Produces<o2::aod::UDMcCollisions> udMCCollisions;
6972 Produces<o2::aod::UDMcParticles> udMCParticles;
@@ -97,6 +100,7 @@ struct UpcCandProducerGlobalMuon {
97100 Configurable<bool > fUseAbsDCA {" fUseAbsDCA" , true , " Use absolute DCA in FwdDCAFitter" };
98101 Configurable<float > fMaxChi2MatchMCHMFT {" fMaxChi2MatchMCHMFT" , 4 .f , " Maximum chi2 for MCH-MFT matching quality filter" };
99102 Configurable<int > fBcWindowMCHMFT {" fBcWindowMCHMFT" , 20 , " BC window for searching MCH-MFT tracks around MCH-MID-MFT anchors" };
103+ Configurable<bool > fKeepBestMuonMatch {" fKeepBestMuonMatch" , true , " Keep only the best MCH-MFT match per MCH track (lowest chi2)" };
100104
101105 using ForwardTracks = o2::soa::Join<o2::aod::FwdTracks, o2::aod::FwdTracksCov>;
102106
@@ -384,13 +388,15 @@ struct UpcCandProducerGlobalMuon {
384388 return propmuon;
385389 }
386390
387- bool addToFwdTable (int64_t candId, int64_t trackId, uint64_t gbc, float trackTime, ForwardTracks const & fwdTracks, const o2::aod::McFwdTrackLabels* mcFwdTrackLabels)
391+ bool addToFwdTable (int64_t candId, int64_t trackId, uint64_t gbc, float trackTime,
392+ ForwardTracks const & fwdTracks, o2::aod::MFTTracks const & mftTracks,
393+ const o2::aod::McFwdTrackLabels* mcFwdTrackLabels)
388394 {
389395 const auto & track = fwdTracks.iteratorAt (trackId);
390396 float px, py, pz;
391397 int sign;
392398
393- // NEW: Fill track type histogram
399+ // Fill track type histogram
394400 histRegistry.fill (HIST (" hTrackTypes" ), track.trackType ());
395401 if (track.trackType () == o2::aod::fwdtrack::ForwardTrackTypeEnum::GlobalMuonTrack ||
396402 track.trackType () == o2::aod::fwdtrack::ForwardTrackTypeEnum::GlobalForwardTrack) {
@@ -402,12 +408,16 @@ struct UpcCandProducerGlobalMuon {
402408 track.trackType () == o2::aod::fwdtrack::ForwardTrackTypeEnum::GlobalForwardTrack;
403409 o2::dataformats::GlobalFwdTrack pft;
404410 if (isGlobal && fEnableMFT ) {
405- // For global tracks, use raw kinematics for cuts;
406- // the FwdDCAFitter handles proper vertex propagation
407- auto trackPar = fwdToTrackPar (track);
408- pft.setParameters (trackPar.getParameters ());
409- pft.setZ (trackPar.getZ ());
410- pft.setCovariances (trackPar.getCovariances ());
411+ // Refit global muon: propagate MCH component to vertex, combine with MFT spatial info
412+ auto mchTrack = track.matchMCHTrack_as <ForwardTracks>();
413+ auto propMuon = propagateToZero (mchTrack);
414+ auto mfttrack = track.matchMFTTrack_as <o2::aod::MFTTracks>();
415+ using SMatrix5 = ROOT::Math::SVector<double , 5 >;
416+ using SMatrix55 = ROOT::Math::SMatrix<double , 5 , 5 , ROOT::Math::MatRepSym<double , 5 >>;
417+ SMatrix5 tpars (mfttrack.x (), mfttrack.y (), mfttrack.phi (), mfttrack.tgl (), mfttrack.signed1Pt ());
418+ SMatrix55 tcovs{};
419+ o2::track::TrackParCovFwd mft{mfttrack.z (), tpars, tcovs, mfttrack.chi2 ()};
420+ pft = o2::aod::fwdtrackutils::refitGlobalMuonCov (propMuon, mft);
411421 } else {
412422 pft = propagateToZero (track);
413423 }
@@ -495,7 +505,33 @@ struct UpcCandProducerGlobalMuon {
495505 return o2::track::TrackParCovFwd{track.z (), tpars, tcovs, track.chi2 ()};
496506 }
497507
508+ // Select the best MCH-MFT match per MCH track based on lowest chi2MatchMCHMFT.
509+ // Multiple global tracks can share the same MCH track with different MFT matches;
510+ // this function keeps only the best one to reduce combinatorial background.
511+ void selectBestMuonMatches (ForwardTracks const & fwdTracks)
512+ {
513+ fBestMuonMatch .clear ();
514+ std::unordered_map<int , std::pair<float , int >> mCandidates ;
515+ for (const auto & muon : fwdTracks) {
516+ if (static_cast <int >(muon.trackType ()) < 2 ) {
517+ auto muonID = muon.matchMCHTrackId ();
518+ auto chi2 = muon.chi2MatchMCHMFT ();
519+ if (mCandidates .find (muonID) == mCandidates .end ()) {
520+ mCandidates [muonID] = {chi2, muon.globalIndex ()};
521+ } else {
522+ if (chi2 < mCandidates [muonID].first ) {
523+ mCandidates [muonID] = {chi2, muon.globalIndex ()};
524+ }
525+ }
526+ }
527+ }
528+ for (auto & pairCand : mCandidates ) {
529+ fBestMuonMatch [pairCand.second .second ] = true ;
530+ }
531+ }
532+
498533 void createCandidates (ForwardTracks const & fwdTracks,
534+ o2::aod::MFTTracks const & mftTracks,
499535 o2::aod::FwdTrkCls const & fwdTrkCls,
500536 o2::aod::AmbiguousFwdTracks const & ambFwdTracks,
501537 o2::aod::BCs const & bcs,
@@ -580,6 +616,11 @@ struct UpcCandProducerGlobalMuon {
580616 vAmbFwdTrackIndexBCs[ambTr.globalIndex ()] = ambTr.bcIds ()[0 ];
581617 }
582618
619+ // Select best MCH-MFT match per MCH track before sorting into BC maps
620+ if (fKeepBestMuonMatch ) {
621+ selectBestMuonMatches (fwdTracks);
622+ }
623+
583624 std::map<uint64_t , std::vector<int64_t >> mapGlobalBcsWithMCHMIDTrackIds;
584625 std::map<uint64_t , std::vector<int64_t >> mapGlobalBcsWithMCHTrackIds;
585626 std::map<uint64_t , std::vector<int64_t >> mapGlobalBcsWithGlobalMuonTrackIds; // MCH-MID-MFT (good timing from MID)
@@ -595,6 +636,13 @@ struct UpcCandProducerGlobalMuon {
595636 trackType != GlobalForwardTrack)
596637 continue ;
597638
639+ // For global tracks, skip if not the best match for this MCH track
640+ if (fKeepBestMuonMatch && static_cast <int >(trackType) < 2 ) {
641+ if (fBestMuonMatch .find (fwdTrack.globalIndex ()) == fBestMuonMatch .end ()) {
642+ continue ;
643+ }
644+ }
645+
598646 auto trackId = fwdTrack.globalIndex ();
599647 int64_t indexBC = vAmbFwdTrackIndex[trackId] < 0 ? vColIndexBCs[fwdTrack.collisionId ()] : vAmbFwdTrackIndexBCs[vAmbFwdTrackIndex[trackId]];
600648 auto globalBC = vGlobalBCs[indexBC] + TMath::FloorNint (fwdTrack.trackTime () / o2::constants::lhc::LHCBunchSpacingNS + kBcTimeRoundingOffset );
@@ -675,7 +723,7 @@ struct UpcCandProducerGlobalMuon {
675723 uint16_t numContrib = 0 ;
676724 double sumPx = 0 ., sumPy = 0 ., sumPz = 0 ., sumE = 0 .;
677725 for (const auto & ianchor : vAnchorIds) {
678- if (!addToFwdTable (candId, ianchor, globalBcAnchor, 0 ., fwdTracks, mcFwdTrackLabels))
726+ if (!addToFwdTable (candId, ianchor, globalBcAnchor, 0 ., fwdTracks, mftTracks, mcFwdTrackLabels))
679727 continue ;
680728 const auto & trk = fwdTracks.iteratorAt (ianchor);
681729 double p2 = trk.px () * trk.px () + trk.py () * trk.py () + trk.pz () * trk.pz ();
@@ -696,7 +744,7 @@ struct UpcCandProducerGlobalMuon {
696744
697745 // Step 3: Write matched MCH-MFT tracks with adjusted track time
698746 for (const auto & [imchMft, gbc] : mapMchMftIdBc) {
699- if (!addToFwdTable (candId, imchMft, gbc, (gbc - globalBcAnchor) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mcFwdTrackLabels))
747+ if (!addToFwdTable (candId, imchMft, gbc, (gbc - globalBcAnchor) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mftTracks, mcFwdTrackLabels))
700748 continue ;
701749 const auto & trk = fwdTracks.iteratorAt (imchMft);
702750 double p2 = trk.px () * trk.px () + trk.py () * trk.py () + trk.pz () * trk.pz ();
@@ -758,7 +806,7 @@ struct UpcCandProducerGlobalMuon {
758806 auto & vMuonIds = gbc_muids.second ;
759807 // writing MCH-MID tracks
760808 for (const auto & imuon : vMuonIds) {
761- if (!addToFwdTable (candId, imuon, globalBcMid, 0 ., fwdTracks, mcFwdTrackLabels))
809+ if (!addToFwdTable (candId, imuon, globalBcMid, 0 ., fwdTracks, mftTracks, mcFwdTrackLabels))
762810 continue ;
763811 numContrib++;
764812 selTrackIds.push_back (imuon);
@@ -769,7 +817,7 @@ struct UpcCandProducerGlobalMuon {
769817 getMchTrackIds (globalBcMid, mapGlobalBcsWithMCHTrackIds, fBcWindowMCH , mapMchIdBc);
770818 // writing MCH-only tracks
771819 for (const auto & [imch, gbc] : mapMchIdBc) {
772- if (!addToFwdTable (candId, imch, gbc, (gbc - globalBcMid) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mcFwdTrackLabels))
820+ if (!addToFwdTable (candId, imch, gbc, (gbc - globalBcMid) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mftTracks, mcFwdTrackLabels))
773821 continue ;
774822 numContrib++;
775823 selTrackIds.push_back (imch);
@@ -816,6 +864,7 @@ struct UpcCandProducerGlobalMuon {
816864 }
817865
818866 void processFwd (ForwardTracks const & fwdTracks,
867+ o2::aod::MFTTracks const & mftTracks,
819868 o2::aod::FwdTrkCls const & fwdTrkCls,
820869 o2::aod::AmbiguousFwdTracks const & ambFwdTracks,
821870 o2::aod::BCs const & bcs,
@@ -824,10 +873,11 @@ struct UpcCandProducerGlobalMuon {
824873 o2::aod::Zdcs const & zdcs)
825874 {
826875 fDoMC = false ;
827- createCandidates (fwdTracks, fwdTrkCls, ambFwdTracks, bcs, collisions, fv0s, zdcs, (o2::aod::McFwdTrackLabels*)nullptr );
876+ createCandidates (fwdTracks, mftTracks, fwdTrkCls, ambFwdTracks, bcs, collisions, fv0s, zdcs, (o2::aod::McFwdTrackLabels*)nullptr );
828877 }
829878
830879 void processFwdMC (ForwardTracks const & fwdTracks,
880+ o2::aod::MFTTracks const & mftTracks,
831881 o2::aod::FwdTrkCls const & fwdTrkCls,
832882 o2::aod::AmbiguousFwdTracks const & ambFwdTracks,
833883 o2::aod::BCs const & bcs,
@@ -840,7 +890,7 @@ struct UpcCandProducerGlobalMuon {
840890 {
841891 fDoMC = true ;
842892 skimMCInfo (mcCollisions, mcParticles);
843- createCandidates (fwdTracks, fwdTrkCls, ambFwdTracks, bcs, collisions, fv0s, zdcs, &mcFwdTrackLabels);
893+ createCandidates (fwdTracks, mftTracks, fwdTrkCls, ambFwdTracks, bcs, collisions, fv0s, zdcs, &mcFwdTrackLabels);
844894 fNewPartIDs .clear ();
845895 }
846896
0 commit comments