From 674f9e7b825d650052b0ce3169a9c4e32c45e446 Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Tue, 7 Jul 2026 11:51:43 +0200 Subject: [PATCH 1/6] Store leg kinematics in photon mixing pool. Replace EMPair with a local PhotonWithLegs struct in the EventMixingHandler pool --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 71 ++++++++++++++++++++------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index d8f2c5c7da8..81d002b4f36 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -302,6 +302,8 @@ struct Photonhbt { Configurable cfgMaxAsymmetry{"cfgMaxAsymmetry", -1.f, "max |p_{T, 1} - p_{T, 2}|/(p_{T, 1} + p_{T, 2}) asymmetry cut"}; } ggpaircuts; + + EMPhotonEventCut fEMEventCut; struct : ConfigurableGroup { std::string prefix = "eventcut_group"; @@ -598,6 +600,21 @@ struct Photonhbt { float alphaTrue = 0.f; }; + struct PhotonWithLegs { + float fPt{0}, fEta{0}, fPhi{0}; + float fVx{0}, fVy{0}, fVz{0}; + std::array fLegPt{}, fLegEta{}, fLegPhi{}; // [0] = e+, [1] = e- + float pt() const { return fPt; } + float eta() const { return fEta; } + float phi() const { return fPhi; } + float vx() const { return fVx; } + float vy() const { return fVy; } + float vz() const { return fVz; } + float legEta(int i) const { return fLegEta[i]; } + float legPhi(int i) const { return fLegPhi[i]; } + float legPt(int i) const { return fLegPt[i]; } +}; + std::map, std::deque>> truthGammaPool; void addSinglePhotonQAHistogramsForStep(const std::string& path) @@ -1833,15 +1850,23 @@ struct Photonhbt { pos1.eta(), ele1.eta(), pos2.eta(), ele2.eta()), obs.deta, obs.dphi, obs.kt); - auto addToPool = [&](auto const& g) { - if (usedPhotonIdsPerCol.insert(g.globalIndex()).second) { - EMPair gtmp(g.pt(), g.eta(), g.phi(), 0.f); - gtmp.setConversionPointXYZ(g.vx(), g.vy(), g.vz()); - emh1->AddTrackToEventPool(keyDFCollision, gtmp); - } - }; - addToPool(g1); - addToPool(g2); + auto addToPool = [&](auto const& g, auto const& pos, auto const& ele) { + if (usedPhotonIdsPerCol.insert(g.globalIndex()).second) { + PhotonWithLegs p; + p.fPt = g.pt(); + p.fEta = g.eta(); + p.fPhi = g.phi(); + p.fVx = g.vx(); + p.fVy = g.vy(); + p.fVz = g.vz(); + p.fLegPt = {static_cast(pos.pt()), static_cast(ele.pt())}; + p.fLegEta = {static_cast(pos.eta()), static_cast(ele.eta())}; + p.fLegPhi = {static_cast(pos.phi()), static_cast(ele.phi())}; + emh1->AddTrackToEventPool(keyDFCollision, p); + } +}; +addToPool(g1, pos1, ele1); +addToPool(g2, pos2, ele2); } if (qaflags.doSinglePhotonQa) { for (const auto& g : photons1Coll) { @@ -2060,15 +2085,23 @@ struct Photonhbt { } } - auto addToPool = [&](auto const& g) { - if (usedPhotonIdsPerCol.insert(g.globalIndex()).second) { - EMPair gtmp(g.pt(), g.eta(), g.phi(), 0.f); - gtmp.setConversionPointXYZ(g.vx(), g.vy(), g.vz()); - emh1->AddTrackToEventPool(keyDFCollision, gtmp); - } - }; - addToPool(g1); - addToPool(g2); + auto addToPool = [&](auto const& g, auto const& pos, auto const& ele) { + if (usedPhotonIdsPerCol.insert(g.globalIndex()).second) { + PhotonWithLegs p; + p.fPt = g.pt(); + p.fEta = g.eta(); + p.fPhi = g.phi(); + p.fVx = g.vx(); + p.fVy = g.vy(); + p.fVz = g.vz(); + p.fLegPt = {static_cast(pos.pt()), static_cast(ele.pt())}; + p.fLegEta = {static_cast(pos.eta()), static_cast(ele.eta())}; + p.fLegPhi = {static_cast(pos.phi()), static_cast(ele.phi())}; + emh1->AddTrackToEventPool(keyDFCollision, p); + } +}; +addToPool(g1, pos1, ele1); +addToPool(g2, pos2, ele2); } if (qaflags.doSinglePhotonQa) { for (const auto& g : photonsColl) { @@ -2516,7 +2549,7 @@ struct Photonhbt { } // end runTruthEfficiency using MyEMH = o2::aod::pwgem::dilepton::utils::EventMixingHandler< - std::tuple, std::pair, EMPair>; + std::tuple, std::pair, PhotonWithLegs>; MyEMH* emh1 = nullptr; MyEMH* emh2 = nullptr; From 3373a98cf3c41d515a884c16a31719814f5f7d20 Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Tue, 7 Jul 2026 12:00:27 +0200 Subject: [PATCH 2/6] Bug fix: fix double filling of hSparse_DEtaDPhi_qinv_kT in MC pairing --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index 81d002b4f36..a901958382c 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -302,7 +302,7 @@ struct Photonhbt { Configurable cfgMaxAsymmetry{"cfgMaxAsymmetry", -1.f, "max |p_{T, 1} - p_{T, 2}|/(p_{T, 1} + p_{T, 2}) asymmetry cut"}; } ggpaircuts; - + EMPhotonEventCut fEMEventCut; struct : ConfigurableGroup { @@ -984,14 +984,6 @@ struct Photonhbt { } else { fRegistryPairMC.fill(HIST(mcDir) + HIST("CF_1D"), hbtanalysis.cfgUseLCMS ? qabs_lcms : qinv, kt, weight); } - float deta_pair = v1.Eta() - v2.Eta(); - float dphi_pair = v1.Phi() - v2.Phi(); - dphi_pair = RecoDecay::constrainAngle(dphi_pair, -o2::constants::math::PI); - if constexpr (ev_id == 0) { - fRegistry.fill(HIST("Pair/same/hSparse_DEtaDPhi_qinv_kT"), deta_pair, dphi_pair, qinv, kt, weight); - } else { - fRegistry.fill(HIST("Pair/mix/hSparse_DEtaDPhi_qinv_kT"), deta_pair, dphi_pair, qinv, kt, weight); - } } template From 3e65e30fc869e26c7def0d476758490eb969be29 Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Tue, 7 Jul 2026 12:06:03 +0200 Subject: [PATCH 3/6] Bug fix: e+/e- assignement in efficiency correctly implemented --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 104 +++++++++++++------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index a901958382c..7cfe5c09baa 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -302,8 +302,6 @@ struct Photonhbt { Configurable cfgMaxAsymmetry{"cfgMaxAsymmetry", -1.f, "max |p_{T, 1} - p_{T, 2}|/(p_{T, 1} + p_{T, 2}) asymmetry cut"}; } ggpaircuts; - - EMPhotonEventCut fEMEventCut; struct : ConfigurableGroup { std::string prefix = "eventcut_group"; @@ -601,19 +599,19 @@ struct Photonhbt { }; struct PhotonWithLegs { - float fPt{0}, fEta{0}, fPhi{0}; - float fVx{0}, fVy{0}, fVz{0}; - std::array fLegPt{}, fLegEta{}, fLegPhi{}; // [0] = e+, [1] = e- - float pt() const { return fPt; } - float eta() const { return fEta; } - float phi() const { return fPhi; } - float vx() const { return fVx; } - float vy() const { return fVy; } - float vz() const { return fVz; } - float legEta(int i) const { return fLegEta[i]; } - float legPhi(int i) const { return fLegPhi[i]; } - float legPt(int i) const { return fLegPt[i]; } -}; + float fPt{0}, fEta{0}, fPhi{0}; + float fVx{0}, fVy{0}, fVz{0}; + std::array fLegPt{}, fLegEta{}, fLegPhi{}; // [0] = e+, [1] = e- + float pt() const { return fPt; } + float eta() const { return fEta; } + float phi() const { return fPhi; } + float vx() const { return fVx; } + float vy() const { return fVy; } + float vz() const { return fVz; } + float legEta(int i) const { return fLegEta[i]; } + float legPhi(int i) const { return fLegPhi[i]; } + float legPt(int i) const { return fLegPt[i]; } + }; std::map, std::deque>> truthGammaPool; @@ -1842,23 +1840,23 @@ struct Photonhbt { pos1.eta(), ele1.eta(), pos2.eta(), ele2.eta()), obs.deta, obs.dphi, obs.kt); - auto addToPool = [&](auto const& g, auto const& pos, auto const& ele) { - if (usedPhotonIdsPerCol.insert(g.globalIndex()).second) { - PhotonWithLegs p; - p.fPt = g.pt(); - p.fEta = g.eta(); - p.fPhi = g.phi(); - p.fVx = g.vx(); - p.fVy = g.vy(); - p.fVz = g.vz(); - p.fLegPt = {static_cast(pos.pt()), static_cast(ele.pt())}; - p.fLegEta = {static_cast(pos.eta()), static_cast(ele.eta())}; - p.fLegPhi = {static_cast(pos.phi()), static_cast(ele.phi())}; - emh1->AddTrackToEventPool(keyDFCollision, p); - } -}; -addToPool(g1, pos1, ele1); -addToPool(g2, pos2, ele2); + auto addToPool = [&](auto const& g, auto const& pos, auto const& ele) { + if (usedPhotonIdsPerCol.insert(g.globalIndex()).second) { + PhotonWithLegs p; + p.fPt = g.pt(); + p.fEta = g.eta(); + p.fPhi = g.phi(); + p.fVx = g.vx(); + p.fVy = g.vy(); + p.fVz = g.vz(); + p.fLegPt = {static_cast(pos.pt()), static_cast(ele.pt())}; + p.fLegEta = {static_cast(pos.eta()), static_cast(ele.eta())}; + p.fLegPhi = {static_cast(pos.phi()), static_cast(ele.phi())}; + emh1->AddTrackToEventPool(keyDFCollision, p); + } + }; + addToPool(g1, pos1, ele1); + addToPool(g2, pos2, ele2); } if (qaflags.doSinglePhotonQa) { for (const auto& g : photons1Coll) { @@ -2077,23 +2075,23 @@ addToPool(g2, pos2, ele2); } } - auto addToPool = [&](auto const& g, auto const& pos, auto const& ele) { - if (usedPhotonIdsPerCol.insert(g.globalIndex()).second) { - PhotonWithLegs p; - p.fPt = g.pt(); - p.fEta = g.eta(); - p.fPhi = g.phi(); - p.fVx = g.vx(); - p.fVy = g.vy(); - p.fVz = g.vz(); - p.fLegPt = {static_cast(pos.pt()), static_cast(ele.pt())}; - p.fLegEta = {static_cast(pos.eta()), static_cast(ele.eta())}; - p.fLegPhi = {static_cast(pos.phi()), static_cast(ele.phi())}; - emh1->AddTrackToEventPool(keyDFCollision, p); - } -}; -addToPool(g1, pos1, ele1); -addToPool(g2, pos2, ele2); + auto addToPool = [&](auto const& g, auto const& pos, auto const& ele) { + if (usedPhotonIdsPerCol.insert(g.globalIndex()).second) { + PhotonWithLegs p; + p.fPt = g.pt(); + p.fEta = g.eta(); + p.fPhi = g.phi(); + p.fVx = g.vx(); + p.fVy = g.vy(); + p.fVz = g.vz(); + p.fLegPt = {static_cast(pos.pt()), static_cast(ele.pt())}; + p.fLegEta = {static_cast(pos.eta()), static_cast(ele.eta())}; + p.fLegPhi = {static_cast(pos.phi()), static_cast(ele.phi())}; + emh1->AddTrackToEventPool(keyDFCollision, p); + } + }; + addToPool(g1, pos1, ele1); + addToPool(g2, pos2, ele2); } if (qaflags.doSinglePhotonQa) { for (const auto& g : photonsColl) { @@ -2276,10 +2274,10 @@ addToPool(g2, pos2, ele2); continue; } const auto d = emmcParticles.iteratorAt(dId); - if (d.pdgCode() == kElectron) { + if (d.pdgCode() == kPositron) { posId = dId; rTrue = std::sqrt(d.vx() * d.vx() + d.vy() * d.vy()); - } else if (d.pdgCode() == kPositron) { + } else if (d.pdgCode() == kElectron) { negId = dId; } } @@ -2537,8 +2535,8 @@ addToPool(g2, pos2, ele2); poolBin.pop_front(); } } // end cfgDoTruthMix - } // end collision loop - } // end runTruthEfficiency + } // end collision loop + } // end runTruthEfficiency using MyEMH = o2::aod::pwgem::dilepton::utils::EventMixingHandler< std::tuple, std::pair, PhotonWithLegs>; From b6a67e0ac1c1440ee8bc4f0bd86ecb7bf9d43431 Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Tue, 7 Jul 2026 12:18:38 +0200 Subject: [PATCH 4/6] Add SE/ME same-sign leg separation QA for pair efficiency extraction --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 55 +++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index 7cfe5c09baa..2110da6447e 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -302,6 +302,13 @@ struct Photonhbt { Configurable cfgMaxAsymmetry{"cfgMaxAsymmetry", -1.f, "max |p_{T, 1} - p_{T, 2}|/(p_{T, 1} + p_{T, 2}) asymmetry cut"}; } ggpaircuts; + struct : ConfigurableGroup { + std::string prefix = "pairsep_group"; + Configurable cfgDoPairSepQA{"cfgDoPairSepQA", true, "fill SE/ME same-sign leg separation histograms"}; + Configurable cfgUSigEta{"cfgUSigEta", 0.02f, "eta scale defining u (axis units only, NOT a cut)"}; + Configurable cfgUSigPhi{"cfgUSigPhi", 0.05f, "phi scale defining u (axis units only, NOT a cut)"}; + } pairsep; + EMPhotonEventCut fEMEventCut; struct : ConfigurableGroup { std::string prefix = "eventcut_group"; @@ -588,6 +595,24 @@ struct Photonhbt { return o; } + struct PairSep { + float dEtaPP{0}, dPhiPP{0}, dEtaNN{0}, dPhiNN{0}; + float u{999.f}; + }; + PairSep computePairSep(float etaP1, float phiP1, float etaN1, float phiN1, + float etaP2, float phiP2, float etaN2, float phiN2) const + { + PairSep s; + s.dEtaPP = etaP1 - etaP2; + s.dPhiPP = RecoDecay::constrainAngle(phiP1 - phiP2, -o2::constants::math::PI); + s.dEtaNN = etaN1 - etaN2; + s.dPhiNN = RecoDecay::constrainAngle(phiN1 - phiN2, -o2::constants::math::PI); + const float sE = pairsep.cfgUSigEta.value, sP = pairsep.cfgUSigPhi.value; + s.u = std::min(std::hypot(s.dEtaPP / sE, s.dPhiPP / sP), + std::hypot(s.dEtaNN / sE, s.dPhiNN / sP)); + return s; + } + struct TruthGamma { int id = -1, posId = -1, negId = -1; float eta = 0.f, phi = 0.f, pt = 0.f; @@ -644,6 +669,19 @@ struct Photonhbt { fRegistry.fill(HIST(base) + HIST("hSparseDeltaRDeltaZQinv"), obs.deltaR, obs.deltaZ, obs.qinv); } + static constexpr std::string_view pairsep_types[2] = {"PairSep/same/", "PairSep/mix/"}; + template + inline void fillPairSep(PairSep const& s, PairQAObservables const& obs) + { + if (!pairsep.cfgDoPairSepQA.value || obs.qinv >= 0.5f) + return; + fRegistry.fill(HIST(pairsep_types[ev_id]) + HIST("hDEtaDPhiKt_PP"), s.dEtaPP, s.dPhiPP, obs.kt); + fRegistry.fill(HIST(pairsep_types[ev_id]) + HIST("hDEtaDPhiKt_NN"), s.dEtaNN, s.dPhiNN, obs.kt); + fRegistry.fill(HIST(pairsep_types[ev_id]) + HIST("hU_Qinv_Kt"), s.u, obs.qinv, obs.kt); + fRegistry.fill(HIST(pairsep_types[ev_id]) + HIST("hU_Qinv_dE"), s.u, obs.qinv, + std::fabs(obs.v1.E() - obs.v2.E())); + } + template inline void fillFullRangeDeltaRCosOA(float qinv, float drOverCosOA) { @@ -794,6 +832,17 @@ struct Photonhbt { fRegistry.addClone("Pair/same/", "Pair/mix/"); + for (const std::string sm : {"PairSep/same/", "PairSep/mix/"}) { + fRegistry.add((sm + "hDEtaDPhiKt_PP").c_str(), "e^{+}e^{+};#Delta#eta;#Delta#phi (rad);k_{T} (GeV/c)", + kTHnSparseF, {{160, -0.4f, 0.4f}, {160, -0.4f, 0.4f}, axisKt}, true); + fRegistry.add((sm + "hDEtaDPhiKt_NN").c_str(), "e^{-}e^{-};#Delta#eta;#Delta#phi (rad);k_{T} (GeV/c)", + kTHnSparseF, {{160, -0.4f, 0.4f}, {160, -0.4f, 0.4f}, axisKt}, true); + fRegistry.add((sm + "hU_Qinv_Kt").c_str(), ";u;q_{inv} (GeV/c);k_{T} (GeV/c)", + kTHnSparseF, {{100, 0.f, 10.f}, {50, 0.f, 0.5f}, axisKt}, true); + fRegistry.add((sm + "hU_Qinv_dE").c_str(), ";u;q_{inv} (GeV/c);#DeltaE (GeV)", + kTHnSparseF, {{50, 0.f, 10.f}, {50, 0.f, 0.5f}, {40, 0.f, 2.f}}, true); + } + fRegistry.add("Pair/same/EtaTopology/hSparse_DEtaDPhi_kT_sameSideV0_sameSideLegs", "both V0 same #eta-side, all legs same side;" "#Delta#eta;#Delta#phi (rad);k_{T} (GeV/c)", @@ -1812,6 +1861,9 @@ struct Photonhbt { continue; idsAfterDR.insert(g1.globalIndex()); idsAfterDR.insert(g2.globalIndex()); + fillPairSep<0>(computePairSep(pos1.eta(), pos1.phi(), ele1.eta(), ele1.phi(), + pos2.eta(), pos2.phi(), ele2.eta(), ele2.phi()), + obs); if (doQA) fillPairQAStep<0, 1>(obs, centForQA, occupancy); if (!passRZCut(obs.deltaR, obs.deltaZ)) @@ -1903,6 +1955,9 @@ struct Photonhbt { fRegistry.fill(HIST("Pair/mix/hDeltaRCosOA"), obs.drOverCosOA); if (obs.drOverCosOA < ggpaircuts.cfgMinDRCosOA) continue; + fillPairSep<1>(computePairSep(g1.legEta(0), g1.legPhi(0), g1.legEta(1), g1.legPhi(1), + g2.legEta(0), g2.legPhi(0), g2.legEta(1), g2.legPhi(1)), + obs); if (doQA) fillPairQAStep<1, 1>(obs, centForQA, occupancy); if (!passRZCut(obs.deltaR, obs.deltaZ)) From e3476dc1ce2bffd815bafe4de51a7e8449889c76 Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Tue, 7 Jul 2026 13:24:20 +0200 Subject: [PATCH 5/6] Fix o2-linter findings --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index 2110da6447e..af86d95cccb 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -673,7 +673,7 @@ struct Photonhbt { template inline void fillPairSep(PairSep const& s, PairQAObservables const& obs) { - if (!pairsep.cfgDoPairSepQA.value || obs.qinv >= 0.5f) + if (!pairsep.cfgDoPairSepQA.value || obs.qinv >= cfgMaxQinvForMCQA.value) return; fRegistry.fill(HIST(pairsep_types[ev_id]) + HIST("hDEtaDPhiKt_PP"), s.dEtaPP, s.dPhiPP, obs.kt); fRegistry.fill(HIST(pairsep_types[ev_id]) + HIST("hDEtaDPhiKt_NN"), s.dEtaNN, s.dPhiNN, obs.kt); @@ -832,7 +832,8 @@ struct Photonhbt { fRegistry.addClone("Pair/same/", "Pair/mix/"); - for (const std::string sm : {"PairSep/same/", "PairSep/mix/"}) { + const std::array pairSepDirs = {"PairSep/same/", "PairSep/mix/"}; + for (const auto& sm : pairSepDirs) { fRegistry.add((sm + "hDEtaDPhiKt_PP").c_str(), "e^{+}e^{+};#Delta#eta;#Delta#phi (rad);k_{T} (GeV/c)", kTHnSparseF, {{160, -0.4f, 0.4f}, {160, -0.4f, 0.4f}, axisKt}, true); fRegistry.add((sm + "hDEtaDPhiKt_NN").c_str(), "e^{-}e^{-};#Delta#eta;#Delta#phi (rad);k_{T} (GeV/c)", From 588208fa78466630e867eceeba57965ce624dc5f Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Tue, 7 Jul 2026 19:03:26 +0000 Subject: [PATCH 6/6] Please consider the following formatting changes --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index af86d95cccb..e7192e8832e 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -2591,8 +2591,8 @@ struct Photonhbt { poolBin.pop_front(); } } // end cfgDoTruthMix - } // end collision loop - } // end runTruthEfficiency + } // end collision loop + } // end runTruthEfficiency using MyEMH = o2::aod::pwgem::dilepton::utils::EventMixingHandler< std::tuple, std::pair, PhotonWithLegs>;