41#include "TLinearFitter.h"
44#include <boost/histogram.hpp>
47namespace bh = boost::histogram;
52 const int snpBins = fitSnp ? angularBins : 1;
53 mHist = bh::make_histogram(
64 mFitSnp =
other.mFitSnp;
65 mApplyCuts =
other.mApplyCuts;
67 mSectorThreshold =
other.mSectorThreshold;
68 m1DThreshold =
other.m1DThreshold;
69 m2DThreshold =
other.m2DThreshold;
70 mFitCut =
other.mFitCut;
71 mFitLowCutFactor =
other.mFitLowCutFactor;
72 mFitPasses =
other.mFitPasses;
76 mCalib =
other.mCalib;
77 mCalibIn =
other.mCalibIn;
79 mMatType =
other.mMatType;
87 if (track.hasBothSidesClusters() || (mApplyCuts && !mCuts.
goodTrack(track))) {
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);
96 std::vector<float> dEdxTotCorr(4);
104 const float dEdxScale =
MipScale / BetheBlochAleph(betaGamma, gasParam.BetheBlochParam[0],
105 gasParam.BetheBlochParam[1], gasParam.BetheBlochParam[2],
106 gasParam.BetheBlochParam[3], gasParam.BetheBlochParam[4]);
110 constexpr std::array<float, 4>
xks{108.475f, 151.7f, 188.8f, 227.65f};
122 if (std::abs(sector - localFrame) > 0.1) {
124 cpTrack.rotateParam(
alpha);
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()) {
138 dEdxMaxCorr[
roc] = corrMax;
139 dEdxTotCorr[
roc] = corrTot;
141 static bool reported =
false;
142 if (!reported && mCalibIn.getDims() >= 0) {
144 LOGP(info,
"Undoing previously applied corrections with mean qTot Params {}",
utils::elementsToString(meanParamTot));
152 if (mDebugOutputStreamer) {
153 const float tgl = track.getTgl();
154 const float p = track.getP();
156 (*mDebugOutputStreamer) <<
"dedx"
157 <<
"dEdxMax=" << dEdxMax
158 <<
"dEdxMaxCorr=" << dEdxMaxCorr
159 <<
"dEdxTot=" << dEdxTot
160 <<
"dEdxTotCorr=" << dEdxTotCorr
170 for (
const auto& track : tracks) {
177 if (
other !=
nullptr) {
178 mHist +=
other->getHist();
182template <
typename Hist>
186 using timer = std::chrono::high_resolution_clock;
188 auto start = timer::now();
192 for (
int i = 0;
i < ax::Sector; ++
i) {
193 stackBins *= hist.axis(
i).size();
195 const bool projSectors = stackMean !=
nullptr;
198 constexpr int stackCount = 144;
202 const int fitStacks = projSectors ? sectors : 1;
204 for (
int fitPass = 0; fitPass < passes; ++fitPass) {
206 auto entry = bh::indexed(hist).begin();
207 for (
int fit = 0; fit < fitCount; ++fit) {
213 fitter.ClearPoints();
216 id.sector =
static_cast<int>(
entry->bin(ax::Sector).center());
218 for (
int bin = 0; bin < stackBins; ++bin, ++
entry) {
219 const int counts = *
entry;
226 double dEdx =
entry->bin(ax::dEdx).center();
229 entry->bin(ax::Snp).center()};
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) {
244 if (stackMean !=
nullptr) {
245 dEdx /= stackMean->getCorrection(
id,
charge);
247 const double error = 1. / sqrt(counts);
248 fitter.AddPoint(inputs, dEdx, error);
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;
255 (*streamer) <<
"fit_standard"
257 <<
"itgl=" << hist.axis(ax::Tgl).index(
entry->bin(ax::Tgl).center())
258 <<
"snp=" << inputs[1]
259 <<
"iter=" << fitPass
262 <<
"isector=" <<
int(
id.sector)
265 <<
"counts=" << counts
266 <<
"oldCorr=" << oldCorr
267 <<
"lowerCut=" << lowerCut
268 <<
"upperCut=" << upperCut
276 float params[paramSize] = {0};
277 for (
int param = 0;
param < fitter.GetNumberFreeParameters(); ++
param) {
283 for (
int i = 0;
i < sectors; ++
i) {
285 const float mean = stackMean->getCorrection(
id,
charge);
288 float scaledParams[paramSize];
289 for (
int i = 0;
i < paramSize; ++
i) {
292 corr.setParams(
id,
charge, scaledParams);
293 corr.setChi2(
id,
charge, fitter.GetChisquare());
294 corr.setEntries(
id,
charge, entries);
298 corr.setChi2(
id,
charge, fitter.GetChisquare());
299 corr.setEntries(
id,
charge, entries);
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]);
305 auto stop = timer::now();
306 std::chrono::duration<float>
time = stop -
start;
307 LOGP(info,
"Calibration fits took: {}",
time.count());
310template <
typename Hist>
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));
319 for (
int i = 0;
i < nbins; ++
i) {
321 bin_indices[axis] =
i;
324 histoProj.at(
i) = hist.at(bin_indices);
330template <
typename Hist>
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();
340 auto histoProj = boost::histogram::make_histogram(
CalibdEdx::FloatAxis(nbinsdedx, binStartX, binEndX));
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());
348 for (
int i = 0;
i < nbinsdedx; ++
i) {
350 bin_indices[ax::dEdx] =
i;
353 const float counts = hist.at(bin_indices);
354 float dEdx = hist.axis(ax::dEdx).value(
i);
357 if (stackMean !=
nullptr) {
359 dEdx /= stackMean->getCorrection(
id,
charge);
363 histoProj(dEdx, bh::weight(counts));
371 using timer = std::chrono::high_resolution_clock;
373 auto start = timer::now();
374 const bool projSectors = stackMean !=
nullptr;
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) {
382 fitter.ClearPoints();
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());
389 for (
int iTgl = 0; iTgl < mHist.axis(ax::Tgl).size(); ++iTgl) {
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;
401 for (
int iter = 0; iter < 2; ++iter) {
409 int maxElementIndex = std::max_element(boostHist1d.begin(), boostHist1d.end()) - boostHist1d.begin() - 1;
410 if (maxElementIndex < 0) {
413 float maxElementCenter = 0.5 * (boostHist1d.axis(0).bin(maxElementIndex).upper() + boostHist1d.axis(0).bin(maxElementIndex).lower());
415 lowerCut = (1.f - mFitLowCutFactor * mFitCut) * maxElementCenter;
416 upperCut = (1.f + mFitCut) * maxElementCenter;
418 lowerCut = mean_vs_tgl - sigma_vs_tgl * mSigmaLower;
419 upperCut = mean_vs_tgl + sigma_vs_tgl * mSigmaUpper;
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)) {
430 boostHist1d = boost::histogram::algorithm::reduce(boostHist1d, boost::histogram::algorithm::shrink(lowerCut, upperCut));
433 auto fitValues = o2::utils::fitGaus<float>(boostHist1d.begin(), boostHist1d.end(),
o2::utils::BinCenterView(boostHist1d.axis(0).begin()),
false);
436 double dEdx = fitValues[1];
439 mHist.axis(ax::Snp).bin(iSnp).center()};
441 const auto fitNPoints = fitValues[3];
442 const float sigma = fitValues[2];
443 const double fitMeanErr = (fitNPoints > 0) ? (sigma / std::sqrt(fitNPoints)) : -1;
445 sigma_vs_tgl = sigma;
448 entries += fitNPoints;
449 if (fitMeanErr > 0) {
450 fitter.AddPoint(inputs,
dEdx, fitMeanErr);
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);
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
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
481 <<
"mSigmaLower=" << mSigmaLower
482 <<
"mSigmaUpper=" << mSigmaUpper
483 <<
"projSectors=" << projSectors
487 LOGP(warning,
"Skipping bin: iTgl {} iSnp {} iSec {} iStack {} iCharge {} iter {}", iTgl, iSnp, iSec, iStack, iCharge, iter);
494 const int fitStatus = fitter.Eval();
496 LOGP(warning,
"Fit failed");
501 float params[paramSize] = {0};
502 for (
int param = 0;
param < fitter.GetNumberFreeParameters(); ++
param) {
508 for (
int i = 0;
i < sectors; ++
i) {
510 const float mean = stackMean->getCorrection(
id,
charge);
513 float scaledParams[paramSize];
514 for (
int i = 0;
i < paramSize; ++
i) {
517 corr.setParams(
id,
charge, scaledParams);
518 corr.setChi2(
id,
charge, fitter.GetChisquare());
519 corr.setEntries(
id,
charge, entries);
523 corr.setChi2(
id,
charge, fitter.GetChisquare());
524 corr.setEntries(
id,
charge, entries);
530 auto stop = timer::now();
531 std::chrono::duration<float>
time = stop -
start;
532 LOGP(info,
"Calibration fits took: {}",
time.count());
540 TLinearFitter fitter(2);
542 if (mFitSnp && entries >= m2DThreshold) {
543 fitter.SetFormula(
"1 ++ x ++ x*x ++ x*x*x ++ x*x*x*x ++ y ++ y*y ++ x*y");
545 }
else if (entries >= m1DThreshold) {
546 fitter.SetFormula(
"1 ++ x ++ x*x ++ x*x*x ++ x*x*x*x");
549 fitter.SetFormula(
"1");
552 LOGP(info,
"Fitting {}D dE/dx correction for GEM stacks with gaussian fits {}", mCalib.getDims(), useGausFits);
555 if (mCalib.getDims() == 0 || entries >= mSectorThreshold) {
557 fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses,
nullptr, mDebugOutputStreamer.get());
559 fitHistGaus(fitter, mCalib,
nullptr);
562 LOGP(info,
"Integrating GEM stacks sectors in dE/dx correction due to low statistics");
567 TLinearFitter meanFitter(0);
568 meanFitter.SetFormula(
"1");
570 fitHist(mHist, meanCorr, meanFitter, mFitCut, mFitLowCutFactor, mFitPasses);
573 fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses, &meanCorr, mDebugOutputStreamer.get());
576 fitHistGaus(fitter, mCalib, &meanCorr);
585 auto dEdxCounts = bh::indexed(projection);
587 auto min_it = std::min_element(dEdxCounts.begin(), dEdxCounts.end());
596THnF* CalibdEdx::getTHnF()
const
598 std::vector<int>
bins{};
599 std::vector<double> axisMin{};
600 std::vector<double> axisMax{};
602 const size_t histRank = mHist.rank();
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());
611 auto hn =
new THnF(
"hdEdxMIP",
"MIP dEdx per GEM stack", histRank,
bins.data(), axisMin.data(), axisMax.data());
618 const size_t histRank = mHist.rank();
619 std::vector<double> xs(histRank);
620 for (
auto&&
entry : bh::indexed(mHist)) {
624 for (
int i = 0;
i < histRank; ++
i) {
628 hn->Fill(xs.data(), *
entry);
633void CalibdEdx::setFromRootHist(
const THnF* hist)
636 int n_dim = hist->GetNdimensions();
639 std::vector<std::pair<double, double>> axis_ranges(n_dim);
640 std::vector<int> bin_counts(n_dim);
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};
653 mHist = bh::make_histogram(
654 FloatAxis(bin_counts[0], axis_ranges[0].
first, axis_ranges[0].second,
"dEdx"),
655 FloatAxis(bin_counts[1], axis_ranges[1].
first, axis_ranges[1].second,
"Tgl"),
656 FloatAxis(bin_counts[2], axis_ranges[2].
first, axis_ranges[2].second,
"Snp"),
657 IntAxis(0, bin_counts[3],
"sector"),
658 IntAxis(0, bin_counts[4],
"stackType"),
659 IntAxis(0, bin_counts[5],
"charge")
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());
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;
678 if (is_underflow_or_overflow) {
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;
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());
695 TFile
f(fileName.data(),
"recreate");
697 TTree
tree(
"hist",
"Saving boost histogram to TTree");
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]);
707 for (
auto&&
entry : bh::indexed(mHist)) {
711 for (
int i = 0;
i < mHist.rank(); ++
i) {
729 mDebugOutputStreamer = std::make_unique<o2::utils::TreeStreamRedirector>(fileName.data(),
"recreate");
735 mDebugOutputStreamer.reset();
740 if (mDebugOutputStreamer) {
741 LOGP(info,
"Closing dump file");
742 mDebugOutputStreamer->Close();
748 TFile
f(outFile,
"RECREATE");
749 f.WriteObject(
this, outName);
751 f.WriteObject(thn,
"histogram_data");
756 TFile
f(inFile,
"READ");
763 THnF* hTmp = (THnF*)
f.Get(
"histogram_data");
768 cal.setFromRootHist(hTmp);
774 mSigmaUpper = upperSigma;
775 mSigmaLower = lowerSigma;
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 of the parameter class for the detector gas.
Header to collect physics constants.
GPUd() value_type estimateLTFast(o2 static GPUd() float estimateLTIncrement(const o2 PropagatorImpl * Instance(bool uninitialized=false)
static const ParameterGas & Instance()
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.
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)
static constexpr float scaleTgl(float tgl, GEMstack rocType)
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
boost::histogram::axis::integer< int, boost::histogram::use_default, boost::histogram::axis::option::none_t > IntAxis
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.
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.
CalibdEdx(const CalibdEdx &other)
copy ctor
bool goodTrack(o2::tpc::TrackTPC const &track)
Axis iterator over bin centers.
GLfloat GLfloat GLfloat alpha
GLint GLint GLsizei GLint GLenum GLenum type
GLenum const GLfloat * params
double mean(std::vector< double >::const_iterator first, std::vector< double >::const_iterator last)
constexpr double MassPionCharged
float to02PiGen(float phi)
std::string elementsToString(Iterator begin, Iterator end, const std::string separator=", ")
Global TPC definitions and constants.
GEMstack
TPC GEM stack types.
constexpr unsigned short CHARGETYPES
constexpr double SECPHIWIDTH
constexpr unsigned char SECTORSPERSIDE
constexpr unsigned char SIDES
constexpr unsigned short GEMSTACKSPERSECTOR
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.
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()))