@@ -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