Skip to content

Commit afa6b71

Browse files
committed
AddGenLevelStudy
1 parent d403c01 commit afa6b71

1 file changed

Lines changed: 219 additions & 7 deletions

File tree

PWGLF/Tasks/Strangeness/hStrangeCorrelation.cxx

Lines changed: 219 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,25 @@ struct HStrangeCorrelation {
302302

303303
struct : ConfigurableGroup {
304304
std::string prefix = "pairLossK0Configurations";
305-
Configurable<bool> compactComparisonMode{"compactComparisonMode", false, "write only the Rec/Truth/Gen/Final comparison and cumulative Rec control variants; suppress the original PairLoss diagnostic suite"};
305+
// processPairLossK0MC is split into three independent parts. Each has its own
306+
// switch, they write into disjoint folders, and any combination of them may
307+
// run in the same job -- including all three at once.
308+
// doStageDiagnostics PairLossK0/{Stage,State,Geometry,Matching,Response,
309+
// TrackQA,V0QA}: the truth-pair reconstruction ladder
310+
// and its close-pair diagnostics
311+
// doRecComparison PairLossK0/Comparison: the cumulative
312+
// Rec/Truth/Gen/Final variant ladder. Runs the exact
313+
// reconstructed correlation path internally, so
314+
// processSameEventHV0s must be off when it is on.
315+
// doGenLevelStudy PairLossK0/GenStudy: generator-level only, see the
316+
// comment on runGenLevelStudy in processPairLossK0MC
317+
Configurable<bool> doStageDiagnostics{"doStageDiagnostics", true, "part 1: fill the PairLossK0 truth-pair reconstruction ladder and its diagnostics"};
318+
Configurable<bool> doRecComparison{"doRecComparison", false, "part 2: fill the PairLossK0/Comparison cumulative Rec/Truth/Gen/Final ladder (runs the exact reconstructed path internally)"};
319+
Configurable<bool> doGenLevelStudy{"doGenLevelStudy", false, "part 3: fill the PairLossK0/GenStudy generated-vs-reconstructed split, using generator-level event selection only"};
320+
// Generated charged multiplicity of the MC collision, counted in |eta| < 0.8
321+
// by mCounter. Plain ConfigurableAxis: unlike the correlation axes it is NOT
322+
// trimmed by skipUnderOverflowInTHn, so what you configure is what you get.
323+
ConfigurableAxis axisGenStudyNch{"axisGenStudyNch", {VARIABLE_WIDTH, 0.0f, 2.0f, 5.0f, 10.0f, 15.0f, 20.0f, 25.0f, 30.0f, 40.0f, 60.0f, 100.0f}, "generated charged multiplicity in |#eta| < 0.8"};
306324
Configurable<bool> doClosureTestStages{"doClosureTestStages", true, "create and fill the whole ClosureTest/PairLossK0 folder: the truth and any-reconstructed-object stages of the truth h-K0 pair, mirroring the first processPairLossK0MC stages"};
307325
Configurable<bool> applyRecoEventSelection{"applyRecoEventSelection", true, "apply the standard reconstructed-event selection in the K0 pair-loss diagnostic"};
308326
Configurable<float> daughterPtMin{"daughterPtMin", 0.05f, "minimum generated daughter pT for the findable K0 category"};
@@ -621,7 +639,7 @@ struct HStrangeCorrelation {
621639
};
622640

623641
// Per-MC-collision context used only while the ordinary Rec implementation
624-
// is running inside compact PairLoss comparison mode. It is thread-local
642+
// is running inside the PairLoss Rec comparison part. It is thread-local
625643
// static because adding another task data member exceeds the number of
626644
// elements supported by the O2 task-reflection machinery.
627645
struct PairLossComparisonContext {
@@ -1440,7 +1458,7 @@ struct HStrangeCorrelation {
14401458
}
14411459
fillCorrelationHistogram(histos.get<THn>(HIST("sameEvent/Signal/") + HIST(V0names[Index])), binFillThn, etaWeight, efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr);
14421460

1443-
// Compact PairLoss control study. Stage 0 is filled from this
1461+
// PairLoss Rec control study. Stage 0 is filled from this
14441462
// exact Rec signal branch, so it is not a hand-written
14451463
// approximation of the reconstructed pair selection. Each next
14461464
// stage adds exactly one cumulative condition.
@@ -2477,7 +2495,7 @@ struct HStrangeCorrelation {
24772495
const AxisSpec axisMultNDim{edgesMult, "mult percentile"};
24782496
const AxisSpec axisMultiplicityNDim{edgesMultiplicity, "Multiplicity"};
24792497

2480-
if (doprocessPairLossK0MC && !pairLossK0Configurations.compactComparisonMode) {
2498+
if (doprocessPairLossK0MC && pairLossK0Configurations.doStageDiagnostics) {
24812499
const AxisSpec axisPairLossEventStage{6, -0.5, 5.5, "Event-selection stage"};
24822500
const AxisSpec axisPairLossNRecoCollisions{11, -0.5, 10.5, "#it{N}_{reco collisions} per MC collision"};
24832501
const AxisSpec axisPairLossStage{PairLossK0NStages, -0.5, static_cast<double>(PairLossK0NStages) - 0.5, "Reconstruction stage"};
@@ -2629,9 +2647,9 @@ struct HStrangeCorrelation {
26292647
}
26302648
}
26312649

2632-
if (doprocessPairLossK0MC && pairLossK0Configurations.compactComparisonMode) {
2650+
if (doprocessPairLossK0MC && pairLossK0Configurations.doRecComparison) {
26332651
if (doprocessSameEventHV0s) {
2634-
LOGF(fatal, "compact PairLoss comparison mode already runs the exact Rec path internally; set processSameEventHV0s=false to avoid double filling");
2652+
LOGF(fatal, "pairLossK0Configurations.doRecComparison already runs the exact Rec path internally; set processSameEventHV0s=false to avoid double filling");
26352653
}
26362654
constexpr int PairLossComparisonNVariants = 16;
26372655
const AxisSpec axisPairLossComparisonVariant{PairLossComparisonNVariants, -0.5, PairLossComparisonNVariants - 0.5, "cumulative Rec control variant"};
@@ -2699,6 +2717,45 @@ struct HStrangeCorrelation {
26992717
}
27002718
}
27012719

2720+
if (doprocessPairLossK0MC && pairLossK0Configurations.doGenLevelStudy) {
2721+
const AxisSpec axisGenStudyNch{pairLossK0Configurations.axisGenStudyNch, "#it{N}_{ch}^{gen} (|#eta| < 0.8)"};
2722+
const AxisSpec axisGenStudyEventStage{4, -0.5, 3.5, "Generated-event selection stage"};
2723+
// Findability of the K0, in exactly the sense the stage ladder uses: it
2724+
// decayed to pi+ pi- and both charged daughters are inside the tracking
2725+
// acceptance set by daughterPtMin / daughterEtaMax. Kept as an axis rather
2726+
// than as a separate folder so that the inclusive and the findable-only
2727+
// answer come out of one and the same object.
2728+
const AxisSpec axisGenStudyFindable{2, -0.5, 1.5, "K^{0}_{S} findable"};
2729+
2730+
histos.add("PairLossK0/GenStudy/hEventCounter", "generator-level event selection", kTH1F, {axisGenStudyEventStage});
2731+
histos.add("PairLossK0/GenStudy/hNch", "generated charged multiplicity of selected MC collisions", kTH1F, {axisGenStudyNch});
2732+
histos.add("PairLossK0/GenStudy/hNRecoCollisions", "reconstructed collisions per selected MC collision", kTH1F, {{11, -0.5, 10.5}});
2733+
2734+
// Gen/ holds every generated object that passes the generator-level
2735+
// selection; Reconstructed/ and NotReconstructed/ split that same set by
2736+
// whether the object has a reconstructed counterpart. All three are filled
2737+
// with generated coordinates, so Gen == Reconstructed + NotReconstructed
2738+
// bin by bin and NotReconstructed/Gen reads directly as the loss.
2739+
histos.add("PairLossK0/GenStudy/Gen/hTrigger", "generated triggers;#it{p}_{T}^{gen} (GeV/#it{c});#eta^{gen};#varphi^{gen};#it{N}_{ch}^{gen}", kTHnF, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi, axisGenStudyNch});
2740+
histos.add("PairLossK0/GenStudy/Gen/hK0Short", "generated K0s;#it{p}_{T}^{gen} (GeV/#it{c});#eta^{gen};#varphi^{gen};#it{N}_{ch}^{gen};findable", kTHnF, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi, axisGenStudyNch, axisGenStudyFindable});
2741+
histos.addClone("PairLossK0/GenStudy/Gen/", "PairLossK0/GenStudy/Reconstructed/");
2742+
histos.addClone("PairLossK0/GenStudy/Gen/", "PairLossK0/GenStudy/NotReconstructed/");
2743+
2744+
for (auto const& histogram : {histos.get<THn>(HIST("PairLossK0/GenStudy/Gen/hK0Short")),
2745+
histos.get<THn>(HIST("PairLossK0/GenStudy/Reconstructed/hK0Short")),
2746+
histos.get<THn>(HIST("PairLossK0/GenStudy/NotReconstructed/hK0Short"))}) {
2747+
histogram->GetAxis(4)->SetBinLabel(1, "not findable");
2748+
histogram->GetAxis(4)->SetBinLabel(2, "findable");
2749+
}
2750+
2751+
auto genStudyEventCounter = histos.get<TH1>(HIST("PairLossK0/GenStudy/hEventCounter"));
2752+
const std::array<std::string_view, 4> genStudyEventLabels = {"MC collisions", "INEL>0 (generated)", "|vtx z| < cut (generated)", "has >= 1 rec collision"};
2753+
for (size_t i = 0; i < genStudyEventLabels.size(); ++i) {
2754+
genStudyEventCounter->GetXaxis()->SetBinLabel(i + 1, genStudyEventLabels[i].data());
2755+
}
2756+
genStudyEventCounter->GetYaxis()->SetTitle("MC collisions");
2757+
}
2758+
27022759
if (doprocessMixedEventHV0sInBuffer || doprocessMixedEventHCascadesInBuffer) {
27032760
validCollisions.resize(histos.get<TH1>(HIST("axes/hMultAxis"))->GetNbinsX() * histos.get<TH1>(HIST("axes/hVertexZAxis"))->GetNbinsX());
27042761
for (size_t i = 0; i < validCollisions.size(); ++i) {
@@ -4185,7 +4242,152 @@ struct HStrangeCorrelation {
41854242
V0DatasWithoutTrackX const& v0Candidates,
41864243
TracksComplete const& tracks)
41874244
{
4188-
if (pairLossK0Configurations.compactComparisonMode) {
4245+
// Part 3: a self-contained generator-level study. It deliberately touches no
4246+
// reconstructed quantity in its event selection or in any of its axes: the
4247+
// event is selected on generated INEL>0 and the generated vertex only, the
4248+
// multiplicity is counted from generated particles, and every object is
4249+
// filled with generated coordinates.
4250+
//
4251+
// Reconstruction enters in exactly one place -- whether a generated object
4252+
// has a reconstructed counterpart at all -- and that splits the very same
4253+
// generated sample into Reconstructed/ and NotReconstructed/. Because all
4254+
// three folders share generated coordinates, Gen == Reconstructed +
4255+
// NotReconstructed bin by bin, so NotReconstructed/Gen reads directly as
4256+
// "in which pT, eta, phi and multiplicity region do generated objects fail
4257+
// to be reconstructed".
4258+
//
4259+
// "Reconstructed" is the loosest possible statement, with no quality
4260+
// selection of any kind: for a trigger, some track in some reconstructed
4261+
// collision of this MC collision carries its MC label; for a K0, some V0
4262+
// candidate carries its MC core. Objects belonging to an MC collision that
4263+
// produced no reconstructed collision at all therefore land in
4264+
// NotReconstructed/ too; hEventCounter and hNRecoCollisions are there so
4265+
// that contribution can be separated out afterwards.
4266+
auto runGenLevelStudy = [&]() {
4267+
histos.fill(HIST("PairLossK0/GenStudy/hEventCounter"), 0.0f);
4268+
4269+
// Generated-level event selection. No reconstructed variable is used.
4270+
if (masterConfigurations.selectINELgtZERO && !o2::pwglf::isINELgt0mc(mcParticles, pdgDB)) {
4271+
return;
4272+
}
4273+
histos.fill(HIST("PairLossK0/GenStudy/hEventCounter"), 1.0f);
4274+
if (std::abs(mcCollision.posZ()) > masterConfigurations.zVertexCut) {
4275+
return;
4276+
}
4277+
histos.fill(HIST("PairLossK0/GenStudy/hEventCounter"), 2.0f);
4278+
if (recCollisions.size() > 0) {
4279+
histos.fill(HIST("PairLossK0/GenStudy/hEventCounter"), 3.0f);
4280+
}
4281+
histos.fill(HIST("PairLossK0/GenStudy/hNRecoCollisions"), recCollisions.size());
4282+
4283+
// Multiplicity of this MC collision: generated charged physical primaries
4284+
// within |eta| < 0.8. Primaries always, independent of every analysis
4285+
// configurable, so that the multiplicity axis keeps one fixed meaning.
4286+
//
4287+
// Deliberately a local counter rather than a member: the shared mCounter
4288+
// only gets its PDG database wired up when processPrediction runs, and its
4289+
// mSelectPrimaries follows doAssocPhysicalPrimary. Adding a second counter
4290+
// as a task member is not an option either -- the struct is already at the
4291+
// member limit that Framework/StructToTuple.h can destructure. The object
4292+
// is a bool and a pointer, so building it per MC collision costs nothing.
4293+
o2::pwglf::ParticleCounter<o2::framework::O2DatabasePDG> genStudyCounter;
4294+
genStudyCounter.mPdgDatabase = pdgDB.service;
4295+
genStudyCounter.mSelectPrimaries = true;
4296+
const float generatedNch = genStudyCounter.countEta08(mcParticles);
4297+
histos.fill(HIST("PairLossK0/GenStudy/hNch"), generatedNch);
4298+
4299+
// Reconstructed-object bookkeeping. The framework has already grouped
4300+
// recCollisions by this MC collision, so dereferencing a reconstructed
4301+
// collision back to its MC collision needs no extra work here, and the
4302+
// generated event selection above is by construction identical for all of
4303+
// them.
4304+
std::unordered_set<int64_t> reconstructedTrackMcIds;
4305+
std::unordered_set<int64_t> reconstructedV0McIds;
4306+
for (auto const& collision : recCollisions) {
4307+
const auto trackSlice = tracks.sliceBy(pairLossTracksPerCollision, collision.globalIndex());
4308+
for (auto const& track : trackSlice) {
4309+
const auto trackLabel = trackLabels.iteratorAt(track.globalIndex());
4310+
if (trackLabel.has_mcParticle()) {
4311+
reconstructedTrackMcIds.insert(trackLabel.mcParticleId());
4312+
}
4313+
}
4314+
const auto v0Slice = v0Candidates.sliceBy(pairLossV0sPerCollision, collision.globalIndex());
4315+
for (auto const& v0 : v0Slice) {
4316+
const auto v0MC = v0MCCores.iteratorAt(v0.globalIndex());
4317+
if (v0MC.particleIdMC() < 0 || v0MC.pdgCode() != PDG_t::kK0Short) {
4318+
continue;
4319+
}
4320+
reconstructedV0McIds.insert(v0MC.particleIdMC());
4321+
}
4322+
}
4323+
4324+
for (auto const& mcParticle : mcParticles) {
4325+
const float genPt = mcParticle.pt();
4326+
const float genEta = mcParticle.eta();
4327+
const float genPhi = mcParticle.phi();
4328+
if (std::abs(genEta) > etaSel) {
4329+
continue;
4330+
}
4331+
4332+
if (isPairLossTriggerPdg(mcParticle.pdgCode()) &&
4333+
genPt >= axisRanges[3][0] && genPt <= axisRanges[3][1] &&
4334+
(!masterConfigurations.doTriggPhysicalPrimary || mcParticle.isPhysicalPrimary())) {
4335+
// Same charge requirement the other two parts apply, so that the three
4336+
// trigger definitions stay comparable.
4337+
auto const* pdgParticle = pdgDB->GetParticle(mcParticle.pdgCode());
4338+
const double charge = pdgParticle != nullptr ? pdgParticle->Charge() : 0.0;
4339+
const int sign = charge > 0.0 ? 1 : (charge < 0.0 ? -1 : 0);
4340+
if (!((triggerParticleCharge > 0 && sign < 0) || (triggerParticleCharge < 0 && sign > 0) || sign == 0)) {
4341+
histos.fill(HIST("PairLossK0/GenStudy/Gen/hTrigger"), genPt, genEta, genPhi, generatedNch);
4342+
if (reconstructedTrackMcIds.count(mcParticle.globalIndex()) > 0) {
4343+
histos.fill(HIST("PairLossK0/GenStudy/Reconstructed/hTrigger"), genPt, genEta, genPhi, generatedNch);
4344+
} else {
4345+
histos.fill(HIST("PairLossK0/GenStudy/NotReconstructed/hTrigger"), genPt, genEta, genPhi, generatedNch);
4346+
}
4347+
}
4348+
}
4349+
4350+
if (mcParticle.pdgCode() == PDG_t::kK0Short &&
4351+
genPt >= axisRanges[2][0] && genPt <= axisRanges[2][1] &&
4352+
(!doAssocPhysicalPrimary || mcParticle.isPhysicalPrimary())) {
4353+
// Same findability definition the stage ladder uses: decayed to
4354+
// pi+ pi- with both charged daughters inside the tracking acceptance.
4355+
// A K0 that is not findable could never have been reconstructed, so
4356+
// splitting on it is what makes NotReconstructed/ interpretable --
4357+
// without it the folder is dominated by decays whose daughters simply
4358+
// left the acceptance.
4359+
bool hasPositiveDaughter = false;
4360+
bool hasNegativeDaughter = false;
4361+
bool daughtersInAcceptance = true;
4362+
for (auto const& daughter : mcParticle.daughters_as<aod::McParticles>()) {
4363+
if (daughter.pdgCode() != PDG_t::kPiPlus && daughter.pdgCode() != -PDG_t::kPiPlus) {
4364+
continue;
4365+
}
4366+
if (daughter.pdgCode() == PDG_t::kPiPlus) {
4367+
hasPositiveDaughter = true;
4368+
} else {
4369+
hasNegativeDaughter = true;
4370+
}
4371+
if (daughter.pt() < pairLossK0Configurations.daughterPtMin ||
4372+
std::abs(daughter.eta()) > pairLossK0Configurations.daughterEtaMax) {
4373+
daughtersInAcceptance = false;
4374+
}
4375+
}
4376+
const float k0Findable = (hasPositiveDaughter && hasNegativeDaughter && daughtersInAcceptance) ? 1.0f : 0.0f;
4377+
4378+
histos.fill(HIST("PairLossK0/GenStudy/Gen/hK0Short"), genPt, genEta, genPhi, generatedNch, k0Findable);
4379+
if (reconstructedV0McIds.count(mcParticle.globalIndex()) > 0) {
4380+
histos.fill(HIST("PairLossK0/GenStudy/Reconstructed/hK0Short"), genPt, genEta, genPhi, generatedNch, k0Findable);
4381+
} else {
4382+
histos.fill(HIST("PairLossK0/GenStudy/NotReconstructed/hK0Short"), genPt, genEta, genPhi, generatedNch, k0Findable);
4383+
}
4384+
}
4385+
}
4386+
};
4387+
4388+
// Part 2. Wrapped in a lambda so that its own early exits leave the other
4389+
// two parts free to run: the three parts are independent, not exclusive.
4390+
auto runRecComparison = [&]() {
41894391
if (recCollisions.size() == 0) {
41904392
return;
41914393
}
@@ -4588,9 +4790,19 @@ struct HStrangeCorrelation {
45884790
}
45894791

45904792
pairLossComparison.clear();
4793+
};
4794+
4795+
if (pairLossK0Configurations.doGenLevelStudy) {
4796+
runGenLevelStudy();
4797+
}
4798+
if (pairLossK0Configurations.doRecComparison) {
4799+
runRecComparison();
4800+
}
4801+
if (!pairLossK0Configurations.doStageDiagnostics) {
45914802
return;
45924803
}
45934804

4805+
// Part 1 follows.
45944806
histos.fill(HIST("PairLossK0/Event/hCounter"), 0.0f);
45954807
histos.fill(HIST("PairLossK0/Event/hNRecoCollisions"), recCollisions.size());
45964808

0 commit comments

Comments
 (0)