From 48d5d76acd5a9273eb2cbb70a29b112c0c7c130f Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Mon, 27 Jul 2026 12:08:49 +0200 Subject: [PATCH 1/4] add switchable crossed-pair (leg-swap) veto with QA to avoid signal from fake photons in CF --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 130 +++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index b621516b429..80222ec76b1 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -149,6 +149,11 @@ struct Photonhbt { float u{999.f}; }; + struct CrossObs { + std::array mee{999.f, 999.f}; // crossed m(e+e-) per combo + std::array dist{999.f, 999.f}; // crossed line-line distance (cm) + }; + struct TruthGamma { int id = -1, posId = -1, negId = -1; float eta = 0.f, phi = 0.f, pt = 0.f; @@ -336,6 +341,15 @@ struct Photonhbt { Configurable cfgUCut{"cfgUCut", 0.f, "min u (sigma units); pairs with u < cut rejected in SE AND ME"}; } pairsep; + struct : ConfigurableGroup { + std::string prefix = "crosspair_group"; + Configurable cfgDoCrossPairQA{"cfgDoCrossPairQA", false, "fill crossed-hypothesis QA (mee_cross, dist_cross vs qinv) for pairs passing all other pair cuts; SE and ME"}; + Configurable cfgDoCrossPairCut{"cfgDoCrossPairCut", false, "reject pairs with a photon-like crossed combination; applied to SE AND ME"}; + Configurable cfgCrossMaxMee{"cfgCrossMaxMee", 0.04f, "veto: crossed m_ee below this (GeV/c^2)"}; + Configurable cfgCrossMaxDist{"cfgCrossMaxDist", 1.0f, "veto: crossed line-line distance below this (cm)"}; + Configurable cfgCrossMaxQinvQA{"cfgCrossMaxQinvQA", 0.3f, "fill crossed QA sparses only below this qinv"}; + } crosspair; + struct : ConfigurableGroup { std::string prefix = "eventcut_group"; Configurable cfgZvtxMin{"cfgZvtxMin", -10.f, "min. Zvtx"}; @@ -656,6 +670,7 @@ struct Photonhbt { addEventHistograms(); addPairCFHistograms(); addPairSepHistograms(); + addCrossPairHistograms(); addSinglePhotonQAHistograms(); addPairQAHistograms(); @@ -704,8 +719,6 @@ struct Photonhbt { fRegistryCF.add("Pair/mix/hDiffBC", "diff. global BC in mixed event;|BC_{current}-BC_{mixed}|", kTH1D, {{10001, -0.5, 10000.5}}, true); } - - // ─── CF: PairSep ──────────────────────── // ─── CF: PairSep ──────────────────────── void addPairSepHistograms() { @@ -717,6 +730,21 @@ struct Photonhbt { } } + void addCrossPairHistograms() + { + if (!(crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value)) { + return; + } + for (const auto& sm : {std::string("Pair/same/CrossPair/"), std::string("Pair/mix/CrossPair/")}) { + fRegistryCF.add((sm + "hSparse_Mee_Dist_Qinv").c_str(), + "crossed-hypothesis QA;m_{ee}^{cross} (GeV/c^{2});crossed line distance (cm);q_{inv} (GeV/c)", + kTHnSparseF, {{100, 0.f, 0.1f}, {100, 0.f, 10.f}, {60, 0.f, 0.3f}}, true); + auto h = fRegistryCF.add((sm + "hVetoCounter").c_str(), "crossed-pair veto;;pairs", kTH1D, {{2, -0.5f, 1.5f}}, true); + h->GetXaxis()->SetBinLabel(1, "evaluated"); + h->GetXaxis()->SetBinLabel(2, "vetoed"); + } + } + // ─── pairQA: single-photon QA per cut step ───────────────────────────────── void addSinglePhotonQAHistograms() { @@ -1246,6 +1274,72 @@ struct Photonhbt { return s.u > pairsep.cfgUCut.value; } + [[nodiscard]] static float lineLineDistance(std::array const& o1, std::array const& d1, + std::array const& o2, std::array const& d2) + { + const std::array w = {o2[0] - o1[0], o2[1] - o1[1], o2[2] - o1[2]}; + const std::array n = {d1[1] * d2[2] - d1[2] * d2[1], + d1[2] * d2[0] - d1[0] * d2[2], + d1[0] * d2[1] - d1[1] * d2[0]}; + const float nMag = std::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); + const float d1Mag2 = d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]; + if (nMag < 1e-6f * d1Mag2) { // near-parallel: point-to-line distance + const float wDotD = (w[0] * d1[0] + w[1] * d1[1] + w[2] * d1[2]) / d1Mag2; + const std::array perp = {w[0] - wDotD * d1[0], w[1] - wDotD * d1[1], w[2] - wDotD * d1[2]}; + return std::sqrt(perp[0] * perp[0] + perp[1] * perp[1] + perp[2] * perp[2]); + } + return std::fabs(w[0] * n[0] + w[1] * n[1] + w[2] * n[2]) / nMag; + } + + [[nodiscard]] CrossObs computeCrossObs(PhotonWithLegs const& a, PhotonWithLegs const& b) const + { + constexpr float kMe = 0.000510999f; // electron mass, GeV/c^2 + CrossObs c; + auto legVec = [](PhotonWithLegs const& p, int i) { + return ROOT::Math::PtEtaPhiMVector(p.fLegPt[i], p.fLegEta[i], p.fLegPhi[i], kMe); + }; + auto legDir = [](PhotonWithLegs const& p, int i) -> std::array { + return {p.fLegPt[i] * std::cos(p.fLegPhi[i]), p.fLegPt[i] * std::sin(p.fLegPhi[i]), + p.fLegPt[i] * std::sinh(p.fLegEta[i])}; + }; + // Leg index 0 = e+, 1 = e- (makePhotonWithLegs filling order). + // Combo 0: a's e+ with b's e-. Combo 1: b's e+ with a's e-. + for (int combo = 0; combo < 2; ++combo) { + PhotonWithLegs const& pPos = (combo == 0) ? a : b; + PhotonWithLegs const& pEle = (combo == 0) ? b : a; + c.mee[combo] = static_cast((legVec(pPos, 0) + legVec(pEle, 1)).M()); + c.dist[combo] = lineLineDistance({pPos.fVx, pPos.fVy, pPos.fVz}, legDir(pPos, 0), + {pEle.fVx, pEle.fVy, pEle.fVz}, legDir(pEle, 1)); + } + return c; + } + + [[nodiscard]] inline bool passCrossPairVeto(CrossObs const& c) const + { + if (!crosspair.cfgDoCrossPairCut.value) { + return true; + } + for (int i = 0; i < 2; ++i) { + if (c.mee[i] < crosspair.cfgCrossMaxMee.value && c.dist[i] < crosspair.cfgCrossMaxDist.value) { + return false; + } + } + return true; + } + + template + inline void fillCrossPair(CrossObs const& c, float qinv, bool vetoed) + { + constexpr auto dir = (ev_id == 0) ? "Pair/same/CrossPair/" : "Pair/mix/CrossPair/"; + fRegistryCF.fill(HIST(dir) + HIST("hVetoCounter"), vetoed ? 1.0 : 0.0); + if (qinv > crosspair.cfgCrossMaxQinvQA.value) { + return; + } + for (int i = 0; i < 2; ++i) { + fRegistryCF.fill(HIST(dir) + HIST("hSparse_Mee_Dist_Qinv"), c.mee[i], c.dist[i], qinv); + } + } + [[nodiscard]] float uTrue(TruthGamma const& g1, TruthGamma const& g2) const { const float sE = pairsep.cfgUSigEta.value, sP = pairsep.cfgUSigPhi.value; @@ -1913,6 +2007,14 @@ struct Photonhbt { if (isInsideEllipse(obs.deta, obs.dphi)) { continue; } + if (crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value) { + const auto cross = computeCrossObs(pwl1, pwl2); + const bool vetoed = !passCrossPairVeto(cross); + fillCrossPair<0>(cross, obs.qinv, vetoed); + if (vetoed) { + continue; + } + } // ───after pair cuts ────────────────────────────────────── fillPairSep<0, true>(sep, obs); @@ -2000,6 +2102,14 @@ struct Photonhbt { if (isInsideEllipse(obs.deta, obs.dphi)) { continue; } + if (crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value) { + const auto cross = computeCrossObs(g1, g2); + const bool vetoed = !passCrossPairVeto(cross); + fillCrossPair<1>(cross, obs.qinv, vetoed); + if (vetoed) { + continue; + } + } fillPairSep<1, true>(sep, obs); if (doQA) { @@ -2122,6 +2232,14 @@ struct Photonhbt { if (isInsideEllipse(obs.deta, obs.dphi)) { continue; } + if (crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value) { + const auto cross = computeCrossObs(pwl1, pwl2); + const bool vetoed = !passCrossPairVeto(cross); + fillCrossPair<0>(cross, obs.qinv, vetoed); + if (vetoed) { + continue; + } + } // ─── after pair cuts ────────────────────────────────────── fillPairSep<0, true>(sep, obs); @@ -2260,6 +2378,14 @@ struct Photonhbt { if (isInsideEllipse(obs.deta, obs.dphi)) { continue; } + if (crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value) { + const auto cross = computeCrossObs(g1, g2); + const bool vetoed = !passCrossPairVeto(cross); + fillCrossPair<1>(cross, obs.qinv, vetoed); + if (vetoed) { + continue; + } + } // ─after pair cuts ────────────────────────────────── fillPairSep<1, true>(sep, obs); From 7a9a4c7b148415c3064ea3a4b509484ba9b33e71 Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Mon, 27 Jul 2026 12:18:46 +0200 Subject: [PATCH 2/4] fix o2-linter errors --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index 80222ec76b1..eb64731c42a 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -1283,7 +1283,7 @@ struct Photonhbt { d1[0] * d2[1] - d1[1] * d2[0]}; const float nMag = std::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); const float d1Mag2 = d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]; - if (nMag < 1e-6f * d1Mag2) { // near-parallel: point-to-line distance + if (nMag < 1e-6f * d1Mag2) { // o2-linter: disable=magic-number near-parallel: point-to-line distance const float wDotD = (w[0] * d1[0] + w[1] * d1[1] + w[2] * d1[2]) / d1Mag2; const std::array perp = {w[0] - wDotD * d1[0], w[1] - wDotD * d1[1], w[2] - wDotD * d1[2]}; return std::sqrt(perp[0] * perp[0] + perp[1] * perp[1] + perp[2] * perp[2]); @@ -1304,7 +1304,7 @@ struct Photonhbt { }; // Leg index 0 = e+, 1 = e- (makePhotonWithLegs filling order). // Combo 0: a's e+ with b's e-. Combo 1: b's e+ with a's e-. - for (int combo = 0; combo < 2; ++combo) { + for (int combo = 0; combo < 2; ++combo) { // o2-linter: disable=magic-number combinations of fake photons PhotonWithLegs const& pPos = (combo == 0) ? a : b; PhotonWithLegs const& pEle = (combo == 0) ? b : a; c.mee[combo] = static_cast((legVec(pPos, 0) + legVec(pEle, 1)).M()); @@ -1319,7 +1319,7 @@ struct Photonhbt { if (!crosspair.cfgDoCrossPairCut.value) { return true; } - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < 2; ++i) { // o2-linter: disable=magic-number combinations of fake photons if (c.mee[i] < crosspair.cfgCrossMaxMee.value && c.dist[i] < crosspair.cfgCrossMaxDist.value) { return false; } @@ -1335,7 +1335,7 @@ struct Photonhbt { if (qinv > crosspair.cfgCrossMaxQinvQA.value) { return; } - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < 2; ++i) { // o2-linter : disable = magic - number combinations of fake photons fRegistryCF.fill(HIST(dir) + HIST("hSparse_Mee_Dist_Qinv"), c.mee[i], c.dist[i], qinv); } } From 21239491b59c52ee5362111c59ea1691529ec59d Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Mon, 27 Jul 2026 14:03:34 +0200 Subject: [PATCH 3/4] fix bracket notation for o2-linter --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index eb64731c42a..0dbea8d9ad8 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -1283,7 +1283,7 @@ struct Photonhbt { d1[0] * d2[1] - d1[1] * d2[0]}; const float nMag = std::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); const float d1Mag2 = d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]; - if (nMag < 1e-6f * d1Mag2) { // o2-linter: disable=magic-number near-parallel: point-to-line distance + if (nMag < 1e-6f * d1Mag2) { // o2-linter: disable=magic-number (near-parallel: point-to-line distance) const float wDotD = (w[0] * d1[0] + w[1] * d1[1] + w[2] * d1[2]) / d1Mag2; const std::array perp = {w[0] - wDotD * d1[0], w[1] - wDotD * d1[1], w[2] - wDotD * d1[2]}; return std::sqrt(perp[0] * perp[0] + perp[1] * perp[1] + perp[2] * perp[2]); @@ -1304,7 +1304,7 @@ struct Photonhbt { }; // Leg index 0 = e+, 1 = e- (makePhotonWithLegs filling order). // Combo 0: a's e+ with b's e-. Combo 1: b's e+ with a's e-. - for (int combo = 0; combo < 2; ++combo) { // o2-linter: disable=magic-number combinations of fake photons + for (int combo = 0; combo < 2; ++combo) { // o2-linter: disable=magic-number (combinations of fake photons) PhotonWithLegs const& pPos = (combo == 0) ? a : b; PhotonWithLegs const& pEle = (combo == 0) ? b : a; c.mee[combo] = static_cast((legVec(pPos, 0) + legVec(pEle, 1)).M()); @@ -1319,7 +1319,7 @@ struct Photonhbt { if (!crosspair.cfgDoCrossPairCut.value) { return true; } - for (int i = 0; i < 2; ++i) { // o2-linter: disable=magic-number combinations of fake photons + for (int i = 0; i < 2; ++i) { // o2-linter: disable=magic-number (combinations of fake photons) if (c.mee[i] < crosspair.cfgCrossMaxMee.value && c.dist[i] < crosspair.cfgCrossMaxDist.value) { return false; } @@ -1335,7 +1335,7 @@ struct Photonhbt { if (qinv > crosspair.cfgCrossMaxQinvQA.value) { return; } - for (int i = 0; i < 2; ++i) { // o2-linter : disable = magic - number combinations of fake photons + for (int i = 0; i < 2; ++i) { // o2-linter: disable = magic - number combinations of fake photons fRegistryCF.fill(HIST(dir) + HIST("hSparse_Mee_Dist_Qinv"), c.mee[i], c.dist[i], qinv); } } From af193f488bdfb92908558c62b2aadaa61a95d378 Mon Sep 17 00:00:00 2001 From: Stefanie Mrozinski Date: Mon, 27 Jul 2026 14:08:27 +0200 Subject: [PATCH 4/4] fix bracket notation --- PWGEM/PhotonMeson/Tasks/photonhbt.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx index 0dbea8d9ad8..f5c0536d734 100644 --- a/PWGEM/PhotonMeson/Tasks/photonhbt.cxx +++ b/PWGEM/PhotonMeson/Tasks/photonhbt.cxx @@ -1335,7 +1335,7 @@ struct Photonhbt { if (qinv > crosspair.cfgCrossMaxQinvQA.value) { return; } - for (int i = 0; i < 2; ++i) { // o2-linter: disable = magic - number combinations of fake photons + for (int i = 0; i < 2; ++i) { // o2-linter: disable=magic-number (combinations of fake photons) fRegistryCF.fill(HIST(dir) + HIST("hSparse_Mee_Dist_Qinv"), c.mee[i], c.dist[i], qinv); } }