Skip to content

Commit b854d16

Browse files
authored
[PWGLF] Add PVQualityStudy in hStrangeCorrelation.cxx (#18480)
1 parent 4aeede3 commit b854d16

1 file changed

Lines changed: 262 additions & 3 deletions

File tree

‎PWGLF/Tasks/Strangeness/hStrangeCorrelation.cxx‎

Lines changed: 262 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,22 @@ struct HStrangeCorrelation {
337337
// reconstruction efficiency, Nch^gen vs Nch^reco and
338338
// Nch^reco vs FT0M (percentile and amplitude) responses,
339339
// see runEventRecoEfficiencyStudy
340+
// doPVQualityStudy PairLossK0/PVQuality: best-collision PV position and
341+
// reco - MC residuals vs multNTracksPVeta1, and the
342+
// MC-collision reconstruction and selection stages vs
343+
// Nch^gen, split by the truth h-K0 content of the event,
344+
// see runPVQualityStudy
340345
Configurable<bool> doStageDiagnostics{"doStageDiagnostics", true, "part 1: fill the PairLossK0 truth-pair reconstruction ladder and its diagnostics"};
341346
Configurable<bool> doRecComparison{"doRecComparison", false, "part 2: fill the PairLossK0/Comparison cumulative Rec/Truth/Gen/Final ladder (runs the exact reconstructed path internally)"};
342347
Configurable<bool> recComparisonBestCollisionOnly{"recComparisonBestCollisionOnly", false, "part 2: run the Rec path of PairLossK0/Comparison only in the best reconstructed collision (most PV contributors) of each MC collision, the one the Gen, Truth and Final stages are selected on. Set false to run it in every reconstructed collision, as the data path does"};
343348
Configurable<bool> doGenLevelStudy{"doGenLevelStudy", false, "part 3: fill the PairLossK0/GenStudy generated-vs-reconstructed split, using generator-level event selection only"};
344349
Configurable<bool> doEventRecoEfficiencyStudy{"doEventRecoEfficiencyStudy", false, "fill PairLossK0/EventRecoEfficiency: Nch^gen (charged primaries, |eta| < 1) of all, reconstructed and selected MC collisions, Nch^gen vs multNTracksPVeta1 and multNTracksPVeta1 vs FT0M percentile and amplitude"};
345350
Configurable<bool> nchGenResponseBestCollisionOnly{"nchGenResponseBestCollisionOnly", false, "fill PairLossK0/EventRecoEfficiency/hNchGenVsNTracksPVeta1 with the best reconstructed collision (most PV contributors) of each MC collision only, instead of with every reconstructed collision; the multNTracksPVeta1 vs FT0M responses stay per reconstructed collision"};
351+
Configurable<bool> doPVQualityStudy{"doPVQualityStudy", false, "fill PairLossK0/PVQuality: best-collision PV x, y and reco - MC residuals vs multNTracksPVeta1, and the MC-collision reconstruction and selection stages vs Nch^gen, split by the truth h-K0 content of the event"};
352+
// Coarse on purpose: the PV histograms are dense THnF with a fine position axis
353+
ConfigurableAxis axisPVQualityNTracks{"axisPVQualityNTracks", {VARIABLE_WIDTH, -0.5f, 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 8.5f, 10.5f, 12.5f, 15.5f, 20.5f, 25.5f, 30.5f, 40.5f, 50.5f, 70.5f, 100.5f, 150.5f}, "multNTracksPVeta1 classes of PairLossK0/PVQuality"};
354+
ConfigurableAxis axisPVPosition{"axisPVPosition", {400, -0.2f, 0.2f}, "reconstructed PV x and y (cm) of PairLossK0/PVQuality"};
355+
ConfigurableAxis axisPVResidual{"axisPVResidual", {200, -0.1f, 0.1f}, "reconstructed minus generated PV x, y and z (cm) of PairLossK0/PVQuality"};
346356
Configurable<bool> applyINELSelectionAtGenLevel{"applyINELSelectionAtGenLevel", false, "part 2 (pp only): replace the reconstructed INEL cuts by Nch^gen >= 1 of the MC collision in the Rec, Gen, Truth and Final stages (selectINELgtN is ignored) and fill only PairLossK0/Comparison/NchGen (Rec and Gen vs Nch^gen, integrated over vertex z and multiplicity); requires doRecComparison and applyNewMCSelection"};
347357
ConfigurableAxis axisComparisonNchGen{"axisComparisonNchGen", {VARIABLE_WIDTH, 0.5f, 4.5f, 9.5f, 19.5f, 1000.5f}, "Nch^gen classes (charged primaries in |eta| < 1) of PairLossK0/Comparison/NchGen"};
348358
// MC-only event quality cut on the Nch^gen vs multNTracksPVeta1 response
@@ -670,6 +680,45 @@ struct HStrangeCorrelation {
670680
"pT truth outside trigger axis",
671681
"Truth charge"};
672682

683+
// Truth content of an MC collision for PairLossK0/PVQuality. Trigger and K0 follow
684+
// the acceptance, primary and leading-trigger requirements of PairLossK0/Comparison/Truth,
685+
// and a pair excludes a trigger that is one of the K0 daughters. An MC collision enters
686+
// every class it belongs to, so the classes overlap and must not be summed:
687+
// HasTrigger, HasPair and K0DaughterPVContributor are subsets of All, NearPair and
688+
// AwayPair subsets of HasPair, and one MC collision can be both NearPair and AwayPair.
689+
// K0DaughterPVContributor needs the best reconstructed collision, so it is empty at the
690+
// first stage of hEventStage.
691+
enum PairLossPVQualityClass : int {
692+
PairLossPVQualityAll = 0,
693+
PairLossPVQualityHasTrigger,
694+
PairLossPVQualityHasPair,
695+
PairLossPVQualityNearPair,
696+
PairLossPVQualityAwayPair,
697+
PairLossPVQualityK0DaughterPVContributor,
698+
PairLossPVQualityNClasses
699+
};
700+
701+
static constexpr std::array<std::string_view, PairLossPVQualityNClasses> PairLossPVQualityClassNames = {
702+
"all",
703+
"truth trigger",
704+
"truth h-K^{0}_{S} pair",
705+
"near-side pair (|#Delta#varphi| < #pi/3)",
706+
"away-side pair (|#Delta#varphi - #pi| < #pi/3)",
707+
"K^{0}_{S} daughter is a PV contributor"};
708+
709+
// MC-collision stages of PairLossK0/PVQuality/hEventStage, each a subset of the previous one
710+
enum PairLossPVQualityStage : int {
711+
PairLossPVQualityStageAll = 0,
712+
PairLossPVQualityStageHasRecoColl,
713+
PairLossPVQualityStageSelected,
714+
PairLossPVQualityNStages
715+
};
716+
717+
static constexpr std::array<std::string_view, PairLossPVQualityNStages> PairLossPVQualityStageNames = {
718+
"MC collision",
719+
"has reconstructed collision",
720+
"best collision selected"};
721+
673722
struct PairLossV0Info {
674723
int64_t globalIndex = -1;
675724
int64_t positiveTrackId = -1;
@@ -2960,6 +3009,52 @@ struct HStrangeCorrelation {
29603009
}
29613010
}
29623011

3012+
if (doprocessPairLossK0MC && pairLossK0Configurations.doPVQualityStudy) {
3013+
// One entry per MC collision with |vz_gen| <= zVertexCut, in every truth-content
3014+
// class it belongs to (PairLossPVQualityClass): the classes overlap.
3015+
// hEventStage: stages nested as in PairLossK0/EventRecoEfficiency, so
3016+
// HasRecoColl/All and Selected/HasRecoColl per class give whether events with a
3017+
// trigger and a K0 lose their PV, or have it rejected, more often.
3018+
// hPos*: the data-like check -- the beam spot is common to all collisions, so a
3019+
// broadening or tail of the best-collision x and y towards low multNTracksPVeta1
3020+
// is a reconstruction effect. hRes*: the same with the generated vertex
3021+
// subtracted. Selection bin 0 holds every best collision, bin 1 only those passing
3022+
// the event selection of PairLossK0/Comparison, so bin 1 is a subset of bin 0.
3023+
const AxisSpec axisPVQualityClass{PairLossPVQualityNClasses, -0.5, static_cast<double>(PairLossPVQualityNClasses) - 0.5, "truth content of the MC collision"};
3024+
const AxisSpec axisPVQualityStage{PairLossPVQualityNStages, -0.5, static_cast<double>(PairLossPVQualityNStages) - 0.5, "MC-collision stage"};
3025+
const AxisSpec axisPVQualitySelection{2, -0.5, 1.5, "best collision"};
3026+
const AxisSpec axisPVQualityNchGen{axesConfigurations.axisNchGen, "#it{N}_{ch}^{gen} (|#eta| < 1)"};
3027+
const AxisSpec axisPVQualityNTracksPVeta1{pairLossK0Configurations.axisPVQualityNTracks, "multNTracksPVeta1"};
3028+
const AxisSpec axisPVQualityPosX{pairLossK0Configurations.axisPVPosition, "#it{x}_{PV}^{reco} (cm)"};
3029+
const AxisSpec axisPVQualityPosY{pairLossK0Configurations.axisPVPosition, "#it{y}_{PV}^{reco} (cm)"};
3030+
const AxisSpec axisPVQualityResX{pairLossK0Configurations.axisPVResidual, "#it{x}_{PV}^{reco} #minus #it{x}_{PV}^{gen} (cm)"};
3031+
const AxisSpec axisPVQualityResY{pairLossK0Configurations.axisPVResidual, "#it{y}_{PV}^{reco} #minus #it{y}_{PV}^{gen} (cm)"};
3032+
const AxisSpec axisPVQualityResZ{pairLossK0Configurations.axisPVResidual, "#it{z}_{PV}^{reco} #minus #it{z}_{PV}^{gen} (cm)"};
3033+
histos.add("PairLossK0/PVQuality/hEventStage", "MC collisions by truth content and stage", kTH3D, {axisPVQualityClass, axisPVQualityStage, axisPVQualityNchGen});
3034+
histos.add("PairLossK0/PVQuality/hPosX", "best-collision PV x", kTHnF, {axisPVQualityClass, axisPVQualitySelection, axisPVQualityNTracksPVeta1, axisPVQualityPosX});
3035+
histos.add("PairLossK0/PVQuality/hPosY", "best-collision PV y", kTHnF, {axisPVQualityClass, axisPVQualitySelection, axisPVQualityNTracksPVeta1, axisPVQualityPosY});
3036+
histos.add("PairLossK0/PVQuality/hResX", "best-collision PV x residual", kTHnF, {axisPVQualityClass, axisPVQualitySelection, axisPVQualityNTracksPVeta1, axisPVQualityResX});
3037+
histos.add("PairLossK0/PVQuality/hResY", "best-collision PV y residual", kTHnF, {axisPVQualityClass, axisPVQualitySelection, axisPVQualityNTracksPVeta1, axisPVQualityResY});
3038+
histos.add("PairLossK0/PVQuality/hResZ", "best-collision PV z residual", kTHnF, {axisPVQualityClass, axisPVQualitySelection, axisPVQualityNTracksPVeta1, axisPVQualityResZ});
3039+
3040+
auto eventStage = histos.get<TH3>(HIST("PairLossK0/PVQuality/hEventStage"));
3041+
for (int i = 0; i < PairLossPVQualityNClasses; ++i) {
3042+
eventStage->GetXaxis()->SetBinLabel(i + 1, PairLossPVQualityClassNames[i].data());
3043+
}
3044+
for (int i = 0; i < PairLossPVQualityNStages; ++i) {
3045+
eventStage->GetYaxis()->SetBinLabel(i + 1, PairLossPVQualityStageNames[i].data());
3046+
}
3047+
for (auto const& pvHistogram : {histos.get<THn>(HIST("PairLossK0/PVQuality/hPosX")), histos.get<THn>(HIST("PairLossK0/PVQuality/hPosY")),
3048+
histos.get<THn>(HIST("PairLossK0/PVQuality/hResX")), histos.get<THn>(HIST("PairLossK0/PVQuality/hResY")),
3049+
histos.get<THn>(HIST("PairLossK0/PVQuality/hResZ"))}) {
3050+
for (int i = 0; i < PairLossPVQualityNClasses; ++i) {
3051+
pvHistogram->GetAxis(0)->SetBinLabel(i + 1, PairLossPVQualityClassNames[i].data());
3052+
}
3053+
pvHistogram->GetAxis(1)->SetBinLabel(1, "all");
3054+
pvHistogram->GetAxis(1)->SetBinLabel(2, "event selection");
3055+
}
3056+
}
3057+
29633058
if (doprocessSameEventHV0s && masterConfigurations.doMultEstimatorResponse) {
29643059
// Same histograms and event selection as the multNTracksPVeta1 vs FT0M responses of
29653060
// PairLossK0/EventRecoEfficiency, filled per reconstructed collision in data
@@ -4822,11 +4917,11 @@ struct HStrangeCorrelation {
48224917
TracksComplete const& tracks)
48234918
{
48244919
// Nch^gen of this MC collision (charged primaries in |eta| < 1), shared by the
4825-
// event-reconstruction-efficiency study, the generated-level INEL selection and
4826-
// the Nch^gen vs multNTracksPVeta1 band cut
4920+
// event-reconstruction-efficiency and PV-quality studies, the generated-level INEL
4921+
// selection and the Nch^gen vs multNTracksPVeta1 band cut
48274922
const bool applyGenInel = pairLossK0Configurations.applyINELSelectionAtGenLevel;
48284923
const bool applyBandCut = pairLossK0Configurations.applyNchResponseBandCut;
4829-
const int nChGen = (pairLossK0Configurations.doEventRecoEfficiencyStudy || applyGenInel || applyBandCut) ? countNchGenEta1(mcParticles) : -1;
4924+
const int nChGen = (pairLossK0Configurations.doEventRecoEfficiencyStudy || pairLossK0Configurations.doPVQualityStudy || applyGenInel || applyBandCut) ? countNchGenEta1(mcParticles) : -1;
48304925

48314926
// Part 3: a self-contained generator-level study. It deliberately touches no
48324927
// reconstructed quantity in its event selection or in any of its axes: the
@@ -5556,9 +5651,173 @@ struct HStrangeCorrelation {
55565651
}
55575652
};
55585653

5654+
// PV quality vs midrapidity multiplicity, split by the truth h-K0 content of the MC
5655+
// collision (PairLossPVQualityClass). Tests whether PVs built from few tracks are
5656+
// biased or badly defined, and whether a trigger and a K0 in the event -- whose
5657+
// displaced daughters can enter the vertex fit -- make it worse: in the PV position
5658+
// and residuals of the best collision, and in how often that collision is missing
5659+
// or fails the event selection of PairLossK0/Comparison
5660+
auto runPVQualityStudy = [&]() {
5661+
if (std::abs(mcCollision.posZ()) > masterConfigurations.zVertexCut) {
5662+
return;
5663+
}
5664+
5665+
// Truth triggers and K0s as in PairLossK0/Comparison/Truth: same acceptance,
5666+
// primary requirements applied before the leading-trigger choice
5667+
struct PVQualityTrigger {
5668+
int64_t globalIndex = -1;
5669+
float pt = 0.0f;
5670+
float phi = 0.0f;
5671+
};
5672+
struct PVQualityK0 {
5673+
float phi = 0.0f;
5674+
int64_t positiveDaughterId = -1;
5675+
int64_t negativeDaughterId = -1;
5676+
};
5677+
std::vector<PVQualityTrigger> pvTriggers;
5678+
std::vector<PVQualityK0> pvK0s;
5679+
for (auto const& mcParticle : mcParticles) {
5680+
int sign = 0;
5681+
if (pairLossTruthTriggerRejection(mcParticle, sign) == PairLossTriggerMatchMatched &&
5682+
(!masterConfigurations.doTriggPhysicalPrimary || mcParticle.isPhysicalPrimary())) {
5683+
pvTriggers.push_back(PVQualityTrigger{mcParticle.globalIndex(), mcParticle.pt(), mcParticle.phi()});
5684+
}
5685+
if (mcParticle.pdgCode() != PDG_t::kK0Short || std::abs(mcParticle.eta()) > etaSel ||
5686+
mcParticle.pt() < axisRanges[2][0] || mcParticle.pt() > axisRanges[2][1] ||
5687+
(doAssocPhysicalPrimary && !mcParticle.isPhysicalPrimary())) {
5688+
continue;
5689+
}
5690+
PVQualityK0 pvK0{mcParticle.phi(), -1, -1};
5691+
for (auto const& daughter : mcParticle.daughters_as<aod::McParticles>()) {
5692+
if (daughter.pdgCode() == PDG_t::kPiPlus) {
5693+
pvK0.positiveDaughterId = daughter.globalIndex();
5694+
} else if (daughter.pdgCode() == -PDG_t::kPiPlus) {
5695+
pvK0.negativeDaughterId = daughter.globalIndex();
5696+
}
5697+
}
5698+
pvK0s.push_back(pvK0);
5699+
}
5700+
if (useTheLeadingParticleAsTrigger && pvTriggers.size() > 1) {
5701+
const auto leadingTrigger = *std::max_element(pvTriggers.begin(), pvTriggers.end(), [](auto const& lhs, auto const& rhs) { return lhs.pt < rhs.pt; });
5702+
pvTriggers.assign(1, leadingTrigger);
5703+
}
5704+
5705+
constexpr double PVQualitySideHalfWidth = PI / 3.0;
5706+
std::array<bool, PairLossPVQualityNClasses> inClass{};
5707+
inClass[PairLossPVQualityAll] = true;
5708+
inClass[PairLossPVQualityHasTrigger] = !pvTriggers.empty();
5709+
for (auto const& trigger : pvTriggers) {
5710+
for (auto const& k0 : pvK0s) {
5711+
if (trigger.globalIndex == k0.positiveDaughterId || trigger.globalIndex == k0.negativeDaughterId) {
5712+
continue;
5713+
}
5714+
const double deltaPhi = computeDeltaPhi(trigger.phi, k0.phi);
5715+
inClass[PairLossPVQualityHasPair] = true;
5716+
if (std::abs(deltaPhi) < PVQualitySideHalfWidth) {
5717+
inClass[PairLossPVQualityNearPair] = true;
5718+
}
5719+
if (std::abs(deltaPhi - PI) < PVQualitySideHalfWidth) {
5720+
inClass[PairLossPVQualityAwayPair] = true;
5721+
}
5722+
}
5723+
}
5724+
5725+
auto fillEventStage = [&](int stage) {
5726+
for (int eventClass = 0; eventClass < PairLossPVQualityNClasses; ++eventClass) {
5727+
if (inClass[eventClass]) {
5728+
histos.fill(HIST("PairLossK0/PVQuality/hEventStage"), eventClass, stage, nChGen);
5729+
}
5730+
}
5731+
};
5732+
fillEventStage(PairLossPVQualityStageAll);
5733+
if (recCollisions.size() == 0) {
5734+
return;
5735+
}
5736+
5737+
// Best collision: the one with the most PV contributors, as in the Rec comparison
5738+
int largestNContributors = -1;
5739+
int64_t bestCollisionId = -1;
5740+
float bestCollisionVtxX = 0.0f;
5741+
float bestCollisionVtxY = 0.0f;
5742+
float bestCollisionVtxZ = 0.0f;
5743+
int bestCollisionNTracksPVeta1 = -1;
5744+
bool bestCollisionSelected = false;
5745+
for (auto const& collision : recCollisions) {
5746+
if (collision.numContrib() <= largestNContributors) {
5747+
continue;
5748+
}
5749+
largestNContributors = collision.numContrib();
5750+
bestCollisionId = collision.globalIndex();
5751+
bestCollisionVtxX = collision.posX();
5752+
bestCollisionVtxY = collision.posY();
5753+
bestCollisionVtxZ = collision.posZ();
5754+
bestCollisionNTracksPVeta1 = collision.multNTracksPVeta1();
5755+
// same event selection as pairLossEventSelected in runRecComparison
5756+
bestCollisionSelected = (!pairLossK0Configurations.applyRecoEventSelection ||
5757+
(masterConfigurations.doPPAnalysis ? isCollisionSelected(collision, !applyGenInel) && (!applyGenInel || isGenInelGt0(nChGen))
5758+
: isCollisionSelectedPbPb(collision, false))) &&
5759+
(!applyBandCut || isInNchResponseBand(nChGen, collision.multNTracksPVeta1()));
5760+
}
5761+
if (bestCollisionId < 0) {
5762+
return;
5763+
}
5764+
5765+
// Direct check of the K0 side of the hypothesis: a daughter of a truth K0 above
5766+
// was used in the vertex fit of the best collision
5767+
std::unordered_set<int64_t> pvK0DaughterIds;
5768+
for (auto const& k0 : pvK0s) {
5769+
for (const int64_t daughterId : {k0.positiveDaughterId, k0.negativeDaughterId}) {
5770+
if (daughterId >= 0) {
5771+
pvK0DaughterIds.insert(daughterId);
5772+
}
5773+
}
5774+
}
5775+
if (!pvK0DaughterIds.empty()) {
5776+
const auto trackSlice = tracks.sliceBy(pairLossTracksPerCollision, bestCollisionId);
5777+
for (auto const& track : trackSlice) {
5778+
if (!track.isPVContributor()) {
5779+
continue;
5780+
}
5781+
const auto trackLabel = trackLabels.iteratorAt(track.globalIndex());
5782+
if (trackLabel.has_mcParticle() && pvK0DaughterIds.contains(trackLabel.mcParticleId())) {
5783+
inClass[PairLossPVQualityK0DaughterPVContributor] = true;
5784+
break;
5785+
}
5786+
}
5787+
}
5788+
5789+
fillEventStage(PairLossPVQualityStageHasRecoColl);
5790+
if (bestCollisionSelected) {
5791+
fillEventStage(PairLossPVQualityStageSelected);
5792+
}
5793+
5794+
const float residualX = bestCollisionVtxX - mcCollision.posX();
5795+
const float residualY = bestCollisionVtxY - mcCollision.posY();
5796+
const float residualZ = bestCollisionVtxZ - mcCollision.posZ();
5797+
auto fillBestCollision = [&](int selectionBin) {
5798+
for (int eventClass = 0; eventClass < PairLossPVQualityNClasses; ++eventClass) {
5799+
if (!inClass[eventClass]) {
5800+
continue;
5801+
}
5802+
histos.fill(HIST("PairLossK0/PVQuality/hPosX"), eventClass, selectionBin, bestCollisionNTracksPVeta1, bestCollisionVtxX);
5803+
histos.fill(HIST("PairLossK0/PVQuality/hPosY"), eventClass, selectionBin, bestCollisionNTracksPVeta1, bestCollisionVtxY);
5804+
histos.fill(HIST("PairLossK0/PVQuality/hResX"), eventClass, selectionBin, bestCollisionNTracksPVeta1, residualX);
5805+
histos.fill(HIST("PairLossK0/PVQuality/hResY"), eventClass, selectionBin, bestCollisionNTracksPVeta1, residualY);
5806+
histos.fill(HIST("PairLossK0/PVQuality/hResZ"), eventClass, selectionBin, bestCollisionNTracksPVeta1, residualZ);
5807+
}
5808+
};
5809+
fillBestCollision(0);
5810+
if (bestCollisionSelected) {
5811+
fillBestCollision(1);
5812+
}
5813+
};
5814+
55595815
if (pairLossK0Configurations.doEventRecoEfficiencyStudy) {
55605816
runEventRecoEfficiencyStudy();
55615817
}
5818+
if (pairLossK0Configurations.doPVQualityStudy) {
5819+
runPVQualityStudy();
5820+
}
55625821
if (pairLossK0Configurations.doGenLevelStudy) {
55635822
runGenLevelStudy();
55645823
}

0 commit comments

Comments
 (0)