|
| 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 f0phiproxy.cxx |
| 13 | +/// \brief task for dipion f0 candidates with a half-momentum kaon proxy and bachelor kaon. |
| 14 | +/// \author Sushanta Tripathy |
| 15 | + |
| 16 | +#include "Common/DataModel/EventSelection.h" |
| 17 | +#include "Common/DataModel/Multiplicity.h" |
| 18 | +#include "Common/DataModel/PIDResponseTOF.h" |
| 19 | +#include "Common/DataModel/PIDResponseTPC.h" |
| 20 | +#include "Common/DataModel/TrackSelectionTables.h" |
| 21 | + |
| 22 | +#include <CommonConstants/PhysicsConstants.h> |
| 23 | +#include <Framework/ASoA.h> |
| 24 | +#include <Framework/AnalysisDataModel.h> |
| 25 | +#include <Framework/AnalysisTask.h> |
| 26 | +#include <Framework/Configurable.h> |
| 27 | +#include <Framework/HistogramRegistry.h> |
| 28 | +#include <Framework/HistogramSpec.h> |
| 29 | +#include <Framework/InitContext.h> |
| 30 | +#include <Framework/OutputObjHeader.h> |
| 31 | +#include <Framework/runDataProcessing.h> |
| 32 | + |
| 33 | +#include <Math/Vector4D.h> // IWYU pragma: keep (do not replace with Math/Vector4Dfwd.h) |
| 34 | +#include <Math/Vector4Dfwd.h> |
| 35 | + |
| 36 | +#include <cmath> |
| 37 | +#include <cstddef> |
| 38 | +#include <cstdint> |
| 39 | +#include <deque> |
| 40 | +#include <map> |
| 41 | +#include <stdexcept> |
| 42 | +#include <utility> |
| 43 | +#include <vector> |
| 44 | + |
| 45 | +using namespace o2; |
| 46 | +using namespace o2::framework; |
| 47 | + |
| 48 | +struct F0phiproxy { |
| 49 | + using Collisions = soa::Join<aod::Collisions, aod::EvSels, aod::Mults>; |
| 50 | + using Tracks = |
| 51 | + soa::Join<aod::Tracks, aod::TracksExtra, aod::TracksDCA, |
| 52 | + aod::TrackSelection, aod::pidTPCFullPi, aod::pidTOFFullPi, |
| 53 | + aod::pidTPCFullKa, aod::pidTOFFullKa>; |
| 54 | + using FourVector = ROOT::Math::PxPyPzMVector; |
| 55 | + Preslice<Tracks> perCollision = aod::track::collisionId; |
| 56 | + HistogramRegistry histos{ |
| 57 | + "histos", |
| 58 | + {}, |
| 59 | + OutputObjHandlingPolicy::AnalysisObject}; |
| 60 | + Configurable<float> vertexMax{"vertexMax", 10.f, |
| 61 | + "Maximum absolute vertex z (cm)"}; |
| 62 | + Configurable<bool> requireSel8{"requireSel8", true, "Require sel8"}; |
| 63 | + Configurable<bool> requireINELgt0{"requireINELgt0", true, |
| 64 | + "Require at least one PV track in |eta|<1"}; |
| 65 | + Configurable<float> trackPtMin{"trackPtMin", 0.15f, |
| 66 | + "Minimum track pT (GeV/c)"}; |
| 67 | + Configurable<float> trackEtaMax{"trackEtaMax", 0.8f, "Maximum track |eta|"}; |
| 68 | + Configurable<bool> requireGlobalTrack{"requireGlobalTrack", true, |
| 69 | + "Require global track selection"}; |
| 70 | + Configurable<bool> requirePVContributor{"requirePVContributor", true, |
| 71 | + "Require PV contributor"}; |
| 72 | + Configurable<float> pionTPC{"pionTPC", 3.f, "Pion TPC nSigma cut"}; |
| 73 | + Configurable<float> pionTOF{"pionTOF", 3.f, "Pion TOF veto when matched"}; |
| 74 | + Configurable<float> kaonTPC{"kaonTPC", 3.f, "Kaon TPC nSigma cut"}; |
| 75 | + Configurable<float> kaonTOF{"kaonTOF", 3.f, "Kaon TOF veto when matched"}; |
| 76 | + Configurable<bool> requireTOF{"requireTOF", false, "Require TOF for both species instead of veto-only"}; |
| 77 | + Configurable<float> dipionYMax{"dipionYMax", 0.5f, "Maximum dipion |y|"}; |
| 78 | + Configurable<float> proxyYMax{"proxyYMax", 0.5f, |
| 79 | + "Maximum proxy+bachelor |y|"}; |
| 80 | + Configurable<float> dipionMassMin{"dipionMassMin", 0.6f, |
| 81 | + "Lower dipion mass for proxy construction"}; |
| 82 | + Configurable<float> dipionMassMax{"dipionMassMax", 1.4f, |
| 83 | + "Upper dipion mass for proxy construction"}; |
| 84 | + Configurable<int> mixDepth{"mixDepth", 5, "Previous events per mixing bin; zero disables mixing"}; |
| 85 | + Configurable<float> mixZWidth{"mixZWidth", 2.f, "Vertex bin width (cm)"}; |
| 86 | + Configurable<float> mixMultWidth{"mixMultWidth", 20.f, |
| 87 | + "PV multiplicity bin width"}; |
| 88 | + |
| 89 | + struct TrackCandidate { |
| 90 | + int64_t id; |
| 91 | + int sign; |
| 92 | + double px, py, pz; |
| 93 | + bool hasTOF; |
| 94 | + [[nodiscard]] FourVector vector(double mass) const |
| 95 | + { |
| 96 | + return {px, py, pz, mass}; |
| 97 | + } |
| 98 | + }; |
| 99 | + struct Event { |
| 100 | + std::vector<TrackCandidate> pions, kaons; |
| 101 | + }; |
| 102 | + |
| 103 | + void init(InitContext const&) |
| 104 | + { |
| 105 | + if (mixDepth < 0 || mixZWidth <= 0.f || mixMultWidth <= 0.f || |
| 106 | + vertexMax <= 0.f || pionTPC <= 0.f || pionTOF <= 0.f || |
| 107 | + kaonTPC <= 0.f || kaonTOF <= 0.f || dipionMassMin >= dipionMassMax) { |
| 108 | + throw std::runtime_error("Invalid F0PhiProxy configuration"); |
| 109 | + } |
| 110 | + AxisSpec massPi{300, 0.2, 1.7, "m_{#pi#pi} (GeV/c^{2})"}; |
| 111 | + AxisSpec massProxy{600, 0.9, 1.5, "m_{K,proxy K} (GeV/c^{2})"}; |
| 112 | + AxisSpec pt{100, 0., 10., "p_{T} (GeV/c)"}; |
| 113 | + AxisSpec mult{100, 0., 200., "N_{PV}(|#eta|<1)"}; |
| 114 | + AxisSpec charge{3, -0.5, 2.5, "dipion: 0=unlike, 1=++, 2=--"}; |
| 115 | + AxisSpec bachelorCharge{2, -1.5, 1.5, "bachelor charge"}; |
| 116 | + AxisSpec tof{2, -0.5, 1.5, "bachelor has TOF"}; |
| 117 | + histos.add("hEventSelection", "Received; selected", HistType::kTH1D, |
| 118 | + {{2, 0.5, 2.5}}); |
| 119 | + histos.add("hMixPartners", "Mixing partners;N", HistType::kTH1D, |
| 120 | + {{101, -0.5, 100.5}}); |
| 121 | + histos.add("hPionTPC", "Quality-selected tracks;pT;nSigmaTPC pion", |
| 122 | + HistType::kTH2F, {pt, {100, -10., 10.}}); |
| 123 | + histos.add("hPionTOF", "TOF-matched quality tracks;pT;nSigmaTOF pion", |
| 124 | + HistType::kTH2F, {pt, {100, -10., 10.}}); |
| 125 | + histos.add("hKaonTPC", "Quality-selected tracks;pT;nSigmaTPC kaon", |
| 126 | + HistType::kTH2F, {pt, {100, -10., 10.}}); |
| 127 | + histos.add("hKaonTOF", "TOF-matched quality tracks;pT;nSigmaTOF kaon", |
| 128 | + HistType::kTH2F, {pt, {100, -10., 10.}}); |
| 129 | + histos.add("hDipionSE", "Dipion SE", HistType::kTHnSparseF, |
| 130 | + {massPi, pt, mult, charge}); |
| 131 | + histos.add("hDipionME", "Dipion ME (raw)", HistType::kTHnSparseF, |
| 132 | + {massPi, pt, mult, charge}); |
| 133 | + // Keep measured dipion mass: no f0 identification by a mass window alone. |
| 134 | + histos.add("hProxySE", "Dipion plus bachelor proxy SE", |
| 135 | + HistType::kTHnSparseF, |
| 136 | + {massProxy, pt, massPi, mult, charge, bachelorCharge, tof}); |
| 137 | + histos.add("hProxyME", "Intact same-event dipion plus mixed bachelor (raw)", |
| 138 | + HistType::kTHnSparseF, |
| 139 | + {massProxy, pt, massPi, mult, charge, bachelorCharge, tof}); |
| 140 | + AxisSpec massKK{600, 0.9, 1.5, "m_{KK} (GeV/c^{2})"}; |
| 141 | + histos.add("hPhiKK", "Unlike-sign kaon control SE", HistType::kTHnSparseF, |
| 142 | + {massKK, pt, mult}); |
| 143 | + histos.add("hPhiKKLikePP", "K+K+ control SE", HistType::kTHnSparseF, |
| 144 | + {massKK, pt, mult}); |
| 145 | + histos.add("hPhiKKLikeMM", "K-K- control SE", HistType::kTHnSparseF, |
| 146 | + {massKK, pt, mult}); |
| 147 | + histos.add("hPhiKKMixed", "Unlike-sign kaon control ME (raw)", HistType::kTHnSparseF, |
| 148 | + {massKK, pt, mult}); |
| 149 | + histos.add("hPhiKKMixedLikePP", "K+K+ control ME (raw)", HistType::kTHnSparseF, |
| 150 | + {massKK, pt, mult}); |
| 151 | + histos.add("hPhiKKMixedLikeMM", "K-K- control ME (raw)", HistType::kTHnSparseF, |
| 152 | + {massKK, pt, mult}); |
| 153 | + } |
| 154 | + |
| 155 | + bool passPID(float tpc, float tof, bool matched, float tpcCut, |
| 156 | + float tofCut) const |
| 157 | + { |
| 158 | + return std::isfinite(tpc) && std::abs(tpc) < tpcCut && |
| 159 | + (!requireTOF.value || matched) && |
| 160 | + (!matched || (std::isfinite(tof) && std::abs(tof) < tofCut)); |
| 161 | + } |
| 162 | + |
| 163 | + static int chargeCategory(int a, int b) |
| 164 | + { |
| 165 | + return a != b ? 0 : (a > 0 ? 1 : 2); |
| 166 | + } |
| 167 | + |
| 168 | + void fillProxy(FourVector const& dipion, TrackCandidate const& first, |
| 169 | + TrackCandidate const& second, |
| 170 | + std::vector<TrackCandidate> const& kaons, float mult, |
| 171 | + bool mixed) |
| 172 | + { |
| 173 | + const FourVector constituent(dipion.Px() * 0.5, dipion.Py() * 0.5, |
| 174 | + dipion.Pz() * 0.5, |
| 175 | + constants::physics::MassKaonCharged); |
| 176 | + const int category = chargeCategory(first.sign, second.sign); |
| 177 | + for (auto const& kaon : kaons) { |
| 178 | + if (!mixed && (kaon.id == first.id || kaon.id == second.id)) { |
| 179 | + continue; |
| 180 | + } |
| 181 | + const auto pair = |
| 182 | + constituent + kaon.vector(constants::physics::MassKaonCharged); |
| 183 | + if (std::abs(pair.Rapidity()) >= proxyYMax) { |
| 184 | + continue; |
| 185 | + } |
| 186 | + // The neutral dipion has no measured constituent-kaon charge: fill once |
| 187 | + // per bachelor. |
| 188 | + if (mixed) { |
| 189 | + histos.fill(HIST("hProxyME"), pair.M(), pair.Pt(), dipion.M(), mult, |
| 190 | + category, kaon.sign, kaon.hasTOF); |
| 191 | + } else { |
| 192 | + histos.fill(HIST("hProxySE"), pair.M(), pair.Pt(), dipion.M(), mult, |
| 193 | + category, kaon.sign, kaon.hasTOF); |
| 194 | + } |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + void fillPhiPair(TrackCandidate const& first, TrackCandidate const& second, float mult, bool mixed) |
| 199 | + { |
| 200 | + const auto pair = first.vector(constants::physics::MassKaonCharged) + |
| 201 | + second.vector(constants::physics::MassKaonCharged); |
| 202 | + if (std::abs(pair.Rapidity()) >= proxyYMax.value) { |
| 203 | + return; |
| 204 | + } |
| 205 | + if (mixed) { |
| 206 | + if (first.sign != second.sign) { |
| 207 | + histos.fill(HIST("hPhiKKMixed"), pair.M(), pair.Pt(), mult); |
| 208 | + } else if (first.sign > 0) { |
| 209 | + histos.fill(HIST("hPhiKKMixedLikePP"), pair.M(), pair.Pt(), mult); |
| 210 | + } else { |
| 211 | + histos.fill(HIST("hPhiKKMixedLikeMM"), pair.M(), pair.Pt(), mult); |
| 212 | + } |
| 213 | + } else { |
| 214 | + if (first.sign != second.sign) { |
| 215 | + histos.fill(HIST("hPhiKK"), pair.M(), pair.Pt(), mult); |
| 216 | + } else if (first.sign > 0) { |
| 217 | + histos.fill(HIST("hPhiKKLikePP"), pair.M(), pair.Pt(), mult); |
| 218 | + } else { |
| 219 | + histos.fill(HIST("hPhiKKLikeMM"), pair.M(), pair.Pt(), mult); |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + void fillPhiControls(Event const& event, std::deque<Event> const& pool, float mult) |
| 225 | + { |
| 226 | + for (size_t i = 0; i < event.kaons.size(); ++i) { |
| 227 | + // Each same-event unordered pair is counted once. |
| 228 | + for (size_t j = i + 1; j < event.kaons.size(); ++j) { |
| 229 | + fillPhiPair(event.kaons[i], event.kaons[j], mult, false); |
| 230 | + } |
| 231 | + // Includes K+current K-previous and K-current K+previous once each. |
| 232 | + // Pool insertion occurs after this call; no event is mixed with itself. |
| 233 | + for (auto const& previous : pool) { |
| 234 | + for (auto const& second : previous.kaons) { |
| 235 | + fillPhiPair(event.kaons[i], second, mult, true); |
| 236 | + } |
| 237 | + } |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + void process(Collisions const& collisions, Tracks const& tracks, |
| 242 | + aod::BCsWithTimestamps const&) |
| 243 | + { |
| 244 | + // Local pools cannot retain track IDs across dataframes. Run is part of the |
| 245 | + // key. |
| 246 | + std::map<std::pair<int, std::pair<int, int>>, std::deque<Event>> pools; |
| 247 | + for (auto const& collision : collisions) { |
| 248 | + histos.fill(HIST("hEventSelection"), 1.); |
| 249 | + if (std::abs(collision.posZ()) >= vertexMax || |
| 250 | + (requireSel8 && !collision.sel8()) || |
| 251 | + (requireINELgt0 && collision.multNTracksPVeta1() < 1)) { |
| 252 | + continue; |
| 253 | + } |
| 254 | + histos.fill(HIST("hEventSelection"), 2.); |
| 255 | + const float mult = collision.multNTracksPVeta1(); |
| 256 | + const int run = collision.bc_as<aod::BCsWithTimestamps>().runNumber(); |
| 257 | + auto& pool = |
| 258 | + pools[{run, |
| 259 | + {static_cast<int>(std::floor(collision.posZ() / mixZWidth)), |
| 260 | + static_cast<int>(std::floor(mult / mixMultWidth))}}]; |
| 261 | + Event event; |
| 262 | + auto selected = tracks.sliceBy(perCollision, collision.globalIndex()); |
| 263 | + for (auto const& track : selected) { |
| 264 | + if (track.sign() == 0 || track.pt() < trackPtMin || |
| 265 | + std::abs(track.eta()) >= trackEtaMax || |
| 266 | + (requireGlobalTrack && !track.isGlobalTrack()) || |
| 267 | + (requirePVContributor && !track.isPVContributor())) { |
| 268 | + continue; |
| 269 | + } |
| 270 | + const bool tof = track.hasTOF(); |
| 271 | + histos.fill(HIST("hPionTPC"), track.pt(), track.tpcNSigmaPi()); |
| 272 | + histos.fill(HIST("hKaonTPC"), track.pt(), track.tpcNSigmaKa()); |
| 273 | + if (tof) { |
| 274 | + histos.fill(HIST("hPionTOF"), track.pt(), track.tofNSigmaPi()); |
| 275 | + histos.fill(HIST("hKaonTOF"), track.pt(), track.tofNSigmaKa()); |
| 276 | + } |
| 277 | + TrackCandidate candidate{.id = track.globalIndex(), .sign = track.sign(), .px = track.px(), .py = track.py(), .pz = track.pz(), .hasTOF = tof}; |
| 278 | + if (passPID(track.tpcNSigmaPi(), track.tofNSigmaPi(), tof, pionTPC, |
| 279 | + pionTOF)) { |
| 280 | + event.pions.push_back(candidate); |
| 281 | + } |
| 282 | + if (passPID(track.tpcNSigmaKa(), track.tofNSigmaKa(), tof, kaonTPC, |
| 283 | + kaonTOF)) { |
| 284 | + event.kaons.push_back(candidate); |
| 285 | + } |
| 286 | + } |
| 287 | + histos.fill(HIST("hMixPartners"), pool.size()); |
| 288 | + for (size_t i = 0; i < event.pions.size(); ++i) { |
| 289 | + auto const& first = event.pions[i]; |
| 290 | + for (size_t j = i + 1; j < event.pions.size(); ++j) { |
| 291 | + auto const& second = event.pions[j]; |
| 292 | + const auto pair = first.vector(constants::physics::MassPionCharged) + |
| 293 | + second.vector(constants::physics::MassPionCharged); |
| 294 | + if (std::abs(pair.Rapidity()) >= dipionYMax) { |
| 295 | + continue; |
| 296 | + } |
| 297 | + histos.fill(HIST("hDipionSE"), pair.M(), pair.Pt(), mult, |
| 298 | + chargeCategory(first.sign, second.sign)); |
| 299 | + if (pair.M() < dipionMassMin || pair.M() >= dipionMassMax) { |
| 300 | + continue; |
| 301 | + } |
| 302 | + fillProxy(pair, first, second, event.kaons, mult, false); |
| 303 | + for (auto const& previous : pool) { |
| 304 | + fillProxy(pair, first, second, previous.kaons, mult, true); |
| 305 | + } |
| 306 | + } |
| 307 | + for (auto const& previous : pool) { |
| 308 | + for (auto const& second : previous.pions) { |
| 309 | + const auto pair = |
| 310 | + first.vector(constants::physics::MassPionCharged) + |
| 311 | + second.vector(constants::physics::MassPionCharged); |
| 312 | + if (std::abs(pair.Rapidity()) < dipionYMax) { |
| 313 | + histos.fill(HIST("hDipionME"), pair.M(), pair.Pt(), mult, |
| 314 | + chargeCategory(first.sign, second.sign)); |
| 315 | + } |
| 316 | + } |
| 317 | + } |
| 318 | + } |
| 319 | + fillPhiControls(event, pool, mult); |
| 320 | + if (mixDepth > 0) { |
| 321 | + pool.push_back(std::move(event)); |
| 322 | + if (pool.size() > static_cast<size_t>(mixDepth.value)) { |
| 323 | + pool.pop_front(); |
| 324 | + } |
| 325 | + } |
| 326 | + } |
| 327 | + } |
| 328 | +}; |
| 329 | + |
| 330 | +WorkflowSpec defineDataProcessing(ConfigContext const& context) |
| 331 | +{ |
| 332 | + return WorkflowSpec{ |
| 333 | + adaptAnalysisTask<F0phiproxy>(context)}; |
| 334 | +} |
0 commit comments