|
| 1 | +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. |
| 2 | +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. |
| 3 | +// All rights not expressly granted are reserved. |
| 4 | +// |
| 5 | +// This software is distributed under the terms of the GNU General Public |
| 6 | +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". |
| 7 | +// |
| 8 | +// In applying this license CERN does not waive the privileges and immunities |
| 9 | +// granted to it by virtue of its status as an Intergovernmental Organization |
| 10 | +// or submit itself to any jurisdiction. |
| 11 | + |
| 12 | +/// \file taskV0PtCharmBulk.cxx |
| 13 | +/// \brief v0 pt for the charm-bulk correlation analysis task |
| 14 | +/// \author Wu Chuntai, UNIPD, CCNU, and INFN Padova |
| 15 | +/// \author Andrea Rossi, INFN Padova |
| 16 | + |
| 17 | +#include "PWGHF/Core/CentralityEstimation.h" |
| 18 | +#include "PWGHF/Core/HfHelper.h" |
| 19 | +#include "PWGHF/DataModel/AliasTables.h" |
| 20 | +#include "PWGHF/DataModel/CandidateReconstructionTables.h" |
| 21 | +#include "PWGHF/DataModel/CandidateSelectionTables.h" |
| 22 | +#include "PWGHF/Utils/utilsEvSelHf.h" |
| 23 | + |
| 24 | +#include "Common/Core/RecoDecay.h" |
| 25 | +#include "Common/DataModel/Centrality.h" |
| 26 | +#include "Common/DataModel/EventSelection.h" |
| 27 | +#include "Common/DataModel/Multiplicity.h" |
| 28 | +#include "Common/DataModel/TrackSelectionTables.h" |
| 29 | + |
| 30 | +#include <CCDB/BasicCCDBManager.h> |
| 31 | +#include <CommonConstants/MathConstants.h> |
| 32 | +#include <CommonConstants/PhysicsConstants.h> |
| 33 | +#include <Framework/ASoA.h> |
| 34 | +#include <Framework/AnalysisDataModel.h> |
| 35 | +#include <Framework/AnalysisHelpers.h> |
| 36 | +#include <Framework/AnalysisTask.h> |
| 37 | +#include <Framework/Configurable.h> |
| 38 | +#include <Framework/HistogramRegistry.h> |
| 39 | +#include <Framework/HistogramSpec.h> |
| 40 | +#include <Framework/InitContext.h> |
| 41 | +#include <Framework/Logger.h> |
| 42 | +#include <Framework/runDataProcessing.h> |
| 43 | + |
| 44 | +#include <THnSparse.h> |
| 45 | +#include <TF1.h> |
| 46 | + |
| 47 | +#include <algorithm> |
| 48 | +#include <array> |
| 49 | +#include <numeric> |
| 50 | +#include <string> |
| 51 | +#include <type_traits> |
| 52 | +#include <vector> |
| 53 | + |
| 54 | +using namespace o2; |
| 55 | +using namespace o2::framework; |
| 56 | +using namespace o2::framework::expressions; |
| 57 | +using namespace o2::hf_centrality; |
| 58 | +using namespace o2::hf_evsel; |
| 59 | + |
| 60 | +enum DecayChannel { |
| 61 | + D0ToPiK = 0, |
| 62 | + D0ToKPi |
| 63 | +}; |
| 64 | + |
| 65 | +struct HfTaskV0PtCharmBulk |
| 66 | +{ |
| 67 | + // General configuration |
| 68 | + Configurable<float> etaAMin{"etaAMin", -0.8, "eta min for A subevent"}; |
| 69 | + Configurable<float> etaAMax{"etaAMax", -0.2, "eta max for A subevent"}; |
| 70 | + Configurable<float> etaBMin{"etaBMin", 0.2, "eta min for B subevent"}; |
| 71 | + Configurable<float> etaBMax{"etaBMax", 0.8, "eta max for B subevent"}; |
| 72 | + Configurable<int> centEstimator{"centEstimator", 2, "Centrality estimation (FT0A: 1, FT0C: 2, FT0M: 3, FV0A: 4)"}; |
| 73 | + Configurable<std::string> ccdbUrl{"ccdbUrl", "http://alice-ccdb.cern.ch", "url of the ccdb repository"}; |
| 74 | + Configurable<std::vector<int>> classMl{"classMl", {0, 2}, "Indexes of BDT scores to be stored. Two indexes max."}; |
| 75 | + |
| 76 | + // Track configuration |
| 77 | + Configurable<int> tpcNClsCrossedRowsMin{"tpcNClsCrossedRowsMin", 70, "min. TPC crossed rows for associated tracks"}; |
| 78 | + Configurable<float> etaTrkMax{"etaTrkMax", 1., "max. track eta"}; |
| 79 | + Configurable<float> ptTrkMin{"ptTrkMin", 0.2, "min. track pT"}; |
| 80 | + Configurable<float> ptTrkMax{"ptTrkMax", 5., "max. track pT"}; |
| 81 | + Configurable<float> dcaXYTrkMax{"dcaXYTrkMax", 1., "max. track DCA XY"}; |
| 82 | + Configurable<float> dcaZTrkMax{"dcaZTrkMax", 1., "max. track DCA Z"}; |
| 83 | + Configurable<bool> usePtDiffDcaXYCut{"usePtDiffDcaXYCut", true, "Use pt-differential DCAxy cut for associated tracks"}; |
| 84 | + Configurable<float> dcaXYTrkNSigmaMax{"dcaXYTrkNSigmaMax", 7, "Cut on number of sigma deviations from expected DCA in the transverse direction"}; |
| 85 | + Configurable<std::string> dcaXYPtPrimTrkFunc{"dcaXYPtPrimTrkFunc", "(0.0026+0.005/(x^1.01))", "Functional form of pt-dependent DCAxy cut"}; |
| 86 | + |
| 87 | + // Candidate configuration |
| 88 | + Configurable<int> selectionFlag{"selectionFlag", 1, "Selection Flag for hadron (ML score tables are required to run the task)"}; |
| 89 | + |
| 90 | + // Event configuration |
| 91 | + Configurable<bool> forceCharmInCollision{"forceCharmInCollision", true, "Flag to force charm in collision"}; |
| 92 | + HfEventSelection hfEvSel; // event selection and monitoring |
| 93 | + |
| 94 | + o2::framework::Service<o2::ccdb::BasicCCDBManager> ccdb{}; |
| 95 | + SliceCache cache; |
| 96 | + TF1* funcDcaXYPtCutPrimTrk = nullptr; |
| 97 | + |
| 98 | + // Subcribe and join the tables |
| 99 | + using TracksTable = soa::Filtered<soa::Join<aod::TracksWDca, aod::TrackSelection, aod::TracksExtra>>; |
| 100 | + using D0CandTable = soa::Filtered<soa::Join<aod::HfCand2Prong, aod::HfSelD0, aod::HfMlD0>>; |
| 101 | + using CollsWithCentMult = soa::Join<aod::Collisions, aod::EvSels, aod::FT0Mults, aod::FV0Mults, aod::CentFT0Ms, aod::CentFT0As, aod::CentFT0Cs, aod::CentFV0As>; |
| 102 | + |
| 103 | + // Select tracks and candidates |
| 104 | + Filter filterSelectTracks = (nabs(aod::track::eta) < etaTrkMax) && (aod::track::pt > ptTrkMin) && (aod::track::pt < ptTrkMax) && (nabs(aod::track::dcaXY) < dcaXYTrkMax) && (nabs(aod::track::dcaZ) < dcaZTrkMax); |
| 105 | + Filter filterSelectD0Candidates = aod::hf_sel_candidate_d0::isSelD0 >= selectionFlag || aod::hf_sel_candidate_d0::isSelD0bar >= selectionFlag; |
| 106 | + |
| 107 | + // pre-slice by collision for tracks |
| 108 | + Preslice<TracksTable> tracksTablePerColl = aod::track::collisionId; |
| 109 | + Preslice<D0CandTable> candD0TablePerColl = aod::hf_cand::collisionId; |
| 110 | + |
| 111 | + // Partitions for selected candidates |
| 112 | + Partition<D0CandTable> selectedD0ToPiK = aod::hf_sel_candidate_d0::isSelD0 >= selectionFlag; |
| 113 | + Partition<D0CandTable> selectedD0ToKPi = aod::hf_sel_candidate_d0::isSelD0bar >= selectionFlag; |
| 114 | + |
| 115 | + // Partition<TracksTable> selectedTracks = (nabs(aod::track::eta) < etaTrkMax) && (aod::track::pt > ptTrkMin) && (aod::track::pt < ptTrkMax) && (nabs(aod::track::dcaXY) < dcaXYTrkMax) && (nabs(aod::track::dcaZ) < dcaZTrkMax); |
| 116 | + |
| 117 | + // THnSparse configuration |
| 118 | + ConfigurableAxis thnConfigAxisCent{"thnConfigAxisCent", {100, 0, 100}, ""}; |
| 119 | + ConfigurableAxis thnConfigAxisCandMass{"thnConfigAxisCandMass", {200, 1.68, 2.08}, ""}; |
| 120 | + ConfigurableAxis thnConfigAxisCandPt{"thnConfigAxisCandPt", {10, 0., 10.}, ""}; |
| 121 | + ConfigurableAxis thnConfigAxisCandEta{"thnConfigAxisCandEta", {200, -1., 1.}, ""}; |
| 122 | + ConfigurableAxis thnConfigAxisMPtTrkA{"thnConfigAxisMPtTrkA", {100, 0., 5.}, ""}; |
| 123 | + ConfigurableAxis thnConfigAxisMPtTrkB{"thnConfigAxisMPtTrkB", {100, 0., 5.}, ""}; |
| 124 | + ConfigurableAxis thnConfigAxisMlOne{"thnConfigAxisMlOne", {100, 0., 1.}, ""}; |
| 125 | + ConfigurableAxis thnConfigAxisMlTwo{"thnConfigAxisMlTwo", {100, 0., 1.}, ""}; |
| 126 | + |
| 127 | + HistogramRegistry registry{"registry", {}}; |
| 128 | + |
| 129 | + void init(InitContext&) { |
| 130 | + std::array<bool, 14> doprocess{doprocessD0WCentMult}; |
| 131 | + if ((std::accumulate(doprocess.begin(), doprocess.end(), 0)) == 0) { |
| 132 | + LOGP(fatal, "At least one process function should be enabled at a time."); |
| 133 | + } |
| 134 | + |
| 135 | + hfEvSel.addHistograms(registry); // collision monitoring |
| 136 | + ccdb->setURL(ccdbUrl); |
| 137 | + ccdb->setCaching(true); |
| 138 | + ccdb->setLocalObjectValidityChecking(); |
| 139 | + |
| 140 | + // Define the axes for the THnSparse |
| 141 | + const AxisSpec axisCent = {thnConfigAxisCent, "Centrality"}; |
| 142 | + const AxisSpec axisCandMass = {thnConfigAxisCandMass, "Inv. mass (GeV/#it{c}^{2})"}; |
| 143 | + const AxisSpec axisCandPt = {thnConfigAxisCandPt, "#it{p}_{T} (GeV/#it{c})"}; |
| 144 | + const AxisSpec axisCandEta = {thnConfigAxisCandEta, "Eta"}; |
| 145 | + const AxisSpec axisMPtTrkA = {thnConfigAxisMPtTrkA, "Mean pT of tracks in subevent A (GeV/#it{c})"}; |
| 146 | + const AxisSpec axisMPtTrkB = {thnConfigAxisMPtTrkB, "Mean pT of tracks in subevent B (GeV/#it{c})"}; |
| 147 | + const AxisSpec axisMlOne = {thnConfigAxisMlOne, "ML score 1"}; |
| 148 | + const AxisSpec axisMlTwo = {thnConfigAxisMlTwo, "ML score 2"}; |
| 149 | + |
| 150 | + std::vector<AxisSpec> axes = {axisCent, axisCandMass, axisCandPt, axisCandEta, axisMPtTrkA, axisMPtTrkB, axisMlOne, axisMlTwo}; |
| 151 | + registry.add("hSparseMeanTrkPtCharm", "THn for mean track pT", HistType::kTHnSparseF, axes); |
| 152 | + |
| 153 | + registry.add("hMeanTrkPtAVsCent", "Mean track pT A vs centrality", HistType::kTH2F, {axisCent, axisMPtTrkA}); |
| 154 | + registry.add("hMeanTrkPtBVsCent", "Mean track pT B vs centrality", HistType::kTH2F, {axisCent, axisMPtTrkB}); |
| 155 | + |
| 156 | + if (usePtDiffDcaXYCut) { |
| 157 | + funcDcaXYPtCutPrimTrk = new TF1("funcDcaXYPtCutPrimTrk", Form("[0]*%s", dcaXYPtPrimTrkFunc.value.data()), 0.001, 100); |
| 158 | + funcDcaXYPtCutPrimTrk->SetParameter(0, dcaXYTrkNSigmaMax); |
| 159 | + LOGF(info, "DCAxy pt-dependence function: %s", Form("[0]*%s", dcaXYPtPrimTrkFunc.value.data())); |
| 160 | + } |
| 161 | + } // End init |
| 162 | + |
| 163 | + /// Function to calculate mean pT of tracks in a given eta range (subevent) for a specific collision |
| 164 | + /// \param tracks are the tracks to be used for mean pT calculation |
| 165 | + /// \param candidates are the D0 candidates to be analyzed |
| 166 | + /// \return a pair of pairs: {{meanPtA, countA}, {meanPtB, countB}} |
| 167 | + template <typename CandT> |
| 168 | + std::pair<float, float> calculateMeanPt(TracksTable const& tracks, CandT const& candidates) |
| 169 | + { |
| 170 | + float sumPtA = 0.f; |
| 171 | + int countA = 0; |
| 172 | + float sumPtB = 0.f; |
| 173 | + int countB = 0; |
| 174 | + std::vector<int> candProngsA; |
| 175 | + std::vector<int> candProngsB; |
| 176 | + |
| 177 | + // Gather the global indices of the candidate daughters |
| 178 | + if constexpr (std::is_same_v<CandT, D0CandTable>) |
| 179 | + { |
| 180 | + for (const auto& cand : candidates) { |
| 181 | + if (cand.eta() > etaAMin && cand.eta() < etaAMax) { |
| 182 | + candProngsA.push_back(cand.prong0Id()); |
| 183 | + candProngsA.push_back(cand.prong1Id()); |
| 184 | + } else if (cand.eta() > etaBMin && cand.eta() < etaBMax) { |
| 185 | + candProngsB.push_back(cand.prong0Id()); |
| 186 | + candProngsB.push_back(cand.prong1Id()); |
| 187 | + } |
| 188 | + } |
| 189 | + } |
| 190 | + |
| 191 | + // Loop over tracks and calculate sum of pT and count for subevent A and B, excluding candidate daughters |
| 192 | + for (const auto& track : tracks) |
| 193 | + { |
| 194 | + float eta = track.eta(); |
| 195 | + float pt = track.pt(); |
| 196 | + |
| 197 | + // Select only global tracks with DCA information or with sufficient TPC crossed rows |
| 198 | + if (track.isGlobalTrackWoDCA() || track.tpcNClsCrossedRows() < tpcNClsCrossedRowsMin) { |
| 199 | + continue; |
| 200 | + } |
| 201 | + |
| 202 | + // Apply DCA cuts |
| 203 | + if (usePtDiffDcaXYCut) |
| 204 | + { |
| 205 | + float const dcaXYTrkCut = funcDcaXYPtCutPrimTrk->Eval(pt); |
| 206 | + if (std::fabs(track.dcaXY()) > dcaXYTrkCut) |
| 207 | + { |
| 208 | + continue; |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + int const trackGlobalIndex = track.globalIndex(); |
| 213 | + // A side |
| 214 | + if (eta < etaAMax && eta > etaAMin) |
| 215 | + { |
| 216 | + if (std::find(candProngsB.begin(), candProngsB.end(), trackGlobalIndex) != candProngsB.end()) |
| 217 | + { |
| 218 | + continue; // skip tracks that are daughters of the candidate in the opposite subevent |
| 219 | + /*TODO: Considering remove the overlapped daughter tracks by recalculating the mean pT |
| 220 | + excluding the candidate daughters on the fly for each candidate*/ |
| 221 | + } |
| 222 | + sumPtA += pt; |
| 223 | + countA++; |
| 224 | + } |
| 225 | + |
| 226 | + // B side |
| 227 | + if (eta < etaBMax && eta > etaBMin) |
| 228 | + { |
| 229 | + if (std::find(candProngsA.begin(), candProngsA.end(), trackGlobalIndex) != candProngsA.end()) |
| 230 | + { |
| 231 | + continue; // skip tracks that are daughters of the candidate in the opposite subevent |
| 232 | + /*TODO: Considering remove the overlapped daughter tracks by recalculating the mean pT |
| 233 | + excluding the candidate daughters on the fly for each candidate*/ |
| 234 | + } |
| 235 | + sumPtB += pt; |
| 236 | + countB++; |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + if (countA == 0 && countB == 0) |
| 241 | + { |
| 242 | + return {std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::quiet_NaN()}; |
| 243 | + // return NaN if no tracks in either subevent |
| 244 | + } else if (countA == 0) |
| 245 | + { |
| 246 | + return {std::numeric_limits<float>::quiet_NaN(), sumPtB / countB}; // NaN for subevent A if no tracks |
| 247 | + } else if (countB == 0) |
| 248 | + { |
| 249 | + return {sumPtA / countA, std::numeric_limits<float>::quiet_NaN()}; // NaN for subevent B if no tracks |
| 250 | + } |
| 251 | + return {sumPtA / countA, sumPtB / countB}; |
| 252 | + } |
| 253 | + |
| 254 | + /// Calculate mean pT of tracks in subevent A and B, and fill the THnSparse |
| 255 | + /// \param collision is the collision with the centrality and multiplicity information |
| 256 | + /// \param tracks are the tracks to be used for mean pT calculation |
| 257 | + /// \param candidates are the D0 candidates to be analyzed |
| 258 | + template <DecayChannel Channel, typename CandT> |
| 259 | + void runCharmBulkAnalysis(CandT const& candidates, TracksTable const& tracks, float cent) |
| 260 | + { |
| 261 | + auto [meanPtA, meanPtB] = calculateMeanPt(tracks, candidates); |
| 262 | + // Loop over candidates and fill the THnSparse |
| 263 | + for (const auto& cand : candidates) |
| 264 | + { |
| 265 | + float invMass = 0.f; |
| 266 | + std::vector<float> outputMl = {-999., -999.}; |
| 267 | + if constexpr (std::is_same_v<CandT, D0CandTable>) { |
| 268 | + switch (Channel) { |
| 269 | + case DecayChannel::D0ToPiK: |
| 270 | + invMass = HfHelper::invMassD0ToPiK(cand); |
| 271 | + for (unsigned int iclass = 0; iclass < classMl->size(); iclass++) { |
| 272 | + outputMl[iclass] = cand.mlProbD0()[classMl->at(iclass)]; |
| 273 | + } |
| 274 | + break; |
| 275 | + case DecayChannel::D0ToKPi: |
| 276 | + invMass = HfHelper::invMassD0barToKPi(cand); |
| 277 | + for (unsigned int iclass = 0; iclass < classMl->size(); iclass++) { |
| 278 | + outputMl[iclass] = cand.mlProbD0bar()[classMl->at(iclass)]; |
| 279 | + } |
| 280 | + break; |
| 281 | + } |
| 282 | + } |
| 283 | + registry.fill(HIST("hSparseMeanTrkPtCharm"), cent, invMass, cand.pt(), cand.eta(), meanPtA, meanPtB, outputMl[0], outputMl[1]); |
| 284 | + registry.fill(HIST("hMeanTrkPtAVsCent"), cent, meanPtA); |
| 285 | + registry.fill(HIST("hMeanTrkPtBVsCent"), cent, meanPtB); |
| 286 | + } |
| 287 | + } |
| 288 | + |
| 289 | + /// Check event selections for collision and fill event selection histograms |
| 290 | + /// \param collision is the collision |
| 291 | + template <typename Coll> |
| 292 | + bool isSelectedHfCollision(Coll const& collision, float& cent) |
| 293 | + { |
| 294 | + o2::hf_evsel::HfCollisionRejectionMask collRejMask{}; |
| 295 | + if (centEstimator == CentralityEstimator::FT0A) |
| 296 | + { |
| 297 | + collRejMask = hfEvSel.getHfCollisionRejectionMask<true, CentralityEstimator::FT0A, aod::BCsWithTimestamps>(collision, cent, ccdb, registry); |
| 298 | + } else if (centEstimator == CentralityEstimator::FT0C) |
| 299 | + { |
| 300 | + collRejMask = hfEvSel.getHfCollisionRejectionMask<true, CentralityEstimator::FT0C, aod::BCsWithTimestamps>(collision, cent, ccdb, registry); |
| 301 | + } else if (centEstimator == CentralityEstimator::FT0M) |
| 302 | + { |
| 303 | + collRejMask = hfEvSel.getHfCollisionRejectionMask<true, CentralityEstimator::FT0M, aod::BCsWithTimestamps>(collision, cent, ccdb, registry); |
| 304 | + } else if (centEstimator == CentralityEstimator::FV0A) |
| 305 | + { |
| 306 | + collRejMask = hfEvSel.getHfCollisionRejectionMask<true, CentralityEstimator::FV0A, aod::BCsWithTimestamps>(collision, cent, ccdb, registry); |
| 307 | + } else |
| 308 | + { |
| 309 | + LOG(fatal) << "Centrality estimator not recognized for collision selection"; |
| 310 | + std::abort(); |
| 311 | + } |
| 312 | + hfEvSel.fillHistograms(collision, collRejMask, cent); |
| 313 | + return collRejMask == 0; |
| 314 | + } |
| 315 | + |
| 316 | + void processD0WCentMult(CollsWithCentMult::iterator const& collision, TracksTable const& tracks, D0CandTable const& /* D0 candidates */) { |
| 317 | + auto tableD0ToPiK = selectedD0ToPiK->sliceByCached(aod::hf_cand::collisionId, collision.globalIndex(), cache); |
| 318 | + auto tableD0ToKPi = selectedD0ToKPi->sliceByCached(aod::hf_cand::collisionId, collision.globalIndex(), cache); |
| 319 | + if (forceCharmInCollision && tableD0ToPiK.size() < 1 && tableD0ToKPi.size() < 1) { |
| 320 | + return; |
| 321 | + } |
| 322 | + float cent = -1.f; |
| 323 | + if (!isSelectedHfCollision(collision, cent)) { |
| 324 | + return; |
| 325 | + } |
| 326 | + runCharmBulkAnalysis<DecayChannel::D0ToPiK>(tableD0ToPiK, tracks, cent); |
| 327 | + runCharmBulkAnalysis<DecayChannel::D0ToKPi>(tableD0ToKPi, tracks, cent); |
| 328 | + } |
| 329 | + PROCESS_SWITCH(HfTaskV0PtCharmBulk, processD0WCentMult, "Process D0 candidates for tracks' mean-pT analysis", true); |
| 330 | +}; // End struct HfTaskV0PtCharmBulk |
| 331 | + |
| 332 | +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) |
| 333 | +{ |
| 334 | + return WorkflowSpec{adaptAnalysisTask<HfTaskV0PtCharmBulk>(cfgc)}; |
| 335 | +} |
0 commit comments