Skip to content

Commit 93b23d0

Browse files
authored
[PWGCF] Add histograms for purity and fraction extraction to track track task in femto framework (#18512)
1 parent da3e833 commit 93b23d0

11 files changed

Lines changed: 469 additions & 38 deletions

‎PWGCF/Femto/Core/femtoUtils.h‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,59 @@ float unBinLogUnsigned(T binned, float magMin, float magMax)
356356
return mag;
357357
}
358358

359+
// ---------------------------------------------------------------------------
360+
// purity/fraction packing: up to 4 species' log-binned nSigma values (see
361+
// binLogSigned<uint16_t>) packed bitwise into a single int64_t column. Slot
362+
// assignment (which species occupies which of the 4 slots) is decided at
363+
// producer init time from which requirePidXxx flags are active, and exposed
364+
// via a self-documenting histogram -- these functions only do the fixed-slot
365+
// bit shifting, nothing species-specific.
366+
// ---------------------------------------------------------------------------
367+
368+
constexpr int PurityNSlots = 4;
369+
370+
/// pack 4 already-binned uint16_t codes (one per slot) into a single int64_t
371+
inline int64_t packPurity4(uint16_t slot0, uint16_t slot1, uint16_t slot2, uint16_t slot3)
372+
{
373+
return (static_cast<int64_t>(slot0) << 48) |
374+
(static_cast<int64_t>(slot1) << 32) |
375+
(static_cast<int64_t>(slot2) << 16) |
376+
static_cast<int64_t>(slot3);
377+
}
378+
379+
/// extract the raw uint16_t code of one of the 4 slots (0..3) from a packed int64_t
380+
inline uint16_t unpackPurity4(int64_t packed, int slot)
381+
{
382+
switch (slot) {
383+
case 0:
384+
return static_cast<uint16_t>((packed >> 48) & 0xFFFF);
385+
case 1:
386+
return static_cast<uint16_t>((packed >> 32) & 0xFFFF);
387+
case 2:
388+
return static_cast<uint16_t>((packed >> 16) & 0xFFFF);
389+
case 3:
390+
return static_cast<uint16_t>(packed & 0xFFFF);
391+
default:
392+
LOG(fatal) << "unpackPurity4: invalid slot " << slot << " (must be 0-3). Breaking...";
393+
return 0;
394+
}
395+
}
396+
397+
/// pack 4 raw signed nSigma values (one per slot, unused slots pass 0) directly into a packed int64_t
398+
inline int64_t packPurityNSigma4(float nSigma0, float nSigma1, float nSigma2, float nSigma3, float magMin, float magMax)
399+
{
400+
return packPurity4(binLogSigned<uint16_t>(nSigma0, magMin, magMax),
401+
binLogSigned<uint16_t>(nSigma1, magMin, magMax),
402+
binLogSigned<uint16_t>(nSigma2, magMin, magMax),
403+
binLogSigned<uint16_t>(nSigma3, magMin, magMax));
404+
}
405+
406+
/// unpack the nSigma value of one of the 4 slots (0..3) from a packed int64_t
407+
inline float unpackPurityNSigma4(int64_t packed, int slot, float magMin, float magMax)
408+
{
409+
return unBinLogSigned<uint16_t>(unpackPurity4(packed, slot), magMin, magMax);
410+
}
411+
359412
}; // namespace utils
360413
}; // namespace o2::analysis::femto
361414
//

