Project
Loading...
Searching...
No Matches
CalibdEdx.cxx
Go to the documentation of this file.
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
13
14#include <algorithm>
15#include <array>
16#include <string>
17#include <vector>
18#include <cmath>
19#include <cstddef>
20#include <gsl/span>
21#include <numeric>
22#include <string_view>
23#include <utility>
24#include <chrono>
25
26// o2 includes
29#include "DataFormatsTPC/Defs.h"
32#include "Framework/Logger.h"
34#include "TPCBase/Utils.h"
36
37// root includes
38#include "TFile.h"
39#include "THn.h"
40#include "TTree.h"
41#include "TLinearFitter.h"
42
43// boost includes
44#include <boost/histogram.hpp>
45
46using namespace o2::tpc;
47namespace bh = boost::histogram;
48
49CalibdEdx::CalibdEdx(int dEdxBins, float mindEdx, float maxdEdx, int angularBins, bool fitSnp)
50 : mFitSnp(fitSnp)
51{
52 const int snpBins = fitSnp ? angularBins : 1;
53 mHist = bh::make_histogram(
54 FloatAxis(dEdxBins, mindEdx * MipScale, maxdEdx * MipScale, "dEdx"),
55 FloatAxis(angularBins, 0, 1, "Tgl"),
56 FloatAxis(snpBins, -1, 1, "Snp"),
57 IntAxis(0, SECTORSPERSIDE * SIDES, "sector"),
58 IntAxis(0, GEMSTACKSPERSECTOR, "stackType"),
59 IntAxis(0, CHARGETYPES, "charge"));
60}
61
63{
64 mFitSnp = other.mFitSnp;
65 mApplyCuts = other.mApplyCuts;
66 mCuts = other.mCuts;
67 mSectorThreshold = other.mSectorThreshold;
68 m1DThreshold = other.m1DThreshold;
69 m2DThreshold = other.m2DThreshold;
70 mFitCut = other.mFitCut;
71 mFitLowCutFactor = other.mFitLowCutFactor;
72 mFitPasses = other.mFitPasses;
73 mTFID = other.mTFID;
74
75 mHist = other.mHist;
76 mCalib = other.mCalib;
77 mCalibIn = other.mCalibIn;
78
79 mMatType = other.mMatType;
80
81 // debug streamer not copied on purpose
82}
83
84void CalibdEdx::fill(const TrackTPC& track)
85{
86 // applying cuts
87 if (track.hasBothSidesClusters() || (mApplyCuts && !mCuts.goodTrack(track))) {
88 return;
89 }
90
91 const auto& dEdx = track.getdEdx();
92 const auto sideOffset = track.hasASideClustersOnly() ? 0 : SECTORSPERSIDE;
93 const std::vector<float> dEdxMax{dEdx.dEdxMaxIROC, dEdx.dEdxMaxOROC1, dEdx.dEdxMaxOROC2, dEdx.dEdxMaxOROC3};
94 const std::vector<float> dEdxTot{dEdx.dEdxTotIROC, dEdx.dEdxTotOROC1, dEdx.dEdxTotOROC2, dEdx.dEdxTotOROC3};
95 std::vector<float> dEdxMaxCorr(4); // for deugging
96 std::vector<float> dEdxTotCorr(4); // for deugging
97
98 // We need a copy of the track to perform propagations
99 o2::track::TrackPar cpTrack = track;
100
101 // Beth-Bloch correction for non MIP tracks
102 const auto& gasParam = ParameterGas::Instance();
103 const float betaGamma = track.getP() / o2::constants::physics::MassPionCharged;
104 const float dEdxScale = MipScale / BetheBlochAleph(betaGamma, gasParam.BetheBlochParam[0],
105 gasParam.BetheBlochParam[1], gasParam.BetheBlochParam[2],
106 gasParam.BetheBlochParam[3], gasParam.BetheBlochParam[4]);
107
108 for (const GEMstack roc : {IROCgem, OROC1gem, OROC2gem, OROC3gem}) {
109 // Local x value of the center pad row of each roc type in cm (IROC, OROC1, ...).
110 constexpr std::array<float, 4> xks{108.475f, 151.7f, 188.8f, 227.65f};
111
112 // propagate track
113 const bool okProp = o2::base::Propagator::Instance()->PropagateToXBxByBz(cpTrack, xks[roc], 0.9f, 2., mMatType);
114 if (!okProp) {
115 continue;
116 }
117
118 // If the track was propagated to a different sector we need to rotate the local frame to get the correct Snp value
119 float sector = std::floor(18.f * cpTrack.getPhiPos() / o2::constants::math::TwoPI);
120 if (mFitSnp) {
121 float localFrame = std::floor(18.f * o2::math_utils::to02PiGen(cpTrack.getAlpha()) / o2::constants::math::TwoPI);
122 if (std::abs(sector - localFrame) > 0.1) {
123 const float alpha = SECPHIWIDTH * (0.5 + sector);
124 cpTrack.rotateParam(alpha);
125 }
126 }
127 const float snp = cpTrack.getSnp();
128 const float tgl = cpTrack.getTgl();
129 const float scaledTgl = scaleTgl(std::abs(tgl), roc);
130 if (track.hasCSideClusters()) {
131 sector += SECTORSPERSIDE;
132 }
133
134 // undo previously done corrections, to allow for residual corrections
135 // output will still be the full correction
136 const float corrMax = mCalibIn.getCorrection(StackID{static_cast<int>(sector), roc}, ChargeType::Max, tgl, snp);
137 const float corrTot = mCalibIn.getCorrection(StackID{static_cast<int>(sector), roc}, ChargeType::Tot, tgl, snp);
138 dEdxMaxCorr[roc] = corrMax;
139 dEdxTotCorr[roc] = corrTot;
140
141 static bool reported = false;
142 if (!reported && mCalibIn.getDims() >= 0) {
143 const auto meanParamTot = mCalibIn.getMeanParams(ChargeType::Tot);
144 LOGP(info, "Undoing previously applied corrections with mean qTot Params {}", utils::elementsToString(meanParamTot));
145 reported = true;
146 }
147
148 mHist(dEdxMax[roc] * dEdxScale * corrMax, scaledTgl, snp, sector, roc, ChargeType::Max);
149 mHist(dEdxTot[roc] * dEdxScale * corrTot, scaledTgl, snp, sector, roc, ChargeType::Tot);
150 }
151
152 if (mDebugOutputStreamer) {
153 const float tgl = track.getTgl();
154 const float p = track.getP();
155
156 (*mDebugOutputStreamer) << "dedx"
157 << "dEdxMax=" << dEdxMax
158 << "dEdxMaxCorr=" << dEdxMaxCorr
159 << "dEdxTot=" << dEdxTot
160 << "dEdxTotCorr=" << dEdxTotCorr
161 << "tgl=" << tgl
162 << "p=" << p
163 << "tfid=" << mTFID
164 << "\n";
165 }
166}
167
168void CalibdEdx::fill(const gsl::span<const TrackTPC> tracks)
169{
170 for (const auto& track : tracks) {
171 fill(track);
172 }
173}
174
176{
177 if (other != nullptr) {
178 mHist += other->getHist();
179 }
180}
181
182template <typename Hist>
183void fitHist(const Hist& hist, CalibdEdxCorrection& corr, TLinearFitter& fitter,
184 const float dEdxCut, const float dEdxLowCutFactor, const int passes, const CalibdEdxCorrection* stackMean = nullptr, o2::utils::TreeStreamRedirector* streamer = nullptr)
185{
186 using timer = std::chrono::high_resolution_clock;
187 using ax = CalibdEdx::Axis;
188 auto start = timer::now();
189
190 // number of bins per stack
191 int stackBins = 1;
192 for (int i = 0; i < ax::Sector; ++i) {
193 stackBins *= hist.axis(i).size();
194 }
195 const bool projSectors = stackMean != nullptr;
196
197 constexpr int sectors = SECTORSPERSIDE * SIDES;
198 constexpr int stackCount = 144;
199 // number of fits to perform
200 const int fitCount = projSectors ? GEMSTACKSPERSECTOR * CHARGETYPES : stackCount * CHARGETYPES;
201 // number of GEM stacks per fit
202 const int fitStacks = projSectors ? sectors : 1;
203
204 for (int fitPass = 0; fitPass < passes; ++fitPass) {
205
206 auto entry = bh::indexed(hist).begin();
207 for (int fit = 0; fit < fitCount; ++fit) {
208 int entries = 0;
209 int outliers = 0;
210 StackID id{};
211 id.type = static_cast<GEMstack>(entry->bin(ax::Stack).center());
212 const auto charge = static_cast<ChargeType>(entry->bin(ax::Charge).center());
213 fitter.ClearPoints();
214
215 for (int stack = 0; stack < fitStacks; ++stack) {
216 id.sector = static_cast<int>(entry->bin(ax::Sector).center());
217
218 for (int bin = 0; bin < stackBins; ++bin, ++entry) {
219 const int counts = *entry;
220 // skip empty bin
221 if (counts == 0) {
222 continue;
223 }
224 entries += counts;
225
226 double dEdx = entry->bin(ax::dEdx).center();
227 double inputs[] = {
228 CalibdEdx::recoverTgl(entry->bin(ax::Tgl).center(), id.type),
229 entry->bin(ax::Snp).center()};
230
231 // ignore tracks with dEdx above a threshold defined by previous fit
232 if (fitPass > 0) {
233 float oldCorr = corr.getCorrection(id, charge, inputs[0], inputs[1]);
234 float lowerCut = (1.f - dEdxLowCutFactor * dEdxCut) * oldCorr;
235 float upperCut = (1.f + dEdxCut) * oldCorr;
236 if (dEdx < lowerCut || dEdx > upperCut) {
237 outliers += counts;
238 continue;
239 }
240 // LOGP(info, "sector: {}, gemType: {}, bin: {}, fitPass: {}, oldCorr: {}, lowerCut: {}, upperCut: {}, dEdx: {}, counts: {}", id.sector, id.type, bin, fitPass, oldCorr, lowerCut, upperCut, dEdx, counts);
241 }
242
243 // scale fitted dEdx using the stacks mean
244 if (stackMean != nullptr) {
245 dEdx /= stackMean->getCorrection(id, charge);
246 }
247 const double error = 1. / sqrt(counts);
248 fitter.AddPoint(inputs, dEdx, error);
249
250 if (streamer) {
251 float oldCorr = corr.getCorrection(id, charge, inputs[0], inputs[1]);
252 float lowerCut = (1.f - dEdxLowCutFactor * dEdxCut) * oldCorr;
253 float upperCut = (1.f + dEdxCut) * oldCorr;
254
255 (*streamer) << "fit_standard"
256 << "dedx=" << dEdx
257 << "itgl=" << hist.axis(ax::Tgl).index(entry->bin(ax::Tgl).center())
258 << "snp=" << inputs[1]
259 << "iter=" << fitPass
260 << "ifit=" << fit
261 << "bin=" << bin
262 << "isector=" << int(id.sector)
263 << "istack=" << int(id.type)
264 << "icharge=" << int(charge)
265 << "counts=" << counts
266 << "oldCorr=" << oldCorr
267 << "lowerCut=" << lowerCut
268 << "upperCut=" << upperCut
269 << "\n";
270 }
271 }
272 }
273 fitter.Eval();
274
275 const auto paramSize = CalibdEdxCorrection::ParamSize;
276 float params[paramSize] = {0};
277 for (int param = 0; param < fitter.GetNumberFreeParameters(); ++param) {
278 params[param] = fitter.GetParameter(param);
279 }
280
281 // with a projected hist, copy the fit to every sector
282 if (projSectors) {
283 for (int i = 0; i < sectors; ++i) {
284 id.sector = i;
285 const float mean = stackMean->getCorrection(id, charge);
286
287 // rescale the params to get the true correction
288 float scaledParams[paramSize];
289 for (int i = 0; i < paramSize; ++i) {
290 scaledParams[i] = params[i] * mean;
291 }
292 corr.setParams(id, charge, scaledParams);
293 corr.setChi2(id, charge, fitter.GetChisquare());
294 corr.setEntries(id, charge, entries);
295 }
296 } else {
297 corr.setParams(id, charge, params);
298 corr.setChi2(id, charge, fitter.GetChisquare());
299 corr.setEntries(id, charge, entries);
300 }
301 LOGP(debug, "Sector: {}, gemType: {}, charge: {}, Fit pass: {} with {} % outliers in {} entries. Fitter Points: {}, mean fit: {}",
302 id.sector, int(id.type), int(charge), fitPass, (float)outliers / (float)entries * 100, entries, fitter.GetNpoints(), params[0]);
303 }
304 }
305 auto stop = timer::now();
306 std::chrono::duration<float> time = stop - start;
307 LOGP(info, "Calibration fits took: {}", time.count());
308}
309
310template <typename Hist>
311auto ProjectBoostHistoXFast(const Hist& hist, std::vector<int>& bin_indices, int axis)
312{
313 const unsigned int nbins = hist.axis(axis).size();
314 const double binStartX = hist.axis(axis).bin(0).lower();
315 const double binEndX = hist.axis(axis).bin(nbins - 1).upper();
316 auto histoProj = boost::histogram::make_histogram(CalibdEdx::FloatAxis(nbins, binStartX, binEndX));
317
318 // loop over all bins of the requested axis
319 for (int i = 0; i < nbins; ++i) {
320 // setting bin of the requested axis
321 bin_indices[axis] = i;
322
323 // access the bin content specified by bin_indices
324 histoProj.at(i) = hist.at(bin_indices);
325 }
326
327 return histoProj;
328}
329
330template <typename Hist>
331auto ProjectBoostHistoXFastAllSectors(const Hist& hist, std::vector<int>& bin_indices, StackID id, const CalibdEdxCorrection* stackMean)
332{
333 // get an average histogram over all stacks of the same type
334 using ax = CalibdEdx::Axis;
335 const unsigned int nbinsdedx = hist.axis(ax::dEdx).size();
336 const double binStartX = hist.axis(ax::dEdx).bin(0).lower();
337 const double binEndX = hist.axis(ax::dEdx).bin(nbinsdedx - 1).upper();
338
339 // make fine histogram to be able to correctly store normalized dEdx values
340 auto histoProj = boost::histogram::make_histogram(CalibdEdx::FloatAxis(nbinsdedx, binStartX, binEndX));
341
342 // loop over sectors for merging the histograms
343 for (int iSec = 0; iSec < hist.axis(ax::Sector).size(); ++iSec) {
344 bin_indices[ax::Sector] = iSec;
345 id.sector = static_cast<int>(hist.axis(ax::Sector).bin(iSec).center());
346
347 // loop over all bins of the requested axis
348 for (int i = 0; i < nbinsdedx; ++i) {
349 // setting bin of the requested axis
350 bin_indices[ax::dEdx] = i;
351
352 // access the bin content specified by bin_indices
353 const float counts = hist.at(bin_indices);
354 float dEdx = hist.axis(ax::dEdx).value(i);
355
356 // scale the dedx to the mean
357 if (stackMean != nullptr) {
358 const auto charge = static_cast<ChargeType>(bin_indices[ax::Charge]);
359 dEdx /= stackMean->getCorrection(id, charge);
360 }
361
362 // fill the histogram with dedx as the bin center and the counts as the weight
363 histoProj(dEdx, bh::weight(counts));
364 }
365 }
366 return histoProj;
367}
368
369void CalibdEdx::fitHistGaus(TLinearFitter& fitter, CalibdEdxCorrection& corr, const CalibdEdxCorrection* stackMean)
370{
371 using timer = std::chrono::high_resolution_clock;
372 using ax = CalibdEdx::Axis;
373 auto start = timer::now();
374 const bool projSectors = stackMean != nullptr;
375 constexpr int sectors = SECTORSPERSIDE * SIDES;
376 for (int iSnp = 0; iSnp < mHist.axis(ax::Snp).size(); ++iSnp) {
377 const int iSecEnd = projSectors ? 1 : mHist.axis(ax::Sector).size();
378 for (int iSec = 0; iSec < iSecEnd; ++iSec) {
379 for (int iStack = 0; iStack < mHist.axis(ax::Stack).size(); ++iStack) {
380 for (int iCharge = 0; iCharge < mHist.axis(ax::Charge).size(); ++iCharge) {
381
382 fitter.ClearPoints();
383 StackID id{};
384 id.type = static_cast<GEMstack>(mHist.axis(ax::Stack).bin(iStack).center());
385 id.sector = static_cast<int>(mHist.axis(ax::Sector).bin(iSec).center());
386 const auto charge = static_cast<ChargeType>(mHist.axis(ax::Charge).bin(iCharge).center());
387 int entries = 0;
388
389 for (int iTgl = 0; iTgl < mHist.axis(ax::Tgl).size(); ++iTgl) {
390 // calculate sigma vs tgl in first iteration
391 // apply cut in n sigma in second iteration
392 float sigma_vs_tgl = 0;
393 float mean_vs_tgl = 0;
394 std::vector<int> bin_indices(ax::Size);
395 bin_indices[ax::Tgl] = iTgl;
396 bin_indices[ax::Snp] = iSnp;
397 bin_indices[ax::Sector] = iSec;
398 bin_indices[ax::Stack] = iStack;
399 bin_indices[ax::Charge] = iCharge;
400
401 for (int iter = 0; iter < 2; ++iter) {
402 auto boostHist1d = projSectors ? ProjectBoostHistoXFastAllSectors(mHist, bin_indices, id, stackMean) : ProjectBoostHistoXFast(mHist, bin_indices, ax::dEdx);
403
404 float lowerCut = 0;
405 float upperCut = 0;
406
407 // make gaussian fit
408 if (iter == 0) {
409 int maxElementIndex = std::max_element(boostHist1d.begin(), boostHist1d.end()) - boostHist1d.begin() - 1;
410 if (maxElementIndex < 0) {
411 maxElementIndex = 0;
412 }
413 float maxElementCenter = 0.5 * (boostHist1d.axis(0).bin(maxElementIndex).upper() + boostHist1d.axis(0).bin(maxElementIndex).lower());
414
415 lowerCut = (1.f - mFitLowCutFactor * mFitCut) * maxElementCenter;
416 upperCut = (1.f + mFitCut) * maxElementCenter;
417 } else {
418 lowerCut = mean_vs_tgl - sigma_vs_tgl * mSigmaLower;
419 upperCut = mean_vs_tgl + sigma_vs_tgl * mSigmaUpper;
420 }
421
422 // Restrict fit range to maximum +- restrictFitRangeToMax
423 double max_range = mHist.axis(ax::dEdx).bin(mHist.axis(ax::dEdx).size() - 1).lower();
424 double min_range = mHist.axis(ax::dEdx).bin(0).lower();
425 if ((upperCut <= lowerCut) || (lowerCut > max_range) || (upperCut < min_range)) {
426 break;
427 }
428
429 // remove up and low bins
430 boostHist1d = boost::histogram::algorithm::reduce(boostHist1d, boost::histogram::algorithm::shrink(lowerCut, upperCut));
431
432 try {
433 auto fitValues = o2::utils::fitGaus<float>(boostHist1d.begin(), boostHist1d.end(), o2::utils::BinCenterView(boostHist1d.axis(0).begin()), false);
434
435 // add the mean from gaus fit to the fitter
436 double dEdx = fitValues[1];
437 double inputs[] = {
438 CalibdEdx::recoverTgl(mHist.axis(ax::Tgl).bin(iTgl).center(), id.type),
439 mHist.axis(ax::Snp).bin(iSnp).center()};
440
441 const auto fitNPoints = fitValues[3];
442 const float sigma = fitValues[2];
443 const double fitMeanErr = (fitNPoints > 0) ? (sigma / std::sqrt(fitNPoints)) : -1;
444 if (iter == 0) {
445 sigma_vs_tgl = sigma;
446 mean_vs_tgl = dEdx;
447 } else {
448 entries += fitNPoints;
449 if (fitMeanErr > 0) {
450 fitter.AddPoint(inputs, dEdx, fitMeanErr);
451 }
452 }
453
454 if (mDebugOutputStreamer) {
455 const int nbinsx = boostHist1d.axis(0).size();
456 std::vector<float> binCenter(nbinsx);
457 std::vector<float> dedx(nbinsx);
458 for (int ix = 0; ix < nbinsx; ix++) {
459 binCenter[ix] = boostHist1d.axis(0).bin(ix).center();
460 dedx[ix] = boostHist1d.at(ix);
461 }
462
463 (*mDebugOutputStreamer) << "fit_gaus"
464 << "fitConstant=" << fitValues[0]
465 << "fitMean=" << dEdx
466 << "fitMeanErr=" << fitMeanErr
467 << "fitSigma=" << sigma_vs_tgl
468 << "fitSum=" << fitNPoints
469 << "dedx=" << binCenter
470 << "counts=" << dedx
471 << "itgl=" << bin_indices[1]
472 << "isnp=" << bin_indices[2]
473 << "isector=" << bin_indices[3]
474 << "istack=" << bin_indices[4]
475 << "icharge=" << bin_indices[5]
476 << "upperCut=" << upperCut
477 << "lowerCut=" << lowerCut
478 << "mFitCut=" << mFitCut
479 << "mFitLowCutFactor=" << mFitLowCutFactor
480 << "iter=" << iter
481 << "mSigmaLower=" << mSigmaLower
482 << "mSigmaUpper=" << mSigmaUpper
483 << "projSectors=" << projSectors
484 << "\n";
485 }
486 } catch (o2::utils::FitGausError_t err) {
487 LOGP(warning, "Skipping bin: iTgl {} iSnp {} iSec {} iStack {} iCharge {} iter {}", iTgl, iSnp, iSec, iStack, iCharge, iter);
488 LOG(warning) << createErrorMessageFitGaus(err);
489 break;
490 }
491 }
492 }
493
494 const int fitStatus = fitter.Eval();
495 if (fitStatus > 0) {
496 LOGP(warning, "Fit failed");
497 continue;
498 }
499
500 const auto paramSize = CalibdEdxCorrection::ParamSize;
501 float params[paramSize] = {0};
502 for (int param = 0; param < fitter.GetNumberFreeParameters(); ++param) {
503 params[param] = fitter.GetParameter(param);
504 }
505
506 // with a projected hist, copy the fit to every sector
507 if (projSectors) {
508 for (int i = 0; i < sectors; ++i) {
509 id.sector = i;
510 const float mean = stackMean->getCorrection(id, charge);
511
512 // rescale the params to get the true correction
513 float scaledParams[paramSize];
514 for (int i = 0; i < paramSize; ++i) {
515 scaledParams[i] = params[i] * mean;
516 }
517 corr.setParams(id, charge, scaledParams);
518 corr.setChi2(id, charge, fitter.GetChisquare());
519 corr.setEntries(id, charge, entries);
520 }
521 } else {
522 corr.setParams(id, charge, params);
523 corr.setChi2(id, charge, fitter.GetChisquare());
524 corr.setEntries(id, charge, entries);
525 }
526 }
527 }
528 }
529 }
530 auto stop = timer::now();
531 std::chrono::duration<float> time = stop - start;
532 LOGP(info, "Calibration fits took: {}", time.count());
533}
534
535void CalibdEdx::finalize(const bool useGausFits)
536{
537 const float entries = minStackEntries();
538 mCalib.clear();
539
540 TLinearFitter fitter(2);
541 // Choose the fit dimension based on the available statistics
542 if (mFitSnp && entries >= m2DThreshold) {
543 fitter.SetFormula("1 ++ x ++ x*x ++ x*x*x ++ x*x*x*x ++ y ++ y*y ++ x*y");
544 mCalib.setDims(2);
545 } else if (entries >= m1DThreshold) {
546 fitter.SetFormula("1 ++ x ++ x*x ++ x*x*x ++ x*x*x*x");
547 mCalib.setDims(1);
548 } else {
549 fitter.SetFormula("1");
550 mCalib.setDims(0);
551 }
552 LOGP(info, "Fitting {}D dE/dx correction for GEM stacks with gaussian fits {}", mCalib.getDims(), useGausFits);
553
554 // if entries below minimum sector threshold, integrate all sectors
555 if (mCalib.getDims() == 0 || entries >= mSectorThreshold) {
556 if (!useGausFits) {
557 fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses, nullptr, mDebugOutputStreamer.get());
558 } else {
559 fitHistGaus(fitter, mCalib, nullptr);
560 }
561 } else {
562 LOGP(info, "Integrating GEM stacks sectors in dE/dx correction due to low statistics");
563
564 // get mean of each GEM stack
565 CalibdEdxCorrection meanCorr{};
566 meanCorr.setDims(0);
567 TLinearFitter meanFitter(0);
568 meanFitter.SetFormula("1");
569 // get the mean dEdx for each stack
570 fitHist(mHist, meanCorr, meanFitter, mFitCut, mFitLowCutFactor, mFitPasses);
571 if (!useGausFits) {
572 // get higher dimension corrections with projected sectors
573 fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses, &meanCorr, mDebugOutputStreamer.get());
574 } else {
575 // get higher dimension corrections with projected sectors
576 fitHistGaus(fitter, mCalib, &meanCorr);
577 }
578 }
579}
580
582{
583 // sum over the dEdx and track-param bins to get the number of entries per stack and charge
584 auto projection = bh::algorithm::project(mHist, std::vector<int>{Axis::Sector, Axis::Stack, Axis::Charge});
585 auto dEdxCounts = bh::indexed(projection);
586 // find the stack with the least number of entries
587 auto min_it = std::min_element(dEdxCounts.begin(), dEdxCounts.end());
588 return *min_it;
589}
590
591bool CalibdEdx::hasEnoughData(float minEntries) const
592{
593 return minStackEntries() >= minEntries;
594}
595
596THnF* CalibdEdx::getTHnF() const
597{
598 std::vector<int> bins{};
599 std::vector<double> axisMin{};
600 std::vector<double> axisMax{};
601
602 const size_t histRank = mHist.rank();
603
604 for (size_t i = 0; i < histRank; ++i) {
605 const auto& ax = mHist.axis(i);
606 bins.push_back(ax.size());
607 axisMin.push_back(*ax.begin());
608 axisMax.push_back(*ax.end());
609 }
610
611 auto hn = new THnF("hdEdxMIP", "MIP dEdx per GEM stack", histRank, bins.data(), axisMin.data(), axisMax.data());
612 return hn;
613}
614
616{
617 auto hn = getTHnF();
618 const size_t histRank = mHist.rank();
619 std::vector<double> xs(histRank);
620 for (auto&& entry : bh::indexed(mHist)) {
621 if (*entry == 0) {
622 continue;
623 }
624 for (int i = 0; i < histRank; ++i) {
625 xs[i] = entry.bin(i).center();
626 }
627
628 hn->Fill(xs.data(), *entry);
629 }
630 return hn;
631}
632
633void CalibdEdx::setFromRootHist(const THnF* hist)
634{
635 // Get the number of dimensions
636 int n_dim = hist->GetNdimensions();
637
638 // Vectors to store axis ranges and bin counts
639 std::vector<std::pair<double, double>> axis_ranges(n_dim); // Min and max of each axis
640 std::vector<int> bin_counts(n_dim); // Number of bins in each dimension
641
642 // Loop over each dimension to extract the bin edges and ranges
643 for (int dim = 0; dim < n_dim; ++dim) {
644 TAxis* axis = hist->GetAxis(dim);
645 int bins = axis->GetNbins();
646 double min = axis->GetXmin();
647 double max = axis->GetXmax();
648 bin_counts[dim] = bins;
649 axis_ranges[dim] = {min, max}; // Store the min and max range for the axis
650 }
651
652 // Define a Boost histogram using the bin edges
653 mHist = bh::make_histogram(
654 FloatAxis(bin_counts[0], axis_ranges[0].first, axis_ranges[0].second, "dEdx"), // dE/dx
655 FloatAxis(bin_counts[1], axis_ranges[1].first, axis_ranges[1].second, "Tgl"), // Tgl
656 FloatAxis(bin_counts[2], axis_ranges[2].first, axis_ranges[2].second, "Snp"), // snp
657 IntAxis(0, bin_counts[3], "sector"), // sector
658 IntAxis(0, bin_counts[4], "stackType"), // stack type
659 IntAxis(0, bin_counts[5], "charge") // charge type
660 );
661
662 // Fill the Boost histogram with the bin contents from the ROOT histogram
663 int total_bins = hist->GetNbins();
664 for (int bin = 0; bin < total_bins; ++bin) {
665 std::vector<int> bin_indices(n_dim);
666 double content = hist->GetBinContent(bin, bin_indices.data()); // Get bin coordinates
667
668 // Check if any coordinate is in the underflow (0) or overflow (nbins+1) bins
669 bool is_underflow_or_overflow = false;
670 for (int dim = 0; dim < n_dim; ++dim) {
671 if ((bin_indices[dim] == 0) || (bin_indices[dim] == bin_counts[dim] + 1)) {
672 is_underflow_or_overflow = true;
673 break;
674 }
675 }
676
677 // Skip underflow/overflow bins
678 if (is_underflow_or_overflow) {
679 continue;
680 }
681
682 // Assign the content to the corresponding bin in the Boost histogram
683 mHist.at(bin_indices[0] - 1, bin_indices[1] - 1, bin_indices[2] - 1, bin_indices[3] - 1, bin_indices[4] - 1, bin_indices[5] - 1) = content;
684 }
685}
686
688{
689 const int uniqueEntries = std::accumulate(mHist.begin(), mHist.end(), 0.0) / GEMSTACKSPERSECTOR / 2;
690 LOGP(info, "Total number of track entries: {}. Min. entries per GEM stack: {}", uniqueEntries, minStackEntries());
691}
692
693void CalibdEdx::writeTTree(std::string_view fileName) const
694{
695 TFile f(fileName.data(), "recreate");
696
697 TTree tree("hist", "Saving boost histogram to TTree");
698
699 // FIXME: infer axis type and remove the hardcoded float
700 std::vector<float> row(mHist.rank());
701 for (int i = 0; i < mHist.rank(); ++i) {
702 tree.Branch(mHist.axis(i).metadata().c_str(), &row[i]);
703 }
704 float count = 0;
705 tree.Branch("counts", &count);
706
707 for (auto&& entry : bh::indexed(mHist)) {
708 if (*entry == 0) {
709 continue;
710 }
711 for (int i = 0; i < mHist.rank(); ++i) {
712 // Rescale Tgl
713 if (Axis::Tgl == i) {
714 row[i] = recoverTgl(entry.bin(i).center(), static_cast<GEMstack>(entry.bin(Axis::Stack).center()));
715 } else {
716 row[i] = entry.bin(i).center();
717 }
718 }
719 count = *entry;
720 tree.Fill();
721 }
722
723 f.Write();
724 f.Close();
725}
726
727void CalibdEdx::enableDebugOutput(std::string_view fileName)
728{
729 mDebugOutputStreamer = std::make_unique<o2::utils::TreeStreamRedirector>(fileName.data(), "recreate");
730}
731
733{
734 // This will call the TreeStream destructor and write any stored data.
735 mDebugOutputStreamer.reset();
736}
737
739{
740 if (mDebugOutputStreamer) {
741 LOGP(info, "Closing dump file");
742 mDebugOutputStreamer->Close();
743 }
744}
745
746void CalibdEdx::dumpToFile(const char* outFile, const char* outName) const
747{
748 TFile f(outFile, "RECREATE");
749 f.WriteObject(this, outName);
750 const auto* thn = getRootHist();
751 f.WriteObject(thn, "histogram_data");
752}
753
754CalibdEdx CalibdEdx::readFromFile(const char* inFile, const char* inName)
755{
756 TFile f(inFile, "READ");
757 auto* obj = (CalibdEdx*)f.Get(inName);
758 if (!obj) {
759 CalibdEdx calTmp;
760 return calTmp;
761 }
762 CalibdEdx cal(*obj);
763 THnF* hTmp = (THnF*)f.Get("histogram_data");
764 if (!hTmp) {
765 CalibdEdx calTmp;
766 return calTmp;
767 }
768 cal.setFromRootHist(hTmp);
769 return cal;
770}
771
772void CalibdEdx::setSigmaFitRange(const float lowerSigma, const float upperSigma)
773{
774 mSigmaUpper = upperSigma;
775 mSigmaLower = lowerSigma;
776}
auto ProjectBoostHistoXFast(const Hist &hist, std::vector< int > &bin_indices, int axis)
auto ProjectBoostHistoXFastAllSectors(const Hist &hist, std::vector< int > &bin_indices, StackID id, const CalibdEdxCorrection *stackMean)
void fitHist(const Hist &hist, CalibdEdxCorrection &corr, TLinearFitter &fitter, const float dEdxCut, const float dEdxLowCutFactor, const int passes, const CalibdEdxCorrection *stackMean=nullptr, o2::utils::TreeStreamRedirector *streamer=nullptr)
This file provides the container used for time based residual dE/dx calibration.
constexpr std::array< float, 5 > xks
Definition PID.cxx:39
const auto bins
Definition PID.cxx:49
int16_t charge
Definition RawEventData.h:5
int16_t time
Definition RawEventData.h:4
int32_t i
Definition of the parameter class for the detector gas.
Header to collect physics constants.
uint32_t roc
Definition RawData.h:3
uint32_t stack
Definition RawData.h:1
std::ostringstream debug
GPUd() value_type estimateLTFast(o2 static GPUd() float estimateLTIncrement(const o2 PropagatorImpl * Instance(bool uninitialized=false)
Definition Propagator.h:143
static constexpr int ParamSize
Number of params per fit.
const std::array< float, ParamSize > getMeanParams(ChargeType charge) const
Parameters averaged over all stacks.
Class that creates dE/dx histograms from a sequence of tracks objects.
Definition CalibdEdx.h:43
void disableDebugOutput()
Disable debug output to file. Also writes and closes stored time slots.
void finalize(const bool useGausFits=true)
static constexpr float recoverTgl(float scaledTgl, GEMstack rocType)
Definition CalibdEdx.h:174
static constexpr float scaleTgl(float tgl, GEMstack rocType)
Definition CalibdEdx.h:173
void print() const
Print statistics info.
boost::histogram::axis::regular< float, boost::histogram::use_default, boost::histogram::use_default, boost::histogram::axis::option::none_t > FloatAxis
Definition CalibdEdx.h:58
boost::histogram::axis::integer< int, boost::histogram::use_default, boost::histogram::axis::option::none_t > IntAxis
Definition CalibdEdx.h:56
void merge(const CalibdEdx *other)
Add counts from another container.
void dumpToFile(const char *outFile, const char *outName) const
dump this object to a file - the boost histogram is converted to a ROOT histogram -
THnF * getRootHist() const
Return calib data as a THn.
int minStackEntries() const
Return the number of hist entries of the gem stack with less statistics.
void finalizeDebugOutput() const
Write debug output to file.
void writeTTree(std::string_view fileName) const
Save the histograms to a TTree.
static CalibdEdx readFromFile(const char *inFile, const char *inName)
read the object from a file
void fill(const TrackTPC &tracks)
Fill histograms using tracks data.
Definition CalibdEdx.cxx:84
void setSigmaFitRange(const float lowerSigma, const float upperSigma)
void enableDebugOutput(std::string_view fileName)
Enable debug output to file of the time slots calibrations outputs and dE/dx histograms.
bool hasEnoughData(float minEntries) const
Check if there are enough data to compute the calibration.
static constexpr float MipScale
Inverse of target dE/dx value for MIPs.
Definition CalibdEdx.h:171
CalibdEdx(const CalibdEdx &other)
copy ctor
Definition CalibdEdx.cxx:62
bool goodTrack(o2::tpc::TrackTPC const &track)
Definition TrackCuts.cxx:31
Axis iterator over bin centers.
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLsizei count
Definition glcorearb.h:399
GLuint entry
Definition glcorearb.h:5735
GLsizeiptr size
Definition glcorearb.h:659
GLdouble f
Definition glcorearb.h:310
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLenum const GLfloat * params
Definition glcorearb.h:272
GLuint start
Definition glcorearb.h:469
GLenum GLfloat param
Definition glcorearb.h:271
double mean(std::vector< double >::const_iterator first, std::vector< double >::const_iterator last)
constexpr float TwoPI
constexpr double MassPionCharged
float to02PiGen(float phi)
Definition Utils.h:70
std::string elementsToString(Iterator begin, Iterator end, const std::string separator=", ")
Definition Utils.h:74
Global TPC definitions and constants.
Definition SimTraits.h:167
GEMstack
TPC GEM stack types.
Definition Defs.h:53
@ IROCgem
Definition Defs.h:53
@ OROC1gem
Definition Defs.h:54
@ OROC3gem
Definition Defs.h:56
@ OROC2gem
Definition Defs.h:55
ChargeType
Definition Defs.h:70
@ Tot
Definition Defs.h:72
@ Max
Definition Defs.h:71
constexpr unsigned short CHARGETYPES
Definition Defs.h:74
constexpr double SECPHIWIDTH
Definition Defs.h:45
constexpr unsigned char SECTORSPERSIDE
Definition Defs.h:40
constexpr unsigned char SIDES
Definition Defs.h:41
constexpr unsigned short GEMSTACKSPERSECTOR
Definition Defs.h:57
const int ix
Definition TrackUtils.h:69
FitGausError_t
Error code for invalid result in the fitGaus process.
std::string createErrorMessageFitGaus(o2::utils::FitGausError_t errorcode)
Printing an error message when then fit returns an invalid result.
GEM stack identification.
Definition Defs.h:77
GEMstack type
Definition Defs.h:79
constexpr size_t min
constexpr size_t max
VectorOfTObjectPtrs other
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
std::vector< int > row