1717// / X and Y are the corrected Q2 of two forward detectors (default FT0A x FT0C)
1818// / from the combined Qvectors table, with the same indexing as jEPDzeroFlowAnalysis.
1919// / Z_D(pT) = N_D(pT) / N_ch. N_D comes from CF2ProngTracks (D0 and D0bar).
20- // / N_ch comes from CFTracks. Both derived tables are indexed to CFCollisions,
20+ // / N_ch comes from CFTracks. With JWeights (jflucWeightsLoader, hadron NUE only),
21+ // / sum 1/epsilon_h into N_ch for hadrons and optionally for D0 denominators
22+ // / (processReferenceCorrected / processD0HadronNch). Full D0 candidate efficiency
23+ // / (J2ProngWeights, processD0Corrected) is not wired in the default train yet.
24+ // / Both derived tables are indexed to CFCollisions,
2125// / so the original collision is matched through CFCollRefs (one row per kept
2226// / collision, written by filter-correlations with cfgTransientTables).
2327// / The D azimuth is not part of the Q-vector.
2630// / Definition: arXiv:2506.18690, Appendix A.
2731
2832#include " PWGCF/DataModel/CorrelationsDerived.h"
33+ #include " PWGCF/JCorran/DataModel/JCatalyst.h"
2934
3035#include " Common/CCDB/EventSelectionParams.h"
3136#include " Common/DataModel/Centrality.h"
4752#include < algorithm>
4853#include < cmath>
4954#include < cstdint>
55+ #include < experimental/type_traits>
5056#include < random>
5157#include < string>
5258#include < unordered_set>
@@ -99,6 +105,7 @@ struct JV02DmesonAnalysis {
99105 Configurable<bool > cfgCutNoTimeFrameBorder{" cfgCutNoTimeFrameBorder" , true , " Require kNoTimeFrameBorder" };
100106 Configurable<bool > cfgCutNoITSROFrameBorder{" cfgCutNoITSROFrameBorder" , true , " Require kNoITSROFrameBorder" };
101107 Configurable<bool > cfgRejectDaughters{" cfgRejectDaughters" , false , " Drop D0 daughter CF tracks from N_ch" };
108+ Configurable<bool > cfgWeightEffIsEfficiency{" cfgWeightEffIsEfficiency" , true , " JWeights.weightEff is epsilon (multiply counts by 1/epsilon). Set false if the table already stores 1/epsilon." };
102109
103110 ConfigurableAxis cfgAxisCent{" cfgAxisCent" , {8 , 0 ., 80 .}, " Centrality axis" };
104111 ConfigurableAxis cfgAxisPtD{" cfgAxisPtD" , {VARIABLE_WIDTH , 1 .f , 2 .f , 3 .f , 4 .f , 5 .f , 6 .f , 8 .f , 12 .f , 16 .f , 24 .f }, " D0 pT axis" };
@@ -115,6 +122,22 @@ struct JV02DmesonAnalysis {
115122 std::mt19937 mRng {1 };
116123 std::uniform_int_distribution<int > mSampleDist ;
117124
125+ template <typename TTrack>
126+ using hasWeightEff = decltype (std::declval<TTrack&>().weightEff());
127+
128+ template <typename TTrack>
129+ float inverseEfficiencyWeight (const TTrack& track) const
130+ {
131+ if constexpr (std::experimental::is_detected<hasWeightEff, TTrack>::value) {
132+ const float w = track.weightEff ();
133+ if (w <= 0 .f || !std::isfinite (w)) {
134+ return 0 .f ;
135+ }
136+ return cfgWeightEffIsEfficiency.value ? 1 .f / w : w;
137+ }
138+ return 1 .f ;
139+ }
140+
118141 int detectorId (const std::string& name) const
119142 {
120143 if (name == " FT0C" ) {
@@ -155,7 +178,7 @@ struct JV02DmesonAnalysis {
155178 const AxisSpec subsampleAxis{mNSubsample , 0 ., static_cast <double >(mNSubsample ), " subsample" };
156179 const AxisSpec qaAxis{6 , 0.5 , 6.5 , " 1 seen; 2 sel8; 3 vertex; 4 quality; 5 centrality; 6 accepted" };
157180
158- if (doprocessReference) {
181+ if (doprocessReference || doprocessReferenceCorrected ) {
159182 histos.add (" hadron/hEventQa" , " 1 seen; 2 sel8; 3 vertex; 4 quality; 5 centrality; 6 accepted" , {HistType::kTH1F , {qaAxis}});
160183 histos.add (" hadron/hEvents" , " Selected events;centrality (%);events" , {HistType::kTH1F , {cfgAxisCent}});
161184 histos.add (" hadron/Prof_XY" , " Bulk #LTXY#GT;centrality (%);#LTXY#GT" , {HistType::kTProfile , {cfgAxisCent}});
@@ -168,7 +191,13 @@ struct JV02DmesonAnalysis {
168191 mHadPtAxis = histos.get <TProfile2D>(HIST (" hadron/Prof_Z" ))->GetYaxis ();
169192 mHadCounts .assign (mHadPtAxis ->GetNbins (), 0 .f );
170193 }
171- if (doprocessD0) {
194+ if (doprocessReference && doprocessReferenceCorrected) {
195+ LOGF (fatal, " Enable only one of processReference and processReferenceCorrected" );
196+ }
197+ if (doprocessD0 + doprocessD0HadronNch + doprocessD0Corrected > 1 ) {
198+ LOGF (fatal, " Enable only one of processD0, processD0HadronNch, and processD0Corrected" );
199+ }
200+ if (doprocessD0 || doprocessD0HadronNch || doprocessD0Corrected) {
172201 histos.add (" D0/hEventQa" , " 1 seen; 2 sel8; 3 vertex; 4 quality; 5 centrality; 6 accepted" , {HistType::kTH1F , {qaAxis}});
173202 histos.add (" D0/hEvents" , " Selected events;centrality (%);events" , {HistType::kTH1F , {cfgAxisCent}});
174203 histos.add (" D0/Prof_XY" , " Bulk #LTXY#GT;centrality (%);#LTXY#GT" , {HistType::kTProfile , {cfgAxisCent}});
@@ -238,7 +267,7 @@ struct JV02DmesonAnalysis {
238267 return std::isfinite (xy);
239268 }
240269
241- template <typename TTracks>
270+ template <typename TTracks, bool kApplyEff >
242271 float countCharged (const TTracks& tracks, const std::unordered_set<int64_t >& daughters, bool differential)
243272 {
244273 float nch = 0 .f ;
@@ -253,11 +282,18 @@ struct JV02DmesonAnalysis {
253282 if (cfgRejectDaughters && daughters.contains (static_cast <int64_t >(track.globalIndex ()))) {
254283 continue ;
255284 }
256- nch += 1 .f ;
285+ float w = 1 .f ;
286+ if constexpr (kApplyEff ) {
287+ w = inverseEfficiencyWeight (track);
288+ if (w <= 0 .f ) {
289+ continue ;
290+ }
291+ }
292+ nch += w;
257293 if (differential && mHadPtAxis != nullptr ) {
258294 const int bin = mHadPtAxis ->FindBin (pt);
259295 if (bin >= 1 && bin <= mHadPtAxis ->GetNbins ()) {
260- mHadCounts [bin - 1 ] += 1 . f ;
296+ mHadCounts [bin - 1 ] += w ;
261297 }
262298 }
263299 }
@@ -291,7 +327,7 @@ struct JV02DmesonAnalysis {
291327 return ;
292328 }
293329 const std::unordered_set<int64_t > noDaughters;
294- const float nch = countCharged (tracks.sliceBy (mPerCFTrack , cfId), noDaughters, true );
330+ const float nch = countCharged<aod::CFTracks, false > (tracks.sliceBy (mPerCFTrack , cfId), noDaughters, true );
295331 if (nch < cfgMinNch) {
296332 return ;
297333 }
@@ -313,6 +349,41 @@ struct JV02DmesonAnalysis {
313349 }
314350 PROCESS_SWITCH (JV02DmesonAnalysis, processReference, " Charged-hadron v02(pT) closure" , true );
315351
352+ void processReferenceCorrected (MyCollisions::iterator const & collision, aod::BCs const &, aod::CFCollisions const &, aod::CFCollRefs const & refs, soa::Join<aod::CFTracks, aod::JWeights> const & tracks)
353+ {
354+ float cent = -1 .f ;
355+ float xy = 0 .f ;
356+ const int step = passedEventStep (collision, cent);
357+ for (int qaBin = 1 ; qaBin <= step; ++qaBin) {
358+ histos.fill (HIST (" hadron/hEventQa" ), static_cast <float >(qaBin));
359+ }
360+ const int cfId = cfCollisionIndex (refs);
361+ if (step < StepAccepted || cfId < 0 || !referenceProduct (collision, xy)) {
362+ return ;
363+ }
364+ const std::unordered_set<int64_t > noDaughters;
365+ const float nch = countCharged<soa::Join<aod::CFTracks, aod::JWeights>, true >(tracks.sliceBy (mPerCFTrack , cfId), noDaughters, true );
366+ if (nch < cfgMinNch) {
367+ return ;
368+ }
369+ histos.fill (HIST (" hadron/hEventQa" ), 6 .f );
370+ const int sample = mSampleDist (mRng );
371+ histos.fill (HIST (" hadron/hEvents" ), cent);
372+ histos.fill (HIST (" hadron/Prof_XY" ), cent, xy);
373+ histos.fill (HIST (" hadron/Prof_Nch" ), cent, nch);
374+ histos.fill (HIST (" hadron/Prof_XY_sub" ), static_cast <float >(sample), cent, xy);
375+ const int nBins = mHadPtAxis ->GetNbins ();
376+ for (int ibin = 1 ; ibin <= nBins; ++ibin) {
377+ const auto pt = static_cast <float >(mHadPtAxis ->GetBinCenter (ibin));
378+ const float z = mHadCounts [ibin - 1 ] / nch;
379+ histos.fill (HIST (" hadron/Prof_Z" ), cent, pt, z);
380+ histos.fill (HIST (" hadron/Prof_XYZ" ), cent, pt, xy * z);
381+ histos.fill (HIST (" hadron/Prof_Z_sub" ), static_cast <float >(sample), cent, pt, z);
382+ histos.fill (HIST (" hadron/Prof_XYZ_sub" ), static_cast <float >(sample), cent, pt, xy * z);
383+ }
384+ }
385+ PROCESS_SWITCH (JV02DmesonAnalysis, processReferenceCorrected, " Charged-hadron v02 with JWeights efficiency" , false );
386+
316387 void processD0 (MyCollisions::iterator const & collision, aod::BCs const &, aod::CFCollisions const &, aod::CFCollRefs const & refs, aod::CF2ProngTracks const & candidates, aod::CFTracks const & tracks)
317388 {
318389 float cent = -1 .f ;
@@ -338,7 +409,7 @@ struct JV02DmesonAnalysis {
338409 daughters.insert (static_cast <int64_t >(cand.cfTrackProng1Id ()));
339410 }
340411 }
341- const float nch = countCharged (eventTracks, daughters, false );
412+ const float nch = countCharged<aod::CFTracks, false > (eventTracks, daughters, false );
342413 if (nch < cfgMinNch) {
343414 return ;
344415 }
@@ -367,6 +438,121 @@ struct JV02DmesonAnalysis {
367438 }
368439 }
369440 PROCESS_SWITCH (JV02DmesonAnalysis, processD0, " D0 and D0bar v02(pT) from CF2ProngTracks" , true );
441+
442+ void processD0HadronNch (MyCollisions::iterator const & collision, aod::BCs const &, aod::CFCollisions const &, aod::CFCollRefs const & refs, aod::CF2ProngTracks const & candidates, soa::Join<aod::CFTracks, aod::JWeights> const & tracks)
443+ {
444+ float cent = -1 .f ;
445+ float xy = 0 .f ;
446+ const int step = passedEventStep (collision, cent);
447+ for (int qaBin = 1 ; qaBin <= step; ++qaBin) {
448+ histos.fill (HIST (" D0/hEventQa" ), static_cast <float >(qaBin));
449+ }
450+ const int cfId = cfCollisionIndex (refs);
451+ if (step < StepAccepted || cfId < 0 || !referenceProduct (collision, xy)) {
452+ return ;
453+ }
454+ auto eventCandidates = candidates.sliceBy (mPerCFCand , cfId);
455+ auto eventTracks = tracks.sliceBy (mPerCFTrack , cfId);
456+
457+ std::unordered_set<int64_t > daughters;
458+ if (cfgRejectDaughters) {
459+ for (const auto & cand : eventCandidates) {
460+ if (!isD0Decay (cand.decay ())) {
461+ continue ;
462+ }
463+ daughters.insert (static_cast <int64_t >(cand.cfTrackProng0Id ()));
464+ daughters.insert (static_cast <int64_t >(cand.cfTrackProng1Id ()));
465+ }
466+ }
467+ const float nch = countCharged<soa::Join<aod::CFTracks, aod::JWeights>, true >(eventTracks, daughters, false );
468+ if (nch < cfgMinNch) {
469+ return ;
470+ }
471+ histos.fill (HIST (" D0/hEventQa" ), 6 .f );
472+ const int sample = mSampleDist (mRng );
473+ const auto sampleCoord = static_cast <float >(sample);
474+ histos.fill (HIST (" D0/hEvents" ), cent);
475+ histos.fill (HIST (" D0/Prof_XY" ), cent, xy);
476+ histos.fill (HIST (" D0/Prof_Nch" ), cent, nch);
477+
478+ const float wRaw = 1 .f / nch;
479+ const float wCorr = xy / nch;
480+ for (const auto & cand : eventCandidates) {
481+ if (!isD0Decay (cand.decay ())) {
482+ continue ;
483+ }
484+ const float pt = cand.pt ();
485+ if (pt < cfgPtMinD || pt >= cfgPtMaxD) {
486+ continue ;
487+ }
488+ const float mass = cand.invMass ();
489+ histos.fill (HIST (" D0/hMassRaw" ), cent, pt, mass, wRaw);
490+ histos.fill (HIST (" D0/hMassCorr" ), cent, pt, mass, wCorr);
491+ histos.fill (HIST (" D0/hMassRawSub" ), sampleCoord, cent, pt, mass, wRaw);
492+ histos.fill (HIST (" D0/hMassCorrSub" ), sampleCoord, cent, pt, mass, wCorr);
493+ }
494+ }
495+ PROCESS_SWITCH (JV02DmesonAnalysis, processD0HadronNch, " D0 v02 with epsilon_h-weighted N_ch only (no D0 efficiency)" , false );
496+
497+ void processD0Corrected (MyCollisions::iterator const & collision, aod::BCs const &, aod::CFCollisions const &, aod::CFCollRefs const & refs,
498+ soa::Join<aod::CF2ProngTracks, aod::J2ProngWeights> const & candidates, soa::Join<aod::CFTracks, aod::JWeights> const & tracks)
499+ {
500+ float cent = -1 .f ;
501+ float xy = 0 .f ;
502+ const int step = passedEventStep (collision, cent);
503+ for (int qaBin = 1 ; qaBin <= step; ++qaBin) {
504+ histos.fill (HIST (" D0/hEventQa" ), static_cast <float >(qaBin));
505+ }
506+ const int cfId = cfCollisionIndex (refs);
507+ if (step < StepAccepted || cfId < 0 || !referenceProduct (collision, xy)) {
508+ return ;
509+ }
510+ auto eventCandidates = candidates.sliceBy (mPerCFCand , cfId);
511+ auto eventTracks = tracks.sliceBy (mPerCFTrack , cfId);
512+
513+ std::unordered_set<int64_t > daughters;
514+ if (cfgRejectDaughters) {
515+ for (const auto & cand : eventCandidates) {
516+ if (!isD0Decay (cand.decay ())) {
517+ continue ;
518+ }
519+ daughters.insert (static_cast <int64_t >(cand.cfTrackProng0Id ()));
520+ daughters.insert (static_cast <int64_t >(cand.cfTrackProng1Id ()));
521+ }
522+ }
523+ const float nch = countCharged<soa::Join<aod::CFTracks, aod::JWeights>, true >(eventTracks, daughters, false );
524+ if (nch < cfgMinNch) {
525+ return ;
526+ }
527+ histos.fill (HIST (" D0/hEventQa" ), 6 .f );
528+ const int sample = mSampleDist (mRng );
529+ const auto sampleCoord = static_cast <float >(sample);
530+ histos.fill (HIST (" D0/hEvents" ), cent);
531+ histos.fill (HIST (" D0/Prof_XY" ), cent, xy);
532+ histos.fill (HIST (" D0/Prof_Nch" ), cent, nch);
533+
534+ for (const auto & cand : eventCandidates) {
535+ if (!isD0Decay (cand.decay ())) {
536+ continue ;
537+ }
538+ const float pt = cand.pt ();
539+ if (pt < cfgPtMinD || pt >= cfgPtMaxD) {
540+ continue ;
541+ }
542+ const float wInvD = inverseEfficiencyWeight (cand);
543+ if (wInvD <= 0 .f ) {
544+ continue ;
545+ }
546+ const float wRaw = wInvD / nch;
547+ const float wCorr = xy * wInvD / nch;
548+ const float mass = cand.invMass ();
549+ histos.fill (HIST (" D0/hMassRaw" ), cent, pt, mass, wRaw);
550+ histos.fill (HIST (" D0/hMassCorr" ), cent, pt, mass, wCorr);
551+ histos.fill (HIST (" D0/hMassRawSub" ), sampleCoord, cent, pt, mass, wRaw);
552+ histos.fill (HIST (" D0/hMassCorrSub" ), sampleCoord, cent, pt, mass, wCorr);
553+ }
554+ }
555+ PROCESS_SWITCH (JV02DmesonAnalysis, processD0Corrected, " D0 v02 with JWeights and J2ProngWeights efficiency" , false );
370556};
371557
372558WorkflowSpec defineDataProcessing (ConfigContext const & cfgc)
0 commit comments