Skip to content

Commit 3feee4b

Browse files
committed
Update hStrangeCorrelation.cxx
1 parent 425c429 commit 3feee4b

1 file changed

Lines changed: 163 additions & 2 deletions

File tree

PWGLF/Tasks/Strangeness/hStrangeCorrelation.cxx

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ struct HStrangeCorrelation {
330330
// trimmed by skipUnderOverflowInTHn, so what you configure is what you get.
331331
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"};
332332
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"};
333+
Configurable<bool> doEventQualityStudy{"doEventQualityStudy", false, "add eight EventQuality folders to the closure PairLossK0 study; requires doClosureTestStages"};
334+
Configurable<int> eventQualityDeltaPhiBins{"eventQualityDeltaPhiBins", 36, "delta phi bins in the sparse event-quality histograms"};
335+
Configurable<int> eventQualityDeltaEtaBins{"eventQualityDeltaEtaBins", 20, "delta eta bins in the sparse event-quality histograms"};
333336
Configurable<bool> applyRecoEventSelection{"applyRecoEventSelection", true, "apply the standard reconstructed-event selection in the K0 pair-loss diagnostic"};
334337
Configurable<bool> fillFinalPairOnce{"fillFinalPairOnce", true, "part 1: fill each truth h-K0 pair at most once at the final stage. Set false to loop over all reconstructed trigger x K0 combinations, as the data path does"};
335338
Configurable<bool> finalPairUseBestCollisionOnly{"finalPairUseBestCollisionOnly", true, "part 1: build the final trigger and K0 objects only in the best collision. Set false to use every associated reconstructed collision that passes the event selection, pairing within one collision, as the data path does"};
@@ -532,6 +535,23 @@ struct HStrangeCorrelation {
532535
"V0 final selection",
533536
"Final reconstructed pair"};
534537

538+
// Independent event classifications, not cumulative cuts. Values are fixed
539+
// diagnostic bins (labels below), and never change the parent pair selection.
540+
static constexpr int PairLossNEventQualityGroups = 8;
541+
static constexpr std::array<std::string_view, PairLossNEventQualityGroups> PairLossEventQualityNames = {
542+
"NContributors", "PVMaxPull", "PVPurity", "CollisionAssociation",
543+
"TimeResolution", "Occupancy", "GenMultiplicity", "EventShape"};
544+
static constexpr std::array<std::array<std::string_view, 6>, PairLossNEventQualityGroups> PairLossEventQualityLabels = {{
545+
{"no contributors", "1-2", "3-5", "6-10", "11-20", ">=21"},
546+
{"invalid covariance/pull", "max |pull| <1", "1<=max |pull|<3", "3<=max |pull|<5", "max |pull|>=5", "unused"},
547+
{"no PV tracks", "missing MC labels", "purity <0.95", "0.95<=purity<1", "purity =1", "unused"},
548+
{"no matched tracks", "one rec collision", "multiple: best fraction >=0.9", "multiple: 0.5<=fraction<0.9", "multiple: fraction <0.5", "unused"},
549+
{"invalid time resolution", "0<sigma_t<25 ns", "25<=sigma_t<100 ns", "100<=sigma_t<500 ns", "sigma_t>=500 ns", "unused"},
550+
{"unavailable", "0", "1-99", "100-499", "500-999", ">=1000"},
551+
{"0", "1-4", "5-9", "10-19", "20-39", ">=40"},
552+
{"fewer than 3 shape tracks", "S_T<0.3", "0.3<=S_T<0.7", "S_T>=0.7", "unused", "unused"},
553+
}};
554+
535555
struct PairLossTrackInfo {
536556
int64_t globalIndex = -1;
537557
float pt = 0.0f;
@@ -3119,6 +3139,32 @@ struct HStrangeCorrelation {
31193139
histos.add("ClosureTest/PairLossK0/Final/sameEvent/K0Short", "truth h-K0 pairs whose trigger and K0 both have a fully selected reconstructed counterpart in the same reconstructed collision", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim});
31203140
histos.add("ClosureTest/PairLossK0/Final/hTrigger", "truth triggers with a fully selected reconstructed counterpart;#it{p}_{T}^{truth} (GeV/#it{c});#eta^{truth};#varphi^{truth}", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi});
31213141
histos.add("ClosureTest/PairLossK0/Final/hK0Short", "truth K0s with a fully selected reconstructed counterpart;#it{p}_{T}^{truth} (GeV/#it{c});#eta^{truth};#varphi^{truth}", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi});
3142+
if (pairLossK0Configurations.doEventQualityStudy) {
3143+
if (pairLossK0Configurations.eventQualityDeltaPhiBins <= 0 || pairLossK0Configurations.eventQualityDeltaEtaBins <= 0) {
3144+
LOGF(fatal, "Event-quality angular bin counts must be positive");
3145+
}
3146+
// Six sparse axes; omit vertex and multiplicity dimensions. Each folder
3147+
// has exactly three histograms, including its event/trigger denominators.
3148+
const std::vector<AxisSpec> qualityAxes = {
3149+
{6, -0.5, 5.5, "event class"},
3150+
{3, -0.5, 2.5, "entry kind"},
3151+
{pairLossK0Configurations.eventQualityDeltaPhiBins, axisRanges[0][0], axisRanges[0][1], "#Delta#varphi^{truth}"},
3152+
{pairLossK0Configurations.eventQualityDeltaEtaBins, axisRanges[1][0], axisRanges[1][1], "#Delta#eta^{truth}"},
3153+
axisPtAssocNDim,
3154+
axisPtTriggerNDim,
3155+
};
3156+
for (int group = 0; group < PairLossNEventQualityGroups; ++group) {
3157+
for (auto const& stage : {"Truth", "AnyTrackBoth", "Final"}) {
3158+
auto histogram = histos.add<THnSparse>(fmt::format("ClosureTest/PairLossK0/EventQuality/{}/{}", PairLossEventQualityNames[group], stage), "unit-weight counts; select entry kind before projection", kTHnSparseF, qualityAxes);
3159+
for (size_t bin = 0; bin < PairLossEventQualityLabels[group].size(); ++bin) {
3160+
histogram->GetAxis(0)->SetBinLabel(bin + 1, PairLossEventQualityLabels[group][bin].data());
3161+
}
3162+
histogram->GetAxis(1)->SetBinLabel(1, "event (common baseline)");
3163+
histogram->GetAxis(1)->SetBinLabel(2, "trigger");
3164+
histogram->GetAxis(1)->SetBinLabel(3, "pair");
3165+
}
3166+
}
3167+
}
31223168
}
31233169
for (int i = 0; i < AssocParticleTypes; i++) {
31243170
if (TESTBIT(doCorrelation, i)) {
@@ -5621,7 +5667,7 @@ struct HStrangeCorrelation {
56215667
}
56225668
}
56235669

5624-
void processClosureTest(aod::McCollision const& /*mcCollision*/,
5670+
void processClosureTest(aod::McCollision const& mcCollision,
56255671
soa::SmallGroups<soa::Join<aod::McCollisionLabels, aod::Collisions, aod::EvSels, aod::CentFT0Ms, aod::CentFT0Cs, aod::PVMults>> const& recCollisions,
56265672
aod::McParticles const& mcParticles,
56275673
aod::V0MCCores const& v0MCCores,
@@ -5838,6 +5884,116 @@ struct HStrangeCorrelation {
58385884

58395885
const float pairLossBestCollisionVtxZ = collision.posZ();
58405886
const float pairLossBestCollisionMultiplicity = masterConfigurations.doPPAnalysis ? collision.centFT0M() : collision.centFT0C();
5887+
std::array<int, PairLossNEventQualityGroups> eventQualityClasses{};
5888+
if (pairLossK0Configurations.doEventQualityStudy) {
5889+
// Category edges correspond to the labels declared above.
5890+
auto classFromEdges = [](auto value, auto const& edges) {
5891+
return static_cast<int>(std::upper_bound(edges.begin(), edges.end(), value) - edges.begin());
5892+
};
5893+
constexpr double PurePVThreshold = 0.95;
5894+
constexpr double DominantCollisionFraction = 0.9;
5895+
constexpr double MajorityCollisionFraction = 0.5;
5896+
constexpr float GenEtaMax = 0.8f;
5897+
constexpr float ShapePtMin = 0.15f;
5898+
constexpr int MinShapeTracks = 3;
5899+
const auto nContributors = collision.numContrib();
5900+
eventQualityClasses[0] = classFromEdges(nContributors, std::array{1u, 3u, 6u, 11u, 21u});
5901+
5902+
// Maximum component pull, not a 3D chi-square: covariance correlations
5903+
// are deliberately not used. Invalid covariance gets its own category.
5904+
const std::array<double, 3> residuals = {collision.posX() - mcCollision.posX(), collision.posY() - mcCollision.posY(), collision.posZ() - mcCollision.posZ()};
5905+
const std::array<double, 3> variances = {collision.covXX(), collision.covYY(), collision.covZZ()};
5906+
double maxPull = 0.;
5907+
bool validPull = true;
5908+
for (size_t component = 0; component < variances.size(); ++component) {
5909+
if (!std::isfinite(variances[component]) || variances[component] <= 0. || !std::isfinite(residuals[component])) {
5910+
validPull = false;
5911+
break;
5912+
}
5913+
maxPull = std::max(maxPull, std::abs(residuals[component]) / std::sqrt(variances[component]));
5914+
}
5915+
eventQualityClasses[1] = !validPull || !std::isfinite(maxPull) ? 0 : 1 + classFromEdges(maxPull, std::array{1., 3., 5.});
5916+
5917+
std::unordered_set<int64_t> currentMcIds;
5918+
int nAnyMatched = 0;
5919+
int nGenCharged = 0;
5920+
int nShapeTracks = 0;
5921+
double sumPt = 0., q2x = 0., q2y = 0.;
5922+
for (auto const& particle : mcParticles) {
5923+
currentMcIds.insert(particle.globalIndex());
5924+
nAnyMatched += pairLossAnyTrackMcParticleIds.count(particle.globalIndex()) != 0;
5925+
if (!particle.isPhysicalPrimary() || !std::isfinite(particle.eta()) || std::abs(particle.eta()) >= GenEtaMax) {
5926+
continue;
5927+
}
5928+
auto const* pdgParticle = pdgDB->GetParticle(particle.pdgCode());
5929+
if (pdgParticle == nullptr || pdgParticle->Charge() == 0.) {
5930+
continue;
5931+
}
5932+
++nGenCharged;
5933+
// Linearized transverse sphericity, using truth primary charged
5934+
// particles with |eta|<0.8 and pT>=0.15 GeV/c (not spherocity).
5935+
if (std::isfinite(particle.pt()) && particle.pt() >= ShapePtMin && std::isfinite(particle.phi())) {
5936+
++nShapeTracks;
5937+
sumPt += particle.pt();
5938+
q2x += particle.pt() * std::cos(2. * particle.phi());
5939+
q2y += particle.pt() * std::sin(2. * particle.phi());
5940+
}
5941+
}
5942+
int nPVTracks = 0, nLabeledPVTracks = 0, nSameMcPVTracks = 0;
5943+
std::unordered_set<int64_t> bestMatchedMcIds;
5944+
const auto bestTracks = tracks.sliceBy(pairLossTracksPerCollision, collision.globalIndex());
5945+
for (auto const& track : bestTracks) {
5946+
const bool sameMc = track.has_mcParticle() && currentMcIds.count(track.mcParticleId()) != 0;
5947+
if (sameMc) {
5948+
bestMatchedMcIds.insert(track.mcParticleId());
5949+
}
5950+
if (track.isPVContributor()) {
5951+
++nPVTracks;
5952+
nLabeledPVTracks += track.has_mcParticle();
5953+
nSameMcPVTracks += sameMc;
5954+
}
5955+
}
5956+
// Missing labels take precedence; purity otherwise uses all PV tracks.
5957+
const double purity = nPVTracks > 0 ? static_cast<double>(nSameMcPVTracks) / nPVTracks : 0.;
5958+
eventQualityClasses[2] = nPVTracks == 0 ? 0 : nLabeledPVTracks < nPVTracks ? 1
5959+
: purity < PurePVThreshold ? 2
5960+
: nSameMcPVTracks < nPVTracks ? 3
5961+
: 4;
5962+
const double bestFraction = nAnyMatched > 0 ? static_cast<double>(bestMatchedMcIds.size()) / nAnyMatched : 0.;
5963+
eventQualityClasses[3] = nAnyMatched == 0 ? 0 : recCollisions.size() == 1 ? 1
5964+
: bestFraction >= DominantCollisionFraction ? 2
5965+
: bestFraction >= MajorityCollisionFraction ? 3
5966+
: 4;
5967+
const double timeResolution = collision.collisionTimeRes(); // ns
5968+
eventQualityClasses[4] = !std::isfinite(timeResolution) || timeResolution <= 0. ? 0 : 1 + classFromEdges(timeResolution, std::array{25., 100., 500.});
5969+
const auto occupancy = collision.trackOccupancyInTimeRange();
5970+
eventQualityClasses[5] = occupancy < 0 ? 0 : 1 + classFromEdges(occupancy, std::array{1, 100, 500, 1000});
5971+
eventQualityClasses[6] = classFromEdges(nGenCharged, std::array{1, 5, 10, 20, 40});
5972+
if (nShapeTracks >= MinShapeTracks && sumPt > 0.) {
5973+
const double sphericity = std::clamp(1. - std::hypot(q2x, q2y) / sumPt, 0., 1.);
5974+
eventQualityClasses[7] = 1 + classFromEdges(sphericity, std::array{0.3, 0.7});
5975+
}
5976+
}
5977+
auto fillEventQuality = [&](int entryKind, float deltaPhi, float deltaEta, float assocPt, float triggerPt, bool hasAny, bool hasFinal) {
5978+
if (!pairLossK0Configurations.doEventQualityStudy) {
5979+
return;
5980+
}
5981+
static_for<0, PairLossNEventQualityGroups - 1>([&](auto i) {
5982+
constexpr int Index = i.value;
5983+
histos.fill(HIST("ClosureTest/PairLossK0/EventQuality/") + HIST(PairLossEventQualityNames[Index]) + HIST("/Truth"), eventQualityClasses[Index], entryKind, deltaPhi, deltaEta, assocPt, triggerPt);
5984+
if (hasAny) {
5985+
histos.fill(HIST("ClosureTest/PairLossK0/EventQuality/") + HIST(PairLossEventQualityNames[Index]) + HIST("/AnyTrackBoth"), eventQualityClasses[Index], entryKind, deltaPhi, deltaEta, assocPt, triggerPt);
5986+
}
5987+
if (hasFinal) {
5988+
histos.fill(HIST("ClosureTest/PairLossK0/EventQuality/") + HIST(PairLossEventQualityNames[Index]) + HIST("/Final"), eventQualityClasses[Index], entryKind, deltaPhi, deltaEta, assocPt, triggerPt);
5989+
}
5990+
});
5991+
};
5992+
// Unused coordinates lie inside regular bins. Select entry kind before
5993+
// projecting; reset pair-coordinate ranges for event/trigger counts.
5994+
const float countDeltaPhi = (axisRanges[0][0] + axisRanges[0][1]) * 0.5f;
5995+
const float countDeltaEta = (axisRanges[1][0] + axisRanges[1][1]) * 0.5f;
5996+
fillEventQuality(0, countDeltaPhi, countDeltaEta, axisRanges[2][0], axisRanges[3][0], true, true);
58415997
std::vector<PairLossTruthTrackInfo> pairLossTruthTriggers;
58425998
std::vector<PairLossTruthK0Info> pairLossTruthK0s;
58435999

@@ -5902,6 +6058,9 @@ struct HStrangeCorrelation {
59026058
// at truth level in Truth/ and AnyTrack/, at any level in AnyTrackK0/ and
59036059
// AnyTrackBoth/, at fully-selected level in Final/.
59046060
for (auto const& truthTrigger : pairLossTruthTriggers) {
6061+
if (pairLossK0Configurations.doEventQualityStudy) {
6062+
fillEventQuality(1, countDeltaPhi, countDeltaEta, axisRanges[2][0], truthTrigger.pt, pairLossAnyTrackMcParticleIds.count(truthTrigger.globalIndex) != 0, pairLossHasFinalTrigger(truthTrigger.globalIndex));
6063+
}
59056064
histos.fill(HIST("ClosureTest/PairLossK0/Truth/hTrigger"), truthTrigger.pt, truthTrigger.eta, truthTrigger.phi);
59066065
histos.fill(HIST("ClosureTest/PairLossK0/AnyTrackK0/hTrigger"), truthTrigger.pt, truthTrigger.eta, truthTrigger.phi);
59076066
if (pairLossAnyTrackMcParticleIds.find(truthTrigger.globalIndex) != pairLossAnyTrackMcParticleIds.end()) {
@@ -5952,7 +6111,9 @@ struct HStrangeCorrelation {
59526111
if (triggerHasAnyTrack && k0HasAnyV0) {
59536112
histos.fill(HIST("ClosureTest/PairLossK0/AnyTrackBoth/sameEvent/K0Short"), truthDeltaPhi, truthDeltaEta, truthK0.pt, truthTrigger.pt, pairLossBestCollisionVtxZ, pairLossBestCollisionMultiplicity);
59546113
}
5955-
if (pairLossHasFinalPair(truthTrigger.globalIndex, truthK0.globalIndex)) {
6114+
const bool hasFinalPair = pairLossHasFinalPair(truthTrigger.globalIndex, truthK0.globalIndex);
6115+
fillEventQuality(2, truthDeltaPhi, truthDeltaEta, truthK0.pt, truthTrigger.pt, triggerHasAnyTrack && k0HasAnyV0, hasFinalPair);
6116+
if (hasFinalPair) {
59566117
histos.fill(HIST("ClosureTest/PairLossK0/Final/sameEvent/K0Short"), truthDeltaPhi, truthDeltaEta, truthK0.pt, truthTrigger.pt, pairLossBestCollisionVtxZ, pairLossBestCollisionMultiplicity);
59576118
}
59586119
}

0 commit comments

Comments
 (0)