From 50238370a142e653c526dbdb6213e3de1257cbe7 Mon Sep 17 00:00:00 2001 From: rolavick Date: Thu, 23 Apr 2026 14:31:57 +0200 Subject: [PATCH 1/4] GlobalMuon matching based on chi2matching --- .../upcCandProducerGlobalMuon.cxx | 78 +++++++++++++++---- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx b/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx index e2df219a838..0041ac26fda 100644 --- a/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx +++ b/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx @@ -14,6 +14,7 @@ /// \author Roman Lavicka, roman.lavicka@cern.ch /// \since 11.02.2026 +#include "Common/Core/fwdtrackUtilities.h" #include "PWGUD/Core/UPCCutparHolder.h" #include "PWGUD/Core/UPCHelpers.h" #include "PWGUD/DataModel/UDTables.h" @@ -55,6 +56,7 @@ #include #include #include +#include #include using namespace o2::framework; @@ -64,6 +66,7 @@ struct UpcCandProducerGlobalMuon { bool fDoMC{false}; std::map fNewPartIDs; + std::map fBestMuonMatch; Produces udMCCollisions; Produces udMCParticles; @@ -97,6 +100,7 @@ struct UpcCandProducerGlobalMuon { Configurable fUseAbsDCA{"fUseAbsDCA", true, "Use absolute DCA in FwdDCAFitter"}; Configurable fMaxChi2MatchMCHMFT{"fMaxChi2MatchMCHMFT", 4.f, "Maximum chi2 for MCH-MFT matching quality filter"}; Configurable fBcWindowMCHMFT{"fBcWindowMCHMFT", 20, "BC window for searching MCH-MFT tracks around MCH-MID-MFT anchors"}; + Configurable fKeepBestMuonMatch{"fKeepBestMuonMatch", true, "Keep only the best MCH-MFT match per MCH track (lowest chi2)"}; using ForwardTracks = o2::soa::Join; @@ -384,13 +388,15 @@ struct UpcCandProducerGlobalMuon { return propmuon; } - bool addToFwdTable(int64_t candId, int64_t trackId, uint64_t gbc, float trackTime, ForwardTracks const& fwdTracks, const o2::aod::McFwdTrackLabels* mcFwdTrackLabels) + bool addToFwdTable(int64_t candId, int64_t trackId, uint64_t gbc, float trackTime, + ForwardTracks const& fwdTracks, o2::aod::MFTTracks const& mftTracks, + const o2::aod::McFwdTrackLabels* mcFwdTrackLabels) { const auto& track = fwdTracks.iteratorAt(trackId); float px, py, pz; int sign; - // NEW: Fill track type histogram + // Fill track type histogram histRegistry.fill(HIST("hTrackTypes"), track.trackType()); if (track.trackType() == o2::aod::fwdtrack::ForwardTrackTypeEnum::GlobalMuonTrack || track.trackType() == o2::aod::fwdtrack::ForwardTrackTypeEnum::GlobalForwardTrack) { @@ -402,12 +408,16 @@ struct UpcCandProducerGlobalMuon { track.trackType() == o2::aod::fwdtrack::ForwardTrackTypeEnum::GlobalForwardTrack; o2::dataformats::GlobalFwdTrack pft; if (isGlobal && fEnableMFT) { - // For global tracks, use raw kinematics for cuts; - // the FwdDCAFitter handles proper vertex propagation - auto trackPar = fwdToTrackPar(track); - pft.setParameters(trackPar.getParameters()); - pft.setZ(trackPar.getZ()); - pft.setCovariances(trackPar.getCovariances()); + // Refit global muon: propagate MCH component to vertex, combine with MFT spatial info + auto mchTrack = track.matchMCHTrack_as(); + auto propMuon = propagateToZero(mchTrack); + auto mfttrack = track.matchMFTTrack_as(); + using SMatrix5 = ROOT::Math::SVector; + using SMatrix55 = ROOT::Math::SMatrix>; + SMatrix5 tpars(mfttrack.x(), mfttrack.y(), mfttrack.phi(), mfttrack.tgl(), mfttrack.signed1Pt()); + SMatrix55 tcovs{}; + o2::track::TrackParCovFwd mft{mfttrack.z(), tpars, tcovs, mfttrack.chi2()}; + pft = o2::aod::fwdtrackutils::refitGlobalMuonCov(propMuon, mft); } else { pft = propagateToZero(track); } @@ -495,7 +505,33 @@ struct UpcCandProducerGlobalMuon { return o2::track::TrackParCovFwd{track.z(), tpars, tcovs, track.chi2()}; } + // Select the best MCH-MFT match per MCH track based on lowest chi2MatchMCHMFT. + // Multiple global tracks can share the same MCH track with different MFT matches; + // this function keeps only the best one to reduce combinatorial background. + void selectBestMuonMatches(ForwardTracks const& fwdTracks) + { + fBestMuonMatch.clear(); + std::unordered_map> mCandidates; + for (const auto& muon : fwdTracks) { + if (static_cast(muon.trackType()) < 2) { + auto muonID = muon.matchMCHTrackId(); + auto chi2 = muon.chi2MatchMCHMFT(); + if (mCandidates.find(muonID) == mCandidates.end()) { + mCandidates[muonID] = {chi2, muon.globalIndex()}; + } else { + if (chi2 < mCandidates[muonID].first) { + mCandidates[muonID] = {chi2, muon.globalIndex()}; + } + } + } + } + for (auto& pairCand : mCandidates) { + fBestMuonMatch[pairCand.second.second] = true; + } + } + void createCandidates(ForwardTracks const& fwdTracks, + o2::aod::MFTTracks const& mftTracks, o2::aod::FwdTrkCls const& fwdTrkCls, o2::aod::AmbiguousFwdTracks const& ambFwdTracks, o2::aod::BCs const& bcs, @@ -580,6 +616,11 @@ struct UpcCandProducerGlobalMuon { vAmbFwdTrackIndexBCs[ambTr.globalIndex()] = ambTr.bcIds()[0]; } + // Select best MCH-MFT match per MCH track before sorting into BC maps + if (fKeepBestMuonMatch) { + selectBestMuonMatches(fwdTracks); + } + std::map> mapGlobalBcsWithMCHMIDTrackIds; std::map> mapGlobalBcsWithMCHTrackIds; std::map> mapGlobalBcsWithGlobalMuonTrackIds; // MCH-MID-MFT (good timing from MID) @@ -595,6 +636,13 @@ struct UpcCandProducerGlobalMuon { trackType != GlobalForwardTrack) continue; + // For global tracks, skip if not the best match for this MCH track + if (fKeepBestMuonMatch && static_cast(trackType) < 2) { + if (fBestMuonMatch.find(fwdTrack.globalIndex()) == fBestMuonMatch.end()) { + continue; + } + } + auto trackId = fwdTrack.globalIndex(); int64_t indexBC = vAmbFwdTrackIndex[trackId] < 0 ? vColIndexBCs[fwdTrack.collisionId()] : vAmbFwdTrackIndexBCs[vAmbFwdTrackIndex[trackId]]; auto globalBC = vGlobalBCs[indexBC] + TMath::FloorNint(fwdTrack.trackTime() / o2::constants::lhc::LHCBunchSpacingNS + kBcTimeRoundingOffset); @@ -675,7 +723,7 @@ struct UpcCandProducerGlobalMuon { uint16_t numContrib = 0; double sumPx = 0., sumPy = 0., sumPz = 0., sumE = 0.; for (const auto& ianchor : vAnchorIds) { - if (!addToFwdTable(candId, ianchor, globalBcAnchor, 0., fwdTracks, mcFwdTrackLabels)) + if (!addToFwdTable(candId, ianchor, globalBcAnchor, 0., fwdTracks, mftTracks, mcFwdTrackLabels)) continue; const auto& trk = fwdTracks.iteratorAt(ianchor); double p2 = trk.px() * trk.px() + trk.py() * trk.py() + trk.pz() * trk.pz(); @@ -696,7 +744,7 @@ struct UpcCandProducerGlobalMuon { // Step 3: Write matched MCH-MFT tracks with adjusted track time for (const auto& [imchMft, gbc] : mapMchMftIdBc) { - if (!addToFwdTable(candId, imchMft, gbc, (gbc - globalBcAnchor) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mcFwdTrackLabels)) + if (!addToFwdTable(candId, imchMft, gbc, (gbc - globalBcAnchor) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mftTracks, mcFwdTrackLabels)) continue; const auto& trk = fwdTracks.iteratorAt(imchMft); double p2 = trk.px() * trk.px() + trk.py() * trk.py() + trk.pz() * trk.pz(); @@ -758,7 +806,7 @@ struct UpcCandProducerGlobalMuon { auto& vMuonIds = gbc_muids.second; // writing MCH-MID tracks for (const auto& imuon : vMuonIds) { - if (!addToFwdTable(candId, imuon, globalBcMid, 0., fwdTracks, mcFwdTrackLabels)) + if (!addToFwdTable(candId, imuon, globalBcMid, 0., fwdTracks, mftTracks, mcFwdTrackLabels)) continue; numContrib++; selTrackIds.push_back(imuon); @@ -769,7 +817,7 @@ struct UpcCandProducerGlobalMuon { getMchTrackIds(globalBcMid, mapGlobalBcsWithMCHTrackIds, fBcWindowMCH, mapMchIdBc); // writing MCH-only tracks for (const auto& [imch, gbc] : mapMchIdBc) { - if (!addToFwdTable(candId, imch, gbc, (gbc - globalBcMid) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mcFwdTrackLabels)) + if (!addToFwdTable(candId, imch, gbc, (gbc - globalBcMid) * o2::constants::lhc::LHCBunchSpacingNS, fwdTracks, mftTracks, mcFwdTrackLabels)) continue; numContrib++; selTrackIds.push_back(imch); @@ -816,6 +864,7 @@ struct UpcCandProducerGlobalMuon { } void processFwd(ForwardTracks const& fwdTracks, + o2::aod::MFTTracks const& mftTracks, o2::aod::FwdTrkCls const& fwdTrkCls, o2::aod::AmbiguousFwdTracks const& ambFwdTracks, o2::aod::BCs const& bcs, @@ -824,10 +873,11 @@ struct UpcCandProducerGlobalMuon { o2::aod::Zdcs const& zdcs) { fDoMC = false; - createCandidates(fwdTracks, fwdTrkCls, ambFwdTracks, bcs, collisions, fv0s, zdcs, (o2::aod::McFwdTrackLabels*)nullptr); + createCandidates(fwdTracks, mftTracks, fwdTrkCls, ambFwdTracks, bcs, collisions, fv0s, zdcs, (o2::aod::McFwdTrackLabels*)nullptr); } void processFwdMC(ForwardTracks const& fwdTracks, + o2::aod::MFTTracks const& mftTracks, o2::aod::FwdTrkCls const& fwdTrkCls, o2::aod::AmbiguousFwdTracks const& ambFwdTracks, o2::aod::BCs const& bcs, @@ -840,7 +890,7 @@ struct UpcCandProducerGlobalMuon { { fDoMC = true; skimMCInfo(mcCollisions, mcParticles); - createCandidates(fwdTracks, fwdTrkCls, ambFwdTracks, bcs, collisions, fv0s, zdcs, &mcFwdTrackLabels); + createCandidates(fwdTracks, mftTracks, fwdTrkCls, ambFwdTracks, bcs, collisions, fv0s, zdcs, &mcFwdTrackLabels); fNewPartIDs.clear(); } From b4d397b5bc92d89e7d143e8f4199a19b2e763e79 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Thu, 23 Apr 2026 12:45:43 +0000 Subject: [PATCH 2/4] Please consider the following formatting changes --- PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx b/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx index 0041ac26fda..4f8da78a1af 100644 --- a/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx +++ b/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx @@ -14,11 +14,12 @@ /// \author Roman Lavicka, roman.lavicka@cern.ch /// \since 11.02.2026 -#include "Common/Core/fwdtrackUtilities.h" #include "PWGUD/Core/UPCCutparHolder.h" #include "PWGUD/Core/UPCHelpers.h" #include "PWGUD/DataModel/UDTables.h" +#include "Common/Core/fwdtrackUtilities.h" + #include #include #include From cd98f9e4d77d8e304cae7689bd484e151dfccd9e Mon Sep 17 00:00:00 2001 From: rolavick Date: Thu, 23 Apr 2026 15:07:06 +0200 Subject: [PATCH 3/4] Making MegaLinter and O2linter happy --- PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx b/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx index 4f8da78a1af..4b1b098ead5 100644 --- a/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx +++ b/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx @@ -58,6 +58,7 @@ #include #include #include +#include #include using namespace o2::framework; @@ -116,9 +117,10 @@ struct UpcCandProducerGlobalMuon { o2::vertexing::FwdDCAFitterN<2> fFwdFitter; // Named constants (avoid magic numbers in expressions) - static constexpr double kBcTimeRoundingOffset = 1.; // Offset used when rounding trackTime to BC units - static constexpr uint16_t kMinTracksForPair = 2; // Minimum tracks required to compute a pair invariant mass - static constexpr uint16_t kMinTracksForCandidate = 1; // Minimum contributors required to save a candidate + static constexpr double kBcTimeRoundingOffset = 1.; // Offset used when rounding trackTime to BC units + static constexpr uint16_t kMinTracksForPair = 2; // Minimum tracks required to compute a pair invariant mass + static constexpr uint16_t kMinTracksForCandidate = 1; // Minimum contributors required to save a candidate + static constexpr int kUpperBoundaryToTrackTypeEnum = 2; // Make sure you use MFT tracks void init(InitContext&) { @@ -514,7 +516,7 @@ struct UpcCandProducerGlobalMuon { fBestMuonMatch.clear(); std::unordered_map> mCandidates; for (const auto& muon : fwdTracks) { - if (static_cast(muon.trackType()) < 2) { + if (static_cast(muon.trackType()) < kUpperBoundaryToTrackTypeEnum) { auto muonID = muon.matchMCHTrackId(); auto chi2 = muon.chi2MatchMCHMFT(); if (mCandidates.find(muonID) == mCandidates.end()) { @@ -526,7 +528,7 @@ struct UpcCandProducerGlobalMuon { } } } - for (auto& pairCand : mCandidates) { + for (const auto& pairCand : mCandidates) { fBestMuonMatch[pairCand.second.second] = true; } } @@ -638,7 +640,7 @@ struct UpcCandProducerGlobalMuon { continue; // For global tracks, skip if not the best match for this MCH track - if (fKeepBestMuonMatch && static_cast(trackType) < 2) { + if (fKeepBestMuonMatch && static_cast(trackType) < kUpperBoundaryToTrackTypeEnum) { if (fBestMuonMatch.find(fwdTrack.globalIndex()) == fBestMuonMatch.end()) { continue; } From 28047cf6a2f49b7b928b663e1ee61cb1e8bd3e47 Mon Sep 17 00:00:00 2001 From: rolavick Date: Thu, 23 Apr 2026 20:38:21 +0200 Subject: [PATCH 4/4] fix unused variable --- PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx b/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx index 4b1b098ead5..76d6412249b 100644 --- a/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx +++ b/PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx @@ -392,7 +392,7 @@ struct UpcCandProducerGlobalMuon { } bool addToFwdTable(int64_t candId, int64_t trackId, uint64_t gbc, float trackTime, - ForwardTracks const& fwdTracks, o2::aod::MFTTracks const& mftTracks, + ForwardTracks const& fwdTracks, o2::aod::MFTTracks const& /*mftTracks*/, const o2::aod::McFwdTrackLabels* mcFwdTrackLabels) { const auto& track = fwdTracks.iteratorAt(trackId);