2121// / \since May 22, 2025
2222// /
2323
24+ #include " ALICE3/DataModel/OTFPIDTrk.h"
25+
2426#include < CCDB/BasicCCDBManager.h>
2527#include < CCDB/CcdbApi.h>
2628#include < Framework/AnalysisDataModel.h>
@@ -122,15 +124,22 @@ class TrackDedxLut
122124 allLoaded = false ;
123125 continue ;
124126 }
125- // cumulative distributions, only for the filled bins
127+ // cumulative distributions and raw moments , only for the filled bins
126128 auto & offset = mOffset [pdg];
127129 auto & cdf = mCdf [pdg];
130+ auto & mean = mMean [pdg];
131+ auto & sigma = mSigma [pdg];
128132 offset.assign (nBins, -1 );
133+ mean.assign (nBins, -1 .f );
134+ sigma.assign (nBins, -1 .f );
129135 for (size_t b = 0 ; b < nBins; ++b) {
130136 const float * hb = h->data () + b * nDedx;
131- double sum = 0 .;
137+ double sum = 0 ., m1 = 0 ., m2 = 0 . ;
132138 for (size_t k = 0 ; k < nDedx; ++k) {
139+ const double x = 0.5 * (mDedxEdges [k] + mDedxEdges [k + 1 ]);
133140 sum += hb[k];
141+ m1 += hb[k] * x;
142+ m2 += hb[k] * x * x;
134143 }
135144 if (sum <= 0 .) {
136145 continue ;
@@ -141,6 +150,9 @@ class TrackDedxLut
141150 cum += hb[k];
142151 cdf.push_back (cum / sum);
143152 }
153+ const double mu = m1 / sum;
154+ mean[b] = mu;
155+ sigma[b] = std::sqrt (std::max (0 ., m2 / sum - mu * mu));
144156 }
145157 }
146158 LOG (info) << " Loaded dE/dx LUT " << filename << " (" << mEtaEdges .size () - 1 << " |eta| x " << mRigEdges .size () - 1
@@ -150,34 +162,67 @@ class TrackDedxLut
150162
151163 // / Sampled dE/dx for uniform random number u, or -1 if there is no LUT entry
152164 float sample (int pdg, float eta, float rigidity, double u) const
165+ {
166+ int bin = -1 , binNext = -1 ;
167+ float weight = 0 .f ;
168+ if (!locate (pdg, eta, rigidity, bin, binNext, weight)) {
169+ return -1 .f ;
170+ }
171+ const auto & offset = mOffset .at (pdg);
172+ const float * cdf = mCdf .at (pdg).data ();
173+ float dedx = inverseCdf (cdf + offset[bin], u);
174+ if (binNext >= 0 ) {
175+ dedx += weight * (inverseCdf (cdf + offset[binNext], u) - dedx);
176+ }
177+ return dedx;
178+ }
179+
180+ bool getMeanSigma (int pdg, float eta, float rigidity, float & mean, float & sigma) const
181+ {
182+ int bin = -1 , binNext = -1 ;
183+ float weight = 0 .f ;
184+ if (!locate (pdg, eta, rigidity, bin, binNext, weight)) {
185+ return false ;
186+ }
187+ const auto & m = mMean .at (pdg);
188+ const auto & s = mSigma .at (pdg);
189+ mean = m[bin];
190+ sigma = s[bin];
191+ if (binNext >= 0 ) {
192+ mean += weight * (m[binNext] - mean);
193+ sigma += weight * (s[binNext] - sigma);
194+ }
195+ return sigma > 0 .f ;
196+ }
197+
198+ private:
199+ // / LUT bin of the track and the neighbouring rigidity bin on its side (-1 if empty)
200+ bool locate (int pdg, float eta, float rigidity, int & bin, int & binNext, float & weight) const
153201 {
154202 const auto it = mOffset .find (pdg);
155203 const float absEta = std::abs (eta);
156204 if (it == mOffset .end () || mEtaEdges .empty () || absEta < mEtaEdges .front () || absEta >= mEtaEdges .back () ||
157205 rigidity < mRigEdges .front () || rigidity >= mRigEdges .back ()) {
158- return - 1 . f ;
206+ return false ;
159207 }
160208 const int nRig = mRigEdges .size () - 1 ;
161209 const int iEta = std::upper_bound (mEtaEdges .begin (), mEtaEdges .end (), absEta) - mEtaEdges .begin () - 1 ;
162210 const int iRig = std::upper_bound (mRigEdges .begin (), mRigEdges .end (), rigidity) - mRigEdges .begin () - 1 ;
163- const int offset = it-> second [ iEta * nRig + iRig] ;
164- if (offset < 0 ) {
165- return - 1 . f ;
211+ bin = iEta * nRig + iRig;
212+ if (it-> second [bin] < 0 ) {
213+ return false ;
166214 }
167- const float * cdf = mCdf .at (pdg).data ();
168- float dedx = inverseCdf (cdf + offset, u);
169-
170- // neighbouring bin on the side of the track, no interpolation if it is empty
215+ binNext = -1 ;
216+ weight = 0 .f ;
171217 const double logRig = std::log (rigidity);
172218 const int iNext = logRig < mLogRigCentres [iRig] ? iRig - 1 : iRig + 1 ;
173219 if (iNext >= 0 && iNext < nRig && it->second [iEta * nRig + iNext] >= 0 ) {
174- const float w = (logRig - mLogRigCentres [iRig]) / ( mLogRigCentres [ iNext] - mLogRigCentres [iRig]) ;
175- dedx += w * ( inverseCdf (cdf + it-> second [iEta * nRig + iNext], u) - dedx );
220+ binNext = iEta * nRig + iNext;
221+ weight = (logRig - mLogRigCentres [iRig]) / ( mLogRigCentres [ iNext] - mLogRigCentres [iRig] );
176222 }
177- return dedx ;
223+ return true ;
178224 }
179225
180- private:
181226 // / dE/dx at which the cumulative distribution reaches u, uniform within the dE/dx bin
182227 float inverseCdf (const float * cdf, double u) const
183228 {
@@ -203,20 +248,26 @@ class TrackDedxLut
203248 std::vector<double > mDedxEdges ;
204249 std::unordered_map<int , std::vector<int >> mOffset ; // |pdg| -> start of the cdf of bin etaBin * nRig + rigBin, -1 if empty
205250 std::unordered_map<int , std::vector<float >> mCdf ; // |pdg| -> cumulative dE/dx distributions of the filled bins
251+ std::unordered_map<int , std::vector<float >> mMean ; // |pdg| -> mean dE/dx per bin, -1 if empty
252+ std::unordered_map<int , std::vector<float >> mSigma ;
206253};
207254
208255struct OnTheFlyTrackerPid {
209256 static constexpr int NumSpecies = 9 ;
210257 static constexpr std::array<int , NumSpecies> PdgCodes = {11 , 13 , 211 , 321 , 2212 , 1000010020 , 1000010030 , 1000020030 , 1000020040 };
211258 static constexpr std::array<const char *, NumSpecies> SpeciesNames = {" Elec" , " Muon" , " Pion" , " Kaon" , " Prot" , " Deut" , " Trit" , " He3" , " Al" };
212259 static constexpr std::array<const char *, NumSpecies> SpeciesLabels = {" #it{e}" , " #it{#mu}" , " #it{#pi}" , " #it{K}" , " #it{p}" , " #it{d}" , " #it{t}" , " #it{^{3}He}" , " #it{^{4}He}" };
260+ static constexpr float NoNsigma = 999 .f;
213261
214262 std::array<std::shared_ptr<TH2 >, NumSpecies> h2dToTvsPperParticle;
215263 std::array<std::shared_ptr<TH2 >, NumSpecies> h2dToTvsPperParticleLin;
216264 std::array<std::shared_ptr<TH3 >, NumSpecies> h3dToTvsPEtaPerParticle;
217265 std::array<std::shared_ptr<TH2 >, NumSpecies> h2dTracks;
218266 std::array<std::shared_ptr<TH2 >, NumSpecies> h2dTracksWithDedx;
219267
268+ Produces<aod::UpgradeTrkPidSignals> tableUpgradeTrkPidSignals;
269+ Produces<aod::UpgradeTrkPids> tableUpgradeTrkPids;
270+
220271 Service<o2::framework::O2DatabasePDG> pdgDatabase{};
221272 Service<o2::ccdb::BasicCCDBManager> ccdb{};
222273 TrackDedxLut mTrackLut ;
@@ -226,7 +277,7 @@ struct OnTheFlyTrackerPid {
226277
227278 Configurable<std::string> trackLut{" trackLut" , " " , " Per-track dE/dx LUT from MakeToTLUT.C (local file or ccdb:<path>)" };
228279 Configurable<int > seed{" seed" , 0 , " Seed for the dE/dx sampling (0: different in every job)" };
229- Configurable<bool > primariesOnly{" primariesOnly" , false , " Only physical primaries (otherwise all tracks, including decay products )" };
280+ Configurable<bool > primariesOnly{" primariesOnly" , false , " Fill the histograms only with physical primaries (the tables are filled for all tracks )" };
230281 Configurable<bool > useTrueKinematics{" useTrueKinematics" , false , " Fill the histograms vs the true instead of the reconstructed p/z and |eta| (the dE/dx is always sampled at the true ones)" };
231282
232283 // dE/dx axis, logarithmic
@@ -315,52 +366,71 @@ struct OnTheFlyTrackerPid {
315366 }
316367 }
317368
369+ // / dE/dx of the track (-1 if there is none) and its nsigma for every species
370+ template <typename TTrack>
371+ float processTrack (TTrack const & track, std::array<float , NumSpecies>& nSigma)
372+ {
373+ if (!track.has_mcParticle ()) {
374+ return -1 .f ;
375+ }
376+ const auto & mcParticle = track.mcParticle ();
377+ const int pdg = std::abs (mcParticle.pdgCode ());
378+ const auto itSpecies = std::find (PdgCodes.begin (), PdgCodes.end (), pdg);
379+ if (itSpecies == PdgCodes.end ()) {
380+ return -1 .f ;
381+ }
382+ const auto * pdgInfo = pdgDatabase->GetParticle (mcParticle.pdgCode ());
383+ if (!pdgInfo || pdgInfo->Charge () == 0 ) {
384+ return -1 .f ;
385+ }
386+ const int iSpecies = std::distance (PdgCodes.begin (), itSpecies);
387+ const float absCharge = std::abs (pdgInfo->Charge ()) / 3 .f ;
388+ const float rigidityTrue = mcParticle.p () / absCharge;
389+ const float dedx = mTrackLut .sample (pdg, mcParticle.eta (), rigidityTrue, mRandom .Rndm ());
390+
391+ if (dedx > 0 .f ) {
392+ for (int i = 0 ; i < NumSpecies; ++i) {
393+ float mean = 0 .f , sigma = 0 .f ;
394+ if (mTrackLut .getMeanSigma (PdgCodes[i], track.eta (), track.p (), mean, sigma)) {
395+ nSigma[i] = (dedx - mean) / sigma;
396+ }
397+ }
398+ }
399+
400+ if (primariesOnly.value && !mcParticle.isPhysicalPrimary ()) {
401+ return dedx;
402+ }
403+ const float rigidity = useTrueKinematics.value ? rigidityTrue : track.p ();
404+ const float absEta = std::abs (useTrueKinematics.value ? mcParticle.eta () : track.eta ());
405+ const float p = rigidity * absCharge;
406+ h2dTracks[iSpecies]->Fill (rigidity, absEta);
407+ if (dedx < 0 .f ) {
408+ return dedx;
409+ }
410+ h2dTracksWithDedx[iSpecies]->Fill (rigidity, absEta);
411+ h3dToTvsPEtaPerParticle[iSpecies]->Fill (rigidity, absEta, dedx);
412+ if (enableLogPBins.value ) {
413+ histos.fill (HIST (" hToTvsMom" ), p, dedx);
414+ histos.fill (HIST (" hToTvsP" ), rigidity, dedx);
415+ h2dToTvsPperParticle[iSpecies]->Fill (rigidity, dedx);
416+ }
417+ if (enableLinearPBins.value ) {
418+ histos.fill (HIST (" hToTvsMomLin" ), p, dedx);
419+ histos.fill (HIST (" hToTvsPLin" ), rigidity, dedx);
420+ h2dToTvsPperParticleLin[iSpecies]->Fill (rigidity, dedx);
421+ }
422+ return dedx;
423+ }
424+
318425 void process (soa::Join<aod::Tracks, aod::McTrackLabels> const & tracks,
319426 aod::McParticles const &)
320427 {
428+ std::array<float , NumSpecies> nSigma{};
321429 for (const auto & track : tracks) {
322- if (!track.has_mcParticle ()) {
323- continue ;
324- }
325- const auto & mcParticle = track.mcParticle ();
326- if (primariesOnly.value && !mcParticle.isPhysicalPrimary ()) {
327- continue ;
328- }
329- const int pdg = std::abs (mcParticle.pdgCode ());
330- const auto itSpecies = std::find (PdgCodes.begin (), PdgCodes.end (), pdg);
331- if (itSpecies == PdgCodes.end ()) {
332- continue ;
333- }
334- const auto * pdgInfo = pdgDatabase->GetParticle (mcParticle.pdgCode ());
335- if (!pdgInfo || pdgInfo->Charge () == 0 ) {
336- continue ;
337- }
338- const int iSpecies = std::distance (PdgCodes.begin (), itSpecies);
339- const float absCharge = std::abs (pdgInfo->Charge ()) / 3 .f ;
340- const float rigidityTrue = mcParticle.p () / absCharge;
341-
342- // the OTF tracks carry p/z
343- const float rigidity = useTrueKinematics.value ? rigidityTrue : track.p ();
344- const float absEta = std::abs (useTrueKinematics.value ? mcParticle.eta () : track.eta ());
345- const float p = rigidity * absCharge;
346- h2dTracks[iSpecies]->Fill (rigidity, absEta);
347-
348- const float dedx = mTrackLut .sample (pdg, mcParticle.eta (), rigidityTrue, mRandom .Rndm ());
349- if (dedx < 0 .f ) {
350- continue ;
351- }
352- h2dTracksWithDedx[iSpecies]->Fill (rigidity, absEta);
353- h3dToTvsPEtaPerParticle[iSpecies]->Fill (rigidity, absEta, dedx);
354- if (enableLogPBins.value ) {
355- histos.fill (HIST (" hToTvsMom" ), p, dedx);
356- histos.fill (HIST (" hToTvsP" ), rigidity, dedx);
357- h2dToTvsPperParticle[iSpecies]->Fill (rigidity, dedx);
358- }
359- if (enableLinearPBins.value ) {
360- histos.fill (HIST (" hToTvsMomLin" ), p, dedx);
361- histos.fill (HIST (" hToTvsPLin" ), rigidity, dedx);
362- h2dToTvsPperParticleLin[iSpecies]->Fill (rigidity, dedx);
363- }
430+ nSigma.fill (NoNsigma);
431+ const float dedx = processTrack (track, nSigma);
432+ tableUpgradeTrkPidSignals (dedx);
433+ tableUpgradeTrkPids (nSigma[0 ], nSigma[1 ], nSigma[2 ], nSigma[3 ], nSigma[4 ], nSigma[5 ], nSigma[6 ], nSigma[7 ], nSigma[8 ]);
364434 }
365435 }
366436};
0 commit comments