GRSISort "v4.0.0.5"
An extension of the ROOT analysis Framework
Loading...
Searching...
No Matches
TPeakFitter.cxx
Go to the documentation of this file.
1#include "TPeakFitter.h"
2#include "TH1.h"
3#include "TClass.h"
4#include "Math/MinimizerOptions.h"
5#include "TVirtualFitter.h"
6#include "TFitResult.h"
7
8#include "Globals.h"
9#include "TGRSIFunctions.h"
10
12
13TPeakFitter::TPeakFitter(const Double_t& rangeLow, const Double_t& rangeHigh)
14 : fRangeLow(rangeLow), fRangeHigh(rangeHigh)
15{
16 fBGToFit = new TF1("fbg", this, &TPeakFitter::DefaultBackgroundFunction, fRangeLow, fRangeHigh, 4, "TPeakFitter", "DefaultBackgroundFunction");
17 fBGToFit->FixParameter(3, 0);
18 fBGToFit->SetLineColor(static_cast<Color_t>(kRed + fColorIndex));
19 if(fVerboseLevel >= EVerbosity::kSubroutines) { std::cout << __PRETTY_FUNCTION__ << ": constructed peak fitter " << this << " using range " << fRangeLow << " - " << fRangeHigh << std::endl; } // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
20}
21
23{
24 if(fVerboseLevel >= EVerbosity::kSubroutines) { std::cout << __PRETTY_FUNCTION__ << ": destroying peak fitter " << this << " for range " << fRangeLow << " - " << fRangeHigh << std::endl; } // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
25}
26
27void TPeakFitter::Print(Option_t* opt) const
28{
29 /// Print information from the fit, opt is passed along to each individual peaks Print function.
30 if(fTotalFitFunction != nullptr) {
31 double xMin = 0.;
32 double xMax = 0.;
33 fTotalFitFunction->GetRange(xMin, xMax);
34 std::cout << "Fitting range: " << xMin << " to " << xMax << std::endl;
35 } else {
36 std::cout << "Range: " << fRangeLow << " to " << fRangeHigh << std::endl;
37 }
38 Int_t counter = 0;
39 if(!fPeaksToFit.empty()) {
40 std::cout << "Peaks: " << std::endl;
41 for(auto* peak : fPeaksToFit) {
42 std::cout << "Peak #" << counter++ << " - ";
43 peak->Print(opt);
44 }
45 std::cout << "Chi2/Ndf = " << fPeaksToFit.front()->GetChi2() << "/" << fPeaksToFit.front()->GetNDF() << " = " << fPeaksToFit.front()->GetReducedChi2() << std::endl;
46 } else {
47 std::cout << "Could not find peak to print" << std::endl;
48 }
49 if(fBGToFit != nullptr) {
50 std::cout << "BG: " << std::endl;
51 fBGToFit->Print(opt);
52 } else {
53 std::cout << "Could not find a BG to print" << std::endl;
54 }
55}
56
58{
59 /// Print the range of the fit and the parameters of each peak on a single line.
60 std::cout << "Range: " << fRangeLow << " to " << fRangeHigh << " - ";
61 int i = 0;
62 for(auto* peak : fPeaksToFit) {
63 std::cout << peak->IsA()->GetName() << " #" << i++ << " ";
64 peak->PrintParameters();
65 }
66 std::cout << std::endl;
67}
68
70{
71 Int_t n_par = 0;
72 for(auto* peak : fPeaksToFit) {
73 n_par += peak->GetNParameters();
74 }
75 if(fBGToFit != nullptr) {
76 n_par += fBGToFit->GetNpar();
77 }
78
79 return n_par;
80}
81
82void TPeakFitter::SetRange(const Double_t& low, const Double_t& high)
83{
84 fRangeLow = low;
85 fRangeHigh = high;
86 if(fTotalFitFunction != nullptr) {
87 fTotalFitFunction->SetTitle(Form("total_fit_%.0f_%.0f", fRangeLow, fRangeHigh));
88 }
89}
90
91TFitResultPtr TPeakFitter::Fit(TH1* fit_hist, Option_t* opt)
92{
93 /// Fit the histogram. Recognized options are "q" for a quiet fit, "retryfit" to retry a fit without parameter limits
94 /// if one of the parameters got close to its limit. All options (apart from "retryfit") are passed on to the actual
95 /// call to TH1::Fit.
96 if(fPeaksToFit.empty()) {
97 std::cout << "No peaks provided!" << std::endl;
98 return {};
99 }
100
101 TString options = opt;
102 options.ToLower();
103 bool quiet = options.Contains("q");
104 bool verbose = options.Contains("v");
105 if(quiet && verbose) {
106 std::cout << "Don't know how to be quiet and verbose at once (" << opt << "), going to be verbose!" << std::endl;
107 quiet = false;
108 }
109 bool retryFit = options.Contains("retryfit");
110 options.ReplaceAll("retryfit", "");
111
112 // store original range
113 int firstBin = fit_hist->GetXaxis()->GetFirst();
114 int lastBin = fit_hist->GetXaxis()->GetLast();
115 // set range to fit range of peak fitter
116 fit_hist->GetXaxis()->SetRangeUser(fRangeLow, fRangeHigh);
117
118 ROOT::Math::MinimizerOptions::SetDefaultMinimizer("Minuit2", "Combination");
119 TVirtualFitter::SetMaxIterations(100000);
120 TVirtualFitter::SetPrecision(1e-4);
121 if(fTotalFitFunction == nullptr) {
122 fTotalFitFunction = new TF1(Form("total_fit_%.0f_%.0f", fRangeLow, fRangeHigh), this, &TPeakFitter::FitFunction, fRangeLow, fRangeHigh, GetNParameters(), "TPeakFitter", "FitFunction");
123 }
124 fTotalFitFunction->SetLineColor(static_cast<Color_t>(kMagenta + fColorIndex));
126 fBGToFit->SetRange(fRangeLow, fRangeHigh);
127 // We need to initialize all of the parameters based on the peak parameters
128 if(fit_hist != nullptr && (!fInitFlag || fit_hist != fLastHistFit)) {
129 if(verbose) { std::cout << "Initializing Fit...." << std::endl; }
131 InitializeParameters(fit_hist);
132 fInitFlag = true;
133 fLastHistFit = fit_hist;
134 }
136 if(verbose) { PrintParameters(); }
137 // Do a first fit to get closer on the parameters
138 fit_hist->Fit(fTotalFitFunction, "RNQ0");
139 // Now try the fit with the options provided
140 fit_hist->Fit(fTotalFitFunction, Form("RNQO%s", options.Data()));
141 // Now do a final fit with integrated bins
142 TFitResultPtr fit_res = fit_hist->Fit(fTotalFitFunction, Form("SRI%s", options.Data()));
143
144 // check the parameter errors
145 if(!TGRSIFunctions::CheckParameterErrors(fit_res, options.Data())) {
146 if(retryFit) {
147 // fit again with all parameters released
148 if(!quiet) { std::cout << GREEN << "Re-fitting with released parameters (without any limits)" << RESET_COLOR << std::endl; }
149 for(int i = 0; i < fTotalFitFunction->GetNpar(); ++i) {
150 // as of now this only works for the first peak and is reliant on the fact that all peaks have the centroig as parameter 1
151 // might be better to check if the parameter name matches "centroid_X" where X is the peak number?
152 if(i == 1) { continue; } // skipping centroid, which should always be parameter 1
153 // only release parameters with limits, not those that have been fixed
154 double min = 0.;
155 double max = 0.;
156 fTotalFitFunction->GetParLimits(i, min, max);
157 if(min != max) { fTotalFitFunction->ReleaseParameter(i); }
158 }
159 fit_res = fit_hist->Fit(fTotalFitFunction, Form("SRI%s", options.Data()));
160 TGRSIFunctions::CheckParameterErrors(fit_res, options.Data());
161 } else {
162 // re-try using minos instead of minuit (only thing from opt that might play a role it 'q' or 'v')
163 if(!quiet) { std::cout << YELLOW << "Re-fitting with \"E\" option to get better error estimation using Minos technique." << RESET_COLOR << std::endl; }
164 fit_res = fit_hist->Fit(fTotalFitFunction, Form("SRIE%s", options.Data()));
165 }
166 }
167
168 if(fit_res.Get() != nullptr) {
169 fTotalFitFunction->SetFitResult(*fit_res); // Not sure if this is needed
170
171 // Once we do the fit, we want to update all of the Peak parameters.
172 UpdatePeakParameters(fit_res, fit_hist);
173 } else if(!quiet) {
174 std::cout << RED << "Failed to get fit result, be aware that the results and especially the error estimates might be wrong!" << RESET_COLOR << std::endl;
175 }
176 fit_hist->Draw("hist");
177 fTotalFitFunction->Draw("same");
178 fPeaksToFit.front()->DrawBackground("same");
179
180 // always print the result of the fit even if not verbose
181 if(!quiet) {
182 std::cout << "****************" << std::endl
183 << "Summary of Fit: " << std::endl;
184 Print();
185 }
186 // restore old range
187 fit_hist->GetXaxis()->SetRange(firstBin, lastBin);
188 // reset the default back to minuit in case we switched to minos in between
189 ROOT::Math::MinimizerOptions::SetDefaultMinimizer("Minuit2", "Combination");
190
191 return fit_res;
192}
193
194void TPeakFitter::UpdatePeakParameters(const TFitResultPtr& fit_res, TH1* fit_hist)
195{
196 // We enter this function once we have performed a fit and need to tell the peaks
197 // what their new parameters should be.
198 Int_t param_counter = 0;
199
200 TF1* global_bg = new TF1("global_bg", this, &TPeakFitter::BackgroundFunction, fRangeLow, fRangeHigh, fTotalFitFunction->GetNpar(), "TPeakFitter", "BackgroundFunction");
201 global_bg->SetParameters(fTotalFitFunction->GetParameters());
202
203 // Start by looping through all of the peaks in the fitter.
204 for(auto* p_it : fPeaksToFit) {
205 TF1* peak_func = p_it->GetFitFunction();
206 // We need to make a clone of the main fit function, and set all of the non-peak parameters to 0.
207 // This will allow us to do the proper integrals. We have to do the same with the covariance matrix
208 // We get the covariance matrix that we are using to update the individual peaks
209 TF1* total_function_copy = new TF1(*fTotalFitFunction);
210 TMatrixDSym covariance_matrix = fit_res->GetCovarianceMatrix();
211 bool goodCovarianceMatrix = true;
212 if(covariance_matrix.GetNrows() < peak_func->GetNpar() || covariance_matrix.GetNcols() < peak_func->GetNpar()) {
213 goodCovarianceMatrix = false;
214 }
215 // Now we need to remove all of the parts of the covariance peak that has nothing to do with this particular peak
216 // This would be the diagonals of the background, and all of the other peaks.
217 Int_t param_to_zero_counter = 0;
218 std::vector<Int_t> param_to_zero_list;
219 for(auto* other_p_it : fPeaksToFit) {
220 if(other_p_it != p_it) {
221 for(int i = 0; i < other_p_it->GetFitFunction()->GetNpar(); ++i) {
222 param_to_zero_list.push_back(param_to_zero_counter);
223 ++param_to_zero_counter;
224 }
225 } else {
226 for(int i = 0; i < peak_func->GetNpar(); ++i) {
227 if(p_it->IsBackgroundParameter(i)) {
228 param_to_zero_list.push_back(param_to_zero_counter);
229 }
230 ++param_to_zero_counter;
231 }
232 Double_t low_range = 0.;
233 Double_t high_range = 0.;
234 fTotalFitFunction->GetRange(low_range, high_range);
235 peak_func->SetRange(low_range, high_range);
236 }
237 }
238 // We have now taken care to zero all of the non-peak parameters in the peak list, so we want to remove the total background contribution
239 for(int i = param_to_zero_counter; i < fTotalFitFunction->GetNpar(); ++i) {
240 param_to_zero_list.push_back(i);
241 }
242
243 // zero other non-peak portions of matrix
244 for(auto i : param_to_zero_list) {
245 for(auto j : param_to_zero_list) {
246 if(goodCovarianceMatrix) { covariance_matrix(i, j) = 0.0; }
247 }
248 total_function_copy->SetParameter(i, 0.0);
249 }
250 if(peak_func != nullptr) {
251 for(int i = 0; i < peak_func->GetNpar(); ++i) {
252 peak_func->SetParameter(i, fTotalFitFunction->GetParameter(param_counter));
253 peak_func->SetParError(i, fTotalFitFunction->GetParError(param_counter));
254 Double_t low = 0.;
255 Double_t high = 0.;
256 fTotalFitFunction->GetParLimits(param_counter, low, high);
257 peak_func->SetParLimits(i, low, high);
258 ++param_counter;
259 // Lets do some integrals meow.
260 }
261 p_it->SetArea(total_function_copy->Integral(p_it->Centroid() - p_it->Width() * 5., p_it->Centroid() + p_it->Width() * 5., 1e-8) / fit_hist->GetBinWidth(1));
262 if(goodCovarianceMatrix) {
263 p_it->SetAreaErr(total_function_copy->IntegralError(p_it->Centroid() - p_it->Width() * 5., p_it->Centroid() + p_it->Width() * 5., total_function_copy->GetParameters(), covariance_matrix.GetMatrixArray(), 1E-5) / fit_hist->GetBinWidth(1));
264 } else if(fVerboseLevel != EVerbosity::kQuiet) {
265 std::cout << "Not setting area error because we don't have a good covariance matrix!" << std::endl;
266 }
267 }
268 total_function_copy->Delete();
269 }
270 if(fBGToFit != nullptr) {
271 for(int i = 0; i < fBGToFit->GetNpar(); ++i) {
272 fBGToFit->SetParameter(i, fTotalFitFunction->GetParameter(param_counter));
273 fBGToFit->SetParError(i, fTotalFitFunction->GetParError(param_counter));
274 ++param_counter;
275 }
276 }
277 // We now have a copy of the global background. Copy and send to every peak
278 for(auto* p_it : fPeaksToFit) {
279 p_it->SetGlobalBackground(new TF1(*global_bg));
280 p_it->SetChi2(fTotalFitFunction->GetChisquare());
281 p_it->SetNDF(fTotalFitFunction->GetNDF());
282 }
283 global_bg->Delete();
284}
285
287{
288 for(auto* p_it : fPeaksToFit) {
289 p_it->InitializeParameters(fit_hist, fRangeLow, fRangeHigh);
290 }
291}
292
294{
295 /// This functions gets the parameters and their limits from the peak functions and
296 /// sets them for the total fit function
297 Int_t param_counter = 0;
298 Int_t peak_counter = 0;
299 for(auto* p_it : fPeaksToFit) {
300 TF1* peak_func = p_it->GetFitFunction();
301 if(peak_func != nullptr) {
302 for(int i = 0; i < peak_func->GetNpar(); ++i) {
303 fTotalFitFunction->SetParName(param_counter, Form("%s_%i", peak_func->GetParName(i), peak_counter));
304 fTotalFitFunction->SetParameter(param_counter, peak_func->GetParameter(i));
305 fTotalFitFunction->SetParError(param_counter, peak_func->GetParError(i));
306 Double_t limit_low = 0.;
307 Double_t limit_high = 0.;
308 peak_func->GetParLimits(i, limit_low, limit_high);
309 fTotalFitFunction->SetParLimits(param_counter, limit_low, limit_high);
310 ++param_counter;
311 }
312 ++peak_counter;
313 }
314 }
315 if(fBGToFit != nullptr) {
316 for(int i = 0; i < fBGToFit->GetNpar(); ++i) {
317 fTotalFitFunction->SetParName(param_counter, fBGToFit->GetParName(i));
318 fTotalFitFunction->SetParameter(param_counter, fBGToFit->GetParameter(i));
319 fTotalFitFunction->SetParError(param_counter, fBGToFit->GetParError(i));
320 Double_t limit_low = 0.;
321 Double_t limit_high = 0.;
322 fBGToFit->GetParLimits(i, limit_low, limit_high);
323 fTotalFitFunction->SetParLimits(param_counter, limit_low, limit_high);
324 ++param_counter;
325 }
326 }
327}
328
329Double_t TPeakFitter::FitFunction(Double_t* dim, Double_t* par)
330{
331 // I want to use the EvalPar command here in order to get the individual peaks
332 Double_t sum = 0;
333 Int_t params_so_far = 0;
334 if(fVerboseLevel >= EVerbosity::kSubroutines) { std::cout << __PRETTY_FUNCTION__ << ": this " << this << " has " << fPeaksToFit.size() << " peaks to be evaluated at x = " << dim[0] << std::endl; } // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
335 for(auto* p_it : fPeaksToFit) {
336 TF1* peakFunction = p_it->GetFitFunction();
337 if(peakFunction == nullptr) {
338 std::cerr << "Failed to get fit function for peak from " << p_it << " at " << p_it->Centroid() << std::endl;
339 return 0.;
340 }
341 if(fVerboseLevel >= EVerbosity::kLoops) { std::cout << "Evaluating fit function " << peakFunction << " using " << peakFunction->GetNpar() << " parameters starting at " << params_so_far << " (" << &par[params_so_far] << ")" << std::endl; }
342 sum += peakFunction->EvalPar(dim, &par[params_so_far]);
343 params_so_far += peakFunction->GetNpar();
344 }
345 sum += fBGToFit->EvalPar(dim, &par[params_so_far]);
346
347 return sum;
348}
349
350Double_t TPeakFitter::BackgroundFunction(Double_t* dim, Double_t* par)
351{
352 Double_t sum = 0;
353 Int_t params_so_far = 0;
354 for(auto* p_it : fPeaksToFit) {
355 TF1* peak_func = p_it->GetBackgroundFunction();
356 sum += peak_func->EvalPar(dim, &par[params_so_far]);
357 params_so_far += peak_func->GetNpar();
358 }
359 sum += fBGToFit->EvalPar(dim, &par[params_so_far]);
360
361 return sum;
362}
363
365{
366 fBGToFit->SetParName(0, "A");
367 fBGToFit->SetParName(1, "B");
368 fBGToFit->SetParName(2, "C");
369 fBGToFit->SetParName(3, "bg_offset");
370
371 Double_t lowLimit = 0.;
372 Double_t highLimit = 0.;
373 fBGToFit->GetParLimits(0, lowLimit, highLimit);
374
375 double value = fBGToFit->GetParameter(0);
376 if(value == 0 && lowLimit == 0 && highLimit == 0) {
377 fBGToFit->SetParLimits(0, 0.0, fit_hist->GetBinContent(fit_hist->FindBin(fRangeHigh)) * 100.);
378 fBGToFit->SetParameter("A", fit_hist->GetBinContent(fit_hist->FindBin(fRangeHigh)));
379 }
380 fBGToFit->GetParLimits(1, lowLimit, highLimit);
381
382 value = fBGToFit->GetParameter(1);
383 if(value == 0 && lowLimit == 0 && highLimit == 0) {
384 fBGToFit->SetParameter("B", fit_hist->GetBinWidth(1) * ((fit_hist->GetBinContent(fit_hist->FindBin(fRangeLow)) - fit_hist->GetBinContent(fit_hist->FindBin(fRangeHigh))) / (fRangeLow - fRangeHigh)));
385 }
386 fBGToFit->GetParLimits(2, lowLimit, highLimit);
387
388 value = fBGToFit->GetParameter(2);
389 if(value == 0 && lowLimit == 0 && highLimit == 0) {
390 fBGToFit->SetParameter("C", 0.0000);
391 fBGToFit->FixParameter(2, 0.00);
392 }
393 fBGToFit->GetParLimits(3, lowLimit, highLimit);
394
395 value = fBGToFit->GetParameter(3);
396 if(value == 0 && lowLimit == 0 && highLimit == 0) {
397 fBGToFit->SetParLimits(3, fRangeLow, fRangeHigh);
398 if(!fPeaksToFit.empty()) {
399 fBGToFit->SetParameter("bg_offset", fPeaksToFit.front()->Centroid());
400 } else {
401 fBGToFit->SetParameter("bg_offset", (fRangeHigh + fRangeLow) / 2.);
402 }
403 }
404}
405
406Double_t TPeakFitter::DefaultBackgroundFunction(Double_t* dim, Double_t* par) // NOLINT(readability-non-const-parameter)
407{
408 Double_t x = dim[0];
409 Double_t A = par[0];
410 Double_t B = par[1];
411 Double_t C = par[2];
412 Double_t bg_offset = par[3];
413
414 return A + B * (x - bg_offset) + C * TMath::Power(x - bg_offset, 2.);
415}
416
417void TPeakFitter::DrawPeaks(Option_t*) const
418{
419 // First we are going to draw the background
420 TF1* bg_to_draw = new TF1;
421 fTotalFitFunction->Copy(*bg_to_draw);
422 bg_to_draw->SetLineColor(static_cast<Color_t>(kRed + fColorIndex));
423
424 for(auto* p_it : fPeaksToFit) {
425 TF1* peak_func = p_it->GetFitFunction();
426 TF1* total_function_copy = new TF1;
427 fTotalFitFunction->Copy(*total_function_copy);
428 // Now we need to remove all of the parts of the covariance peak that has nothing to do with this particular peak
429 // This would be the diagonals of the background, and all of the other peaks.
430 Int_t param_to_zero_counter = 0;
431 for(auto* other_p_it : fPeaksToFit) {
432 if(other_p_it != p_it) {
433 for(int i = 0; i < other_p_it->GetFitFunction()->GetNpar(); ++i) {
434 total_function_copy->SetParameter(param_to_zero_counter, 0.0);
435 ++param_to_zero_counter;
436 }
437 } else {
438 for(int i = 0; i < peak_func->GetNpar(); ++i) {
439 if(!p_it->IsBackgroundParameter(i)) {
440 bg_to_draw->SetParameter(param_to_zero_counter, 0.0);
441 }
442 ++param_to_zero_counter;
443 }
444 }
445 }
446 total_function_copy->Draw("same");
447 }
448 bg_to_draw->Draw("same");
449}
#define RED
Definition Globals.h:9
EVerbosity
Definition Globals.h:143
#define YELLOW
Definition Globals.h:7
#define GREEN
Definition Globals.h:8
#define RESET_COLOR
Definition Globals.h:5
void UpdatePeakParameters(const TFitResultPtr &fit_res, TH1 *fit_hist)
TF1 * fTotalFitFunction
Definition TPeakFitter.h:99
void PrintParameters() const
void SetRange(const Double_t &low, const Double_t &high)
void Print(Option_t *opt="") const override
Int_t GetNParameters() const
void InitializeBackgroundParameters(TH1 *fit_hist)
Double_t FitFunction(Double_t *dim, Double_t *par)
Double_t fRangeLow
void InitializeParameters(TH1 *fit_hist)
int fColorIndex
this index is added to the colors kRed for the total function and kMagenta for the individual peaks
void DrawPeaks(Option_t *="") const
std::list< TSinglePeak * > fPeaksToFit
Definition TPeakFitter.h:96
Double_t BackgroundFunction(Double_t *dim, Double_t *par)
Double_t fRangeHigh
static EVerbosity fVerboseLevel
Changes verbosity of code.
TFitResultPtr Fit(TH1 *fit_hist, Option_t *opt="")
Double_t DefaultBackgroundFunction(Double_t *dim, Double_t *par)
TH1 * fLastHistFit
TF1 * fBGToFit
Definition TPeakFitter.h:97
void UpdateFitterParameters()
bool CheckParameterErrors(const TFitResultPtr &fitres, std::string opt="")