Skip to content

Commit 85847df

Browse files
authored
[PWGEM] add switchable crossed-pair (leg-swap) veto with QA (#17215)
1 parent f09298b commit 85847df

1 file changed

Lines changed: 128 additions & 2 deletions

File tree

PWGEM/PhotonMeson/Tasks/photonhbt.cxx

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ struct Photonhbt {
149149
float u{999.f};
150150
};
151151

152+
struct CrossObs {
153+
std::array<float, 2> mee{999.f, 999.f}; // crossed m(e+e-) per combo
154+
std::array<float, 2> dist{999.f, 999.f}; // crossed line-line distance (cm)
155+
};
156+
152157
struct TruthGamma {
153158
int id = -1, posId = -1, negId = -1;
154159
float eta = 0.f, phi = 0.f, pt = 0.f;
@@ -336,6 +341,15 @@ struct Photonhbt {
336341
Configurable<float> cfgUCut{"cfgUCut", 0.f, "min u (sigma units); pairs with u < cut rejected in SE AND ME"};
337342
} pairsep;
338343

344+
struct : ConfigurableGroup {
345+
std::string prefix = "crosspair_group";
346+
Configurable<bool> cfgDoCrossPairQA{"cfgDoCrossPairQA", false, "fill crossed-hypothesis QA (mee_cross, dist_cross vs qinv) for pairs passing all other pair cuts; SE and ME"};
347+
Configurable<bool> cfgDoCrossPairCut{"cfgDoCrossPairCut", false, "reject pairs with a photon-like crossed combination; applied to SE AND ME"};
348+
Configurable<float> cfgCrossMaxMee{"cfgCrossMaxMee", 0.04f, "veto: crossed m_ee below this (GeV/c^2)"};
349+
Configurable<float> cfgCrossMaxDist{"cfgCrossMaxDist", 1.0f, "veto: crossed line-line distance below this (cm)"};
350+
Configurable<float> cfgCrossMaxQinvQA{"cfgCrossMaxQinvQA", 0.3f, "fill crossed QA sparses only below this qinv"};
351+
} crosspair;
352+
339353
struct : ConfigurableGroup {
340354
std::string prefix = "eventcut_group";
341355
Configurable<float> cfgZvtxMin{"cfgZvtxMin", -10.f, "min. Zvtx"};
@@ -656,6 +670,7 @@ struct Photonhbt {
656670
addEventHistograms();
657671
addPairCFHistograms();
658672
addPairSepHistograms();
673+
addCrossPairHistograms();
659674
addSinglePhotonQAHistograms();
660675
addPairQAHistograms();
661676

@@ -704,8 +719,6 @@ struct Photonhbt {
704719

705720
fRegistryCF.add("Pair/mix/hDiffBC", "diff. global BC in mixed event;|BC_{current}-BC_{mixed}|", kTH1D, {{10001, -0.5, 10000.5}}, true);
706721
}
707-
708-
// ─── CF: PairSep ────────────────────────
709722
// ─── CF: PairSep ────────────────────────
710723
void addPairSepHistograms()
711724
{
@@ -717,6 +730,21 @@ struct Photonhbt {
717730
}
718731
}
719732

733+
void addCrossPairHistograms()
734+
{
735+
if (!(crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value)) {
736+
return;
737+
}
738+
for (const auto& sm : {std::string("Pair/same/CrossPair/"), std::string("Pair/mix/CrossPair/")}) {
739+
fRegistryCF.add((sm + "hSparse_Mee_Dist_Qinv").c_str(),
740+
"crossed-hypothesis QA;m_{ee}^{cross} (GeV/c^{2});crossed line distance (cm);q_{inv} (GeV/c)",
741+
kTHnSparseF, {{100, 0.f, 0.1f}, {100, 0.f, 10.f}, {60, 0.f, 0.3f}}, true);
742+
auto h = fRegistryCF.add<TH1>((sm + "hVetoCounter").c_str(), "crossed-pair veto;;pairs", kTH1D, {{2, -0.5f, 1.5f}}, true);
743+
h->GetXaxis()->SetBinLabel(1, "evaluated");
744+
h->GetXaxis()->SetBinLabel(2, "vetoed");
745+
}
746+
}
747+
720748
// ─── pairQA: single-photon QA per cut step ─────────────────────────────────
721749
void addSinglePhotonQAHistograms()
722750
{
@@ -1246,6 +1274,72 @@ struct Photonhbt {
12461274
return s.u > pairsep.cfgUCut.value;
12471275
}
12481276

1277+
[[nodiscard]] static float lineLineDistance(std::array<float, 3> const& o1, std::array<float, 3> const& d1,
1278+
std::array<float, 3> const& o2, std::array<float, 3> const& d2)
1279+
{
1280+
const std::array<float, 3> w = {o2[0] - o1[0], o2[1] - o1[1], o2[2] - o1[2]};
1281+
const std::array<float, 3> n = {d1[1] * d2[2] - d1[2] * d2[1],
1282+
d1[2] * d2[0] - d1[0] * d2[2],
1283+
d1[0] * d2[1] - d1[1] * d2[0]};
1284+
const float nMag = std::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
1285+
const float d1Mag2 = d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2];
1286+
if (nMag < 1e-6f * d1Mag2) { // o2-linter: disable=magic-number (near-parallel: point-to-line distance)
1287+
const float wDotD = (w[0] * d1[0] + w[1] * d1[1] + w[2] * d1[2]) / d1Mag2;
1288+
const std::array<float, 3> perp = {w[0] - wDotD * d1[0], w[1] - wDotD * d1[1], w[2] - wDotD * d1[2]};
1289+
return std::sqrt(perp[0] * perp[0] + perp[1] * perp[1] + perp[2] * perp[2]);
1290+
}
1291+
return std::fabs(w[0] * n[0] + w[1] * n[1] + w[2] * n[2]) / nMag;
1292+
}
1293+
1294+
[[nodiscard]] CrossObs computeCrossObs(PhotonWithLegs const& a, PhotonWithLegs const& b) const
1295+
{
1296+
constexpr float kMe = 0.000510999f; // electron mass, GeV/c^2
1297+
CrossObs c;
1298+
auto legVec = [](PhotonWithLegs const& p, int i) {
1299+
return ROOT::Math::PtEtaPhiMVector(p.fLegPt[i], p.fLegEta[i], p.fLegPhi[i], kMe);
1300+
};
1301+
auto legDir = [](PhotonWithLegs const& p, int i) -> std::array<float, 3> {
1302+
return {p.fLegPt[i] * std::cos(p.fLegPhi[i]), p.fLegPt[i] * std::sin(p.fLegPhi[i]),
1303+
p.fLegPt[i] * std::sinh(p.fLegEta[i])};
1304+
};
1305+
// Leg index 0 = e+, 1 = e- (makePhotonWithLegs filling order).
1306+
// Combo 0: a's e+ with b's e-. Combo 1: b's e+ with a's e-.
1307+
for (int combo = 0; combo < 2; ++combo) { // o2-linter: disable=magic-number (combinations of fake photons)
1308+
PhotonWithLegs const& pPos = (combo == 0) ? a : b;
1309+
PhotonWithLegs const& pEle = (combo == 0) ? b : a;
1310+
c.mee[combo] = static_cast<float>((legVec(pPos, 0) + legVec(pEle, 1)).M());
1311+
c.dist[combo] = lineLineDistance({pPos.fVx, pPos.fVy, pPos.fVz}, legDir(pPos, 0),
1312+
{pEle.fVx, pEle.fVy, pEle.fVz}, legDir(pEle, 1));
1313+
}
1314+
return c;
1315+
}
1316+
1317+
[[nodiscard]] inline bool passCrossPairVeto(CrossObs const& c) const
1318+
{
1319+
if (!crosspair.cfgDoCrossPairCut.value) {
1320+
return true;
1321+
}
1322+
for (int i = 0; i < 2; ++i) { // o2-linter: disable=magic-number (combinations of fake photons)
1323+
if (c.mee[i] < crosspair.cfgCrossMaxMee.value && c.dist[i] < crosspair.cfgCrossMaxDist.value) {
1324+
return false;
1325+
}
1326+
}
1327+
return true;
1328+
}
1329+
1330+
template <int ev_id>
1331+
inline void fillCrossPair(CrossObs const& c, float qinv, bool vetoed)
1332+
{
1333+
constexpr auto dir = (ev_id == 0) ? "Pair/same/CrossPair/" : "Pair/mix/CrossPair/";
1334+
fRegistryCF.fill(HIST(dir) + HIST("hVetoCounter"), vetoed ? 1.0 : 0.0);
1335+
if (qinv > crosspair.cfgCrossMaxQinvQA.value) {
1336+
return;
1337+
}
1338+
for (int i = 0; i < 2; ++i) { // o2-linter: disable=magic-number (combinations of fake photons)
1339+
fRegistryCF.fill(HIST(dir) + HIST("hSparse_Mee_Dist_Qinv"), c.mee[i], c.dist[i], qinv);
1340+
}
1341+
}
1342+
12491343
[[nodiscard]] float uTrue(TruthGamma const& g1, TruthGamma const& g2) const
12501344
{
12511345
const float sE = pairsep.cfgUSigEta.value, sP = pairsep.cfgUSigPhi.value;
@@ -1913,6 +2007,14 @@ struct Photonhbt {
19132007
if (isInsideEllipse(obs.deta, obs.dphi)) {
19142008
continue;
19152009
}
2010+
if (crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value) {
2011+
const auto cross = computeCrossObs(pwl1, pwl2);
2012+
const bool vetoed = !passCrossPairVeto(cross);
2013+
fillCrossPair<0>(cross, obs.qinv, vetoed);
2014+
if (vetoed) {
2015+
continue;
2016+
}
2017+
}
19162018

19172019
// ───after pair cuts ──────────────────────────────────────
19182020
fillPairSep<0, true>(sep, obs);
@@ -2000,6 +2102,14 @@ struct Photonhbt {
20002102
if (isInsideEllipse(obs.deta, obs.dphi)) {
20012103
continue;
20022104
}
2105+
if (crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value) {
2106+
const auto cross = computeCrossObs(g1, g2);
2107+
const bool vetoed = !passCrossPairVeto(cross);
2108+
fillCrossPair<1>(cross, obs.qinv, vetoed);
2109+
if (vetoed) {
2110+
continue;
2111+
}
2112+
}
20032113

20042114
fillPairSep<1, true>(sep, obs);
20052115
if (doQA) {
@@ -2122,6 +2232,14 @@ struct Photonhbt {
21222232
if (isInsideEllipse(obs.deta, obs.dphi)) {
21232233
continue;
21242234
}
2235+
if (crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value) {
2236+
const auto cross = computeCrossObs(pwl1, pwl2);
2237+
const bool vetoed = !passCrossPairVeto(cross);
2238+
fillCrossPair<0>(cross, obs.qinv, vetoed);
2239+
if (vetoed) {
2240+
continue;
2241+
}
2242+
}
21252243

21262244
// ─── after pair cuts ──────────────────────────────────────
21272245
fillPairSep<0, true>(sep, obs);
@@ -2260,6 +2378,14 @@ struct Photonhbt {
22602378
if (isInsideEllipse(obs.deta, obs.dphi)) {
22612379
continue;
22622380
}
2381+
if (crosspair.cfgDoCrossPairQA.value || crosspair.cfgDoCrossPairCut.value) {
2382+
const auto cross = computeCrossObs(g1, g2);
2383+
const bool vetoed = !passCrossPairVeto(cross);
2384+
fillCrossPair<1>(cross, obs.qinv, vetoed);
2385+
if (vetoed) {
2386+
continue;
2387+
}
2388+
}
22632389

22642390
// ─after pair cuts ──────────────────────────────────
22652391
fillPairSep<1, true>(sep, obs);

0 commit comments

Comments
 (0)