Skip to content

Commit 0357781

Browse files
authored
[PWGCF] TwoParticleCorrelations - Add dihadron correlations (AliceO2Group#11074)
1 parent a6a1861 commit 0357781

File tree

2 files changed

+390
-0
lines changed

2 files changed

+390
-0
lines changed

PWGCF/TwoParticleCorrelations/Tasks/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ o2physics_add_dpl_workflow(neutron-proton-corr-zdc
5858
PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore
5959
COMPONENT_NAME Analysis)
6060

61+
o2physics_add_dpl_workflow(di-hadron-cor
62+
SOURCES diHadronCor.cxx
63+
PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore O2Physics::PWGCFCore
64+
COMPONENT_NAME Analysis)
65+
6166

6267

6368

Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
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 diHadronCor.cxx
13+
/// \brief di-hadron correlation for O-O, Pb-Pb collisions
14+
/// \author Zhiyong Lu ([email protected])
15+
/// \since May/03/2025
16+
17+
#include <CCDB/BasicCCDBManager.h>
18+
#include "TRandom3.h"
19+
#include <vector>
20+
#include <string>
21+
22+
#include "Framework/runDataProcessing.h"
23+
#include "Framework/AnalysisTask.h"
24+
#include "Framework/AnalysisDataModel.h"
25+
#include "Framework/ASoAHelpers.h"
26+
#include "Framework/StepTHn.h"
27+
#include "Framework/HistogramRegistry.h"
28+
#include "Framework/RunningWorkflowInfo.h"
29+
#include "CommonConstants/MathConstants.h"
30+
#include "Common/Core/RecoDecay.h"
31+
32+
#include "Common/DataModel/EventSelection.h"
33+
#include "Common/DataModel/Multiplicity.h"
34+
#include "Common/DataModel/TrackSelectionTables.h"
35+
#include "Common/DataModel/Centrality.h"
36+
#include "PWGCF/DataModel/CorrelationsDerived.h"
37+
#include "Common/DataModel/CollisionAssociationTables.h"
38+
#include "PWGCF/Core/CorrelationContainer.h"
39+
#include "PWGCF/Core/PairCuts.h"
40+
#include "DataFormatsParameters/GRPObject.h"
41+
#include "DataFormatsParameters/GRPMagField.h"
42+
43+
using namespace o2;
44+
using namespace o2::framework;
45+
using namespace o2::framework::expressions;
46+
namespace o2::aod
47+
{
48+
namespace di_hadron_cor
49+
{
50+
DECLARE_SOA_COLUMN(Multiplicity, multiplicity, int);
51+
}
52+
DECLARE_SOA_TABLE(Multiplicity, "AOD", "MULTIPLICITY",
53+
di_hadron_cor::Multiplicity);
54+
55+
} // namespace o2::aod
56+
57+
// define the filtered collisions and tracks
58+
#define O2_DEFINE_CONFIGURABLE(NAME, TYPE, DEFAULT, HELP) Configurable<TYPE> NAME{#NAME, DEFAULT, HELP};
59+
60+
struct DiHadronCor {
61+
Service<ccdb::BasicCCDBManager> ccdb;
62+
63+
O2_DEFINE_CONFIGURABLE(cfgCutVtxZ, float, 10.0f, "Accepted z-vertex range")
64+
O2_DEFINE_CONFIGURABLE(cfgCutPtMin, float, 0.2f, "minimum accepted track pT")
65+
O2_DEFINE_CONFIGURABLE(cfgCutPtMax, float, 10.0f, "maximum accepted track pT")
66+
O2_DEFINE_CONFIGURABLE(cfgCutEta, float, 0.8f, "Eta cut")
67+
O2_DEFINE_CONFIGURABLE(cfgCutMerging, float, 0.0, "Merging cut on track merge")
68+
O2_DEFINE_CONFIGURABLE(cfgSelCollByNch, bool, true, "Select collisions by Nch or centrality")
69+
O2_DEFINE_CONFIGURABLE(cfgCutMultMin, int, 0, "Minimum multiplicity for collision")
70+
O2_DEFINE_CONFIGURABLE(cfgCutMultMax, int, 10, "Maximum multiplicity for collision")
71+
O2_DEFINE_CONFIGURABLE(cfgCutCentMin, float, 60.0f, "Minimum centrality for collision")
72+
O2_DEFINE_CONFIGURABLE(cfgCutCentMax, float, 80.0f, "Maximum centrality for collision")
73+
O2_DEFINE_CONFIGURABLE(cfgMixEventNumMin, int, 5, "Minimum number of events to mix")
74+
O2_DEFINE_CONFIGURABLE(cfgRadiusLow, float, 0.8, "Low radius for merging cut")
75+
O2_DEFINE_CONFIGURABLE(cfgRadiusHigh, float, 2.5, "High radius for merging cut")
76+
O2_DEFINE_CONFIGURABLE(cfgSampleSize, double, 10, "Sample size for mixed event")
77+
O2_DEFINE_CONFIGURABLE(cfgCentEstimator, int, 0, "0:FT0C; 1:FT0CVariant1; 2:FT0M; 3:FT0A")
78+
O2_DEFINE_CONFIGURABLE(cfgCentFT0CMin, float, 0.0f, "Minimum centrality (FT0C) to cut events in filter")
79+
O2_DEFINE_CONFIGURABLE(cfgCentFT0CMax, float, 100.0f, "Maximum centrality (FT0C) to cut events in filter")
80+
81+
SliceCache cache;
82+
SliceCache cacheNch;
83+
84+
ConfigurableAxis axisVertex{"axisVertex", {10, -10, 10}, "vertex axis for histograms"};
85+
ConfigurableAxis axisMultiplicity{"axisMultiplicity", {VARIABLE_WIDTH, 0, 5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 80, 100}, "multiplicity axis for histograms"};
86+
ConfigurableAxis axisCentrality{"axisCentrality", {VARIABLE_WIDTH, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90}, "centrality axis for histograms"};
87+
ConfigurableAxis axisPt{"axisPt", {VARIABLE_WIDTH, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 10.0}, "pt axis for histograms"};
88+
ConfigurableAxis axisDeltaPhi{"axisDeltaPhi", {72, -PIHalf, PIHalf * 3}, "delta phi axis for histograms"};
89+
ConfigurableAxis axisDeltaEta{"axisDeltaEta", {48, -2.4, 2.4}, "delta eta axis for histograms"};
90+
ConfigurableAxis axisPtTrigger{"axisPtTrigger", {VARIABLE_WIDTH, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 10.0}, "pt trigger axis for histograms"};
91+
ConfigurableAxis axisPtAssoc{"axisPtAssoc", {VARIABLE_WIDTH, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 10.0}, "pt associated axis for histograms"};
92+
ConfigurableAxis axisVtxMix{"axisVtxMix", {VARIABLE_WIDTH, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, "vertex axis for mixed event histograms"};
93+
ConfigurableAxis axisMultMix{"axisMultMix", {VARIABLE_WIDTH, 0, 5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 80, 100}, "multiplicity / centrality axis for mixed event histograms"};
94+
ConfigurableAxis axisSample{"axisSample", {cfgSampleSize, 0, cfgSampleSize}, "sample axis for histograms"};
95+
96+
ConfigurableAxis axisVertexEfficiency{"axisVertexEfficiency", {10, -10, 10}, "vertex axis for efficiency histograms"};
97+
ConfigurableAxis axisEtaEfficiency{"axisEtaEfficiency", {20, -1.0, 1.0}, "eta axis for efficiency histograms"};
98+
ConfigurableAxis axisPtEfficiency{"axisPtEfficiency", {VARIABLE_WIDTH, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5, 3.75, 4.0, 4.5, 5.0, 6.0, 7.0, 8.0}, "pt axis for efficiency histograms"};
99+
100+
// make the filters and cuts.
101+
Filter collisionFilter = (nabs(aod::collision::posZ) < cfgCutVtxZ) && (aod::evsel::sel8) == true && (aod::cent::centFT0C > cfgCentFT0CMin) && (aod::cent::centFT0C < cfgCentFT0CMax);
102+
Filter trackFilter = (nabs(aod::track::eta) < cfgCutEta) && (aod::track::pt > cfgCutPtMin) && (aod::track::pt < cfgCutPtMax) && ((requireGlobalTrackInFilter()) || (aod::track::isGlobalTrackSDD == (uint8_t) true));
103+
104+
// Define the outputs
105+
OutputObj<CorrelationContainer> same{"sameEvent"};
106+
OutputObj<CorrelationContainer> mixed{"mixedEvent"};
107+
HistogramRegistry registry{"registry"};
108+
109+
// define global variables
110+
TRandom3* gRandom = new TRandom3();
111+
enum CentEstimators {
112+
kCentFT0C = 0,
113+
kCentFT0CVariant1,
114+
kCentFT0M,
115+
kCentFV0A,
116+
// Count the total number of enum
117+
kCount_CentEstimators
118+
};
119+
enum EventType {
120+
SameEvent = 1,
121+
MixedEvent = 3
122+
};
123+
124+
using AodCollisions = soa::Filtered<soa::Join<aod::Collisions, aod::EvSel, aod::CentFT0Cs, aod::CentFT0CVariant1s, aod::CentFT0Ms, aod::CentFV0As, aod::Mults>>; // aod::CentFT0Cs
125+
using AodTracks = soa::Filtered<soa::Join<aod::Tracks, aod::TrackSelection, aod::TracksExtra>>;
126+
127+
void init(InitContext&)
128+
{
129+
const AxisSpec axisPhi{72, 0.0, constants::math::TwoPI, "#varphi"};
130+
const AxisSpec axisEta{40, -1., 1., "#eta"};
131+
132+
ccdb->setURL("http://alice-ccdb.cern.ch");
133+
ccdb->setCaching(true);
134+
ccdb->setLocalObjectValidityChecking();
135+
136+
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
137+
ccdb->setCreatedNotAfter(now);
138+
139+
LOGF(info, "Starting init");
140+
141+
// Make histograms to check the distributions after cuts
142+
registry.add("deltaEta_deltaPhi_same", "", {HistType::kTH2D, {axisDeltaPhi, axisDeltaEta}}); // check to see the delta eta and delta phi distribution
143+
registry.add("deltaEta_deltaPhi_mixed", "", {HistType::kTH2D, {axisDeltaPhi, axisDeltaEta}});
144+
registry.add("Phi", "Phi", {HistType::kTH1D, {axisPhi}});
145+
registry.add("Eta", "Eta", {HistType::kTH1D, {axisEta}});
146+
registry.add("pT", "pT", {HistType::kTH1D, {axisPtTrigger}});
147+
registry.add("Nch", "N_{ch}", {HistType::kTH1D, {axisMultiplicity}});
148+
registry.add("Nch_used", "N_{ch}", {HistType::kTH1D, {axisMultiplicity}}); // histogram to see how many events are in the same and mixed event
149+
std::string hCentTitle = "Centrality distribution, Estimator " + std::to_string(cfgCentEstimator);
150+
registry.add("Centrality", hCentTitle.c_str(), {HistType::kTH1D, {axisCentrality}});
151+
registry.add("Centrality_used", hCentTitle.c_str(), {HistType::kTH1D, {axisCentrality}}); // histogram to see how many events are in the same and mixed event
152+
registry.add("zVtx", "zVtx", {HistType::kTH1D, {axisVertex}});
153+
154+
registry.add("Trig_hist", "", {HistType::kTHnSparseF, {{axisSample, axisVertex, axisPtTrigger}}});
155+
156+
registry.add("eventcount", "bin", {HistType::kTH1F, {{4, 0, 4, "bin"}}}); // histogram to see how many events are in the same and mixed event
157+
158+
std::vector<AxisSpec> corrAxis = {{axisSample, "Sample"},
159+
{axisVertex, "z-vtx (cm)"},
160+
{axisPtTrigger, "p_{T} (GeV/c)"},
161+
{axisPtAssoc, "p_{T} (GeV/c)"},
162+
{axisDeltaPhi, "#Delta#varphi (rad)"},
163+
{axisDeltaEta, "#Delta#eta"}};
164+
std::vector<AxisSpec> effAxis = {
165+
{axisVertexEfficiency, "z-vtx (cm)"},
166+
{axisPtEfficiency, "p_{T} (GeV/c)"},
167+
{axisEtaEfficiency, "#eta"},
168+
};
169+
std::vector<AxisSpec> userAxis;
170+
171+
same.setObject(new CorrelationContainer("sameEvent", "sameEvent", corrAxis, effAxis, userAxis));
172+
mixed.setObject(new CorrelationContainer("mixedEvent", "mixedEvent", corrAxis, effAxis, userAxis));
173+
}
174+
175+
int getMagneticField(uint64_t timestamp)
176+
{
177+
// Get the magnetic field
178+
static o2::parameters::GRPMagField* grpo = nullptr;
179+
if (grpo == nullptr) {
180+
grpo = ccdb->getForTimeStamp<o2::parameters::GRPMagField>("/GLO/Config/GRPMagField", timestamp);
181+
if (grpo == nullptr) {
182+
LOGF(fatal, "GRP object not found for timestamp %llu", timestamp);
183+
return 0;
184+
}
185+
LOGF(info, "Retrieved GRP for timestamp %llu with magnetic field of %d kG", timestamp, grpo->getNominalL3Field());
186+
}
187+
return grpo->getNominalL3Field();
188+
}
189+
190+
template <typename TCollision>
191+
float getCentrality(TCollision const& collision)
192+
{
193+
float cent;
194+
switch (cfgCentEstimator) {
195+
case kCentFT0C:
196+
cent = collision.centFT0C();
197+
break;
198+
case kCentFT0CVariant1:
199+
cent = collision.centFT0CVariant1();
200+
break;
201+
case kCentFT0M:
202+
cent = collision.centFT0M();
203+
break;
204+
case kCentFV0A:
205+
cent = collision.centFV0A();
206+
break;
207+
default:
208+
cent = collision.centFT0C();
209+
}
210+
return cent;
211+
}
212+
213+
// fill multiple histograms
214+
template <typename TCollision, typename TTracks>
215+
void fillYield(TCollision collision, TTracks tracks) // function to fill the yield and etaphi histograms.
216+
{
217+
float cent = getCentrality(collision);
218+
registry.fill(HIST("Centrality"), cent);
219+
220+
registry.fill(HIST("Nch"), tracks.size());
221+
registry.fill(HIST("zVtx"), collision.posZ());
222+
223+
for (auto const& track1 : tracks) {
224+
registry.fill(HIST("Phi"), RecoDecay::constrainAngle(track1.phi(), 0.0));
225+
registry.fill(HIST("Eta"), track1.eta());
226+
registry.fill(HIST("pT"), track1.pt());
227+
}
228+
}
229+
230+
template <typename TTrack, typename TTrackAssoc>
231+
float getDPhiStar(TTrack const& track1, TTrackAssoc const& track2, float radius, int magField)
232+
{
233+
float charge1 = track1.sign();
234+
float charge2 = track2.sign();
235+
236+
float phi1 = track1.phi();
237+
float phi2 = track2.phi();
238+
239+
float pt1 = track1.pt();
240+
float pt2 = track2.pt();
241+
242+
int fbSign = (magField > 0) ? 1 : -1;
243+
244+
float dPhiStar = phi1 - phi2 - charge1 * fbSign * std::asin(0.075 * radius / pt1) + charge2 * fbSign * std::asin(0.075 * radius / pt2);
245+
246+
if (dPhiStar > constants::math::PI)
247+
dPhiStar = constants::math::TwoPI - dPhiStar;
248+
if (dPhiStar < -constants::math::PI)
249+
dPhiStar = -constants::math::TwoPI - dPhiStar;
250+
251+
return dPhiStar;
252+
}
253+
254+
//
255+
template <CorrelationContainer::CFStep step, typename TTracks, typename TTracksAssoc>
256+
void fillCorrelations(TTracks tracks1, TTracksAssoc tracks2, float posZ, int system, int magneticField, float cent) // function to fill the Output functions (sparse) and the delta eta and delta phi histograms
257+
{
258+
259+
if (system == SameEvent) {
260+
registry.fill(HIST("Centrality_used"), cent);
261+
registry.fill(HIST("Nch_used"), tracks1.size());
262+
}
263+
264+
int fSampleIndex = gRandom->Uniform(0, cfgSampleSize);
265+
266+
// loop over all tracks
267+
for (auto const& track1 : tracks1) {
268+
269+
if (system == SameEvent) {
270+
registry.fill(HIST("Trig_hist"), fSampleIndex, posZ, track1.pt());
271+
}
272+
273+
for (auto const& track2 : tracks2) {
274+
275+
if (track1.pt() <= track2.pt())
276+
continue; // skip if the trigger pt is less than the associate pt
277+
278+
float deltaPhi = RecoDecay::constrainAngle(track1.phi() - track2.phi(), -PIHalf);
279+
float deltaEta = track1.eta() - track2.eta();
280+
281+
if (std::abs(deltaEta) < cfgCutMerging) {
282+
283+
double dPhiStarHigh = getDPhiStar(track1, track2, cfgRadiusHigh, magneticField);
284+
double dPhiStarLow = getDPhiStar(track1, track2, cfgRadiusLow, magneticField);
285+
286+
const double kLimit = 3.0 * cfgCutMerging;
287+
288+
bool bIsBelow = false;
289+
290+
if (std::abs(dPhiStarLow) < kLimit || std::abs(dPhiStarHigh) < kLimit || dPhiStarLow * dPhiStarHigh < 0) {
291+
for (double rad(cfgRadiusLow); rad < cfgRadiusHigh; rad += 0.01) {
292+
double dPhiStar = getDPhiStar(track1, track2, rad, magneticField);
293+
if (std::abs(dPhiStar) < kLimit) {
294+
bIsBelow = true;
295+
break;
296+
}
297+
}
298+
if (bIsBelow)
299+
continue;
300+
}
301+
}
302+
303+
// fill the right sparse and histograms
304+
if (system == SameEvent) {
305+
306+
same->getPairHist()->Fill(step, fSampleIndex, posZ, track1.pt(), track2.pt(), deltaPhi, deltaEta);
307+
registry.fill(HIST("deltaEta_deltaPhi_same"), deltaPhi, deltaEta);
308+
} else if (system == MixedEvent) {
309+
310+
mixed->getPairHist()->Fill(step, fSampleIndex, posZ, track1.pt(), track2.pt(), deltaPhi, deltaEta);
311+
registry.fill(HIST("deltaEta_deltaPhi_mixed"), deltaPhi, deltaEta);
312+
}
313+
}
314+
}
315+
}
316+
317+
void processSame(AodCollisions::iterator const& collision, AodTracks const& tracks, aod::BCsWithTimestamps const&)
318+
{
319+
320+
auto bc = collision.bc_as<aod::BCsWithTimestamps>();
321+
322+
registry.fill(HIST("eventcount"), SameEvent); // because its same event i put it in the 1 bin
323+
324+
fillYield(collision, tracks);
325+
float cent = getCentrality(collision);
326+
327+
if (cfgSelCollByNch && (tracks.size() < cfgCutMultMin || tracks.size() >= cfgCutMultMax)) {
328+
return;
329+
}
330+
if (!cfgSelCollByNch && (cent < cfgCutCentMin || cent >= cfgCutCentMax)) {
331+
return;
332+
}
333+
334+
fillCorrelations<CorrelationContainer::kCFStepReconstructed>(tracks, tracks, collision.posZ(), SameEvent, getMagneticField(bc.timestamp()), cent);
335+
}
336+
PROCESS_SWITCH(DiHadronCor, processSame, "Process same event", true);
337+
338+
// the process for filling the mixed events
339+
void processMixed(AodCollisions const& collisions, AodTracks const& tracks, aod::BCsWithTimestamps const&)
340+
{
341+
342+
auto getTracksSize = [&tracks, this](AodCollisions::iterator const& collision) {
343+
auto associatedTracks = tracks.sliceByCached(o2::aod::track::collisionId, collision.globalIndex(), this->cache);
344+
auto mult = associatedTracks.size();
345+
return mult;
346+
};
347+
348+
using MixedBinning = FlexibleBinningPolicy<std::tuple<decltype(getTracksSize)>, aod::collision::PosZ, decltype(getTracksSize)>;
349+
350+
MixedBinning binningOnVtxAndMult{{getTracksSize}, {axisVtxMix, axisMultMix}, true};
351+
352+
auto tracksTuple = std::make_tuple(tracks, tracks);
353+
Pair<AodCollisions, AodTracks, AodTracks, MixedBinning> pair{binningOnVtxAndMult, cfgMixEventNumMin, -1, collisions, tracksTuple, &cache}; // -1 is the number of the bin to skip
354+
for (auto const& [collision1, tracks1, collision2, tracks2] : pair) {
355+
registry.fill(HIST("eventcount"), MixedEvent); // fill the mixed event in the 3 bin
356+
auto bc = collision1.bc_as<aod::BCsWithTimestamps>();
357+
358+
if (cfgSelCollByNch && (tracks1.size() < cfgCutMultMin || tracks1.size() >= cfgCutMultMax))
359+
continue;
360+
361+
if (cfgSelCollByNch && (tracks2.size() < cfgCutMultMin || tracks2.size() >= cfgCutMultMax))
362+
continue;
363+
364+
float cent1 = getCentrality(collision1);
365+
float cent2 = getCentrality(collision2);
366+
367+
if (!cfgSelCollByNch && (cent1 < cfgCutCentMin || cent1 >= cfgCutCentMax))
368+
continue;
369+
370+
if (!cfgSelCollByNch && (cent2 < cfgCutCentMin || cent2 >= cfgCutCentMax))
371+
continue;
372+
373+
fillCorrelations<CorrelationContainer::kCFStepReconstructed>(tracks1, tracks2, collision1.posZ(), MixedEvent, getMagneticField(bc.timestamp()), cent1);
374+
}
375+
}
376+
377+
PROCESS_SWITCH(DiHadronCor, processMixed, "Process mixed events", true);
378+
};
379+
380+
WorkflowSpec defineDataProcessing(ConfigContext const& cfgc)
381+
{
382+
return WorkflowSpec{
383+
adaptAnalysisTask<DiHadronCor>(cfgc),
384+
};
385+
}

0 commit comments

Comments
 (0)