Skip to content

Commit 5023837

Browse files
committed
GlobalMuon matching based on chi2matching
1 parent 6f50b7b commit 5023837

1 file changed

Lines changed: 64 additions & 14 deletions

File tree

PWGUD/TableProducer/upcCandProducerGlobalMuon.cxx

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
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"
@@ -55,6 +56,7 @@
5556
#include <map>
5657
#include <numeric>
5758
#include <string>
59+
#include <unordered_map>
5860
#include <vector>
5961

6062
using 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

Comments
 (0)