2727#include < Framework/Array2D.h>
2828#include < Framework/HistogramRegistry.h>
2929
30+ #include < Math/Vector3D.h> // IWYU pragma: keep
31+ #include < Math/Vector3Dfwd.h>
32+ #include < Math/VectorUtil.h>
3033#include < TMath.h>
3134#include < TNamed.h>
3235
@@ -183,6 +186,7 @@ class V0PhotonCut : public TNamed
183186 kRZLine ,
184187 kOnWwireIB ,
185188 kOnWwireOB ,
189+ kIsTooClose ,
186190 // leg cut
187191 kTrackPtRange ,
188192 kTrackEtaRange ,
@@ -206,6 +210,12 @@ class V0PhotonCut : public TNamed
206210 kNCuts
207211 };
208212
213+ enum class TooCloseCuts : uint8_t {
214+ kNoCut = 0 ,
215+ kDistance3D = 1 ,
216+ kRadAndAngle = 2
217+ };
218+
209219 // / \brief add histograms to registry
210220 // / \param fRegistry pointer to histogram registry
211221 void addQAHistograms (o2::framework::HistogramRegistry* fRegistry = nullptr ) const
@@ -268,26 +278,27 @@ class V0PhotonCut : public TNamed
268278 hPhotonQualityCuts->GetXaxis ()->SetBinLabel (12 , " RZ_{line}" );
269279 hPhotonQualityCuts->GetXaxis ()->SetBinLabel (13 , " Wire_{IB}" );
270280 hPhotonQualityCuts->GetXaxis ()->SetBinLabel (14 , " Wire_{OB}" );
271- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (15 , " #it{p}_{T,leg}" );
272- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (16 , " #it{#eta}_{leg}" );
273- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (17 , " #it{N}_{cl,TPC}" );
274- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (18 , " #it{N}_{cr,TPC}" );
275- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (19 , " #it{N}_{cr,TPC}/#it{N}_{cl,TPC}" );
276- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (20 , " FracSharedCl" );
277- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (21 , " #chi^{2}_{TPC}/NDF" );
278- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (22 , " #it{N#sigma}_{e,TPC}" );
279- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (23 , " #it{N#sigma}_{#pi,TPC}" );
280- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (24 , " DCA_{xy}" );
281- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (25 , " DCA_{z}" );
282- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (26 , " #it{N}_{cl,ITS}" );
283- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (27 , " #chi^{2}_{ITS}/NDF" );
284- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (28 , " size_{ITS}" );
285- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (29 , " ITSTPC" );
286- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (30 , " ITSOnly" );
287- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (31 , " TPCOnly" );
288- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (32 , " TPCTRD" );
289- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (33 , " TPCTOF" );
290- hPhotonQualityCuts->GetXaxis ()->SetBinLabel (34 , " Out" );
281+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (15 , " IsTooClose" );
282+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (16 , " #it{p}_{T,leg}" );
283+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (17 , " #it{#eta}_{leg}" );
284+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (18 , " #it{N}_{cl,TPC}" );
285+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (19 , " #it{N}_{cr,TPC}" );
286+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (20 , " #it{N}_{cr,TPC}/#it{N}_{cl,TPC}" );
287+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (21 , " FracSharedCl" );
288+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (22 , " #chi^{2}_{TPC}/NDF" );
289+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (23 , " #it{N#sigma}_{e,TPC}" );
290+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (24 , " #it{N#sigma}_{#pi,TPC}" );
291+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (25 , " DCA_{xy}" );
292+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (26 , " DCA_{z}" );
293+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (27 , " #it{N}_{cl,ITS}" );
294+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (28 , " #chi^{2}_{ITS}/NDF" );
295+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (29 , " size_{ITS}" );
296+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (30 , " ITSTPC" );
297+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (31 , " ITSOnly" );
298+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (32 , " TPCOnly" );
299+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (33 , " TPCTRD" );
300+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (34 , " TPCTOF" );
301+ hPhotonQualityCuts->GetXaxis ()->SetBinLabel (35 , " Out" );
291302 }
292303 }
293304
@@ -351,6 +362,119 @@ class V0PhotonCut : public TNamed
351362 fRegistry ->fill (HIST (" QA/V0Photon/after/Neg/hTPCHits" ), ele.tpcNClsFound (), ele.tpcNClsCrossedRows ());
352363 }
353364
365+ // / \brief creates a mask for the V0s if they are too close to another V0 and have higher chi^2
366+ // / \param v0s V0 table
367+ template <o2::soa::is_table TV0>
368+ void createCloseV0CutMask (TV0 const & v0s) const
369+ {
370+ const bool useDistance3D = (mTooCloseType == TooCloseCuts::kDistance3D );
371+ const float windowWidth = useDistance3D ? std::sqrt (mMinV0DistSquared ) : mDeltaR ;
372+ const float cosMinAngle = std::cos (mMinOpeningAngle );
373+
374+ int tableSize = v0s.size ();
375+ std::vector<uint8_t > rejectMask (tableSize, 0 );
376+
377+ if (mTooCloseType == TooCloseCuts::kNoCut ) {
378+ mRejectMask = rejectMask;
379+ return ;
380+ }
381+
382+ auto currentV0Iter = v0s.begin ();
383+ auto otherV0Iter = v0s.begin ();
384+
385+ int groupStart = 0 ;
386+ while (groupStart < tableSize) {
387+
388+ // --- find the end of this collision's group ---
389+ int currentCollisionId = v0s.iteratorAt (groupStart).collisionId ();
390+ int groupEnd = groupStart + 1 ;
391+ while (groupEnd < tableSize && v0s.iteratorAt (groupEnd).collisionId () == currentCollisionId) {
392+ groupEnd++;
393+ }
394+
395+ int groupSize = groupEnd - groupStart;
396+
397+ std::vector<std::pair<int , float >> indexedRadii (groupSize);
398+ for (int k = 0 ; k < groupSize; k++) {
399+ currentV0Iter.setCursor (groupStart + k);
400+ indexedRadii[k] = {groupStart + k, currentV0Iter.v0radius ()};
401+ }
402+ std::sort (indexedRadii.begin (), indexedRadii.end (), [](const auto & a, const auto & b) {
403+ return a.second < b.second ;
404+ });
405+ // extract sorted indices and pre-sorted radii
406+ std::vector<int > sortedIndices (groupSize);
407+ std::vector<float > sortedRadii (groupSize);
408+ for (int k = 0 ; k < groupSize; k++) {
409+ sortedIndices[k] = indexedRadii[k].first ;
410+ sortedRadii[k] = indexedRadii[k].second ;
411+ }
412+
413+ // --- sliding window within this group ---
414+ int windowStart = 0 ; // reset per group
415+ for (int i = 0 ; i < groupSize; i++) {
416+
417+ float currentRadius = sortedRadii[i];
418+ while (windowStart < groupSize && sortedRadii[windowStart] < currentRadius - windowWidth) {
419+ windowStart++;
420+ }
421+
422+ currentV0Iter.setCursor (sortedIndices[i]);
423+
424+ float vx1 = currentV0Iter.vx ();
425+ float vy1 = currentV0Iter.vy ();
426+ float vz1 = currentV0Iter.vz ();
427+
428+ float px1 = currentV0Iter.px ();
429+ float py1 = currentV0Iter.py ();
430+ float pz1 = currentV0Iter.pz ();
431+ float chi2I = currentV0Iter.chiSquareNDF ();
432+
433+ for (int j = windowStart; j < groupSize; j++) {
434+ if (j == i) {
435+ continue ;
436+ }
437+ if (sortedRadii[j] > currentRadius + windowWidth) {
438+ break ;
439+ }
440+
441+ otherV0Iter.setCursor (sortedIndices[j]);
442+
443+ bool tooClose = false ;
444+ if (useDistance3D) {
445+ float dx = vx1 - otherV0Iter.vx ();
446+ float dy = vy1 - otherV0Iter.vy ();
447+ float dz = vz1 - otherV0Iter.vz ();
448+ float distSquared = dx * dx + dy * dy + dz * dz;
449+ tooClose = distSquared < mMinV0DistSquared ;
450+ } else {
451+ float px2 = otherV0Iter.px (), py2 = otherV0Iter.py (), pz2 = otherV0Iter.pz ();
452+ float dot = px1 * px2 + py1 * py2 + pz1 * pz2;
453+ float mag1 = px1 * px1 + py1 * py1 + pz1 * pz1;
454+ float mag2 = px2 * px2 + py2 * py2 + pz2 * pz2;
455+ float denom = std::sqrt (mag1 * mag2);
456+ if (denom > 0 ) {
457+ float cosAngle = dot / denom;
458+ cosAngle = std::clamp (cosAngle, -1 .0f , 1 .0f );
459+ tooClose = cosAngle > cosMinAngle;
460+ }
461+ }
462+
463+ if (tooClose) {
464+ if (chi2I > otherV0Iter.chiSquareNDF ()) {
465+ rejectMask[sortedIndices[i]] = 1 ;
466+
467+ } else {
468+ rejectMask[sortedIndices[j]] = 1 ;
469+ }
470+ }
471+ }
472+ }
473+ groupStart = groupEnd;
474+ }
475+ mRejectMask = rejectMask;
476+ }
477+
354478 // / \brief check if given v0 photon survives all cuts
355479 // / \param flags EMBitFlags where results will be stored
356480 // / \param v0s v0 photon table to check
@@ -360,6 +484,8 @@ class V0PhotonCut : public TNamed
360484 if (v0s.size () <= 0 ) {
361485 return ;
362486 }
487+
488+ createCloseV0CutMask (v0s);
363489 // auto legIter = legs.begin();
364490 // auto legEnd = legs.end();
365491 size_t iV0 = 0 ;
@@ -489,6 +615,13 @@ class V0PhotonCut : public TNamed
489615 }
490616 }
491617
618+ if (!IsSelectedV0 (v0, V0PhotonCuts::kIsTooClose )) {
619+ if (doQA) {
620+ fRegistry ->fill (HIST (" QA/V0Photon/hPhotonQualityCuts" ), static_cast <int >(V0PhotonCuts::kIsTooClose ) + 1 , v0Pt);
621+ }
622+ return false ;
623+ }
624+
492625 for (const auto & track : {pos, ele}) {
493626 if (!IsSelectedTrack (track, V0PhotonCuts::kTrackPtRange )) {
494627 if (doQA) {
@@ -707,11 +840,24 @@ class V0PhotonCut : public TNamed
707840 case V0PhotonCuts::kAP :
708841 return std::pow (v0.alpha () / mMaxAlpha , 2 ) + std::pow (v0.qtarm () / mMaxQt , 2 ) < 1.0 ;
709842
710- case V0PhotonCuts::kPsiPair :
711- return true ;
843+ // TODO: implement fully
844+ case V0PhotonCuts::kPsiPair : {
845+ if constexpr (requires { v0.psipair (); }) {
846+ // return (std::fabs(v0.psipair() < 0.18f * std::exp( -0.55f * v0.chiSquareNDF())));
847+ return true ;
848+ } else {
849+ return true ;
850+ }
851+ }
712852
713- case V0PhotonCuts::kPhiV :
714- return true ;
853+ // TODO: implement fully
854+ case V0PhotonCuts::kPhiV : {
855+ if constexpr (requires { v0.phiv (); }) {
856+ return true ;
857+ } else {
858+ return true ;
859+ }
860+ }
715861
716862 case V0PhotonCuts::kRxy : {
717863 if (v0.v0radius () < mMinRxy || mMaxRxy < v0.v0radius ()) {
@@ -775,6 +921,12 @@ class V0PhotonCut : public TNamed
775921 float dxy = std::sqrt (std::pow (x - x_exp, 2 ) + std::pow (y - y_exp, 2 ));
776922 return !(dxy > margin_xy);
777923 }
924+ case V0PhotonCuts::kIsTooClose : {
925+ if (mRejectMask .size () == 0 ) {
926+ return true ;
927+ }
928+ return (mRejectMask [v0.globalIndex ()] == 0 );
929+ }
778930 default :
779931 return false ;
780932 }
@@ -950,6 +1102,11 @@ class V0PhotonCut : public TNamed
9501102 void SetOnWwireOB (bool flag = false );
9511103 void RejectITSib (bool flag = false );
9521104
1105+ void setTooCloseType (V0PhotonCut::TooCloseCuts type);
1106+ void setMinV0DistSquared (float value);
1107+ void setDeltaR (float value);
1108+ void setMinOpeningAngle (float value);
1109+
9531110 void SetTrackPtRange (float minPt = 0 .f, float maxPt = 1e10f);
9541111 void SetTrackEtaRange (float minEta = -1e10f, float maxEta = 1e10f);
9551112 void SetMinNClustersTPC (int minNClustersTPC);
@@ -1017,6 +1174,11 @@ class V0PhotonCut : public TNamed
10171174 bool mIsOnWwireIB {false };
10181175 bool mIsOnWwireOB {false };
10191176 bool mRejectITSib {false };
1177+ TooCloseCuts mTooCloseType {V0PhotonCut::TooCloseCuts::kRadAndAngle }; // for TooCloseV0Cut: either squared distance between conversion points OR opening angle and deltaR
1178+ float mMinV0DistSquared {1 .}; // for TooCloseV0Cut: cut value when using squared distance between conversion points
1179+ float mDeltaR {6 .}; // for TooCloseV0Cut: V0PhotonCut::TooCloseCuts::kRadAndAngle when deltaR < this -> compare chi2
1180+ float mMinOpeningAngle {0.02 }; // for TooCloseV0Cut: V0PhotonCut::TooCloseCuts::kRadAndAngle when opening angle < this -> compare chi2
1181+ mutable std::vector<uint8_t > mRejectMask {};
10201182
10211183 // ML cuts
10221184 bool mApplyMlCuts {false };
0 commit comments