|
| 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 | +/// \brief A filter task for non prompt deuterons from beauty-hadron decays |
| 13 | +/// \author Marta Razza marta.razza@cern.ch |
| 14 | +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch |
| 15 | +/// \since May 25, 2026 |
| 16 | + |
| 17 | +#include "Common/Core/Zorro.h" |
| 18 | +#include "Common/Core/ZorroSummary.h" |
| 19 | +#include "Common/Core/trackUtilities.h" |
| 20 | +#include "Common/DataModel/CollisionAssociationTables.h" |
| 21 | +#include "Common/DataModel/EventSelection.h" |
| 22 | +#include "Common/DataModel/PIDResponseTOF.h" |
| 23 | +#include "Common/DataModel/PIDResponseTPC.h" |
| 24 | +#include "Common/DataModel/TrackSelectionTables.h" |
| 25 | + |
| 26 | +#include <CCDB/BasicCCDBManager.h> |
| 27 | +#include <DataFormatsParameters/GRPMagField.h> |
| 28 | +#include <DetectorsBase/Propagator.h> |
| 29 | +#include <Framework/AnalysisDataModel.h> |
| 30 | +#include <Framework/AnalysisHelpers.h> |
| 31 | +#include <Framework/AnalysisTask.h> |
| 32 | +#include <Framework/Configurable.h> |
| 33 | +#include <Framework/HistogramRegistry.h> |
| 34 | +#include <Framework/HistogramSpec.h> |
| 35 | +#include <Framework/InitContext.h> |
| 36 | +#include <Framework/OutputObjHeader.h> |
| 37 | +#include <Framework/runDataProcessing.h> |
| 38 | +#include <ReconstructionDataFormats/Track.h> |
| 39 | +#include <ReconstructionDataFormats/TrackParametrization.h> |
| 40 | + |
| 41 | +#include <TH1.h> |
| 42 | + |
| 43 | +#include <array> |
| 44 | +#include <chrono> |
| 45 | +#include <cmath> |
| 46 | +#include <string> |
| 47 | + |
| 48 | +using namespace o2; |
| 49 | +using namespace o2::framework; |
| 50 | +using namespace o2::framework::expressions; |
| 51 | + |
| 52 | +struct HfTaskDeuteronFromLb { |
| 53 | + |
| 54 | + Zorro zorro; |
| 55 | + o2::base::Propagator::MatCorrType noMatCorr = o2::base::Propagator::MatCorrType::USEMatCorrNONE; |
| 56 | + |
| 57 | + Configurable<float> cutzvertex{"cutzvertex", 10.0f, "Accepted z-vertex range (cm)"}; |
| 58 | + Configurable<bool> applySkimming{"applySkimming", false, "Skimmed dataset processing"}; |
| 59 | + Configurable<std::string> cfgSkimming{"cfgSkimming", "fH2fromLb", "Configurable for skimming"}; |
| 60 | + Configurable<bool> sel8{"sel8", true, "sel8 event selection"}; |
| 61 | + Configurable<bool> separateAntideuterons{"separateAntideuterons", true, "Fill FromLb histos for primary deuterons whose mother is Lb, Primary histos for all others"}; |
| 62 | + Configurable<float> cfgEta{"cfgEta", 0.8f, "Track eta selection"}; |
| 63 | + Configurable<float> cfgTPCNclsFound{"cfgTPCNclsFound", 100, "Minimum TPC clusters found"}; |
| 64 | + Configurable<float> cfgTPCChi2Ncl{"cfgTPCChi2Ncl", 4.0f, "Maximum TPC chi2 per N clusters"}; |
| 65 | + Configurable<float> cfgITSChi2Ncl{"cfgITSChi2Ncl", 36.0f, "Maximum ITS chi2 per N clusters"}; |
| 66 | + Configurable<float> cfgITScls{"cfgITScls", 2, "Minimum ITS clusters"}; |
| 67 | + Configurable<float> cfgMaxPt{"cfgMaxPt", 5.0f, "Maximum pT cut"}; |
| 68 | + Configurable<float> cfgMinPt{"cfgMinPt", 0.5f, "Minimum pT cut"}; |
| 69 | + Configurable<float> cfgTPCNsigma{"cfgTPCNsigma", 4.0f, "TPC n sigma for deuteron PID"}; |
| 70 | + Configurable<float> cfgTofNsigmaMin{"cfgTofNsigmaMin", 3.0f, "TOF n sigma min for deuteron PID"}; |
| 71 | + Configurable<float> cfgTofNsigmaMax{"cfgTofNsigmaMax", 4.0f, "TOF n sigma max for deuteron PID"}; |
| 72 | + Configurable<float> ptThresholdPid{"ptThresholdPid", 1.0f, "pT threshold to switch between 4 and 3 sigmas for TOF PID"}; |
| 73 | + Configurable<float> rapidityCut{"rapidityCut", 0.5f, "Rapidity cut"}; |
| 74 | + // PDG codes |
| 75 | + Configurable<int> pdgCodeMother{"pdgCodeMother", -5122, "PDG code of the mother particle (default: anti-Lambda_b)"}; |
| 76 | + Configurable<int> pdgCodeDaughter{"pdgCodeDaughter", -1000010020, "PDG code of the daughter particle (default: anti-deuteron)"}; |
| 77 | + |
| 78 | + int mRunNumber = 0; |
| 79 | + float d_bz = 0.f; |
| 80 | + int mCurrentRun = -1; |
| 81 | + |
| 82 | + framework::Service<ccdb::BasicCCDBManager> ccdb; |
| 83 | + |
| 84 | + using CollisionCandidates = o2::soa::Join<o2::aod::Collisions, o2::aod::EvSels>; |
| 85 | + using MCTrackCandidates = o2::soa::Join<o2::aod::TracksIU, o2::aod::TracksExtra, o2::aod::TracksDCA, o2::aod::McTrackLabels>; |
| 86 | + using MCCollisionCandidates = o2::soa::Join<o2::aod::Collisions, o2::aod::EvSels, o2::aod::McCollisionLabels>; |
| 87 | + using TrackCandidates = o2::soa::Join<o2::aod::Tracks, o2::aod::TracksCov, o2::aod::TracksExtra, o2::aod::TracksDCA, o2::aod::TrackSelection, o2::aod::pidTPCFullDe, o2::aod::pidTOFFullDe>; |
| 88 | + |
| 89 | + Preslice<o2::aod::TrackAssoc> trackIndicesPerCollision = o2::aod::track_association::collisionId; |
| 90 | + |
| 91 | + HistogramRegistry QAHistos{"QAHistos", {}, OutputObjHandlingPolicy::AnalysisObject, false, true}; |
| 92 | + HistogramRegistry registry{"registry", {}, OutputObjHandlingPolicy::AnalysisObject}; |
| 93 | + |
| 94 | + OutputObj<ZorroSummary> zorroSummary{"zorroSummary"}; |
| 95 | + OutputObj<TH1D> hProcessedEvents{TH1D("hProcessedEvents", "Event filtered;; Number of events", 4, 0., 4.)}; |
| 96 | + |
| 97 | + void init(framework::InitContext&) |
| 98 | + { |
| 99 | + ccdb->setURL("http://alice-ccdb.cern.ch"); |
| 100 | + ccdb->setCaching(true); |
| 101 | + ccdb->setLocalObjectValidityChecking(); |
| 102 | + ccdb->setCreatedNotAfter(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()); |
| 103 | + |
| 104 | + if (applySkimming) { |
| 105 | + zorroSummary.setObject(zorro.getZorroSummary()); |
| 106 | + } |
| 107 | + |
| 108 | + ConfigurableAxis ptAxis{"ptAxis", {100, 0., 10.f}, "p_{T} GeV/c"}; |
| 109 | + ConfigurableAxis nSigmaAxis{"nSigmaAxis", {200, -10.f, 10.f}, "nSigma"}; |
| 110 | + ConfigurableAxis dcaXyAxis{"dcaXyAxis", {1000, -0.2f, 0.2f}, "DCA xy (cm)"}; |
| 111 | + ConfigurableAxis dcaZAxis{"dcaZAxis", {1000, -0.2f, 0.2f}, "DCA z (cm)"}; |
| 112 | + |
| 113 | + QAHistos.add("hVtxZ", "Z-Vertex distribution after selection;Z (cm)", HistType::kTH1F, {{100, -50, 50}}); |
| 114 | + QAHistos.add("ptGeneratedLb", "ptGeneratedLb", HistType::kTH1F, {ptAxis}); |
| 115 | + QAHistos.add("ptAntiDeuteronPrimary", "ptAntiDeuteronPrimaryReco", HistType::kTH1F, {ptAxis}); |
| 116 | + QAHistos.add("ptAntiDeuteronFromLb", "ptAntiDeuteronFromLbReco", HistType::kTH1F, {ptAxis}); |
| 117 | + QAHistos.add("hDCAxy-Primary", "DCAxy-Primary", {HistType::kTH1D, {{400, -0.2f, 0.2f, "DCA xy (cm)"}}}); |
| 118 | + QAHistos.add("hDCAxy-FromLb", "DCAxy-FromLb", {HistType::kTH1D, {{400, -0.2f, 0.2f, "DCA xy (cm)"}}}); |
| 119 | + QAHistos.add("hDCAxyVsPt", "DCAxy #bar{d} vs p_{T}", {HistType::kTH2D, {ptAxis, dcaXyAxis}}); |
| 120 | + QAHistos.add("hDCAzVsPt", "DCAz #bar{d} vs p_{T}", {HistType::kTH2D, {ptAxis, dcaZAxis}}); |
| 121 | + QAHistos.add("hnSigmaTPCVsPt", "n#sigma TPC vs p_{T} for #bar{d} hypothesis; p_{T} (GeV/c); n#sigma TPC", {HistType::kTH2D, {ptAxis, nSigmaAxis}}); |
| 122 | + QAHistos.add("hnSigmaTOFVsPt", "n#sigma TOF vs p_{T} for #bar{d} hypothesis; p_{T} (GeV/c); n#sigma TOF", {HistType::kTH2D, {ptAxis, nSigmaAxis}}); |
| 123 | + QAHistos.add("ptAntiDeuteron", "ptAntiDeuteron", {HistType::kTH1F, {ptAxis}}); |
| 124 | + QAHistos.add("etaAntideuteron", "etaAntideuteron", {HistType::kTH1F, {{100, -1.0f, 1.0f, "eta #bar{d}"}}}); |
| 125 | + |
| 126 | + hProcessedEvents->GetXaxis()->SetBinLabel(1, "Events processed"); |
| 127 | + hProcessedEvents->GetXaxis()->SetBinLabel(2, "ZORRO"); |
| 128 | + hProcessedEvents->GetXaxis()->SetBinLabel(3, "sel8"); |
| 129 | + hProcessedEvents->GetXaxis()->SetBinLabel(4, "z-vertex"); |
| 130 | + } |
| 131 | + |
| 132 | + void initCCDB(o2::aod::BCsWithTimestamps::iterator const& bc) |
| 133 | + { |
| 134 | + if (mRunNumber == bc.runNumber()) { |
| 135 | + return; |
| 136 | + } |
| 137 | + d_bz = 0.f; |
| 138 | + |
| 139 | + if (applySkimming) { |
| 140 | + zorro.initCCDB(ccdb.service, bc.runNumber(), bc.timestamp(), cfgSkimming.value); |
| 141 | + zorro.populateHistRegistry(registry, bc.runNumber()); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + template <typename T1> |
| 146 | + bool passedSingleTrackSelection(const T1& track) |
| 147 | + { |
| 148 | + if (std::abs(track.eta()) > cfgEta) |
| 149 | + return false; |
| 150 | + if (!track.hasITS()) |
| 151 | + return false; |
| 152 | + if (!track.hasTPC()) |
| 153 | + return false; |
| 154 | + if (!track.hasTOF()) |
| 155 | + return false; |
| 156 | + if (track.tpcNClsFound() < cfgTPCNclsFound) |
| 157 | + return false; |
| 158 | + if (track.tpcChi2NCl() > cfgTPCChi2Ncl) |
| 159 | + return false; |
| 160 | + if (track.itsChi2NCl() > cfgITSChi2Ncl) |
| 161 | + return false; |
| 162 | + if (track.itsNCls() < cfgITScls) |
| 163 | + return false; |
| 164 | + if (track.pt() > cfgMaxPt) |
| 165 | + return false; |
| 166 | + if (track.pt() < cfgMinPt) |
| 167 | + return false; |
| 168 | + if (track.sign() > 0) |
| 169 | + return false; |
| 170 | + |
| 171 | + return true; |
| 172 | + } |
| 173 | + |
| 174 | + void processData(CollisionCandidates const& collisions, |
| 175 | + o2::aod::TrackAssoc const& trackIndices, |
| 176 | + TrackCandidates const& tracks, |
| 177 | + o2::aod::BCsWithTimestamps const&) |
| 178 | + { |
| 179 | + for (const auto& collision : collisions) { |
| 180 | + if (mCurrentRun != collision.bc_as<o2::aod::BCsWithTimestamps>().runNumber()) { |
| 181 | + o2::parameters::GRPMagField* grpo = ccdb->getForTimeStamp<o2::parameters::GRPMagField>("GLO/Config/GRPMagField", collision.bc_as<o2::aod::BCsWithTimestamps>().timestamp()); |
| 182 | + o2::base::Propagator::initFieldFromGRP(grpo); |
| 183 | + mCurrentRun = collision.bc_as<o2::aod::BCsWithTimestamps>().runNumber(); |
| 184 | + } |
| 185 | + |
| 186 | + const auto& bc = collision.bc_as<o2::aod::BCsWithTimestamps>(); |
| 187 | + initCCDB(bc); |
| 188 | + hProcessedEvents->Fill(0.5); |
| 189 | + if (applySkimming) { |
| 190 | + if (!zorro.isSelected(bc.globalBC())) { |
| 191 | + continue; |
| 192 | + } |
| 193 | + } |
| 194 | + hProcessedEvents->Fill(1.5); |
| 195 | + if (sel8 && !collision.sel8()) { |
| 196 | + continue; |
| 197 | + } |
| 198 | + hProcessedEvents->Fill(2.5); |
| 199 | + if (std::abs(collision.posZ()) > cutzvertex) { |
| 200 | + continue; |
| 201 | + } |
| 202 | + hProcessedEvents->Fill(3.5); |
| 203 | + QAHistos.fill(HIST("hVtxZ"), collision.posZ()); |
| 204 | + |
| 205 | + const auto& trackIdsThisCollision = trackIndices.sliceBy(trackIndicesPerCollision, collision.globalIndex()); |
| 206 | + |
| 207 | + for (const auto& trackId : trackIdsThisCollision) { |
| 208 | + const auto& track = tracks.rawIteratorAt(trackId.trackId()); |
| 209 | + std::array<float, 2> dca{track.dcaXY(), track.dcaZ()}; |
| 210 | + |
| 211 | + if (track.collisionId() != collision.globalIndex()) { |
| 212 | + auto trackPar = getTrackParCov(track); |
| 213 | + o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, trackPar, 2.f, noMatCorr, &dca); |
| 214 | + } |
| 215 | + |
| 216 | + if (!passedSingleTrackSelection(track)) { |
| 217 | + continue; |
| 218 | + } |
| 219 | + |
| 220 | + const bool isTPCDe = std::abs(track.tpcNSigmaDe()) < cfgTPCNsigma; |
| 221 | + const bool isTOFDe_min = std::abs(track.tofNSigmaDe()) > cfgTofNsigmaMin; |
| 222 | + const bool isTOFDe_max = std::abs(track.tofNSigmaDe()) < cfgTofNsigmaMax; |
| 223 | + |
| 224 | + if (track.pt() < ptThresholdPid) { |
| 225 | + if (isTPCDe && isTOFDe_max) { |
| 226 | + QAHistos.fill(HIST("ptAntiDeuteron"), track.pt()); |
| 227 | + QAHistos.fill(HIST("etaAntideuteron"), track.eta()); |
| 228 | + QAHistos.fill(HIST("hDCAxyVsPt"), track.pt(), dca[0]); |
| 229 | + QAHistos.fill(HIST("hDCAzVsPt"), track.pt(), dca[1]); |
| 230 | + QAHistos.fill(HIST("hnSigmaTPCVsPt"), track.pt(), track.tpcNSigmaDe()); |
| 231 | + QAHistos.fill(HIST("hnSigmaTOFVsPt"), track.pt(), track.tofNSigmaDe()); |
| 232 | + } |
| 233 | + } else { |
| 234 | + if (isTPCDe && isTOFDe_min && isTOFDe_max) { |
| 235 | + QAHistos.fill(HIST("ptAntiDeuteron"), track.pt()); |
| 236 | + QAHistos.fill(HIST("etaAntideuteron"), track.eta()); |
| 237 | + QAHistos.fill(HIST("hDCAxyVsPt"), track.pt(), dca[0]); |
| 238 | + QAHistos.fill(HIST("hDCAzVsPt"), track.pt(), dca[1]); |
| 239 | + QAHistos.fill(HIST("hnSigmaTPCVsPt"), track.pt(), track.tpcNSigmaDe()); |
| 240 | + QAHistos.fill(HIST("hnSigmaTOFVsPt"), track.pt(), track.tofNSigmaDe()); |
| 241 | + } |
| 242 | + } |
| 243 | + } |
| 244 | + } |
| 245 | + } |
| 246 | + PROCESS_SWITCH(HfTaskDeuteronFromLb, processData, "processData", false); |
| 247 | + |
| 248 | + void processMC(MCCollisionCandidates::iterator const&, MCTrackCandidates const& tracks, o2::aod::McParticles const&) |
| 249 | + { |
| 250 | + for (const auto& track : tracks) { |
| 251 | + if (!passedSingleTrackSelection(track)) { |
| 252 | + continue; |
| 253 | + } |
| 254 | + |
| 255 | + if (!track.has_mcParticle()) { |
| 256 | + continue; |
| 257 | + } |
| 258 | + |
| 259 | + auto mcParticle = track.mcParticle(); |
| 260 | + if (mcParticle.pdgCode() == pdgCodeDaughter) { |
| 261 | + if (std::abs(mcParticle.y()) > rapidityCut) { |
| 262 | + continue; |
| 263 | + } |
| 264 | + if (mcParticle.isPhysicalPrimary()) { |
| 265 | + bool isFromLb = false; |
| 266 | + if (separateAntideuterons) { |
| 267 | + for (const auto& mom : mcParticle.mothers_as<o2::aod::McParticles>()) { |
| 268 | + if (mom.pdgCode() == pdgCodeMother) { |
| 269 | + isFromLb = true; |
| 270 | + break; |
| 271 | + } |
| 272 | + } |
| 273 | + } |
| 274 | + if (isFromLb) { |
| 275 | + QAHistos.fill(HIST("hDCAxy-FromLb"), track.dcaXY()); |
| 276 | + QAHistos.fill(HIST("ptAntiDeuteronFromLb"), track.pt()); |
| 277 | + } else { |
| 278 | + QAHistos.fill(HIST("hDCAxy-Primary"), track.dcaXY()); |
| 279 | + QAHistos.fill(HIST("ptAntiDeuteronPrimary"), track.pt()); |
| 280 | + } |
| 281 | + } |
| 282 | + } |
| 283 | + } |
| 284 | + } |
| 285 | + PROCESS_SWITCH(HfTaskDeuteronFromLb, processMC, "processMC", true); |
| 286 | + |
| 287 | + void processGen(o2::aod::McCollision const&, o2::aod::McParticles const& mcParticles) |
| 288 | + { |
| 289 | + hProcessedEvents->Fill(0.5); |
| 290 | + for (const auto& mcParticle : mcParticles) { |
| 291 | + if (mcParticle.pdgCode() == pdgCodeMother) { |
| 292 | + if (std::abs(mcParticle.y()) <= rapidityCut) { |
| 293 | + QAHistos.fill(HIST("ptGeneratedLb"), mcParticle.pt()); |
| 294 | + } |
| 295 | + } |
| 296 | + |
| 297 | + if (mcParticle.pdgCode() == pdgCodeDaughter) { |
| 298 | + if (std::abs(mcParticle.y()) > rapidityCut) |
| 299 | + continue; |
| 300 | + |
| 301 | + bool isFromLb = false; |
| 302 | + if (mcParticle.has_mothers()) { |
| 303 | + for (const auto& mom : mcParticle.mothers_as<o2::aod::McParticles>()) { |
| 304 | + if (mom.pdgCode() == pdgCodeMother) { |
| 305 | + isFromLb = true; |
| 306 | + break; |
| 307 | + } |
| 308 | + } |
| 309 | + } |
| 310 | + |
| 311 | + if (isFromLb) { |
| 312 | + QAHistos.fill(HIST("ptAntiDeuteronFromLb"), mcParticle.pt()); |
| 313 | + } else if (mcParticle.isPhysicalPrimary()) { |
| 314 | + QAHistos.fill(HIST("ptAntiDeuteronPrimary"), mcParticle.pt()); |
| 315 | + } |
| 316 | + } |
| 317 | + } |
| 318 | + } |
| 319 | + PROCESS_SWITCH(HfTaskDeuteronFromLb, processGen, "processGen", false); |
| 320 | +}; |
| 321 | + |
| 322 | +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) |
| 323 | +{ |
| 324 | + return WorkflowSpec{ |
| 325 | + adaptAnalysisTask<HfTaskDeuteronFromLb>(cfgc)}; |
| 326 | +} |
0 commit comments