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).bin(
i).center();
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 {}, minStackEntries {}, m2DThreshold {}, m1DThreshold {}, mFitSnp {}",
553 mCalib.getDims(), useGausFits, entries, m2DThreshold, m1DThreshold, mFitSnp);
556 if (mCalib.getDims() == 0 || entries >= mSectorThreshold) {
558 fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses,
nullptr, mDebugOutputStreamer.get());
560 fitHistGaus(fitter, mCalib,
nullptr);
563 LOGP(info,
"Integrating GEM stacks sectors in dE/dx correction due to low statistics");
568 if (averageSectors) {
573 TLinearFitter meanFitter(0);
574 meanFitter.SetFormula(
"1");
575 fitHist(mHist, meanCorr, meanFitter, mFitCut, mFitLowCutFactor, mFitPasses,
nullptr, mDebugOutputStreamer.get());
579 fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses, &meanCorr, mDebugOutputStreamer.get());
582 fitHistGaus(fitter, mCalib, &meanCorr);
591 auto dEdxCounts = bh::indexed(projection);
593 auto min_it = std::min_element(dEdxCounts.begin(), dEdxCounts.end());
602THnF* CalibdEdx::getTHnF()
const
604 std::vector<int>
bins{};
605 std::vector<double> axisMin{};
606 std::vector<double> axisMax{};
608 const size_t histRank = mHist.rank();
610 for (
size_t i = 0;
i < histRank; ++
i) {
611 const auto& ax = mHist.axis(
i);
612 bins.push_back(ax.size());
613 axisMin.push_back(*ax.begin());
614 axisMax.push_back(*ax.end());
617 auto hn =
new THnF(
"hdEdxMIP",
"MIP dEdx per GEM stack", histRank,
bins.data(), axisMin.data(), axisMax.data());
624 const size_t histRank = mHist.rank();
625 std::vector<double> xs(histRank);
626 for (
auto&&
entry : bh::indexed(mHist)) {
630 for (
int i = 0;
i < histRank; ++
i) {
634 hn->Fill(xs.data(), *
entry);
639void CalibdEdx::setFromRootHist(
const THnF* hist)
642 int n_dim = hist->GetNdimensions();
645 std::vector<std::pair<double, double>> axis_ranges(n_dim);
646 std::vector<int> bin_counts(n_dim);
649 for (
int dim = 0; dim < n_dim; ++dim) {
650 TAxis* axis = hist->GetAxis(dim);
651 int bins = axis->GetNbins();
652 double min = axis->GetXmin();
653 double max = axis->GetXmax();
654 bin_counts[dim] =
bins;
655 axis_ranges[dim] = {
min,
max};
659 mHist = bh::make_histogram(
660 FloatAxis(bin_counts[0], axis_ranges[0].
first, axis_ranges[0].second,
"dEdx"),
661 FloatAxis(bin_counts[1], axis_ranges[1].
first, axis_ranges[1].second,
"Tgl"),
662 FloatAxis(bin_counts[2], axis_ranges[2].
first, axis_ranges[2].second,
"Snp"),
663 IntAxis(0, bin_counts[3],
"sector"),
664 IntAxis(0, bin_counts[4],
"stackType"),
665 IntAxis(0, bin_counts[5],
"charge")
669 int total_bins = hist->GetNbins();
670 for (
int bin = 0; bin < total_bins; ++bin) {
671 std::vector<int> bin_indices(n_dim);
672 double content = hist->GetBinContent(bin, bin_indices.data());
675 bool is_underflow_or_overflow =
false;
676 for (
int dim = 0; dim < n_dim; ++dim) {
677 if ((bin_indices[dim] == 0) || (bin_indices[dim] == bin_counts[dim] + 1)) {
678 is_underflow_or_overflow =
true;
684 if (is_underflow_or_overflow) {
689 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;
695 const int uniqueEntries = std::accumulate(mHist.begin(), mHist.end(), 0.0) /
GEMSTACKSPERSECTOR / 2;
696 LOGP(info,
"Total number of track entries: {}. Min. entries per GEM stack: {}", uniqueEntries,
minStackEntries());
701 TFile
f(fileName.data(),
"recreate");
703 TTree
tree(
"hist",
"Saving boost histogram to TTree");
706 std::vector<float>
row(mHist.rank());
707 for (
int i = 0;
i < mHist.rank(); ++
i) {
708 tree.Branch(mHist.axis(
i).metadata().c_str(), &
row[
i]);
713 for (
auto&&
entry : bh::indexed(mHist)) {
717 for (
int i = 0;
i < mHist.rank(); ++
i) {
735 mDebugOutputStreamer = std::make_unique<o2::utils::TreeStreamRedirector>(fileName.data(),
"recreate");
741 mDebugOutputStreamer.reset();
746 if (mDebugOutputStreamer) {
747 LOGP(info,
"Closing dump file");
748 mDebugOutputStreamer->Close();
754 TFile
f(outFile,
"RECREATE");
755 f.WriteObject(
this,
"calib");
757 f.WriteObject(thn,
"histogram_data");
762 std::unique_ptr<TFile>
f(TFile::Open(inFile));
763 if (!
f ||
f->IsZombie()) {
764 LOGP(error,
"Could not open file: {}", inFile);
771 LOGP(error,
"Could not read CalibdEdx object from file: {}", inFile);
776 THnF* hTmp =
f->Get<THnF>(
"histogram_data");
782 LOGP(warning,
"Could not read histogram from file: {}. Returning empty histogram", inFile);
785 cal.setFromRootHist(hTmp);
791 mSigmaUpper = upperSigma;
792 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.
void setUnity()
set all corrections to 1, used for default initialization and to reset corrections
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.
static constexpr float recoverTgl(float scaledTgl, GEMstack rocType)
static constexpr float scaleTgl(float tgl, GEMstack rocType)
void print() const
Print statistics info.
void finalize(const bool useGausFits=true, const bool averageSectors=false)
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.
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 dumpToFile(const char *outFile)
dump this object to a file - the boost histogram is converted to a ROOT histogram -
void finalizeDebugOutput() const
Write debug output to file.
void writeTTree(std::string_view fileName) const
Save the histograms to a TTree.
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 CalibdEdx readFromFile(const char *inFile)
read the object from a file
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()))