Skip to content

Commit 175b110

Browse files
authored
[PWGCF] [PWGCF:Jcorran] J v02 dmeson[hadron efficiency correction] (#18462)
1 parent a29b8ce commit 175b110

3 files changed

Lines changed: 202 additions & 9 deletions

File tree

‎PWGCF/JCorran/Tasks/CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ o2physics_add_dpl_workflow(epdzeroflow-analysis
4141

4242
o2physics_add_dpl_workflow(j-v02-dmeson-analysis
4343
SOURCES jV02DmesonAnalysis.cxx
44-
PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore O2Physics::PWGCFCore
44+
PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore O2Physics::PWGCFCore O2Physics::JCorran
4545
COMPONENT_NAME Analysis)
4646

4747
o2physics_add_dpl_workflow(j-v02-dmeson-toy

‎PWGCF/JCorran/Tasks/jV02DmesonAnalysis.cxx‎

Lines changed: 194 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
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.
@@ -26,6 +30,7 @@
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"
@@ -47,6 +52,7 @@
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

372558
WorkflowSpec defineDataProcessing(ConfigContext const& cfgc)

‎PWGCF/JCorran/Tasks/jV02DmesonToy.cxx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
/// Expected D0 v02, with an empty sideband, is 0.36.
2222

2323
#include "PWGCF/DataModel/CorrelationsDerived.h"
24+
#include "PWGCF/JCorran/DataModel/JCatalyst.h"
2425

2526
#include "Common/CCDB/EventSelectionParams.h"
2627
#include "Common/DataModel/Centrality.h"
@@ -56,6 +57,7 @@ struct JV02DmesonToy {
5657
Produces<aod::CFCollRefs> cfRefs;
5758
Produces<aod::CFTracks> cfTracks;
5859
Produces<aod::CF2ProngTracks> cfCandidates;
60+
Produces<aod::JWeights> jWeights;
5961

6062
void fill()
6163
{
@@ -101,6 +103,9 @@ struct JV02DmesonToy {
101103
const float pt = itrk < nSignalTracks ? 1.1f : 0.3f;
102104
const int8_t sign = itrk % EventPair == 0 ? int8_t{1} : int8_t{-1};
103105
cfTracks(ievt, pt, 0.f, 0.f, sign, uint8_t{1});
106+
// Closure for hadron efficiency: signal pT bin eps=0.5, rest eps=1 (v02 unchanged if all eps equal).
107+
const float eff = pt > 1.f ? 0.5f : 1.f;
108+
jWeights(1.f, eff);
104109
}
105110
const int nD0 = high ? 4 : 1;
106111
for (int icand = 0; icand < nD0; ++icand) {
@@ -127,6 +132,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const&)
127132
OutputForTable<aod::CFCollRefs>::spec(),
128133
OutputForTable<aod::CFTracks>::spec(),
129134
OutputForTable<aod::CF2ProngTracks>::spec(),
135+
OutputForTable<aod::JWeights>::spec(),
130136
OutputSpec{"TFN", "TFNumber"},
131137
OutputSpec{"TFF", "TFFilename"},
132138
},
@@ -142,6 +148,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const&)
142148
toy.cfRefs.resetCursor(pc.outputs().make<TableBuilder>(OutputForTable<aod::CFCollRefs>::ref()));
143149
toy.cfTracks.resetCursor(pc.outputs().make<TableBuilder>(OutputForTable<aod::CFTracks>::ref()));
144150
toy.cfCandidates.resetCursor(pc.outputs().make<TableBuilder>(OutputForTable<aod::CF2ProngTracks>::ref()));
151+
toy.jWeights.resetCursor(pc.outputs().make<TableBuilder>(OutputForTable<aod::JWeights>::ref()));
145152
toy.fill();
146153
pc.outputs().make<uint64_t>(Output{"TFN", "TFNumber"}) = 1;
147154
pc.outputs().make<std::string>(Output{"TFF", "TFFilename"}) = std::string{"toy"};

0 commit comments

Comments
 (0)