Skip to content

Commit 58d26a7

Browse files
sustripathySushanta Tripathyalibuild
authored
[PWGLF] Add lambdastar-deuteron and f0-phi proxy tasks for coalescence studies (#17959)
Co-authored-by: Sushanta Tripathy <sushantatripathy@Sushantas-MacBook-Pro-M4-Max.local> Co-authored-by: ALICE Action Bot <alibuild@cern.ch>
1 parent 0e032cd commit 58d26a7

3 files changed

Lines changed: 2397 additions & 0 deletions

File tree

PWGLF/Tasks/Resonances/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,13 @@ o2physics_add_dpl_workflow(xi1530kaoncorrelation
338338
SOURCES xi1530kaoncorrelation.cxx
339339
PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore
340340
COMPONENT_NAME Analysis)
341+
342+
o2physics_add_dpl_workflow(f0phiproxy
343+
SOURCES f0phiproxy.cxx
344+
PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore
345+
COMPONENT_NAME Analysis)
346+
347+
o2physics_add_dpl_workflow(lambdastarproxy
348+
SOURCES lambdastarproxy.cxx
349+
PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore
350+
COMPONENT_NAME Analysis)
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
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

Comments
 (0)