‎PWGCF/Femto/Core/modes.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ enum class Mode : uint32_t {
5353
kMc = bit<Mode>(2),
5454
kSe = bit<Mode>(3),
5555
kMe = bit<Mode>(4),
56+
kFitVars = bit<Mode>(5), // purity/fraction histograms (tracks: packed nSigma tables; V0s: cpa) -- gates booking at init time
5657
kReco_Qa = kReco | kQa,
5758
kReco_Mc = kReco | kMc,
5859
kReco_Qa_Mc = kReco | kQa | kMc,
@@ -62,6 +63,10 @@ enum class Mode : uint32_t {
6263
kMe_Reco_Mc = kMe | kReco | kMc,
6364
kSe_Mc = kSe | kMc,
6465
kMe_Mc = kMe | kMc,
66+
kSe_Reco_FitVars = kSe | kReco | kFitVars,
67+
kMe_Reco_FitVars = kMe | kReco | kFitVars,
68+
kSe_Reco_Mc_FitVars = kSe | kReco | kMc | kFitVars,
69+
kMe_Reco_Mc_FitVars = kMe | kReco | kMc | kFitVars,
6570
};
6671

6772
enum class System : uint32_t {

‎PWGCF/Femto/Core/pairBuilder.h‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ class PairTrackTrackBuilder
9191
std::map<T12, std::vector<o2::framework::AxisSpec>> const& trackHistSpec2,
9292
std::map<T13, std::vector<o2::framework::AxisSpec>> const& pairHistSpec,
9393
std::map<T14, std::vector<o2::framework::AxisSpec>> const& cprHistSpec,
94-
std::map<T15, std::vector<o2::framework::AxisSpec>> const& pairCleanerHistSpec)
94+
std::map<T15, std::vector<o2::framework::AxisSpec>> const& pairCleanerHistSpec,
95+
int puritySlotMask1 = 1,
96+
int puritySlotMask2 = 1)
9597
{
9698

9799
// check if correlate the same tracks or not
@@ -106,8 +108,8 @@ class PairTrackTrackBuilder
106108
if (mSameSpecies) {
107109
mTrackCleaner1.init(confTrackCleaner1);
108110

109-
mTrackHistManagerSe1.template init<modeSe>(registry, trackHistSpec1, confTrackSelection1);
110-
mTrackHistManagerMe1.template init<modeMe>(registry, trackHistSpec1, confTrackSelection1);
111+
mTrackHistManagerSe1.template init<modeSe>(registry, trackHistSpec1, confTrackSelection1, puritySlotMask1);
112+
mTrackHistManagerMe1.template init<modeMe>(registry, trackHistSpec1, confTrackSelection1, puritySlotMask1);
111113

112114
mPairHistManagerSe.setMass(confTrackSelection1.pdgCodeAbs.value, confTrackSelection1.pdgCodeAbs.value);
113115
mPairHistManagerSe.setCharge(confTrackSelection1.chargeAbs.value, confTrackSelection1.chargeAbs.value);
@@ -119,10 +121,10 @@ class PairTrackTrackBuilder
119121
} else {
120122
mTrackCleaner1.init(confTrackCleaner1);
121123
mTrackCleaner2.init(confTrackCleaner2);
122-
mTrackHistManagerSe1.template init<modeSe>(registry, trackHistSpec1, confTrackSelection1);
123-
mTrackHistManagerSe2.template init<modeSe>(registry, trackHistSpec2, confTrackSelection2);
124-
mTrackHistManagerMe1.template init<modeMe>(registry, trackHistSpec1, confTrackSelection1);
125-
mTrackHistManagerMe2.template init<modeMe>(registry, trackHistSpec2, confTrackSelection2);
124+
mTrackHistManagerSe1.template init<modeSe>(registry, trackHistSpec1, confTrackSelection1, puritySlotMask1);
125+
mTrackHistManagerSe2.template init<modeSe>(registry, trackHistSpec2, confTrackSelection2, puritySlotMask2);
126+
mTrackHistManagerMe1.template init<modeMe>(registry, trackHistSpec1, confTrackSelection1, puritySlotMask1);
127+
mTrackHistManagerMe2.template init<modeMe>(registry, trackHistSpec2, confTrackSelection2, puritySlotMask2);
126128

127129
mPairHistManagerSe.setMass(confTrackSelection1.pdgCodeAbs.value, confTrackSelection2.pdgCodeAbs.value);
128130
mPairHistManagerSe.setCharge(confTrackSelection1.chargeAbs.value, confTrackSelection2.chargeAbs.value);

‎PWGCF/Femto/Core/trackBuilder.h‎

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,9 @@ struct TrackBuilderProducts : o2::framework::ProducesGroup {
598598
o2::framework::Produces<o2::aod::FDeuteronPids> producedDeuteronPids;
599599
o2::framework::Produces<o2::aod::FTritonPids> producedTritonPids;
600600
o2::framework::Produces<o2::aod::FHeliumPids> producedHeliumPids;
601+
o2::framework::Produces<o2::aod::FItsPurity> producedItsPurity;
602+
o2::framework::Produces<o2::aod::FTpcPurity> producedTpcPurity;
603+
o2::framework::Produces<o2::aod::FTofPurity> producedTofPurity;
601604
};
602605

603606
struct ConfTrackTables : o2::framework::ConfigurableGroup {
@@ -615,6 +618,9 @@ struct ConfTrackTables : o2::framework::ConfigurableGroup {
615618
o2::framework::Configurable<int> produceDeuteronPids{"produceDeuteronPids", -1, "Produce DeuteronPids (-1: auto; 0 off; 1 on)"};
616619
o2::framework::Configurable<int> produceTritonPids{"produceTritonPids", -1, "Produce TritonPids (-1: auto; 0 off; 1 on)"};
617620
o2::framework::Configurable<int> produceHeliumPids{"produceHeliumPids", -1, "Produce HeliumPids (-1: auto; 0 off; 1 on)"};
621+
o2::framework::Configurable<int> produceItsPurity{"produceItsPurity", -1, "Produce ItsPurity (-1: auto; 0 off; 1 on)"};
622+
o2::framework::Configurable<int> produceTpcPurity{"produceTpcPurity", -1, "Produce TpcPurity (-1: auto; 0 off; 1 on)"};
623+
o2::framework::Configurable<int> produceTofPurity{"produceTofPurity", -1, "Produce TofPurity (-1: auto; 0 off; 1 on)"};
618624
};
619625

620626
template <auto& SelectionHistName, auto& FilterHistName>
@@ -642,6 +648,52 @@ class TrackBuilder
642648
mProduceDeuteronPids = utils::enableTable("FDeuteronPids_001", table.produceDeuteronPids.value, initContext);
643649
mProduceTritonPids = utils::enableTable("FTritonPids_001", table.produceTritonPids.value, initContext);
644650
mProduceHeliumPids = utils::enableTable("FHeliumPids_001", table.produceHeliumPids.value, initContext);
651+
mProduceItsPurity = utils::enableTable("FItsPurity_001", table.produceItsPurity.value, initContext);
652+
mProduceTpcPurity = utils::enableTable("FTpcPurity_001", table.produceTpcPurity.value, initContext);
653+
mProduceTofPurity = utils::enableTable("FTofPurity_001", table.produceTofPurity.value, initContext);
654+
655+
if (mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity) {
656+
// assign each active species (requirePidXxx == true) to the next free slot (0-3), in
657+
// canonical order (PuritySpeciesName); species with requirePidXxx == false get no slot.
658+
const std::array<bool, PuritySpeciesLast> requirePid = {
659+
config.requirePidElectron.value, config.requirePidPion.value, config.requirePidKaon.value,
660+
config.requirePidProton.value, config.requirePidDeuteron.value, config.requirePidTriton.value, config.requirePidHelium.value};
661+
662+
int nextSlot = 0;
663+
for (int i = 0; i < PuritySpeciesLast; i++) {
664+
if (!requirePid[i]) {
665+
continue;
666+
}
667+
if (nextSlot >= utils::PurityNSlots) {
668+
LOG(fatal) << "More than " << utils::PurityNSlots << " species have requirePidXxx enabled, "
669+
<< "but the purity tables only have " << utils::PurityNSlots << " slots. "
670+
<< "Disable requirePidXxx for species you don't need purity info for. Breaking...";
671+
}
672+
mPuritySlot[i] = nextSlot;
673+
nextSlot++;
674+
}
675+
676+
// one bin per species (own SelectionName -> own cutculator group), plus the usual
677+
// trailing "All analyzed"/"All passed" pair that cutculator always skips by position.
678+
// BitPosition___X (unassigned species) is deliberately excluded from cutculator's
679+
// "neutral" category, so nothing is selectable for a species that isn't configured here.
680+
// Picking the one assigned bit for a species gives exactly 1<<slot -- the one-hot
681+
// puritySlotMask value to paste into the downstream TrackSelection, with no changes
682+
// needed in cutculator.py itself.
683+
registry->add("hPuritySlotAssignment", "Purity slot assignment", o2::framework::HistType::kTH1F, {{PuritySpeciesLast + 2, -0.5, PuritySpeciesLast + 2 - 0.5}});
684+
auto hSlotAssignment = registry->get<TH1>(HIST("hPuritySlotAssignment"));
685+
for (int i = 0; i < PuritySpeciesLast; i++) {
686+
std::string bitPosition = mPuritySlot[i] >= 0 ? std::to_string(mPuritySlot[i]) : "X";
687+
std::string value = mPuritySlot[i] >= 0 ? ("Slot " + std::to_string(mPuritySlot[i])) : "Not configured";
688+
std::string label = std::string("SelectionName___") + PuritySpeciesName[i] +
689+
":::MinimalCut___0:::OptionalCut___0:::BitPosition___" + bitPosition +
690+
":::Value___" + value;
691+
hSlotAssignment->GetXaxis()->SetBinLabel(i + 1, label.c_str());
692+
hSlotAssignment->Fill(i, 1);
693+
}
694+
hSlotAssignment->GetXaxis()->SetBinLabel(PuritySpeciesLast + 1, "All analyzed");
695+
hSlotAssignment->GetXaxis()->SetBinLabel(PuritySpeciesLast + 2, "All passed");
696+
}
645697

646698
if (mProduceTracks && mProduceLiteTracks) {
647699
LOG(fatal) << "FTracks and FLiteTracks are mutually exclusive -- enable only one. "
@@ -660,7 +712,7 @@ class TrackBuilder
660712
<< "or switch to FTracks if CollisionTables.produceCollisions is enabled instead.";
661713
}
662714

663-
if (mProduceTracks || mProduceLiteTracks || mProduceTrackMasks || mProduceTrackMass || mProduceTrackDcas || mProduceTrackExtras || mProduceElectronPids || mProducePionPids || mProduceKaonPids || mProduceProtonPids || mProduceDeuteronPids || mProduceTritonPids || mProduceHeliumPids) {
715+
if (mProduceTracks || mProduceLiteTracks || mProduceTrackMasks || mProduceTrackMass || mProduceTrackDcas || mProduceTrackExtras || mProduceElectronPids || mProducePionPids || mProduceKaonPids || mProduceProtonPids || mProduceDeuteronPids || mProduceTritonPids || mProduceHeliumPids || mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity) {
664716
mFillAnyTable = true;
665717
} else {
666718
LOG(info) << "No tables configured, Selection object will not be configured...";
@@ -797,6 +849,41 @@ class TrackBuilder
797849
}
798850
trackProducts.producedHeliumPids(itsHe, track.tpcNSigmaHe(), track.tofNSigmaHe());
799851
}
852+
if (mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity) {
853+
// raw nSigma per species, same values already used for the FXxxPids blocks above --
854+
// unused slots (species without a requirePidXxx flag, or not covered by this track
855+
// type) stay at the 0 placeholder, same contract as the missing-Tof case
856+
std::array<float, PuritySpeciesLast> itsNSigma = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
857+
std::array<float, PuritySpeciesLast> tpcNSigma = {track.tpcNSigmaEl(), track.tpcNSigmaPi(), track.tpcNSigmaKa(), track.tpcNSigmaPr(), track.tpcNSigmaDe(), track.tpcNSigmaTr(), track.tpcNSigmaHe()};
858+
std::array<float, PuritySpeciesLast> tofNSigma = {track.tofNSigmaEl(), track.tofNSigmaPi(), track.tofNSigmaKa(), track.tofNSigmaPr(), track.tofNSigmaDe(), track.tofNSigmaTr(), track.tofNSigmaHe()};
859+
860+
if constexpr (requires(T1 t) { t.itsNSigmaEl(); }) {
861+
itsNSigma = {track.itsNSigmaEl(), track.itsNSigmaPi(), track.itsNSigmaKa(), track.itsNSigmaPr(), track.itsNSigmaDe(), track.itsNSigmaTr(), track.itsNSigmaHe()};
862+
}
863+
864+
std::array<float, utils::PurityNSlots> itsSlots = {0.f, 0.f, 0.f, 0.f};
865+
std::array<float, utils::PurityNSlots> tpcSlots = {0.f, 0.f, 0.f, 0.f};
866+
std::array<float, utils::PurityNSlots> tofSlots = {0.f, 0.f, 0.f, 0.f};
867+
bool hasTof = track.hasTOF();
868+
for (int i = 0; i < PuritySpeciesLast; i++) {
869+
if (mPuritySlot[i] < 0) {
870+
continue;
871+
}
872+
itsSlots[mPuritySlot[i]] = itsNSigma[i];
873+
tpcSlots[mPuritySlot[i]] = tpcNSigma[i];
874+
tofSlots[mPuritySlot[i]] = hasTof ? tofNSigma[i] : 0.f; // PurityHasTof is the gate; 0 here is just a placeholder
875+
}
876+
877+
if (mProduceItsPurity) {
878+
trackProducts.producedItsPurity(utils::packPurityNSigma4(itsSlots[0], itsSlots[1], itsSlots[2], itsSlots[3], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax));
879+
}
880+
if (mProduceTpcPurity) {
881+
trackProducts.producedTpcPurity(utils::packPurityNSigma4(tpcSlots[0], tpcSlots[1], tpcSlots[2], tpcSlots[3], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax));
882+
}
883+
if (mProduceTofPurity) {
884+
trackProducts.producedTofPurity(utils::packPurityNSigma4(tofSlots[0], tofSlots[1], tofSlots[2], tofSlots[3], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax), hasTof);
885+
}
886+
}
800887
return true;
801888
}
802889

@@ -892,6 +979,16 @@ class TrackBuilder
892979
bool mProduceDeuteronPids = false;
893980
bool mProduceTritonPids = false;
894981
bool mProduceHeliumPids = false;
982+
bool mProduceItsPurity = false;
983+
bool mProduceTpcPurity = false;
984+
bool mProduceTofPurity = false;
985+
986+
// canonical species order for purity slot assignment -- fixed, not configurable (see
987+
// init() for how requirePidXxx flags map onto slots 0-3)
988+
static constexpr int PuritySpeciesLast = 7;
989+
static constexpr std::array<const char*, PuritySpeciesLast> PuritySpeciesName = {"Electron", "Pion", "Kaon", "Proton", "Deuteron", "Triton", "Helium"};
990+
// mPuritySlot[i] = assigned slot (0-3) for species i (PuritySpeciesName order), or -1 if not assigned
991+
std::array<int, PuritySpeciesLast> mPuritySlot = {-1, -1, -1, -1, -1, -1, -1};
895992

896993
std::unordered_map<int64_t, int64_t> indexMap; // for mapping tracks to daughters of lambdas, cascades and resonances ...
897994
};
@@ -900,6 +997,12 @@ struct TrackBuilderDerivedToDerivedProducts : o2::framework::ProducesGroup {
900997
o2::framework::Produces<o2::aod::StoredFTracks> producedTracks;
901998
o2::framework::Produces<o2::aod::StoredFTrackMasks> producedTrackMasks;
902999
o2::framework::Produces<o2::aod::StoredFTrackMass> producedTrackMass;
1000+
// purity/fraction, copied through if the input track type has them joined (same
1001+
// compile-time-detected, opt-in-by-type pattern as producedTrackMass above)
1002+
o2::framework::Produces<o2::aod::StoredFItsPurity> producedItsPurity;
1003+
o2::framework::Produces<o2::aod::StoredFTpcPurity> producedTpcPurity;
1004+
o2::framework::Produces<o2::aod::StoredFTofPurity> producedTofPurity;
1005+
o2::framework::Produces<o2::aod::StoredFTrackDcas> producedTrackDcas;
9031006
};
9041007

9051008
struct ConfTrackTablesDerivedToDerived : o2::framework::ConfigurableGroup {
@@ -977,6 +1080,14 @@ class TrackBuilderDerivedToDerived
9771080
if constexpr (utils::HasMass<T1>) {
9781081
trackProducts.producedTrackMass(track.mass());
9791082
}
1083+
if constexpr (requires(T1 t) { t.itsPackedNSigma(); }) {
1084+
trackProducts.producedItsPurity(track.itsPackedNSigma());
1085+
trackProducts.producedTpcPurity(track.tpcPackedNSigma());
1086+
trackProducts.producedTofPurity(track.tofPackedNSigma(), track.purityHasTof());
1087+
}
1088+
if constexpr (requires(T1 t) { t.dcaXY(); }) {
1089+
trackProducts.producedTrackDcas(track.dcaXY(), track.dcaZ());
1090+
}
9801091

9811092
const int64_t idx = trackProducts.producedTracks.lastIndex();
9821093
indexMap.emplace(track.globalIndex(), idx);

0 commit comments

Comments
 (0)