diff --git a/PWGCF/Femto/Core/femtoUtils.h b/PWGCF/Femto/Core/femtoUtils.h index 07b6e47705a..22dc4fa37e9 100644 --- a/PWGCF/Femto/Core/femtoUtils.h +++ b/PWGCF/Femto/Core/femtoUtils.h @@ -356,6 +356,59 @@ float unBinLogUnsigned(T binned, float magMin, float magMax) return mag; } +// --------------------------------------------------------------------------- +// purity/fraction packing: up to 4 species' log-binned nSigma values (see +// binLogSigned) packed bitwise into a single int64_t column. Slot +// assignment (which species occupies which of the 4 slots) is decided at +// producer init time from which requirePidXxx flags are active, and exposed +// via a self-documenting histogram -- these functions only do the fixed-slot +// bit shifting, nothing species-specific. +// --------------------------------------------------------------------------- + +constexpr int PurityNSlots = 4; + +/// pack 4 already-binned uint16_t codes (one per slot) into a single int64_t +inline int64_t packPurity4(uint16_t slot0, uint16_t slot1, uint16_t slot2, uint16_t slot3) +{ + return (static_cast(slot0) << 48) | + (static_cast(slot1) << 32) | + (static_cast(slot2) << 16) | + static_cast(slot3); +} + +/// extract the raw uint16_t code of one of the 4 slots (0..3) from a packed int64_t +inline uint16_t unpackPurity4(int64_t packed, int slot) +{ + switch (slot) { + case 0: + return static_cast((packed >> 48) & 0xFFFF); + case 1: + return static_cast((packed >> 32) & 0xFFFF); + case 2: + return static_cast((packed >> 16) & 0xFFFF); + case 3: + return static_cast(packed & 0xFFFF); + default: + LOG(fatal) << "unpackPurity4: invalid slot " << slot << " (must be 0-3). Breaking..."; + return 0; + } +} + +/// pack 4 raw signed nSigma values (one per slot, unused slots pass 0) directly into a packed int64_t +inline int64_t packPurityNSigma4(float nSigma0, float nSigma1, float nSigma2, float nSigma3, float magMin, float magMax) +{ + return packPurity4(binLogSigned(nSigma0, magMin, magMax), + binLogSigned(nSigma1, magMin, magMax), + binLogSigned(nSigma2, magMin, magMax), + binLogSigned(nSigma3, magMin, magMax)); +} + +/// unpack the nSigma value of one of the 4 slots (0..3) from a packed int64_t +inline float unpackPurityNSigma4(int64_t packed, int slot, float magMin, float magMax) +{ + return unBinLogSigned(unpackPurity4(packed, slot), magMin, magMax); +} + }; // namespace utils }; // namespace o2::analysis::femto // diff --git a/PWGCF/Femto/Core/modes.h b/PWGCF/Femto/Core/modes.h index e0a70e644ec..0b0bf75121e 100644 --- a/PWGCF/Femto/Core/modes.h +++ b/PWGCF/Femto/Core/modes.h @@ -53,6 +53,7 @@ enum class Mode : uint32_t { kMc = bit(2), kSe = bit(3), kMe = bit(4), + kFitVars = bit(5), // purity/fraction histograms (tracks: packed nSigma tables; V0s: cpa) -- gates booking at init time kReco_Qa = kReco | kQa, kReco_Mc = kReco | kMc, kReco_Qa_Mc = kReco | kQa | kMc, @@ -62,6 +63,10 @@ enum class Mode : uint32_t { kMe_Reco_Mc = kMe | kReco | kMc, kSe_Mc = kSe | kMc, kMe_Mc = kMe | kMc, + kSe_Reco_FitVars = kSe | kReco | kFitVars, + kMe_Reco_FitVars = kMe | kReco | kFitVars, + kSe_Reco_Mc_FitVars = kSe | kReco | kMc | kFitVars, + kMe_Reco_Mc_FitVars = kMe | kReco | kMc | kFitVars, }; enum class System : uint32_t { diff --git a/PWGCF/Femto/Core/pairBuilder.h b/PWGCF/Femto/Core/pairBuilder.h index dd157bbb0ae..a2c46bbb875 100644 --- a/PWGCF/Femto/Core/pairBuilder.h +++ b/PWGCF/Femto/Core/pairBuilder.h @@ -91,7 +91,9 @@ class PairTrackTrackBuilder std::map> const& trackHistSpec2, std::map> const& pairHistSpec, std::map> const& cprHistSpec, - std::map> const& pairCleanerHistSpec) + std::map> const& pairCleanerHistSpec, + int puritySlotMask1 = 1, + int puritySlotMask2 = 1) { // check if correlate the same tracks or not @@ -106,8 +108,8 @@ class PairTrackTrackBuilder if (mSameSpecies) { mTrackCleaner1.init(confTrackCleaner1); - mTrackHistManagerSe1.template init(registry, trackHistSpec1, confTrackSelection1); - mTrackHistManagerMe1.template init(registry, trackHistSpec1, confTrackSelection1); + mTrackHistManagerSe1.template init(registry, trackHistSpec1, confTrackSelection1, puritySlotMask1); + mTrackHistManagerMe1.template init(registry, trackHistSpec1, confTrackSelection1, puritySlotMask1); mPairHistManagerSe.setMass(confTrackSelection1.pdgCodeAbs.value, confTrackSelection1.pdgCodeAbs.value); mPairHistManagerSe.setCharge(confTrackSelection1.chargeAbs.value, confTrackSelection1.chargeAbs.value); @@ -119,10 +121,10 @@ class PairTrackTrackBuilder } else { mTrackCleaner1.init(confTrackCleaner1); mTrackCleaner2.init(confTrackCleaner2); - mTrackHistManagerSe1.template init(registry, trackHistSpec1, confTrackSelection1); - mTrackHistManagerSe2.template init(registry, trackHistSpec2, confTrackSelection2); - mTrackHistManagerMe1.template init(registry, trackHistSpec1, confTrackSelection1); - mTrackHistManagerMe2.template init(registry, trackHistSpec2, confTrackSelection2); + mTrackHistManagerSe1.template init(registry, trackHistSpec1, confTrackSelection1, puritySlotMask1); + mTrackHistManagerSe2.template init(registry, trackHistSpec2, confTrackSelection2, puritySlotMask2); + mTrackHistManagerMe1.template init(registry, trackHistSpec1, confTrackSelection1, puritySlotMask1); + mTrackHistManagerMe2.template init(registry, trackHistSpec2, confTrackSelection2, puritySlotMask2); mPairHistManagerSe.setMass(confTrackSelection1.pdgCodeAbs.value, confTrackSelection2.pdgCodeAbs.value); mPairHistManagerSe.setCharge(confTrackSelection1.chargeAbs.value, confTrackSelection2.chargeAbs.value); diff --git a/PWGCF/Femto/Core/trackBuilder.h b/PWGCF/Femto/Core/trackBuilder.h index 8150e840011..c98ec68c8e4 100644 --- a/PWGCF/Femto/Core/trackBuilder.h +++ b/PWGCF/Femto/Core/trackBuilder.h @@ -598,6 +598,9 @@ struct TrackBuilderProducts : o2::framework::ProducesGroup { o2::framework::Produces producedDeuteronPids; o2::framework::Produces producedTritonPids; o2::framework::Produces producedHeliumPids; + o2::framework::Produces producedItsPurity; + o2::framework::Produces producedTpcPurity; + o2::framework::Produces producedTofPurity; }; struct ConfTrackTables : o2::framework::ConfigurableGroup { @@ -615,6 +618,9 @@ struct ConfTrackTables : o2::framework::ConfigurableGroup { o2::framework::Configurable produceDeuteronPids{"produceDeuteronPids", -1, "Produce DeuteronPids (-1: auto; 0 off; 1 on)"}; o2::framework::Configurable produceTritonPids{"produceTritonPids", -1, "Produce TritonPids (-1: auto; 0 off; 1 on)"}; o2::framework::Configurable produceHeliumPids{"produceHeliumPids", -1, "Produce HeliumPids (-1: auto; 0 off; 1 on)"}; + o2::framework::Configurable produceItsPurity{"produceItsPurity", -1, "Produce ItsPurity (-1: auto; 0 off; 1 on)"}; + o2::framework::Configurable produceTpcPurity{"produceTpcPurity", -1, "Produce TpcPurity (-1: auto; 0 off; 1 on)"}; + o2::framework::Configurable produceTofPurity{"produceTofPurity", -1, "Produce TofPurity (-1: auto; 0 off; 1 on)"}; }; template @@ -642,6 +648,52 @@ class TrackBuilder mProduceDeuteronPids = utils::enableTable("FDeuteronPids_001", table.produceDeuteronPids.value, initContext); mProduceTritonPids = utils::enableTable("FTritonPids_001", table.produceTritonPids.value, initContext); mProduceHeliumPids = utils::enableTable("FHeliumPids_001", table.produceHeliumPids.value, initContext); + mProduceItsPurity = utils::enableTable("FItsPurity_001", table.produceItsPurity.value, initContext); + mProduceTpcPurity = utils::enableTable("FTpcPurity_001", table.produceTpcPurity.value, initContext); + mProduceTofPurity = utils::enableTable("FTofPurity_001", table.produceTofPurity.value, initContext); + + if (mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity) { + // assign each active species (requirePidXxx == true) to the next free slot (0-3), in + // canonical order (PuritySpeciesName); species with requirePidXxx == false get no slot. + const std::array requirePid = { + config.requirePidElectron.value, config.requirePidPion.value, config.requirePidKaon.value, + config.requirePidProton.value, config.requirePidDeuteron.value, config.requirePidTriton.value, config.requirePidHelium.value}; + + int nextSlot = 0; + for (int i = 0; i < PuritySpeciesLast; i++) { + if (!requirePid[i]) { + continue; + } + if (nextSlot >= utils::PurityNSlots) { + LOG(fatal) << "More than " << utils::PurityNSlots << " species have requirePidXxx enabled, " + << "but the purity tables only have " << utils::PurityNSlots << " slots. " + << "Disable requirePidXxx for species you don't need purity info for. Breaking..."; + } + mPuritySlot[i] = nextSlot; + nextSlot++; + } + + // one bin per species (own SelectionName -> own cutculator group), plus the usual + // trailing "All analyzed"/"All passed" pair that cutculator always skips by position. + // BitPosition___X (unassigned species) is deliberately excluded from cutculator's + // "neutral" category, so nothing is selectable for a species that isn't configured here. + // Picking the one assigned bit for a species gives exactly 1<add("hPuritySlotAssignment", "Purity slot assignment", o2::framework::HistType::kTH1F, {{PuritySpeciesLast + 2, -0.5, PuritySpeciesLast + 2 - 0.5}}); + auto hSlotAssignment = registry->get(HIST("hPuritySlotAssignment")); + for (int i = 0; i < PuritySpeciesLast; i++) { + std::string bitPosition = mPuritySlot[i] >= 0 ? std::to_string(mPuritySlot[i]) : "X"; + std::string value = mPuritySlot[i] >= 0 ? ("Slot " + std::to_string(mPuritySlot[i])) : "Not configured"; + std::string label = std::string("SelectionName___") + PuritySpeciesName[i] + + ":::MinimalCut___0:::OptionalCut___0:::BitPosition___" + bitPosition + + ":::Value___" + value; + hSlotAssignment->GetXaxis()->SetBinLabel(i + 1, label.c_str()); + hSlotAssignment->Fill(i, 1); + } + hSlotAssignment->GetXaxis()->SetBinLabel(PuritySpeciesLast + 1, "All analyzed"); + hSlotAssignment->GetXaxis()->SetBinLabel(PuritySpeciesLast + 2, "All passed"); + } if (mProduceTracks && mProduceLiteTracks) { LOG(fatal) << "FTracks and FLiteTracks are mutually exclusive -- enable only one. " @@ -660,7 +712,7 @@ class TrackBuilder << "or switch to FTracks if CollisionTables.produceCollisions is enabled instead."; } - if (mProduceTracks || mProduceLiteTracks || mProduceTrackMasks || mProduceTrackMass || mProduceTrackDcas || mProduceTrackExtras || mProduceElectronPids || mProducePionPids || mProduceKaonPids || mProduceProtonPids || mProduceDeuteronPids || mProduceTritonPids || mProduceHeliumPids) { + if (mProduceTracks || mProduceLiteTracks || mProduceTrackMasks || mProduceTrackMass || mProduceTrackDcas || mProduceTrackExtras || mProduceElectronPids || mProducePionPids || mProduceKaonPids || mProduceProtonPids || mProduceDeuteronPids || mProduceTritonPids || mProduceHeliumPids || mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity) { mFillAnyTable = true; } else { LOG(info) << "No tables configured, Selection object will not be configured..."; @@ -797,6 +849,41 @@ class TrackBuilder } trackProducts.producedHeliumPids(itsHe, track.tpcNSigmaHe(), track.tofNSigmaHe()); } + if (mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity) { + // raw nSigma per species, same values already used for the FXxxPids blocks above -- + // unused slots (species without a requirePidXxx flag, or not covered by this track + // type) stay at the 0 placeholder, same contract as the missing-Tof case + std::array itsNSigma = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}; + std::array tpcNSigma = {track.tpcNSigmaEl(), track.tpcNSigmaPi(), track.tpcNSigmaKa(), track.tpcNSigmaPr(), track.tpcNSigmaDe(), track.tpcNSigmaTr(), track.tpcNSigmaHe()}; + std::array tofNSigma = {track.tofNSigmaEl(), track.tofNSigmaPi(), track.tofNSigmaKa(), track.tofNSigmaPr(), track.tofNSigmaDe(), track.tofNSigmaTr(), track.tofNSigmaHe()}; + + if constexpr (requires(T1 t) { t.itsNSigmaEl(); }) { + itsNSigma = {track.itsNSigmaEl(), track.itsNSigmaPi(), track.itsNSigmaKa(), track.itsNSigmaPr(), track.itsNSigmaDe(), track.itsNSigmaTr(), track.itsNSigmaHe()}; + } + + std::array itsSlots = {0.f, 0.f, 0.f, 0.f}; + std::array tpcSlots = {0.f, 0.f, 0.f, 0.f}; + std::array tofSlots = {0.f, 0.f, 0.f, 0.f}; + bool hasTof = track.hasTOF(); + for (int i = 0; i < PuritySpeciesLast; i++) { + if (mPuritySlot[i] < 0) { + continue; + } + itsSlots[mPuritySlot[i]] = itsNSigma[i]; + tpcSlots[mPuritySlot[i]] = tpcNSigma[i]; + tofSlots[mPuritySlot[i]] = hasTof ? tofNSigma[i] : 0.f; // PurityHasTof is the gate; 0 here is just a placeholder + } + + if (mProduceItsPurity) { + trackProducts.producedItsPurity(utils::packPurityNSigma4(itsSlots[0], itsSlots[1], itsSlots[2], itsSlots[3], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax)); + } + if (mProduceTpcPurity) { + trackProducts.producedTpcPurity(utils::packPurityNSigma4(tpcSlots[0], tpcSlots[1], tpcSlots[2], tpcSlots[3], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax)); + } + if (mProduceTofPurity) { + trackProducts.producedTofPurity(utils::packPurityNSigma4(tofSlots[0], tofSlots[1], tofSlots[2], tofSlots[3], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax), hasTof); + } + } return true; } @@ -892,6 +979,16 @@ class TrackBuilder bool mProduceDeuteronPids = false; bool mProduceTritonPids = false; bool mProduceHeliumPids = false; + bool mProduceItsPurity = false; + bool mProduceTpcPurity = false; + bool mProduceTofPurity = false; + + // canonical species order for purity slot assignment -- fixed, not configurable (see + // init() for how requirePidXxx flags map onto slots 0-3) + static constexpr int PuritySpeciesLast = 7; + static constexpr std::array PuritySpeciesName = {"Electron", "Pion", "Kaon", "Proton", "Deuteron", "Triton", "Helium"}; + // mPuritySlot[i] = assigned slot (0-3) for species i (PuritySpeciesName order), or -1 if not assigned + std::array mPuritySlot = {-1, -1, -1, -1, -1, -1, -1}; std::unordered_map indexMap; // for mapping tracks to daughters of lambdas, cascades and resonances ... }; @@ -900,6 +997,12 @@ struct TrackBuilderDerivedToDerivedProducts : o2::framework::ProducesGroup { o2::framework::Produces producedTracks; o2::framework::Produces producedTrackMasks; o2::framework::Produces producedTrackMass; + // purity/fraction, copied through if the input track type has them joined (same + // compile-time-detected, opt-in-by-type pattern as producedTrackMass above) + o2::framework::Produces producedItsPurity; + o2::framework::Produces producedTpcPurity; + o2::framework::Produces producedTofPurity; + o2::framework::Produces producedTrackDcas; }; struct ConfTrackTablesDerivedToDerived : o2::framework::ConfigurableGroup { @@ -977,6 +1080,14 @@ class TrackBuilderDerivedToDerived if constexpr (utils::HasMass) { trackProducts.producedTrackMass(track.mass()); } + if constexpr (requires(T1 t) { t.itsPackedNSigma(); }) { + trackProducts.producedItsPurity(track.itsPackedNSigma()); + trackProducts.producedTpcPurity(track.tpcPackedNSigma()); + trackProducts.producedTofPurity(track.tofPackedNSigma(), track.purityHasTof()); + } + if constexpr (requires(T1 t) { t.dcaXY(); }) { + trackProducts.producedTrackDcas(track.dcaXY(), track.dcaZ()); + } const int64_t idx = trackProducts.producedTracks.lastIndex(); indexMap.emplace(track.globalIndex(), idx); diff --git a/PWGCF/Femto/Core/trackHistManager.h b/PWGCF/Femto/Core/trackHistManager.h index 634512c8666..b02fc512f4e 100644 --- a/PWGCF/Femto/Core/trackHistManager.h +++ b/PWGCF/Femto/Core/trackHistManager.h @@ -121,6 +121,13 @@ enum TrackHist { kTpctofDeuteron, kTpctofTriton, kTpctofHelium, + // purity (species picked at runtime via puritySlotMask, see hPuritySlotAssignment) + kItsPurity, + kTpcPurity, + kTofPurity, + kTpcitsPurity, + kTpctofPurity, + kFractionDca, // mc kOrigin, kPdg, @@ -154,6 +161,11 @@ struct ConfTrackBinning : o2::framework::ConfigurableGroup { o2::framework::ConfigurableAxis sign{"sign", {{3, -1.5, 1.5}}, "Sign"}; o2::framework::ConfigurableAxis mass{"mass", {{200, 0.f, 2.f}}, "Mass (if enabled, plot TOF mass, otherwise PDG mass)"}; o2::framework::ConfigurableAxis pdgCodes{"pdgCodes", {{8001, -4000.5, 4000.5}}, "MC ONLY: PDG codes of selected tracks"}; + o2::framework::Configurable puritySlotMask{"puritySlotMask", 1, "Bitmask (bit N = slot N) selecting which purity slot to plot, read off hPuritySlotAssignment in the producer output."}; + o2::framework::ConfigurableAxis purityNSigma{"purityNSigma", {{3000, -15, 15}}, "nSigma decoded from the packed purity tables (species picked via puritySlotMask); signed, for the raw Its/Tpc/Tof values"}; + o2::framework::ConfigurableAxis purityNSigmaComb{"purityNSigmaComb", {{2000, 0, 20}}, "Combined nSigma (hypot of two raw values); always non-negative, for Tpc+Its/Tpc+Tof"}; + o2::framework::ConfigurableAxis fractionDcaXy{"fractionDcaXy", {{300, -0.3, 0.3}}, "DCA_xy (fraction estimation, needs FTrackDcas joined)"}; + o2::framework::ConfigurableAxis fractionDcaZ{"fractionDcaZ", {{300, -0.3, 0.3}}, "DCA_z (fraction estimation, needs FTrackDcas joined)"}; }; constexpr const char PrefixTrackBinning1[] = "TrackBinning1"; @@ -401,6 +413,12 @@ constexpr std::array, kTrackHistLast> {kTpctofDeuteron, o2::framework::HistType::kTH2F, "hTpctofPidDeuteron", "TPC+TOF PID Deuteron; p (GeV/#it{c}) ; #sqrt{n#sigma_{TPC,de}^{2}+n#sigma_{TOF,de}^{2}}"}, {kTpctofTriton, o2::framework::HistType::kTH2F, "hTpctofPidTriton", "TPC+TOF PID Triton; p (GeV/#it{c}) ; #sqrt{n#sigma_{TPC,tr}^{2}+n#sigma_{TOF,tr}^{2}}"}, {kTpctofHelium, o2::framework::HistType::kTH2F, "hTpctofPidHelium", "TPC+TOF PID Helium; p (GeV/#it{c}) ; #sqrt{n#sigma_{TPC,he}^{2}+n#sigma_{TOF,he}^{2}}"}, + {kItsPurity, o2::framework::HistType::kTH2F, "hItsPurity", "ITS purity nSigma (species picked via puritySlotMask); p_{T} (GeV/#it{c}) ; n#sigma_{ITS}"}, + {kTpcPurity, o2::framework::HistType::kTH2F, "hTpcPurity", "TPC purity nSigma (species picked via puritySlotMask); p_{T} (GeV/#it{c}) ; n#sigma_{TPC}"}, + {kTofPurity, o2::framework::HistType::kTH2F, "hTofPurity", "TOF purity nSigma (species picked via puritySlotMask); p_{T} (GeV/#it{c}) ; n#sigma_{TOF}"}, + {kTpcitsPurity, o2::framework::HistType::kTH2F, "hTpcitsPurity", "TPC+ITS combined purity nSigma (species picked via puritySlotMask); p_{T} (GeV/#it{c}) ; #sqrt{n#sigma_{TPC}^{2}+n#sigma_{ITS}^{2}}"}, + {kTpctofPurity, o2::framework::HistType::kTH2F, "hTpctofPurity", "TPC+TOF combined purity nSigma (species picked via puritySlotMask); p_{T} (GeV/#it{c}) ; #sqrt{n#sigma_{TPC}^{2}+n#sigma_{TOF}^{2}}"}, + {kFractionDca, o2::framework::HistType::kTHnSparseF, "hFractionDca", "DCA for fraction extraction; p_{T} (GeV/#it{c}) ; DCA_{xy} (cm) ; DCA_{z} (cm)"}, {kOrigin, o2::framework::HistType::kTH1F, "hOrigin", "Status Codes (=Origin); Status Code; Entries"}, {kPdg, o2::framework::HistType::kTH1F, "hPdg", "PDG Codes of selected tracks; PDG Code; Entries"}, {kPdgMother, o2::framework::HistType::kTH1F, "hPdgMother", "PDG Codes of mother of selected tracks; PDG Code; Entries"}, @@ -420,12 +438,18 @@ constexpr std::array, kTrackHistLast> }}; // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define TRACK_HIST_ANALYSIS_MAP(conf) \ - {kPt, {(conf).pt}}, \ - {kEta, {(conf).eta}}, \ - {kPhi, {(conf).phi}}, \ - {kSign, {(conf).sign}}, \ - {kMass, {(conf).mass}}, +#define TRACK_HIST_ANALYSIS_MAP(conf) \ + {kPt, {(conf).pt}}, \ + {kEta, {(conf).eta}}, \ + {kPhi, {(conf).phi}}, \ + {kSign, {(conf).sign}}, \ + {kMass, {(conf).mass}}, \ + {kItsPurity, {(conf).pt, (conf).purityNSigma}}, \ + {kTpcPurity, {(conf).pt, (conf).purityNSigma}}, \ + {kTofPurity, {(conf).pt, (conf).purityNSigma}}, \ + {kTpcitsPurity, {(conf).pt, (conf).purityNSigmaComb}}, \ + {kTpctofPurity, {(conf).pt, (conf).purityNSigmaComb}}, \ + {kFractionDca, {(conf).pt, (conf).fractionDcaXy, (conf).fractionDcaZ}}, // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define TRACK_HIST_QA_MAP(confAnalysis, confQa) \ @@ -630,6 +654,7 @@ constexpr std::string_view AnalysisDir = "Analysis/"; constexpr std::string_view QaDir = "QA/"; constexpr std::string_view PidDir = "PID/"; constexpr std::string_view McDir = "MC/"; +constexpr std::string_view FitVarsDir = "FitVars/"; template class TrackHistManager @@ -642,11 +667,13 @@ class TrackHistManager template void init(o2::framework::HistogramRegistry* registry, std::map> const& Specs, - T const& ConfTrackSelection) + T const& ConfTrackSelection, + int puritySlotMask = 1) { mHistogramRegistry = registry; mAbsCharge = std::abs(ConfTrackSelection.chargeAbs.value); mPdgCode = std::abs(ConfTrackSelection.pdgCodeAbs.value) * ConfTrackSelection.chargeSign.value; + mPuritySlotMask = puritySlotMask; if constexpr (isFlagSet(mode, modes::Mode::kReco)) { this->initAnalysis(Specs); } @@ -656,6 +683,9 @@ class TrackHistManager if constexpr (isFlagSet(mode, modes::Mode::kMc)) { this->initMc(Specs); } + if constexpr (isFlagSet(mode, modes::Mode::kFitVars)) { + this->initFitVars(Specs); + } } template @@ -711,6 +741,9 @@ class TrackHistManager if constexpr (isFlagSet(mode, modes::Mode::kQa)) { this->fillQa(track); } + if constexpr (isFlagSet(mode, modes::Mode::kFitVars)) { + this->fillFitVars(track); + } } template @@ -725,6 +758,9 @@ class TrackHistManager if constexpr (isFlagSet(mode, modes::Mode::kMc)) { this->template fillMc(track, col, mcParticles, mcMothers, mcPartonicMothers); } + if constexpr (isFlagSet(mode, modes::Mode::kFitVars)) { + this->fillFitVars(track); + } } private: @@ -765,6 +801,19 @@ class TrackHistManager mHistogramRegistry->add(analysisDir + getHistNameV2(kMass, HistTable), getHistDesc(kMass, HistTable), getHistType(kMass, HistTable), {Specs.at(kMass)}); } + // purity (packed nSigma tables) and fraction (DCA) -- only called when mode carries kFitVars, + // which also implies the track type actually has the relevant tables joined + void initFitVars(std::map> const& Specs) + { + std::string fitVarsDir = std::string(prefix) + std::string(FitVarsDir); + mHistogramRegistry->add(fitVarsDir + getHistNameV2(kItsPurity, HistTable), getHistDesc(kItsPurity, HistTable), getHistType(kItsPurity, HistTable), {Specs.at(kItsPurity)}); + mHistogramRegistry->add(fitVarsDir + getHistNameV2(kTpcPurity, HistTable), getHistDesc(kTpcPurity, HistTable), getHistType(kTpcPurity, HistTable), {Specs.at(kTpcPurity)}); + mHistogramRegistry->add(fitVarsDir + getHistNameV2(kTofPurity, HistTable), getHistDesc(kTofPurity, HistTable), getHistType(kTofPurity, HistTable), {Specs.at(kTofPurity)}); + mHistogramRegistry->add(fitVarsDir + getHistNameV2(kTpcitsPurity, HistTable), getHistDesc(kTpcitsPurity, HistTable), getHistType(kTpcitsPurity, HistTable), {Specs.at(kTpcitsPurity)}); + mHistogramRegistry->add(fitVarsDir + getHistNameV2(kTpctofPurity, HistTable), getHistDesc(kTpctofPurity, HistTable), getHistType(kTpctofPurity, HistTable), {Specs.at(kTpctofPurity)}); + mHistogramRegistry->add(fitVarsDir + getHistNameV2(kFractionDca, HistTable), getHistDesc(kFractionDca, HistTable), getHistType(kFractionDca, HistTable), {Specs.at(kFractionDca)}); + } + void initQa(std::map> const& Specs) { std::string qaDir = std::string(prefix) + std::string(QaDir); @@ -948,6 +997,55 @@ class TrackHistManager } } + // purity (packed nSigma tables) and fraction (DCA) -- fills whichever of the two is actually + // joined on T, independently of the other; mPuritySlotMask picks the species slot (one bit set, + // e.g. from cutculator reading hPuritySlotAssignment), everything is filled unconditionally + // once that slot is decoded -- no momentum-regime selection, the analyst picks the right + // combination/range downstream + template + void fillFitVars(T const& track) + { + if constexpr (requires(T t) { t.itsNSigmaSlot0(); }) { + float itsNSigma = 0.f; + float tpcNSigma = 0.f; + float tofNSigma = 0.f; + switch (mPuritySlotMask) { + case 1: + itsNSigma = track.itsNSigmaSlot0(); + tpcNSigma = track.tpcNSigmaSlot0(); + tofNSigma = track.tofNSigmaSlot0(); + break; + case 2: + itsNSigma = track.itsNSigmaSlot1(); + tpcNSigma = track.tpcNSigmaSlot1(); + tofNSigma = track.tofNSigmaSlot1(); + break; + case 4: + itsNSigma = track.itsNSigmaSlot2(); + tpcNSigma = track.tpcNSigmaSlot2(); + tofNSigma = track.tofNSigmaSlot2(); + break; + case 8: + itsNSigma = track.itsNSigmaSlot3(); + tpcNSigma = track.tpcNSigmaSlot3(); + tofNSigma = track.tofNSigmaSlot3(); + break; + default: + LOG(fatal) << "puritySlotMask must have exactly one bit set (1, 2, 4 or 8), got " << mPuritySlotMask << ". Breaking..."; + } + mHistogramRegistry->fill(HIST(prefix) + HIST(FitVarsDir) + HIST(getHistName(kItsPurity, HistTable)), mAbsCharge * track.pt(), itsNSigma); + mHistogramRegistry->fill(HIST(prefix) + HIST(FitVarsDir) + HIST(getHistName(kTpcPurity, HistTable)), mAbsCharge * track.pt(), tpcNSigma); + mHistogramRegistry->fill(HIST(prefix) + HIST(FitVarsDir) + HIST(getHistName(kTpcitsPurity, HistTable)), mAbsCharge * track.pt(), std::hypot(tpcNSigma, itsNSigma)); + if (track.purityHasTof()) { + mHistogramRegistry->fill(HIST(prefix) + HIST(FitVarsDir) + HIST(getHistName(kTofPurity, HistTable)), mAbsCharge * track.pt(), tofNSigma); + mHistogramRegistry->fill(HIST(prefix) + HIST(FitVarsDir) + HIST(getHistName(kTpctofPurity, HistTable)), mAbsCharge * track.pt(), std::hypot(tpcNSigma, tofNSigma)); + } + } + if constexpr (requires(T t) { t.dcaXY(); }) { + mHistogramRegistry->fill(HIST(prefix) + HIST(FitVarsDir) + HIST(getHistName(kFractionDca, HistTable)), mAbsCharge * track.pt(), track.dcaXY(), track.dcaZ()); + } + } + template void fillQa(T const& track) { @@ -1161,6 +1259,7 @@ class TrackHistManager o2::framework::HistogramRegistry* mHistogramRegistry = nullptr; int mAbsCharge = 1; int mPdgCode = 0; + int mPuritySlotMask = 1; bool mPlot2d = false; bool mPlotElectronPid = false; bool mPlotPionPid = false; diff --git a/PWGCF/Femto/DataModel/FemtoTables.h b/PWGCF/Femto/DataModel/FemtoTables.h index 6fa5651970f..9de9dbe28b3 100644 --- a/PWGCF/Femto/DataModel/FemtoTables.h +++ b/PWGCF/Femto/DataModel/FemtoTables.h @@ -391,6 +391,38 @@ DECLARE_SOA_DYNAMIC_COLUMN(TpctofNSigmaDe, tpctofNSigmaDe, [](float tpc, float t DECLARE_SOA_DYNAMIC_COLUMN(TpctofNSigmaTr, tpctofNSigmaTr, [](float tpc, float tof) -> float { return std::hypot(tpc, tof); }); //! Combined Nsigma separation with Tpc and Tof for triton DECLARE_SOA_DYNAMIC_COLUMN(TpctofNSigmaHe, tpctofNSigmaHe, [](float tpc, float tof) -> float { return std::hypot(tpc, tof); }); //! Combined Nsigma separation with Tpc and Tof for helium3 +// --------------------------------------------------------------------------- +// purity: up to 4 species' nSigma (per detector: Its, Tpc, Tof) packed into a +// single int64_t column each, see utils::packPurity4/unpackPurity4. Which +// species occupies which of the 4 slots is decided at producer init time +// (from the active requirePidXxx flags, in canonical species order) and +// exposed via a self-documenting histogram -- it is not stored per-row. +// --------------------------------------------------------------------------- +constexpr float NSigmaPurityMagMin = 1e-3f; // resolution floor near the signal peak +constexpr float NSigmaPurityMagMax = 50.f; // clamp for the tails + +DECLARE_SOA_COLUMN(ItsPackedNSigma, itsPackedNSigma, int64_t); //! 4 species' Its nSigma, log-binned and packed +DECLARE_SOA_COLUMN(TpcPackedNSigma, tpcPackedNSigma, int64_t); //! 4 species' Tpc nSigma, log-binned and packed +DECLARE_SOA_COLUMN(TofPackedNSigma, tofPackedNSigma, int64_t); //! 4 species' Tof nSigma, log-binned and packed +// if false, all 4 Tof slots are meaningless placeholders (producer writes 0 to all of them) -- +// check this flag before reading any TofNSigmaSlotN; never infer missingness from the slot value +DECLARE_SOA_COLUMN(PurityHasTof, purityHasTof, bool); //! whether the Tof slots are meaningful for this track (species-independent) + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +// each dynamic column below is bound to its PackedNSigma column at the table-declaration +// site, e.g. femtotracks::ItsNSigmaSlot0 +#define FEMTO_PURITY_SLOT_COLUMN(Detector, detector) \ + DECLARE_SOA_DYNAMIC_COLUMN(Detector##NSigmaSlot0, detector##NSigmaSlot0, [](int64_t packed) -> float { return o2::analysis::femto::utils::unpackPurityNSigma4(packed, 0, NSigmaPurityMagMin, NSigmaPurityMagMax); }); \ + DECLARE_SOA_DYNAMIC_COLUMN(Detector##NSigmaSlot1, detector##NSigmaSlot1, [](int64_t packed) -> float { return o2::analysis::femto::utils::unpackPurityNSigma4(packed, 1, NSigmaPurityMagMin, NSigmaPurityMagMax); }); \ + DECLARE_SOA_DYNAMIC_COLUMN(Detector##NSigmaSlot2, detector##NSigmaSlot2, [](int64_t packed) -> float { return o2::analysis::femto::utils::unpackPurityNSigma4(packed, 2, NSigmaPurityMagMin, NSigmaPurityMagMax); }); \ + DECLARE_SOA_DYNAMIC_COLUMN(Detector##NSigmaSlot3, detector##NSigmaSlot3, [](int64_t packed) -> float { return o2::analysis::femto::utils::unpackPurityNSigma4(packed, 3, NSigmaPurityMagMin, NSigmaPurityMagMax); }); + +FEMTO_PURITY_SLOT_COLUMN(Its, its) +FEMTO_PURITY_SLOT_COLUMN(Tpc, tpc) +FEMTO_PURITY_SLOT_COLUMN(Tof, tof) +#undef FEMTO_PURITY_SLOT_COLUMN +// NOLINTEND(cppcoreguidelines-macro-usage) + } // namespace femtotracks // table for basic track information @@ -445,6 +477,7 @@ DECLARE_SOA_TABLE_STAGED_VERSIONED(FTrackDcas_001, "FTRACKDCAS", 1, //! track dc femtotracks::DcaZ, femtotracks::Dca); using FTrackDcas = FTrackDcas_001; +using StoredFTrackDcas = StoredFTrackDcas_001; // table for extra track information DECLARE_SOA_TABLE_STAGED_VERSIONED(FTrackExtras_001, "FTRACKEXTRA", 1, //! track extra information @@ -523,6 +556,39 @@ using FHeliumPids = FHeliumPids_001; using FTrackPids = soa::Join; +// tables for track purity extraction: up to 4 species' nSigma packed per detector (see +// femtotracks::PurityNSlots / utils::packPurity4). Slot assignment is not stored here -- +// it is exposed separately via a self-documenting histogram at producer init time. +DECLARE_SOA_TABLE_STAGED_VERSIONED(FItsPurity_001, "FITSPURITY", 1, //! up to 4 species' Its nSigma, packed + femtotracks::ItsPackedNSigma, + femtotracks::ItsNSigmaSlot0, + femtotracks::ItsNSigmaSlot1, + femtotracks::ItsNSigmaSlot2, + femtotracks::ItsNSigmaSlot3); +using FItsPurity = FItsPurity_001; +using StoredFItsPurity = StoredFItsPurity_001; + +DECLARE_SOA_TABLE_STAGED_VERSIONED(FTpcPurity_001, "FTPCPURITY", 1, //! up to 4 species' Tpc nSigma, packed + femtotracks::TpcPackedNSigma, + femtotracks::TpcNSigmaSlot0, + femtotracks::TpcNSigmaSlot1, + femtotracks::TpcNSigmaSlot2, + femtotracks::TpcNSigmaSlot3); +using FTpcPurity = FTpcPurity_001; +using StoredFTpcPurity = StoredFTpcPurity_001; + +// also carries PurityHasTof (species-independent: whether this track reached Tof at all) -- +// the most natural home for it, since it only matters for interpreting the Tof slots +DECLARE_SOA_TABLE_STAGED_VERSIONED(FTofPurity_001, "FTOFPURITY", 1, //! up to 4 species' Tof nSigma, packed + femtotracks::TofPackedNSigma, + femtotracks::PurityHasTof, + femtotracks::TofNSigmaSlot0, + femtotracks::TofNSigmaSlot1, + femtotracks::TofNSigmaSlot2, + femtotracks::TofNSigmaSlot3); +using FTofPurity = FTofPurity_001; +using StoredFTofPurity = StoredFTofPurity_001; + namespace femtotwotrackresonances { // columns for resonance bit masks @@ -753,6 +819,11 @@ DECLARE_SOA_TABLE_STAGED_VERSIONED(FLambdaExtras_001, "FLAMBDAEXTRA", 1, //! lam using FLambdaExtras = FLambdaExtras_001; +// table for lambda fraction extraction (cpa only -- mass is already in FLambdas, no need to duplicate it) +DECLARE_SOA_TABLE_STAGED_VERSIONED(FLambdaFrac_001, "FLAMBDAFRAC", 1, //! lambda cpa for fraction extraction + femtov0s::CosPa); +using FLambdaFrac = FLambdaFrac_001; + // table for basic k0short information DECLARE_SOA_TABLE_STAGED_VERSIONED(FK0shorts_001, "FK0SHORT", 1, //! femto k0shorts o2::soa::Index<>, @@ -815,6 +886,11 @@ DECLARE_SOA_TABLE_STAGED_VERSIONED(FK0shortExtras_001, "FK0SHORTEXTRA", 1, //! k using FK0shortExtras = FK0shortExtras_001; +// table for k0short fraction extraction (cpa only -- mass is already in FK0shorts, no need to duplicate it) +DECLARE_SOA_TABLE_STAGED_VERSIONED(FK0shortFrac_001, "FK0SHORTFRAC", 1, //! k0short cpa for fraction extraction + femtov0s::CosPa); +using FK0shortFrac = FK0shortFrac_001; + namespace femtophotons { // columns for bit masks @@ -1323,6 +1399,12 @@ DECLARE_SOA_TABLE_STAGED_VERSIONED(FXiExtras_001, "FXIEXTRA", 1, //! xi extra in femtocascades::CascadePa); using FXiExtras = FXiExtras_001; +// table for xi fraction extraction (cascade cpa only -- mass is already in FXis, and the daughter +// lambda's own cpa is not needed here) +DECLARE_SOA_TABLE_STAGED_VERSIONED(FXiFrac_001, "FXIFRAC", 1, //! xi cpa for fraction extraction + femtocascades::CascadeCosPa); +using FXiFrac = FXiFrac_001; + DECLARE_SOA_TABLE_STAGED_VERSIONED(FOmegas_001, "FOMEGA", 1, //! femto omegas o2::soa::Index<>, femtobase::stored::FColId, @@ -1389,6 +1471,12 @@ DECLARE_SOA_TABLE_STAGED_VERSIONED(FOmegaExtras_001, "FOMEGAEXTRA", 1, //! omega femtocascades::CascadePa); using FOmegaExtras = FOmegaExtras_001; +// table for omega fraction extraction (cascade cpa only -- mass is already in FOmegas, and the daughter +// lambda's own cpa is not needed here) +DECLARE_SOA_TABLE_STAGED_VERSIONED(FOmegaFrac_001, "FOMEGAFRAC", 1, //! omega cpa for fraction extraction + femtocascades::CascadeCosPa); +using FOmegaFrac = FOmegaFrac_001; + namespace femtocharmhadrons { // bitmask column diff --git a/PWGCF/Femto/TableProducer/femtoProducerDerivedToDerived.cxx b/PWGCF/Femto/TableProducer/femtoProducerDerivedToDerived.cxx index ea290785cd6..b8cbdd436f1 100644 --- a/PWGCF/Femto/TableProducer/femtoProducerDerivedToDerived.cxx +++ b/PWGCF/Femto/TableProducer/femtoProducerDerivedToDerived.cxx @@ -46,6 +46,9 @@ struct FemtoProducerDerivedToDerived { using FemtoTracks = o2::soa::Join; using FemtoTracksWithMass = o2::soa::Join; + // opt-in: carries the packed purity tables and FTrackDcas through to the re-written output -- + // needs the upstream producer's TrackTables.produceItsPurity/produceTpcPurity/produceTofPurity/produceTrackDcas enabled + using FemtoTracksWithPurity = o2::soa::Join; using FemtoLambdas = o2::soa::Join; using FemtoK0shorts = o2::soa::Join; using FemtoRho0s = o2::soa::Join; @@ -85,6 +88,10 @@ struct FemtoProducerDerivedToDerived { o2::framework::Partition trackWithMassPartition2 = MAKE_TRACK_PARTITION_WITH_MASS(trackSelections2); o2::framework::Preslice perColTracksWithMass = o2::aod::femtobase::stored::fColId; + o2::framework::Partition trackWithPurityPartition1 = MAKE_TRACK_PARTITION(trackSelections1); + o2::framework::Partition trackWithPurityPartition2 = MAKE_TRACK_PARTITION(trackSelections2); + o2::framework::Preslice perColTracksWithPurity = o2::aod::femtobase::stored::fColId; + // v0 builder v0builder::V0BuilderDerivedToDerived v0Builder; v0builder::V0BuilderDerivedToDerivedProducts v0BuilderProducts; @@ -159,7 +166,7 @@ struct FemtoProducerDerivedToDerived { void init(o2::framework::InitContext& /*context*/) { const int activeProcesses = - static_cast(doprocessTracks) + static_cast(doprocessTracksWithMass) + + static_cast(doprocessTracks) + static_cast(doprocessTracksWithMass) + static_cast(doprocessTracksWithPurity) + static_cast(doprocessTracksLambdas) + static_cast(doprocessTracksWithMassLambdas) + static_cast(doprocessLambdas) + static_cast(doprocessTracksXis) + static_cast(doprocessTracksOmegas) + @@ -203,6 +210,17 @@ struct FemtoProducerDerivedToDerived { } PROCESS_SWITCH(FemtoProducerDerivedToDerived, processTracksWithMass, "Process tracks with mass", false); + void processTracksWithPurity(FilteredFemtoCollision const& col, FemtoTracksWithPurity const& tracks) + { + if (trackBuilder.collisionHasTooFewTracks(col, tracks, trackWithPurityPartition1, trackWithPurityPartition2, cache)) { + return; + } + trackBuilder.reset(tracks); + collisionBuilder.processCollision(col, collisionBuilderProducts); + trackBuilder.processTracks(col, tracks, trackWithPurityPartition1, trackWithPurityPartition2, cache, trackBuilderProducts, collisionBuilderProducts); + } + PROCESS_SWITCH(FemtoProducerDerivedToDerived, processTracksWithPurity, "Process tracks, carrying through purity/fraction tables", false); + void processTracksLambdas(FilteredFemtoCollision const& col, FemtoTracks const& tracks, FemtoLambdas const& lambdas) { if (trackBuilder.collisionHasTooFewTracks(col, tracks, trackPartition1, trackPartition2, cache) || v0Builder.collisionHasTooFewLambdas(col, lambdas, lambdaPartition, cache)) { diff --git a/PWGCF/Femto/Tasks/femtoMcParticleQa.cxx b/PWGCF/Femto/Tasks/femtoMcParticleQa.cxx index f7f0706e16a..ca7560853da 100644 --- a/PWGCF/Femto/Tasks/femtoMcParticleQa.cxx +++ b/PWGCF/Femto/Tasks/femtoMcParticleQa.cxx @@ -81,18 +81,27 @@ struct FemtoMcParticleQa { hRegistry.print(); }; - void process(FilteredFemtoMcCollision const& col, FemtoMcParticles const& /*mcParticles*/, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) + // deliberately takes the full (ungrouped) FilteredFemtoMcCollisions table rather than its ::iterator: + // pairing an ::iterator first argument with FemtoMcParticles as a second table argument would make + // DPL auto-register a sorted per-collision slicing cache for fMcColId, which then collides with the + // manual sliceByCachedUnsorted() below (see the same workaround in femtoDndetaPair.cxx/femtoDndetaTriplet.cxx); + // FemtoMcParticles must still appear as an argument (even though it's only accessed via the partition) + // so DPL actually subscribes to it -- dropping it entirely leaves mcParticlesPartition1 unbound and + // segfaults on the first sliceByCachedUnsorted() call + void process(FilteredFemtoMcCollisions const& cols, FemtoMcParticles const& /*mcParticles*/, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) { - auto mcParticleSlice = mcParticlesPartition1->sliceByCachedUnsorted(o2::aod::femtomcparticle::fMcColId, col.globalIndex(), cache); - if (mcParticleSlice.size() == 0) { - return; - } - colHistManager.fill(col); - for (auto const& mcParticle : mcParticleSlice) { - if (!mcParticleCleaner.isClean(mcParticle, mcMothers, mcPartonicMothers)) { + for (auto const& col : cols) { + auto mcParticleSlice = mcParticlesPartition1->sliceByCachedUnsorted(o2::aod::femtomcparticle::fMcColId, col.globalIndex(), cache); + if (mcParticleSlice.size() == 0) { continue; } - mcParticleHistManager1.fill(mcParticle, mcMothers, mcPartonicMothers); + colHistManager.fill(col); + for (auto const& mcParticle : mcParticleSlice) { + if (!mcParticleCleaner.isClean(mcParticle, mcMothers, mcPartonicMothers)) { + continue; + } + mcParticleHistManager1.fill(mcParticle, mcMothers, mcPartonicMothers); + } } } }; diff --git a/PWGCF/Femto/Tasks/femtoPairMcParticleMcParticle.cxx b/PWGCF/Femto/Tasks/femtoPairMcParticleMcParticle.cxx index 3f623ab411f..f4eb09769b3 100644 --- a/PWGCF/Femto/Tasks/femtoPairMcParticleMcParticle.cxx +++ b/PWGCF/Femto/Tasks/femtoPairMcParticleMcParticle.cxx @@ -132,9 +132,16 @@ struct FemtoPairMcParticleMcParticle { hRegistry.print(); }; - void processSameEvent(FilteredFemtoMcCollision const& col, FemtoMcParticles const& mcParticles, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) + // deliberately takes the full (ungrouped) FilteredFemtoMcCollisions table rather than its ::iterator: + // pairing an ::iterator first argument with FemtoMcParticles as a second table argument would make + // DPL auto-register a sorted per-collision slicing cache for fMcColId, which then collides with the + // manual sliceByCachedUnsorted() used inside the builder (see the same workaround in + // femtoDndetaPair.cxx/femtoDndetaTriplet.cxx); we run the per-collision loop ourselves instead + void processSameEvent(FilteredFemtoMcCollisions const& cols, FemtoMcParticles const& mcParticles, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) { - pairMcParticleMcParticleBuilder.processSameEvent(col, mcParticles, mcMothers, mcPartonicMothers, mcParticlesPartition1, mcParticlesPartition2, cache); + for (auto const& col : cols) { + pairMcParticleMcParticleBuilder.processSameEvent(col, mcParticles, mcMothers, mcPartonicMothers, mcParticlesPartition1, mcParticlesPartition2, cache); + } } PROCESS_SWITCH(FemtoPairMcParticleMcParticle, processSameEvent, "Enable processing same event processing", true); diff --git a/PWGCF/Femto/Tasks/femtoPairTrackTrack.cxx b/PWGCF/Femto/Tasks/femtoPairTrackTrack.cxx index d1e1e423f4f..28b9ce92d65 100644 --- a/PWGCF/Femto/Tasks/femtoPairTrackTrack.cxx +++ b/PWGCF/Femto/Tasks/femtoPairTrackTrack.cxx @@ -65,7 +65,15 @@ struct FemtoPairTrackTrack { // for analysis which require particles at high pt, add tof mass so sidebands can be used using FemtoTracksWithMass = o2::soa::Join; - using FemtoTracksWithLabel = o2::soa::Join; + // opt-in: adds the packed purity tables (nSigma, see TrackBinning1/2.puritySlotMask) and FTrackDcas + // (fraction estimation) for the FitVars histograms. Needs the producer's + // TrackTables.produceItsPurity/produceTpcPurity/produceTofPurity/produceTrackDcas enabled. + using FemtoTracksWithPurity = o2::soa::Join; + + // the Mc process functions always carry the purity/fraction tables (not opt-in like the data + // path's WithPurity variant) -- requires the producer's TrackTables.produceItsPurity/ + // produceTpcPurity/produceTofPurity/produceTrackDcas enabled whenever MC processing is used + using FemtoTracksWithLabel = o2::soa::Join; using FemtoMcParticlesWithLabel = o2::soa::Join; @@ -93,6 +101,10 @@ struct FemtoPairTrackTrack { o2::framework::Partition trackWithMassPartition2 = MAKE_TRACK_PARTITION_WITH_MASS(confTrackSelections2); o2::framework::Preslice perColtracksWithMass = o2::aod::femtobase::stored::fColId; + o2::framework::Partition trackWithPurityPartition1 = MAKE_TRACK_PARTITION(confTrackSelections1); + o2::framework::Partition trackWithPurityPartition2 = MAKE_TRACK_PARTITION(confTrackSelections2); + o2::framework::Preslice perColtracksWithPurity = o2::aod::femtobase::stored::fColId; + o2::framework::Partition trackWithLabelPartition1 = MAKE_TRACK_PARTITION(confTrackSelections1); o2::framework::Partition trackWithLabelPartition2 = MAKE_TRACK_PARTITION(confTrackSelections2); o2::framework::Preslice perColtracksWithLabel = o2::aod::femtobase::stored::fColId; @@ -130,14 +142,15 @@ struct FemtoPairTrackTrack { void init(o2::framework::InitContext&) { - if ((static_cast(doprocessSameEvent) + static_cast(doprocessSameEventWithMass) + static_cast(doprocessSameEventWithEventShape) + static_cast(doprocessSameEventMc)) > 1 || - (static_cast(doprocessMixedEvent) + static_cast(doprocessMixedEventWithMass) + static_cast(doprocessMixedEventWithEventShape) + static_cast(doprocessMixedEventMc)) > 1) { + if ((static_cast(doprocessSameEvent) + static_cast(doprocessSameEventWithMass) + static_cast(doprocessSameEventWithEventShape) + static_cast(doprocessSameEventWithPurity) + static_cast(doprocessSameEventMc)) > 1 || + (static_cast(doprocessMixedEvent) + static_cast(doprocessMixedEventWithMass) + static_cast(doprocessMixedEventWithEventShape) + static_cast(doprocessMixedEventWithPurity) + static_cast(doprocessMixedEventMc)) > 1) { LOG(fatal) << "More than 1 same or mixed event process function is activated. Breaking..."; } bool processData = doprocessSameEvent || doprocessMixedEvent || doprocessSameEventWithMass || doprocessMixedEventWithMass || doprocessSameEventWithEventShape || doprocessMixedEventWithEventShape; + bool processPurity = doprocessSameEventWithPurity || doprocessMixedEventWithPurity; bool processMc = doprocessSameEventMc || doprocessMixedEventMc; - if (processData && processMc) { - LOG(fatal) << "Both data and mc processing is activated. Breaking..."; + if (static_cast(processData) + static_cast(processPurity) + static_cast(processMc) > 1) { + LOG(fatal) << "More than one of data, purity or mc processing is activated. Breaking..."; } // setup columnpolicy for binning @@ -155,7 +168,13 @@ struct FemtoPairTrackTrack { std::map> cprHistSpec = closepairrejection::makeCprHistSpecMap(confCpr); std::map> pairCleanerHistSpec = paircleaner::makePairCleanerHistSpecMap(confPairCleaner); - if (processData) { + if (processPurity) { + colHistSpec = colhistmanager::makeColHistSpecMap(confCollisionBinning); + trackHistSpec1 = trackhistmanager::makeTrackHistSpecMap(confTrackBinning1); + trackHistSpec2 = trackhistmanager::makeTrackHistSpecMap(confTrackBinning2); + pairHistSpec = pairhistmanager::makePairHistSpecMap(confPairBinning, confMixing); + pairTrackTrackBuilder.init(&hRegistry, confCollisionBinning, confTrackSelections1, confTrackSelections2, confTrackCleaner1, confTrackCleaner2, confCpr, confMixing, confPairBinning, confPairCuts, colHistSpec, trackHistSpec1, trackHistSpec2, pairHistSpec, cprHistSpec, pairCleanerHistSpec, confTrackBinning1.puritySlotMask.value, confTrackBinning2.puritySlotMask.value); + } else if (processData) { colHistSpec = colhistmanager::makeColHistSpecMap(confCollisionBinning); trackHistSpec1 = trackhistmanager::makeTrackHistSpecMap(confTrackBinning1); trackHistSpec2 = trackhistmanager::makeTrackHistSpecMap(confTrackBinning2); @@ -166,7 +185,7 @@ struct FemtoPairTrackTrack { trackHistSpec1 = trackhistmanager::makeTrackMcHistSpecMap(confTrackBinning1); trackHistSpec2 = trackhistmanager::makeTrackMcHistSpecMap(confTrackBinning2); pairHistSpec = pairhistmanager::makePairMcHistSpecMap(confPairBinning, confMixing); - pairTrackTrackBuilder.init(&hRegistry, confCollisionBinning, confTrackSelections1, confTrackSelections2, confTrackCleaner1, confTrackCleaner2, confCpr, confMixing, confPairBinning, confPairCuts, colHistSpec, trackHistSpec1, trackHistSpec2, pairHistSpec, cprHistSpec, pairCleanerHistSpec); + pairTrackTrackBuilder.init(&hRegistry, confCollisionBinning, confTrackSelections1, confTrackSelections2, confTrackCleaner1, confTrackCleaner2, confCpr, confMixing, confPairBinning, confPairCuts, colHistSpec, trackHistSpec1, trackHistSpec2, pairHistSpec, cprHistSpec, pairCleanerHistSpec, confTrackBinning1.puritySlotMask.value, confTrackBinning2.puritySlotMask.value); } hRegistry.print(); }; @@ -183,6 +202,12 @@ struct FemtoPairTrackTrack { } PROCESS_SWITCH(FemtoPairTrackTrack, processSameEventWithMass, "Enable processing same event processing (with track masses)", false); + void processSameEventWithPurity(FilteredFemtoCollision const& col, FemtoTracksWithPurity const& tracks) + { + pairTrackTrackBuilder.processSameEvent(col, tracks, trackWithPurityPartition1, trackWithPurityPartition2, cache); + } + PROCESS_SWITCH(FemtoPairTrackTrack, processSameEventWithPurity, "Enable processing same event processing (with purity/fraction tables)", false); + void processSameEventWithEventShape(FilteredFemtoCollisionWithEventShape const& col, FemtoTracks const& tracks) { pairTrackTrackBuilder.processSameEvent(col, tracks, trackPartition1, trackPartition2, cache); @@ -191,7 +216,7 @@ struct FemtoPairTrackTrack { void processSameEventMc(FilteredFemtoCollisionWithLabel const& col, o2::aod::FMcCols const& mcCols, FemtoTracksWithLabel const& tracks, FemtoMcParticlesWithLabel const& mcParticles, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) { - pairTrackTrackBuilder.processSameEvent(col, mcCols, tracks, trackWithLabelPartition1, trackWithLabelPartition2, mcParticles, mcMothers, mcPartonicMothers, cache); + pairTrackTrackBuilder.processSameEvent(col, mcCols, tracks, trackWithLabelPartition1, trackWithLabelPartition2, mcParticles, mcMothers, mcPartonicMothers, cache); } PROCESS_SWITCH(FemtoPairTrackTrack, processSameEventMc, "Enable processing same event processing", false); @@ -207,6 +232,12 @@ struct FemtoPairTrackTrack { } PROCESS_SWITCH(FemtoPairTrackTrack, processMixedEventWithMass, "Enable processing mixed event processing (with track masses)", false); + void processMixedEventWithPurity(FilteredFemtoCollisions const& cols, FemtoTracksWithPurity const& tracks) + { + pairTrackTrackBuilder.processMixedEvent(cols, tracks, trackWithPurityPartition1, trackWithPurityPartition2, cache, mixBinsVtxMult, mixBinsVtxCent, mixBinsVtxMultCent, mixBinsVtxCentEventPlaneAngle); + } + PROCESS_SWITCH(FemtoPairTrackTrack, processMixedEventWithPurity, "Enable processing mixed event processing (with purity/fraction tables)", false); + void processMixedEventWithEventShape(FilteredFemtoCollisionsWithEventShape const& cols, FemtoTracks const& tracks) { pairTrackTrackBuilder.processMixedEvent(cols, tracks, trackPartition1, trackPartition2, cache, mixBinsVtxMult, mixBinsVtxCent, mixBinsVtxMultCent, mixBinsVtxCentEventPlaneAngle); @@ -215,7 +246,7 @@ struct FemtoPairTrackTrack { void processMixedEventMc(FilteredFemtoCollisionsWithLabel const& cols, o2::aod::FMcCols const& mcCols, FemtoTracksWithLabel const& tracks, FemtoMcParticlesWithLabel const& mcParticles, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) { - pairTrackTrackBuilder.processMixedEvent(cols, mcCols, tracks, trackWithLabelPartition1, trackWithLabelPartition2, mcParticles, mcMothers, mcPartonicMothers, cache, mixBinsVtxMult, mixBinsVtxCent, mixBinsVtxMultCent, mixBinsVtxCentEventPlaneAngle); + pairTrackTrackBuilder.processMixedEvent(cols, mcCols, tracks, trackWithLabelPartition1, trackWithLabelPartition2, mcParticles, mcMothers, mcPartonicMothers, cache, mixBinsVtxMult, mixBinsVtxCent, mixBinsVtxMultCent, mixBinsVtxCentEventPlaneAngle); } PROCESS_SWITCH(FemtoPairTrackTrack, processMixedEventMc, "Enable processing mixed event processing", false); }; diff --git a/PWGCF/Femto/Tasks/femtoTripletMcParticleMcParticleMcParticle.cxx b/PWGCF/Femto/Tasks/femtoTripletMcParticleMcParticleMcParticle.cxx index 60dfa3a3411..c2cee7d7321 100644 --- a/PWGCF/Femto/Tasks/femtoTripletMcParticleMcParticleMcParticle.cxx +++ b/PWGCF/Femto/Tasks/femtoTripletMcParticleMcParticleMcParticle.cxx @@ -75,6 +75,7 @@ struct FemtoTripletMcParticleMcParticleMcParticle { o2::framework::Partition mcParticlesPartition1 = MAKE_MC_PARTICLE_PARTITION(confMcParticleSelection1); o2::framework::Partition mcParticlesPartition2 = MAKE_MC_PARTICLE_PARTITION(confMcParticleSelection2); o2::framework::Partition mcParticlesPartition3 = MAKE_MC_PARTICLE_PARTITION(confMcParticleSelection3); + o2::framework::PresliceUnsorted perColParticles = o2::aod::femtomcparticle::fMcColId; // setup triplets triplethistmanager::ConfTripletBinning confTripletBinning; @@ -133,9 +134,16 @@ struct FemtoTripletMcParticleMcParticleMcParticle { hRegistry.print(); }; - void processSameEvent(FilteredFemtoMcCollision const& col, FemtoMcParticles const& mcParticles, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) + // deliberately takes the full (ungrouped) FilteredFemtoMcCollisions table rather than its ::iterator: + // pairing an ::iterator first argument with FemtoMcParticles as a second table argument would make + // DPL auto-register a sorted per-collision slicing cache for fMcColId, which then collides with the + // manual sliceByCachedUnsorted() used inside the builder (see the same workaround in + // femtoDndetaPair.cxx/femtoDndetaTriplet.cxx); we run the per-collision loop ourselves instead + void processSameEvent(FilteredFemtoMcCollisions const& cols, FemtoMcParticles const& mcParticles, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) { - tripletMcParticleMcParticleMcParticleBuilder.processSameEvent(col, mcParticles, mcMothers, mcPartonicMothers, mcParticlesPartition1, mcParticlesPartition2, mcParticlesPartition3, cache); + for (auto const& col : cols) { + tripletMcParticleMcParticleMcParticleBuilder.processSameEvent(col, mcParticles, mcMothers, mcPartonicMothers, mcParticlesPartition1, mcParticlesPartition2, mcParticlesPartition3, cache); + } } PROCESS_SWITCH(FemtoTripletMcParticleMcParticleMcParticle, processSameEvent, "Enable processing same event processing", true);