Analysis-Utilities 26.9.9
C++/ROOT utilities for nuclear measurement data analysis
Loading...
Searching...
No Matches
FittingUtils.cpp
Go to the documentation of this file.
1#include "FittingUtils.hpp"
2
3void FittingUtils::SaveInteractiveParams(const TString &input_name,
4 const TString &peak_name) {
5 TString fits_dir = PlottingUtils::GetPlotsBaseDir() + "/fits";
6 gSystem->mkdir(fits_dir, kTRUE);
7 TString filename = fits_dir + "/" + peak_name + "_" + input_name + ".fits";
8 std::ofstream out(filename.Data());
9 if (!out.is_open()) {
10 std::cerr << "WARNING: Could not save interactive params to " << filename
11 << std::endl;
12 return;
13 }
14 Int_t npar = fit_function_->GetNpar();
15 out << std::setprecision(15);
16 out << "RANGE " << fit_range_low_ << " " << fit_range_high_;
17 out << std::endl;
18 for (Int_t i = 0; i < npar; i++) {
19 Double_t lo = 0, hi = 0;
20 fit_function_->GetParLimits(i, lo, hi);
21 Bool_t fixed = (lo >= hi);
22 out << fit_function_->GetParName(i) << " " << fit_function_->GetParameter(i)
23 << " " << (fixed ? 1 : 0);
24 out << std::endl;
25 }
26 out.close();
27 std::cout << "Saved interactive params to " << filename << std::endl;
28}
29
30Bool_t FittingUtils::LoadInteractiveParams(const TString &input_name,
31 const TString &peak_name) {
32 TString filename = PlottingUtils::GetPlotsBaseDir() + "/fits/" + peak_name +
33 "_" + input_name + ".fits";
34 std::ifstream in(filename.Data());
35 if (!in.is_open())
36 return kFALSE;
37
38 Int_t npar = fit_function_->GetNpar();
39 std::string token;
40 Int_t idx = 0;
41
42 // First line: RANGE low high
43 in >> token;
44 if (token == "RANGE") {
45 Double_t rlo, rhi;
46 in >> rlo >> rhi;
47 fit_range_low_ = rlo;
48 fit_range_high_ = rhi;
49 fit_function_->SetRange(rlo, rhi);
50 }
51
52 // Remaining lines: name value fixed
53 Double_t value;
54 Int_t fixed;
55 while (in >> token >> value >> fixed && idx < npar) {
56 fit_function_->SetParameter(idx, value);
57 if (fixed)
58 fit_function_->FixParameter(idx, value);
59 idx++;
60 }
61 in.close();
62
63 if (idx != npar) {
64 std::cerr << "WARNING: Parameter count mismatch in " << filename
65 << " (expected " << npar << ", got " << idx << ")" << std::endl;
66 return kFALSE;
67 }
68
69 std::cout << "Loaded interactive params from " << filename << std::endl;
70 return kTRUE;
71}
72
73Double_t FittingFunctions::Gaussian(Double_t *x, Double_t *par) {
74 Double_t mu = par[0];
75 Double_t sigma = par[1];
76 Double_t z = (x[0] - mu) / sigma;
77 Double_t gaus_amplitude = par[2];
78 return gaus_amplitude * TMath::Exp(-0.5 * z * z);
79}
80
81Double_t FittingFunctions::LinearBackground(Double_t *x, Double_t *par) {
82 Double_t bkg_constant = par[0];
83 Double_t lin_bkg_slope = par[1];
84 return lin_bkg_slope * x[0] + bkg_constant;
85}
86
87Double_t FittingFunctions::LowTail(Double_t *x, Double_t *par) {
88 Double_t mu = par[0];
89 Double_t sigma = par[1];
90 Double_t exp_tail_amplitude = par[2];
91 Double_t exp_tail_ratio = par[3];
92 Double_t lin_tail_amplitude = par[4];
93 Double_t lin_tail_slope = par[5];
94
95 if (sigma <= 0)
96 return 0;
97
98 if (exp_tail_amplitude == 0 && lin_tail_amplitude == 0)
99 return 0;
100
101 Double_t y = x[0] - mu;
102 Double_t tau = exp_tail_ratio * sigma;
103
104 Double_t exp_term = (exp_tail_amplitude == 0 || tau <= 0)
105 ? 0
106 : exp_tail_amplitude * TMath::Exp(y / tau);
107 Double_t lin_term = lin_tail_amplitude == 0
108 ? 0
109 : lin_tail_amplitude * (1.0 + lin_tail_slope * y);
110 Double_t erfc_term = 1.0 - TMath::Erf(y / (TMath::Sqrt(2) * sigma));
111
112 return (exp_term + lin_term) * erfc_term;
113}
114
115Double_t FittingFunctions::HighTail(Double_t *x, Double_t *par) {
116 Double_t mu = par[0];
117 Double_t sigma = par[1];
118 Double_t exp_tail_amplitude = par[2];
119 Double_t exp_tail_ratio = par[3];
120
121 if (sigma <= 0)
122 return 0;
123
124 if (exp_tail_amplitude == 0)
125 return 0;
126
127 Double_t y = mu - x[0];
128 Double_t tau = exp_tail_ratio * sigma;
129 if (tau <= 0)
130 return 0;
131
132 Double_t exp_term = exp_tail_amplitude * TMath::Exp(y / tau);
133 Double_t erfc_term = 1.0 - TMath::Erf(y / (TMath::Sqrt(2) * sigma));
134
135 return exp_term * erfc_term;
136}
137
138Double_t FittingFunctions::Step(Double_t *x, Double_t *par) {
139 Double_t mu = par[0];
140 Double_t sigma = par[1];
141 if (sigma <= 0)
142 return 0;
143
144 Double_t z = (x[0] - mu) / sigma;
145 Double_t step_amplitude = par[2];
146
147 Double_t denominator = TMath::Power(1 + TMath::Exp(z), 2);
148 if (denominator < 1e-100)
149 return 0;
150
151 return step_amplitude / denominator;
152}
153
154Double_t FittingFunctions::PeakFunction(Double_t *x, Double_t *par) {
155 Double_t mu = par[0];
156 Double_t sigma = par[1];
157 Double_t gaus_amplitude = par[2];
158 // par[3,4,6,8] are ratios of gaus_amplitude
159 Double_t step_amplitude = par[3] * gaus_amplitude;
160 Double_t low_exp_tail_amplitude = par[4] * gaus_amplitude;
161 Double_t low_exp_tail_ratio = par[5];
162 Double_t low_lin_tail_amplitude = par[6] * gaus_amplitude;
163 Double_t low_lin_tail_slope = par[7];
164 Double_t high_exp_tail_amplitude = par[8] * gaus_amplitude;
165 Double_t high_exp_tail_ratio = par[9];
166 Double_t bkg_constant = par[10];
167 Double_t lin_bkg_slope = par[11];
168
169 Double_t gaus_par[3] = {mu, sigma, gaus_amplitude};
170 Double_t bkg_par[2] = {bkg_constant, lin_bkg_slope};
171 Double_t step_par[3] = {mu, sigma, step_amplitude};
172 Double_t low_tail_par[6] = {mu,
173 sigma,
174 low_exp_tail_amplitude,
175 low_exp_tail_ratio,
176 low_lin_tail_amplitude,
177 low_lin_tail_slope};
178 Double_t high_tail_par[4] = {mu, sigma, high_exp_tail_amplitude,
179 high_exp_tail_ratio};
180
181 return Gaussian(x, gaus_par) + LinearBackground(x, bkg_par) +
182 Step(x, step_par) + LowTail(x, low_tail_par) +
183 HighTail(x, high_tail_par);
184}
185
186Double_t FittingFunctions::DoublePeakFunction(Double_t *x, Double_t *par) {
187 // Peak 1: params 0-9
188 Double_t mu1 = par[0];
189 Double_t sigma1 = par[1];
190 Double_t gaus_amplitude1 = par[2];
191 Double_t step_amplitude1 = par[3] * gaus_amplitude1;
192 Double_t low_exp_tail_amplitude1 = par[4] * gaus_amplitude1;
193 Double_t low_exp_tail_ratio1 = par[5];
194 Double_t low_lin_tail_amplitude1 = par[6] * gaus_amplitude1;
195 Double_t low_lin_tail_slope1 = par[7];
196 Double_t high_exp_tail_amplitude1 = par[8] * gaus_amplitude1;
197 Double_t high_exp_tail_ratio1 = par[9];
198
199 // Peak 2: params 10-19
200 Double_t mu2 = par[10];
201 Double_t sigma2 = par[11];
202 Double_t gaus_amplitude2 = par[12];
203 Double_t step_amplitude2 = par[13] * gaus_amplitude2;
204 Double_t low_exp_tail_amplitude2 = par[14] * gaus_amplitude2;
205 Double_t low_exp_tail_ratio2 = par[15];
206 Double_t low_lin_tail_amplitude2 = par[16] * gaus_amplitude2;
207 Double_t low_lin_tail_slope2 = par[17];
208 Double_t high_exp_tail_amplitude2 = par[18] * gaus_amplitude2;
209 Double_t high_exp_tail_ratio2 = par[19];
210
211 // Background: params 20-21
212 Double_t bkg_const = par[20];
213 Double_t bkg_slope = par[21];
214
215 Double_t gaus1_par[3] = {mu1, sigma1, gaus_amplitude1};
216 Double_t step1_par[3] = {mu1, sigma1, step_amplitude1};
217 Double_t low_tail1_par[6] = {mu1,
218 sigma1,
219 low_exp_tail_amplitude1,
220 low_exp_tail_ratio1,
221 low_lin_tail_amplitude1,
222 low_lin_tail_slope1};
223 Double_t high_tail1_par[4] = {mu1, sigma1, high_exp_tail_amplitude1,
224 high_exp_tail_ratio1};
225
226 Double_t gaus2_par[3] = {mu2, sigma2, gaus_amplitude2};
227 Double_t step2_par[3] = {mu2, sigma2, step_amplitude2};
228 Double_t low_tail2_par[6] = {mu2,
229 sigma2,
230 low_exp_tail_amplitude2,
231 low_exp_tail_ratio2,
232 low_lin_tail_amplitude2,
233 low_lin_tail_slope2};
234 Double_t high_tail2_par[4] = {mu2, sigma2, high_exp_tail_amplitude2,
235 high_exp_tail_ratio2};
236
237 Double_t bkg_par[2] = {bkg_const, bkg_slope};
238
239 return Gaussian(x, gaus1_par) + Step(x, step1_par) +
240 LowTail(x, low_tail1_par) + HighTail(x, high_tail1_par) +
241 Gaussian(x, gaus2_par) + Step(x, step2_par) +
242 LowTail(x, low_tail2_par) + HighTail(x, high_tail2_par) +
243 LinearBackground(x, bkg_par);
244}
245
246Double_t FittingFunctions::TriplePeakFunction(Double_t *x, Double_t *par) {
247 // Peak 1: params 0-9
248 Double_t mu1 = par[0];
249 Double_t sigma1 = par[1];
250 Double_t gaus_amplitude1 = par[2];
251 Double_t step_amplitude1 = par[3] * gaus_amplitude1;
252 Double_t low_exp_tail_amplitude1 = par[4] * gaus_amplitude1;
253 Double_t low_exp_tail_ratio1 = par[5];
254 Double_t low_lin_tail_amplitude1 = par[6] * gaus_amplitude1;
255 Double_t low_lin_tail_slope1 = par[7];
256 Double_t high_exp_tail_amplitude1 = par[8] * gaus_amplitude1;
257 Double_t high_exp_tail_ratio1 = par[9];
258
259 // Peak 2: params 10-19
260 Double_t mu2 = par[10];
261 Double_t sigma2 = par[11];
262 Double_t gaus_amplitude2 = par[12];
263 Double_t step_amplitude2 = par[13] * gaus_amplitude2;
264 Double_t low_exp_tail_amplitude2 = par[14] * gaus_amplitude2;
265 Double_t low_exp_tail_ratio2 = par[15];
266 Double_t low_lin_tail_amplitude2 = par[16] * gaus_amplitude2;
267 Double_t low_lin_tail_slope2 = par[17];
268 Double_t high_exp_tail_amplitude2 = par[18] * gaus_amplitude2;
269 Double_t high_exp_tail_ratio2 = par[19];
270
271 // Peak 3: params 20-29
272 Double_t mu3 = par[20];
273 Double_t sigma3 = par[21];
274 Double_t gaus_amplitude3 = par[22];
275 Double_t step_amplitude3 = par[23] * gaus_amplitude3;
276 Double_t low_exp_tail_amplitude3 = par[24] * gaus_amplitude3;
277 Double_t low_exp_tail_ratio3 = par[25];
278 Double_t low_lin_tail_amplitude3 = par[26] * gaus_amplitude3;
279 Double_t low_lin_tail_slope3 = par[27];
280 Double_t high_exp_tail_amplitude3 = par[28] * gaus_amplitude3;
281 Double_t high_exp_tail_ratio3 = par[29];
282
283 // Background: params 30-31
284 Double_t bkg_const = par[30];
285 Double_t bkg_slope = par[31];
286
287 Double_t gaus1_par[3] = {mu1, sigma1, gaus_amplitude1};
288 Double_t step1_par[3] = {mu1, sigma1, step_amplitude1};
289 Double_t low_tail1_par[6] = {mu1,
290 sigma1,
291 low_exp_tail_amplitude1,
292 low_exp_tail_ratio1,
293 low_lin_tail_amplitude1,
294 low_lin_tail_slope1};
295 Double_t high_tail1_par[4] = {mu1, sigma1, high_exp_tail_amplitude1,
296 high_exp_tail_ratio1};
297
298 Double_t gaus2_par[3] = {mu2, sigma2, gaus_amplitude2};
299 Double_t step2_par[3] = {mu2, sigma2, step_amplitude2};
300 Double_t low_tail2_par[6] = {mu2,
301 sigma2,
302 low_exp_tail_amplitude2,
303 low_exp_tail_ratio2,
304 low_lin_tail_amplitude2,
305 low_lin_tail_slope2};
306 Double_t high_tail2_par[4] = {mu2, sigma2, high_exp_tail_amplitude2,
307 high_exp_tail_ratio2};
308
309 Double_t gaus3_par[3] = {mu3, sigma3, gaus_amplitude3};
310 Double_t step3_par[3] = {mu3, sigma3, step_amplitude3};
311 Double_t low_tail3_par[6] = {mu3,
312 sigma3,
313 low_exp_tail_amplitude3,
314 low_exp_tail_ratio3,
315 low_lin_tail_amplitude3,
316 low_lin_tail_slope3};
317 Double_t high_tail3_par[4] = {mu3, sigma3, high_exp_tail_amplitude3,
318 high_exp_tail_ratio3};
319
320 Double_t bkg_par[2] = {bkg_const, bkg_slope};
321
322 return Gaussian(x, gaus1_par) + Step(x, step1_par) +
323 LowTail(x, low_tail1_par) + HighTail(x, high_tail1_par) +
324 Gaussian(x, gaus2_par) + Step(x, step2_par) +
325 LowTail(x, low_tail2_par) + HighTail(x, high_tail2_par) +
326 Gaussian(x, gaus3_par) + Step(x, step3_par) +
327 LowTail(x, low_tail3_par) + HighTail(x, high_tail3_par) +
328 LinearBackground(x, bkg_par);
329}
330
331FittingUtils::FittingUtils(TH1 *working_hist, Float_t fit_range_low,
332 Float_t fit_range_high, Bool_t use_flat_background,
333 Bool_t use_step, Bool_t use_low_exp_tail,
334 Bool_t use_low_lin_tail, Bool_t use_high_exp_tail) {
335
336 working_hist_ = static_cast<TH1 *>(working_hist->Clone());
337 // Detach from gDirectory so its lifetime is bound to this object, not to
338 // whatever TFile happens to be current at construction time.
339 working_hist_->SetDirectory(nullptr);
340 fit_range_low_ = fit_range_low;
341 fit_range_high_ = fit_range_high;
342 use_flat_background_ = use_flat_background;
343 use_step_ = use_step;
344 use_low_exp_tail_ = use_low_exp_tail;
345 use_low_lin_tail_ = use_low_lin_tail;
346 use_high_exp_tail_ = use_high_exp_tail;
347 use_manual_init_ = kFALSE;
348 interactive_ = kFALSE;
349
350 fit_function_ = new TF1("PeakFunction", &FittingFunctions::PeakFunction,
351 fit_range_low_, fit_range_high_, 12);
352
353 fit_function_->SetParName(0, "Mu");
354 fit_function_->SetParName(1, "Sigma");
355 fit_function_->SetParName(2, "GausAmplitude");
356 fit_function_->SetParName(3, "StepAmplitude");
357 fit_function_->SetParName(4, "LowExpTailAmplitude");
358 fit_function_->SetParName(5, "LowExpTailRatio");
359 fit_function_->SetParName(6, "LowLinTailAmplitude");
360 fit_function_->SetParName(7, "LowLinTailSlope");
361 fit_function_->SetParName(8, "HighExpTailAmplitude");
362 fit_function_->SetParName(9, "HighExpTailRatio");
363 fit_function_->SetParName(10, "BkgConstant");
364 fit_function_->SetParName(11, "LinBkgSlope");
365
366 Double_t mu_init = (fit_range_low_ + fit_range_high_) / 2;
367 Double_t range_width = fit_range_high_ - fit_range_low_;
368 Double_t sigma_init = range_width * 0.01;
369
370 Double_t max_bin = working_hist_->GetMaximumBin();
371 Double_t peak_height = working_hist_->GetBinContent(max_bin);
372 Double_t bkg_estimate = EstimateBackground();
373
374 // Gaussian parameters
375 fit_function_->SetParLimits(0, fit_range_low_, fit_range_high_);
376 fit_function_->SetParameter(0, mu_init);
377 fit_function_->SetParLimits(1, range_width * 0.001, range_width * 0.5);
378 fit_function_->SetParameter(1, sigma_init);
379 fit_function_->SetParLimits(2, 0, peak_height * 0.999);
380 fit_function_->SetParameter(2, peak_height * 0.999);
381
382 // Step ratio (fraction of gaussian amplitude)
383 fit_function_->SetParLimits(3, 0, 0.5);
384 if (use_step_)
385 fit_function_->SetParameter(3, 0);
386 else
387 fit_function_->FixParameter(3, 0);
388
389 // Low tail parameters (amplitudes as ratios of gaussian)
390 fit_function_->SetParLimits(4, 0, 0.5);
391 if (use_low_exp_tail_)
392 fit_function_->SetParameter(4, 0.1);
393 else
394 fit_function_->FixParameter(4, 0);
395
396 fit_function_->SetParLimits(5, 1.0, tail_ratio_max_);
397 if (use_low_exp_tail_)
398 fit_function_->SetParameter(5, 1.5);
399 else
400 fit_function_->FixParameter(5, 1);
401
402 fit_function_->SetParLimits(6, 0, 0.5);
403 if (use_low_lin_tail_)
404 fit_function_->SetParameter(6, 0.1);
405 else
406 fit_function_->FixParameter(6, 0);
407
408 fit_function_->SetParLimits(7, -0.1, 0.1);
409 if (use_low_lin_tail_)
410 fit_function_->SetParameter(7, 0);
411 else
412 fit_function_->FixParameter(7, 0);
413
414 // High tail parameters (amplitude as ratio of gaussian)
415 fit_function_->SetParLimits(8, 0, 0.5);
416 if (use_high_exp_tail_)
417 fit_function_->SetParameter(8, 0.1);
418 else
419 fit_function_->FixParameter(8, 0);
420
421 fit_function_->SetParLimits(9, 1.0, tail_ratio_max_);
422 if (use_high_exp_tail_)
423 fit_function_->SetParameter(9, 1.5);
424 else
425 fit_function_->FixParameter(9, 1);
426
427 // Background parameters
428 fit_function_->SetParLimits(10, 0, peak_height * 0.999);
429 fit_function_->SetParameter(10, bkg_estimate);
430
431 if (!use_flat_background_)
432 fit_function_->SetParLimits(11, -1000, 1000);
433 else
434 fit_function_->FixParameter(11, 0);
435
436 std::cout << "Fit configuration:" << std::endl;
437 std::cout << std::endl;
438 if (use_flat_background_) {
439 std::cout << "Background: FLAT" << std::endl;
440 } else {
441 std::cout << "Background: LINEAR" << std::endl;
442 }
443 std::cout << "Step function: " << (use_step_ ? "ENABLED" : "DISABLED")
444 << std::endl;
445 std::cout << "Low exponential tail: "
446 << (use_low_exp_tail_ ? "ENABLED" : "DISABLED") << std::endl;
447 std::cout << "Low linear tail: "
448 << (use_low_lin_tail_ ? "ENABLED" : "DISABLED") << std::endl;
449 std::cout << "High exponential tail: "
450 << (use_high_exp_tail_ ? "ENABLED" : "DISABLED") << std::endl;
451}
452
454 delete fit_function_;
455 fit_function_ = nullptr;
456 delete working_hist_;
457 working_hist_ = nullptr;
458}
459
460void FittingUtils::SortPeaksByMu(Int_t num_peaks) {
461 // Bubble sort peak blocks (10 params each) by ascending mu
462 for (Int_t i = 0; i < num_peaks - 1; i++) {
463 for (Int_t j = 0; j < num_peaks - i - 1; j++) {
464 Double_t mu_j = fit_function_->GetParameter(j * 10);
465 Double_t mu_next = fit_function_->GetParameter((j + 1) * 10);
466 if (mu_j > mu_next) {
467 std::cout << "Sorting peaks: swapping peak " << j + 1 << " (mu=" << mu_j
468 << ") and peak " << j + 2 << " (mu=" << mu_next << ")"
469 << std::endl;
470 for (Int_t k = 0; k < 10; k++) {
471 Int_t idx_a = j * 10 + k;
472 Int_t idx_b = (j + 1) * 10 + k;
473
474 Double_t tmp_val = fit_function_->GetParameter(idx_a);
475 Double_t tmp_err = fit_function_->GetParError(idx_a);
476 Double_t lo_a, hi_a, lo_b, hi_b;
477 fit_function_->GetParLimits(idx_a, lo_a, hi_a);
478 fit_function_->GetParLimits(idx_b, lo_b, hi_b);
479
480 fit_function_->SetParameter(idx_a,
481 fit_function_->GetParameter(idx_b));
482 fit_function_->SetParError(idx_a, fit_function_->GetParError(idx_b));
483 fit_function_->SetParLimits(idx_a, lo_b, hi_b);
484
485 fit_function_->SetParameter(idx_b, tmp_val);
486 fit_function_->SetParError(idx_b, tmp_err);
487 fit_function_->SetParLimits(idx_b, lo_a, hi_a);
488 }
489 }
490 }
491 }
492}
493
494void FittingUtils::SetManualParameters(const std::vector<Double_t> &params) {
495 if (params.size() != (size_t)fit_function_->GetNpar()) {
496 std::cerr << "ERROR: Manual parameters size (" << params.size()
497 << ") doesn't match number of fit parameters ("
498 << fit_function_->GetNpar() << ")" << std::endl;
499 return;
500 }
501
502 manual_params_ = params;
503 use_manual_init_ = kTRUE;
504
505 // Apply the parameters immediately
506 for (size_t i = 0; i < params.size(); i++) {
507 fit_function_->FixParameter(i, params[i]);
508 }
509
510 std::cout << "Manual parameters set:" << std::endl;
511 for (size_t i = 0; i < params.size(); i++) {
512 std::cout << " Par[" << i << "] " << fit_function_->GetParName(i) << " = "
513 << params[i] << std::endl;
514 }
515}
516
517void FittingUtils::SetManualParameter(Int_t index, Double_t value) {
518 if (index < 0 || index >= fit_function_->GetNpar()) {
519 std::cerr << "ERROR: Parameter index " << index << " out of range [0, "
520 << fit_function_->GetNpar() - 1 << "]" << std::endl;
521 return;
522 }
523
524 if (!use_manual_init_) {
525 manual_params_.resize(fit_function_->GetNpar(), 0.0);
526 use_manual_init_ = kTRUE;
527 }
528
529 manual_params_[index] = value;
530 fit_function_->FixParameter(index, value);
531
532 std::cout << "Set Par[" << index << "] " << fit_function_->GetParName(index)
533 << " = " << value << std::endl;
534}
535
536void FittingUtils::AppendPeakGraphs(std::vector<TGraph *> &components,
537 Int_t param_offset, Style_t line_style,
538 TF1 *background, Int_t npts,
539 Double_t x_step) {
540 Double_t ga = fit_function_->GetParameter(param_offset + 2);
541 Width_t line_width = PlottingUtils::GetLineWidth();
542
543 TF1 *peak =
545 fit_range_low_, fit_range_high_, 3);
546 peak->SetParameter(0, fit_function_->GetParameter(param_offset + 0));
547 peak->SetParameter(1, fit_function_->GetParameter(param_offset + 1));
548 peak->SetParameter(2, ga);
549 TGraph *peak_graph = new TGraph(npts);
550 for (Int_t i = 0; i < npts; i++) {
551 Double_t x = fit_range_low_ + i * x_step;
552 peak_graph->SetPoint(i, x, peak->Eval(x) + background->Eval(x));
553 }
554 peak_graph->SetLineColor(kBlack);
555 peak_graph->SetLineStyle(line_style);
556 peak_graph->SetLineWidth(line_width);
557 components.push_back(peak_graph);
558 delete peak;
559
561 fit_range_low_, fit_range_high_, 3);
562 step->SetParameter(0, fit_function_->GetParameter(param_offset + 0));
563 step->SetParameter(1, fit_function_->GetParameter(param_offset + 1));
564 step->SetParameter(2, fit_function_->GetParameter(param_offset + 3) * ga);
565 TGraph *step_graph = new TGraph(npts);
566 for (Int_t i = 0; i < npts; i++) {
567 Double_t x = fit_range_low_ + i * x_step;
568 step_graph->SetPoint(i, x, step->Eval(x) + background->Eval(x));
569 }
570 step_graph->SetLineColor(kGray);
571 step_graph->SetLineStyle(line_style);
572 step_graph->SetLineWidth(line_width);
573 components.push_back(step_graph);
574 delete step;
575
576 TF1 *low_tail =
578 fit_range_low_, fit_range_high_, 6);
579 low_tail->SetParameter(0, fit_function_->GetParameter(param_offset + 0));
580 low_tail->SetParameter(1, fit_function_->GetParameter(param_offset + 1));
581 low_tail->SetParameter(2, fit_function_->GetParameter(param_offset + 4) * ga);
582 low_tail->SetParameter(3, fit_function_->GetParameter(param_offset + 5));
583 low_tail->SetParameter(4, fit_function_->GetParameter(param_offset + 6) * ga);
584 low_tail->SetParameter(5, fit_function_->GetParameter(param_offset + 7));
585 TGraph *low_tail_graph = new TGraph(npts);
586 for (Int_t i = 0; i < npts; i++) {
587 Double_t x = fit_range_low_ + i * x_step;
588 low_tail_graph->SetPoint(i, x, low_tail->Eval(x) + background->Eval(x));
589 }
590 low_tail_graph->SetLineColor(kRed);
591 low_tail_graph->SetLineStyle(line_style);
592 low_tail_graph->SetLineWidth(line_width);
593 components.push_back(low_tail_graph);
594 delete low_tail;
595
596 TF1 *high_tail =
598 fit_range_low_, fit_range_high_, 4);
599 high_tail->SetParameter(0, fit_function_->GetParameter(param_offset + 0));
600 high_tail->SetParameter(1, fit_function_->GetParameter(param_offset + 1));
601 high_tail->SetParameter(2,
602 fit_function_->GetParameter(param_offset + 8) * ga);
603 high_tail->SetParameter(3, fit_function_->GetParameter(param_offset + 9));
604 TGraph *high_tail_graph = new TGraph(npts);
605 for (Int_t i = 0; i < npts; i++) {
606 Double_t x = fit_range_low_ + i * x_step;
607 high_tail_graph->SetPoint(i, x, high_tail->Eval(x) + background->Eval(x));
608 }
609 high_tail_graph->SetLineColor(kOrange);
610 high_tail_graph->SetLineStyle(line_style);
611 high_tail_graph->SetLineWidth(line_width);
612 components.push_back(high_tail_graph);
613 delete high_tail;
614}
615
616void FittingUtils::PlotFitSinglePeak(const TString input_name,
617 const TString peak_name,
618 const TString label) {
619 Int_t npts = 1000;
620 Double_t x_step = (fit_range_high_ - fit_range_low_) / (npts - 1);
621 Width_t line_width = PlottingUtils::GetLineWidth();
622
623 TGraph *total_graph = new TGraph(npts);
624 for (Int_t i = 0; i < npts; i++) {
625 Double_t x = fit_range_low_ + i * x_step;
626 total_graph->SetPoint(i, x, fit_function_->Eval(x));
627 }
628 total_graph->SetLineColor(kAzure);
629 total_graph->SetLineWidth(line_width);
630
631 TF1 *background = new TF1("background", FittingFunctions::LinearBackground,
632 fit_range_low_, fit_range_high_, 2);
633 background->SetParameter(0, fit_function_->GetParameter(10));
634 background->SetParameter(1, fit_function_->GetParameter(11));
635
636 TGraph *background_graph = new TGraph(npts);
637 for (Int_t i = 0; i < npts; i++) {
638 Double_t x = fit_range_low_ + i * x_step;
639 background_graph->SetPoint(i, x, background->Eval(x));
640 }
641 background_graph->SetLineColor(kGreen);
642 background_graph->SetLineWidth(line_width);
643
644 std::vector<TGraph *> components;
645 components.push_back(background_graph);
646
647 Double_t ga = fit_function_->GetParameter(2);
648
649 TF1 *peak = new TF1("gaussian", FittingFunctions::Gaussian, fit_range_low_,
650 fit_range_high_, 3);
651 peak->SetParameter(0, fit_function_->GetParameter(0));
652 peak->SetParameter(1, fit_function_->GetParameter(1));
653 peak->SetParameter(2, fit_function_->GetParameter(2));
654 TGraph *peak_graph = new TGraph(npts);
655 for (Int_t i = 0; i < npts; i++) {
656 Double_t x = fit_range_low_ + i * x_step;
657 peak_graph->SetPoint(i, x, peak->Eval(x) + background->Eval(x));
658 }
659 peak_graph->SetLineColor(kBlack);
660 peak_graph->SetLineWidth(line_width);
661 components.push_back(peak_graph);
662 delete peak;
663
664 if (TMath::Abs(fit_function_->GetParameter(3)) > 1e-6) {
665 TF1 *step = new TF1("step", FittingFunctions::Step, fit_range_low_,
666 fit_range_high_, 3);
667 step->SetParameter(0, fit_function_->GetParameter(0));
668 step->SetParameter(1, fit_function_->GetParameter(1));
669 step->SetParameter(2, fit_function_->GetParameter(3) * ga);
670 TGraph *step_graph = new TGraph(npts);
671 for (Int_t i = 0; i < npts; i++) {
672 Double_t x = fit_range_low_ + i * x_step;
673 step_graph->SetPoint(i, x, step->Eval(x) + background->Eval(x));
674 }
675 step_graph->SetLineColor(kGray);
676 step_graph->SetLineWidth(line_width);
677 components.push_back(step_graph);
678 delete step;
679 }
680
681 if (TMath::Abs(fit_function_->GetParameter(4)) > 1e-6 ||
682 TMath::Abs(fit_function_->GetParameter(6)) > 1e-6) {
683 TF1 *low_tail = new TF1("lowtail", FittingFunctions::LowTail,
684 fit_range_low_, fit_range_high_, 6);
685 low_tail->SetParameter(0, fit_function_->GetParameter(0));
686 low_tail->SetParameter(1, fit_function_->GetParameter(1));
687 low_tail->SetParameter(2, fit_function_->GetParameter(4) * ga);
688 low_tail->SetParameter(3, fit_function_->GetParameter(5));
689 low_tail->SetParameter(4, fit_function_->GetParameter(6) * ga);
690 low_tail->SetParameter(5, fit_function_->GetParameter(7));
691 TGraph *low_tail_graph = new TGraph(npts);
692 for (Int_t i = 0; i < npts; i++) {
693 Double_t x = fit_range_low_ + i * x_step;
694 low_tail_graph->SetPoint(i, x, low_tail->Eval(x) + background->Eval(x));
695 }
696 low_tail_graph->SetLineColor(kRed);
697 low_tail_graph->SetLineWidth(line_width);
698 components.push_back(low_tail_graph);
699 delete low_tail;
700 }
701
702 if (TMath::Abs(fit_function_->GetParameter(8)) > 1e-6) {
703 TF1 *high_tail = new TF1("hightail", FittingFunctions::HighTail,
704 fit_range_low_, fit_range_high_, 4);
705 high_tail->SetParameter(0, fit_function_->GetParameter(0));
706 high_tail->SetParameter(1, fit_function_->GetParameter(1));
707 high_tail->SetParameter(2, fit_function_->GetParameter(8) * ga);
708 high_tail->SetParameter(3, fit_function_->GetParameter(9));
709 TGraph *high_tail_graph = new TGraph(npts);
710 for (Int_t i = 0; i < npts; i++) {
711 Double_t x = fit_range_low_ + i * x_step;
712 high_tail_graph->SetPoint(i, x, high_tail->Eval(x) + background->Eval(x));
713 }
714 high_tail_graph->SetLineColor(kOrange);
715 high_tail_graph->SetLineWidth(line_width);
716 components.push_back(high_tail_graph);
717 delete high_tail;
718 }
719
720 delete background;
721
723 working_hist_, total_graph, components, fit_range_low_, fit_range_high_,
724 peak_name + "_" + input_name, "fits", label, kTRUE);
725
726 delete total_graph;
727 for (Int_t i = 0; i < (Int_t)components.size(); i++) {
728 delete components[i];
729 }
730}
731
732void FittingUtils::PlotFitDoublePeak(const TString input_name,
733 const TString peak_name,
734 const TString label) {
735 Int_t npts = 1000;
736 Double_t x_step = (fit_range_high_ - fit_range_low_) / (npts - 1);
737 Width_t line_width = PlottingUtils::GetLineWidth();
738
739 TGraph *total_graph = new TGraph(npts);
740 for (Int_t i = 0; i < npts; i++) {
741 Double_t x = fit_range_low_ + i * x_step;
742 total_graph->SetPoint(i, x, fit_function_->Eval(x));
743 }
744 total_graph->SetLineColor(kAzure);
745 total_graph->SetLineWidth(line_width);
746
747 TF1 *background = new TF1("background", FittingFunctions::LinearBackground,
748 fit_range_low_, fit_range_high_, 2);
749 background->SetParameter(0, fit_function_->GetParameter(20));
750 background->SetParameter(1, fit_function_->GetParameter(21));
751
752 TGraph *background_graph = new TGraph(npts);
753 for (Int_t i = 0; i < npts; i++) {
754 Double_t x = fit_range_low_ + i * x_step;
755 background_graph->SetPoint(i, x, background->Eval(x));
756 }
757 background_graph->SetLineColor(kGreen);
758 background_graph->SetLineWidth(line_width);
759
760 std::vector<TGraph *> components;
761 components.push_back(background_graph);
762
763 AppendPeakGraphs(components, 0, 1, background, npts, x_step);
764 AppendPeakGraphs(components, 10, 3, background, npts, x_step);
765
766 delete background;
767
769 working_hist_, total_graph, components, fit_range_low_, fit_range_high_,
770 peak_name + "_" + input_name, "fits", label, kTRUE);
771
772 delete total_graph;
773 for (Int_t i = 0; i < (Int_t)components.size(); i++) {
774 delete components[i];
775 }
776}
777
778void FittingUtils::PlotFitTriplePeak(const TString input_name,
779 const TString peak_name,
780 const TString label) {
781 Int_t npts = 1000;
782 Double_t x_step = (fit_range_high_ - fit_range_low_) / (npts - 1);
783 Width_t line_width = PlottingUtils::GetLineWidth();
784
785 TGraph *total_graph = new TGraph(npts);
786 for (Int_t i = 0; i < npts; i++) {
787 Double_t x = fit_range_low_ + i * x_step;
788 total_graph->SetPoint(i, x, fit_function_->Eval(x));
789 }
790 total_graph->SetLineColor(kAzure);
791 total_graph->SetLineWidth(line_width);
792
793 TF1 *background = new TF1("background", FittingFunctions::LinearBackground,
794 fit_range_low_, fit_range_high_, 2);
795 background->SetParameter(0, fit_function_->GetParameter(30));
796 background->SetParameter(1, fit_function_->GetParameter(31));
797
798 TGraph *background_graph = new TGraph(npts);
799 for (Int_t i = 0; i < npts; i++) {
800 Double_t x = fit_range_low_ + i * x_step;
801 background_graph->SetPoint(i, x, background->Eval(x));
802 }
803 background_graph->SetLineColor(kGreen);
804 background_graph->SetLineWidth(line_width);
805
806 std::vector<TGraph *> components;
807 components.push_back(background_graph);
808
809 AppendPeakGraphs(components, 0, 1, background, npts, x_step);
810 AppendPeakGraphs(components, 10, 3, background, npts, x_step);
811 AppendPeakGraphs(components, 20, 4, background, npts, x_step);
812
813 delete background;
814
816 working_hist_, total_graph, components, fit_range_low_, fit_range_high_,
817 peak_name + "_" + input_name, "fits", label, kTRUE);
818
819 delete total_graph;
820 for (Int_t i = 0; i < (Int_t)components.size(); i++) {
821 delete components[i];
822 }
823}
824
825Double_t FittingUtils::EstimateBackground() {
826 Int_t left_bin = working_hist_->FindBin(fit_range_low_);
827 Int_t right_bin = working_hist_->FindBin(fit_range_high_);
828
829 Int_t n_sideband = (right_bin - left_bin) / 10;
830 Double_t left_avg = 0, right_avg = 0;
831
832 for (Int_t i = 0; i < n_sideband; i++) {
833 left_avg += working_hist_->GetBinContent(left_bin + i);
834 right_avg += working_hist_->GetBinContent(right_bin - i);
835 }
836
837 return (left_avg + right_avg) / (2.0 * n_sideband);
838}
839
840Double_t FittingUtils::ClampToBounds(Int_t param_index, Double_t value) {
841 Double_t low, high;
842 fit_function_->GetParLimits(param_index, low, high);
843
844 if (low < high) {
845 return TMath::Max(low, TMath::Min(value, high));
846 }
847 return value;
848}
849
850FitResult FittingUtils::FitSinglePeak(const TString input_name,
851 const TString peak_name) {
852 FitResult results;
853 results.peaks.emplace_back(); // 1 peak, default -1
854
855 Bool_t fit_valid = kFALSE;
856 Double_t final_chi2 = 0;
857
858 if (interactive_) {
859 if (LoadInteractiveParams(input_name, peak_name)) {
860 TFitResultPtr refit = working_hist_->Fit(fit_function_, "LSMRBENR+");
861 if (refit.Get() && refit->IsValid())
862 final_chi2 = refit->Chi2() / refit->Ndf();
863 else if (fit_function_->GetNDF() > 0)
864 final_chi2 = fit_function_->GetChisquare() / fit_function_->GetNDF();
865 std::cout << "Refit from saved params chi2/ndf = " << final_chi2
866 << std::endl;
867 fit_valid = kTRUE;
868 } else {
869 Bool_t was_batch = gROOT->IsBatch();
870 gROOT->SetBatch(kFALSE);
871 if (LaunchInteractiveFitEditor(working_hist_, fit_function_,
872 fit_range_low_, fit_range_high_, 1,
873 peak_name + " / " + input_name)) {
874 Double_t rlo_tmp, rhi_tmp;
875 fit_function_->GetRange(rlo_tmp, rhi_tmp);
876 fit_range_low_ = rlo_tmp;
877 fit_range_high_ = rhi_tmp;
878 final_chi2 = fit_function_->GetChisquare() / fit_function_->GetNDF();
879 std::cout << "Interactive chi2/ndf = " << final_chi2 << std::endl;
880 SaveInteractiveParams(input_name, peak_name);
881 fit_valid = kTRUE;
882 }
883 gROOT->SetBatch(was_batch);
884 }
885 } else {
886 // Automated fitting: initial fit, component testing, final fit
887
888 // Fix all optional components to disabled state for baseline fit
889 fit_function_->FixParameter(3, 0); // StepAmplitude
890 fit_function_->FixParameter(4, 0); // LowExpTailAmplitude
891 fit_function_->FixParameter(5, 1); // LowExpTailRatio
892 fit_function_->FixParameter(6, 0); // LowLinTailAmplitude
893 fit_function_->FixParameter(7, 0); // LowLinTailSlope
894 fit_function_->FixParameter(8, 0); // HighExpTailAmplitude
895 fit_function_->FixParameter(9, 1); // HighExpTailRatio
896
897 if (use_flat_background_) {
898 fit_function_->FixParameter(11, 0);
899 }
900
901 if (use_manual_init_) {
902 std::cout << "Using manually initialized parameters" << std::endl;
903 for (size_t i = 0; i < manual_params_.size(); i++) {
904 fit_function_->SetParameter(i, manual_params_[i]);
905 }
906 std::cout << "Skipping auto-initialization, using provided values"
907 << std::endl;
908 } else {
909 if (!use_flat_background_) {
910 TF1 *bkg_only = new TF1("bkg_temp", FittingFunctions::LinearBackground,
911 fit_range_low_, fit_range_high_, 2);
912 Double_t exclude_low =
913 fit_function_->GetParameter(0) - 3 * fit_function_->GetParameter(1);
914 working_hist_->Fit(bkg_only, "QN0R", "", fit_range_low_, exclude_low);
915 fit_function_->SetParameter(
916 10, ClampToBounds(10, bkg_only->GetParameter(0)));
917 fit_function_->SetParameter(
918 11, ClampToBounds(11, bkg_only->GetParameter(1)));
919 delete bkg_only;
920 }
921 }
922
923 // Initial fit with just Gaussian + Background
924 TFitResultPtr initial_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
925
926 if (!initial_fit.Get() || !initial_fit->IsValid()) {
927 std::cout << "ERROR: Initial fit failed" << std::endl;
928 return results;
929 }
930
931 Double_t best_chi2 = initial_fit->Chi2() / initial_fit->Ndf();
932 std::cout << "Initial chi2/ndf = " << best_chi2 << std::endl;
933
934 Double_t gaus_amp = TMath::Abs(fit_function_->GetParameter(2));
935 Double_t peak_height =
936 working_hist_->GetBinContent(working_hist_->GetMaximumBin());
937 Double_t range_width = fit_range_high_ - fit_range_low_;
938 Double_t bkg_estimate = EstimateBackground();
939
940 std::cout << "Background mode: "
941 << (use_flat_background_ ? "FLAT" : "LINEAR") << std::endl;
942
943 Int_t npar = fit_function_->GetNpar();
944 std::vector<Double_t> best_params(npar);
945 std::vector<Double_t> best_errors(npar);
946 for (Int_t i = 0; i < npar; i++) {
947 best_params[i] = fit_function_->GetParameter(i);
948 best_errors[i] = fit_function_->GetParError(i);
949 }
950
951 // Low-side group testing (step + low tails together)
952 Bool_t any_low_side = use_step_ || use_low_exp_tail_ || use_low_lin_tail_;
953
954 if (any_low_side) {
955 std::cout << "Testing low-side component group..." << std::endl;
956
957 if (use_step_) {
958 fit_function_->ReleaseParameter(3);
959 fit_function_->SetParLimits(3, 0, 0.5);
960 if (!use_manual_init_)
961 fit_function_->SetParameter(3, 0.15);
962 }
963 if (use_low_exp_tail_) {
964 fit_function_->ReleaseParameter(4);
965 fit_function_->ReleaseParameter(5);
966 fit_function_->SetParLimits(4, 0, 0.5);
967 fit_function_->SetParLimits(5, 1.0, tail_ratio_max_);
968 if (!use_manual_init_) {
969 fit_function_->SetParameter(4, 0.15);
970 fit_function_->SetParameter(5, 1.5);
971 }
972 }
973 if (use_low_lin_tail_) {
974 fit_function_->ReleaseParameter(6);
975 fit_function_->ReleaseParameter(7);
976 fit_function_->SetParLimits(6, 0, 0.5);
977 fit_function_->SetParLimits(7, -0.1, 0.1);
978 if (!use_manual_init_) {
979 fit_function_->SetParameter(6, 0.15);
980 fit_function_->SetParameter(7, 0);
981 }
982 }
983
984 TFitResultPtr group_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
985
986 if (group_fit.Get() && group_fit->IsValid()) {
987 Double_t chi2_group = group_fit->Chi2() / group_fit->Ndf();
988 std::cout << "Low-side group chi2/ndf: " << chi2_group << " vs "
989 << best_chi2 << std::endl;
990
991 if (chi2_group < best_chi2) {
992 std::cout << "Low-side group ACCEPTED, pruning..." << std::endl;
993 best_chi2 = chi2_group;
994 for (Int_t i = 0; i < npar; i++) {
995 best_params[i] = fit_function_->GetParameter(i);
996 best_errors[i] = fit_function_->GetParError(i);
997 }
998
999 // Prune step
1000 if (use_step_) {
1001 fit_function_->FixParameter(3, 0);
1002 TFitResultPtr prune_fit =
1003 working_hist_->Fit(fit_function_, "LSMBNQ0R");
1004 if (prune_fit.Get() && prune_fit->IsValid() &&
1005 prune_fit->Chi2() / prune_fit->Ndf() <= best_chi2) {
1006 std::cout << " Step pruned (not needed)" << std::endl;
1007 best_chi2 = prune_fit->Chi2() / prune_fit->Ndf();
1008 for (Int_t i = 0; i < npar; i++) {
1009 best_params[i] = fit_function_->GetParameter(i);
1010 best_errors[i] = fit_function_->GetParError(i);
1011 }
1012 } else {
1013 std::cout << " Step retained" << std::endl;
1014 fit_function_->ReleaseParameter(3);
1015 fit_function_->SetParLimits(3, 0, 0.5);
1016 for (Int_t i = 0; i < npar; i++) {
1017 fit_function_->SetParameter(i, best_params[i]);
1018 fit_function_->SetParError(i, best_errors[i]);
1019 }
1020 }
1021 }
1022
1023 // Prune low exp tail
1024 if (use_low_exp_tail_) {
1025 fit_function_->FixParameter(4, 0);
1026 fit_function_->FixParameter(5, 1);
1027 TFitResultPtr prune_fit =
1028 working_hist_->Fit(fit_function_, "LSMBNQ0R");
1029 if (prune_fit.Get() && prune_fit->IsValid() &&
1030 prune_fit->Chi2() / prune_fit->Ndf() <= best_chi2) {
1031 std::cout << " Low exp tail pruned (not needed)" << std::endl;
1032 best_chi2 = prune_fit->Chi2() / prune_fit->Ndf();
1033 for (Int_t i = 0; i < npar; i++) {
1034 best_params[i] = fit_function_->GetParameter(i);
1035 best_errors[i] = fit_function_->GetParError(i);
1036 }
1037 } else {
1038 std::cout << " Low exp tail retained" << std::endl;
1039 fit_function_->ReleaseParameter(4);
1040 fit_function_->ReleaseParameter(5);
1041 fit_function_->SetParLimits(4, 0, 0.5);
1042 fit_function_->SetParLimits(5, 1.0, tail_ratio_max_);
1043 for (Int_t i = 0; i < npar; i++) {
1044 fit_function_->SetParameter(i, best_params[i]);
1045 fit_function_->SetParError(i, best_errors[i]);
1046 }
1047 }
1048 }
1049
1050 // Prune low lin tail
1051 if (use_low_lin_tail_) {
1052 fit_function_->FixParameter(6, 0);
1053 fit_function_->FixParameter(7, 0);
1054 TFitResultPtr prune_fit =
1055 working_hist_->Fit(fit_function_, "LSMBNQ0R");
1056 if (prune_fit.Get() && prune_fit->IsValid() &&
1057 prune_fit->Chi2() / prune_fit->Ndf() <= best_chi2) {
1058 std::cout << " Low lin tail pruned (not needed)" << std::endl;
1059 best_chi2 = prune_fit->Chi2() / prune_fit->Ndf();
1060 for (Int_t i = 0; i < npar; i++) {
1061 best_params[i] = fit_function_->GetParameter(i);
1062 best_errors[i] = fit_function_->GetParError(i);
1063 }
1064 } else {
1065 std::cout << " Low lin tail retained" << std::endl;
1066 fit_function_->ReleaseParameter(6);
1067 fit_function_->ReleaseParameter(7);
1068 fit_function_->SetParLimits(6, 0, 0.5);
1069 fit_function_->SetParLimits(7, -0.1, 0.1);
1070 for (Int_t i = 0; i < npar; i++) {
1071 fit_function_->SetParameter(i, best_params[i]);
1072 fit_function_->SetParError(i, best_errors[i]);
1073 }
1074 }
1075 }
1076 } else {
1077 std::cout << "Low-side group REJECTED" << std::endl;
1078 if (use_step_)
1079 fit_function_->FixParameter(3, 0);
1080 if (use_low_exp_tail_) {
1081 fit_function_->FixParameter(4, 0);
1082 fit_function_->FixParameter(5, 1);
1083 }
1084 if (use_low_lin_tail_) {
1085 fit_function_->FixParameter(6, 0);
1086 fit_function_->FixParameter(7, 0);
1087 }
1088 for (Int_t i = 0; i < npar; i++) {
1089 fit_function_->SetParameter(i, best_params[i]);
1090 fit_function_->SetParError(i, best_errors[i]);
1091 }
1092 }
1093 } else {
1094 std::cout << "Low-side group fit FAILED" << std::endl;
1095 if (use_step_)
1096 fit_function_->FixParameter(3, 0);
1097 if (use_low_exp_tail_) {
1098 fit_function_->FixParameter(4, 0);
1099 fit_function_->FixParameter(5, 1);
1100 }
1101 if (use_low_lin_tail_) {
1102 fit_function_->FixParameter(6, 0);
1103 fit_function_->FixParameter(7, 0);
1104 }
1105 for (Int_t i = 0; i < npar; i++) {
1106 fit_function_->SetParameter(i, best_params[i]);
1107 fit_function_->SetParError(i, best_errors[i]);
1108 }
1109 }
1110 }
1111
1112 // High tail testing (independent)
1113 if (use_high_exp_tail_) {
1114 std::cout << "Testing high exponential tail..." << std::endl;
1115
1116 fit_function_->ReleaseParameter(8);
1117 fit_function_->ReleaseParameter(9);
1118 fit_function_->SetParLimits(8, 0, 0.5);
1119 fit_function_->SetParLimits(9, 1.0, tail_ratio_max_);
1120
1121 if (!use_manual_init_) {
1122 fit_function_->SetParameter(8, 0.15);
1123 fit_function_->SetParameter(9, 1.5);
1124 }
1125
1126 TFitResultPtr htail_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1127
1128 if (htail_fit.Get() && htail_fit->IsValid()) {
1129 Double_t chi2_with_htail = htail_fit->Chi2() / htail_fit->Ndf();
1130 std::cout << "Chi2/ndf: " << chi2_with_htail << " vs " << best_chi2
1131 << std::endl;
1132
1133 if (chi2_with_htail < best_chi2) {
1134 std::cout << "High exp tail ACCEPTED" << std::endl;
1135 best_chi2 = chi2_with_htail;
1136 for (Int_t i = 0; i < npar; i++) {
1137 best_params[i] = fit_function_->GetParameter(i);
1138 best_errors[i] = fit_function_->GetParError(i);
1139 }
1140 } else {
1141 std::cout << "High exp tail REJECTED" << std::endl;
1142 fit_function_->FixParameter(8, 0);
1143 fit_function_->FixParameter(9, 1);
1144 for (Int_t i = 0; i < npar; i++) {
1145 fit_function_->SetParameter(i, best_params[i]);
1146 fit_function_->SetParError(i, best_errors[i]);
1147 }
1148 }
1149 } else {
1150 std::cout << "High exp tail fit FAILED" << std::endl;
1151 fit_function_->FixParameter(8, 0);
1152 fit_function_->FixParameter(9, 1);
1153 for (Int_t i = 0; i < npar; i++) {
1154 fit_function_->SetParameter(i, best_params[i]);
1155 fit_function_->SetParError(i, best_errors[i]);
1156 }
1157 }
1158 }
1159
1160 // Final fit
1161 std::cout << "Final fit with selected components..." << std::endl;
1162 for (Int_t i = 0; i < npar; i++) {
1163 fit_function_->SetParameter(i, best_params[i]);
1164 fit_function_->SetParError(i, best_errors[i]);
1165 }
1166
1167 if (use_flat_background_) {
1168 fit_function_->FixParameter(11, 0);
1169 }
1170
1171 TFitResultPtr final_fit = working_hist_->Fit(fit_function_, "LSMRBENR+");
1172
1173 if (final_fit.Get() && final_fit->IsValid()) {
1174 final_chi2 = final_fit->Chi2() / final_fit->Ndf();
1175 fit_valid = kTRUE;
1176 std::cout << "Final chi2/ndf = " << final_chi2 << std::endl;
1177 }
1178 }
1179
1180 if (fit_valid) {
1181 TString chi2label = Form("#chi^{2}/ndf = %.3f", final_chi2);
1182 PlotFitSinglePeak(input_name, peak_name, chi2label);
1183
1184 PeakFitResult peak;
1185 peak.mu = fit_function_->GetParameter(0);
1186 peak.mu_error = fit_function_->GetParError(0);
1187 peak.sigma = fit_function_->GetParameter(1);
1188 peak.sigma_error = fit_function_->GetParError(1);
1189 peak.gaus_amplitude = fit_function_->GetParameter(2);
1190 peak.gaus_amplitude_error = fit_function_->GetParError(2);
1191 // Convert ratios back to absolute amplitudes
1192 Double_t ga = peak.gaus_amplitude;
1193 peak.step_amplitude = fit_function_->GetParameter(3) * ga;
1194 peak.step_amplitude_error = fit_function_->GetParError(3) * ga;
1195 peak.low_exp_tail_amplitude = fit_function_->GetParameter(4) * ga;
1196 peak.low_exp_tail_amplitude_error = fit_function_->GetParError(4) * ga;
1197 peak.low_exp_tail_ratio = fit_function_->GetParameter(5);
1198 peak.low_exp_tail_ratio_error = fit_function_->GetParError(5);
1199 peak.low_lin_tail_amplitude = fit_function_->GetParameter(6) * ga;
1200 peak.low_lin_tail_amplitude_error = fit_function_->GetParError(6) * ga;
1201 peak.low_lin_tail_slope = fit_function_->GetParameter(7);
1202 peak.low_lin_tail_slope_error = fit_function_->GetParError(7);
1203 peak.high_exp_tail_amplitude = fit_function_->GetParameter(8) * ga;
1204 peak.high_exp_tail_amplitude_error = fit_function_->GetParError(8) * ga;
1205 peak.high_exp_tail_ratio = fit_function_->GetParameter(9);
1206 peak.high_exp_tail_ratio_error = fit_function_->GetParError(9);
1207
1208 results.peaks[0] = peak;
1209 results.bkg_constant = fit_function_->GetParameter(10);
1210 results.bkg_constant_error = fit_function_->GetParError(10);
1211 results.lin_bkg_slope = fit_function_->GetParameter(11);
1212 results.lin_bkg_slope_error = fit_function_->GetParError(11);
1213 results.reduced_chi2 = final_chi2;
1214 results.valid = kTRUE;
1215 } else {
1216 std::cout << "ERROR: Fit did not converge" << std::endl;
1217 }
1218
1219 return results;
1220}
1221
1223 const TString peak_name,
1224 Double_t mu1_init, Double_t mu2_init) {
1225 FitResult results;
1226 results.peaks.emplace_back(); // peak 1, default -1
1227 results.peaks.emplace_back(); // peak 2, default -1
1228
1229 if (mu1_init > mu2_init) {
1230 std::cout << "WARNING: mu1_init > mu2_init, swapping initial values"
1231 << std::endl;
1232 Double_t temp = mu1_init;
1233 mu1_init = mu2_init;
1234 mu2_init = temp;
1235 }
1236
1237 delete fit_function_;
1238 fit_function_ = new TF1("DoublePeak", &FittingFunctions::DoublePeakFunction,
1239 fit_range_low_, fit_range_high_, 22);
1240
1241 // Peak 1 param names (offset 0)
1242 fit_function_->SetParName(0, "Mu1");
1243 fit_function_->SetParName(1, "Sigma1");
1244 fit_function_->SetParName(2, "GausAmplitude1");
1245 fit_function_->SetParName(3, "StepAmplitude1");
1246 fit_function_->SetParName(4, "LowExpTailAmplitude1");
1247 fit_function_->SetParName(5, "LowExpTailRatio1");
1248 fit_function_->SetParName(6, "LowLinTailAmplitude1");
1249 fit_function_->SetParName(7, "LowLinTailSlope1");
1250 fit_function_->SetParName(8, "HighExpTailAmplitude1");
1251 fit_function_->SetParName(9, "HighExpTailRatio1");
1252
1253 // Peak 2 param names (offset 10)
1254 fit_function_->SetParName(10, "Mu2");
1255 fit_function_->SetParName(11, "Sigma2");
1256 fit_function_->SetParName(12, "GausAmplitude2");
1257 fit_function_->SetParName(13, "StepAmplitude2");
1258 fit_function_->SetParName(14, "LowExpTailAmplitude2");
1259 fit_function_->SetParName(15, "LowExpTailRatio2");
1260 fit_function_->SetParName(16, "LowLinTailAmplitude2");
1261 fit_function_->SetParName(17, "LowLinTailSlope2");
1262 fit_function_->SetParName(18, "HighExpTailAmplitude2");
1263 fit_function_->SetParName(19, "HighExpTailRatio2");
1264
1265 // Background param names
1266 fit_function_->SetParName(20, "BkgConst");
1267 fit_function_->SetParName(21, "BkgSlope");
1268
1269 Double_t range_width = fit_range_high_ - fit_range_low_;
1270 Double_t sigma_init = range_width * 0.01;
1271 Double_t peak_height =
1272 working_hist_->GetBinContent(working_hist_->GetMaximumBin());
1273 Double_t bkg_estimate = EstimateBackground();
1274
1275 // Peak 1 and 2 limits (amplitude params are ratios)
1276 for (Int_t p = 0; p < 2; p++) {
1277 Int_t o = p * 10;
1278 fit_function_->SetParLimits(o + 0, fit_range_low_, fit_range_high_);
1279 fit_function_->SetParLimits(o + 1, range_width * 0.001, range_width * 0.5);
1280 fit_function_->SetParLimits(o + 2, 0, peak_height * 0.999);
1281 fit_function_->SetParLimits(o + 3, 0, 0.5);
1282 fit_function_->SetParLimits(o + 4, 0, 0.5);
1283 fit_function_->SetParLimits(o + 5, 1.0, tail_ratio_max_);
1284 fit_function_->SetParLimits(o + 6, 0, 0.5);
1285 fit_function_->SetParLimits(o + 7, -0.1, 0.1);
1286 fit_function_->SetParLimits(o + 8, 0, 0.5);
1287 fit_function_->SetParLimits(o + 9, 1.0, tail_ratio_max_);
1288 }
1289
1290 fit_function_->SetParameter(0, mu1_init);
1291 fit_function_->SetParameter(1, sigma_init);
1292 fit_function_->SetParameter(2, peak_height * 0.999);
1293
1294 fit_function_->SetParameter(10, mu2_init);
1295 fit_function_->SetParameter(11, sigma_init);
1296 fit_function_->SetParameter(12, peak_height * 0.999);
1297
1298 // Fix disabled optional components on both peaks
1299 for (Int_t p = 0; p < 2; p++) {
1300 Int_t o = p * 10;
1301 if (use_step_)
1302 fit_function_->SetParameter(o + 3, 0);
1303 else
1304 fit_function_->FixParameter(o + 3, 0);
1305
1306 if (use_low_exp_tail_) {
1307 fit_function_->SetParameter(o + 4, 0);
1308 fit_function_->SetParameter(o + 5, 1.5);
1309 } else {
1310 fit_function_->FixParameter(o + 4, 0);
1311 fit_function_->FixParameter(o + 5, 1);
1312 }
1313
1314 if (use_low_lin_tail_) {
1315 fit_function_->SetParameter(o + 6, 0);
1316 fit_function_->SetParameter(o + 7, 0);
1317 } else {
1318 fit_function_->FixParameter(o + 6, 0);
1319 fit_function_->FixParameter(o + 7, 0);
1320 }
1321
1322 if (use_high_exp_tail_) {
1323 fit_function_->SetParameter(o + 8, 0);
1324 fit_function_->SetParameter(o + 9, 1.5);
1325 } else {
1326 fit_function_->FixParameter(o + 8, 0);
1327 fit_function_->FixParameter(o + 9, 1);
1328 }
1329 }
1330
1331 // Background
1332 fit_function_->SetParLimits(20, 0, peak_height * 0.999);
1333 if (!use_flat_background_) {
1334 fit_function_->SetParLimits(21, -0.1 * bkg_estimate / range_width,
1335 0.1 * bkg_estimate / range_width);
1336 }
1337 fit_function_->SetParameter(20, bkg_estimate);
1338 fit_function_->SetParameter(21, 0);
1339 if (use_flat_background_) {
1340 fit_function_->FixParameter(21, 0);
1341 }
1342
1343 Bool_t fit_valid = kFALSE;
1344 Double_t final_chi2 = 0;
1345
1346 if (interactive_) {
1347 if (LoadInteractiveParams(input_name, peak_name)) {
1348 TFitResultPtr refit = working_hist_->Fit(fit_function_, "LSMRBENR+");
1349 if (refit.Get() && refit->IsValid())
1350 final_chi2 = refit->Chi2() / refit->Ndf();
1351 else if (fit_function_->GetNDF() > 0)
1352 final_chi2 = fit_function_->GetChisquare() / fit_function_->GetNDF();
1353 std::cout << "Refit from saved params chi2/ndf = " << final_chi2
1354 << std::endl;
1355 fit_valid = kTRUE;
1356 } else {
1357 Bool_t was_batch = gROOT->IsBatch();
1358 gROOT->SetBatch(kFALSE);
1359 if (LaunchInteractiveFitEditor(working_hist_, fit_function_,
1360 fit_range_low_, fit_range_high_, 2,
1361 peak_name + " / " + input_name)) {
1362 Double_t rlo_tmp, rhi_tmp;
1363 fit_function_->GetRange(rlo_tmp, rhi_tmp);
1364 fit_range_low_ = rlo_tmp;
1365 fit_range_high_ = rhi_tmp;
1366 final_chi2 = fit_function_->GetChisquare() / fit_function_->GetNDF();
1367 std::cout << "Interactive chi2/ndf = " << final_chi2 << std::endl;
1368 SaveInteractiveParams(input_name, peak_name);
1369 fit_valid = kTRUE;
1370 }
1371 gROOT->SetBatch(was_batch);
1372 }
1373 } else {
1374 // Fix all optional components for baseline fit
1375 fit_function_->FixParameter(3, 0);
1376 fit_function_->FixParameter(4, 0);
1377 fit_function_->FixParameter(5, 1);
1378 fit_function_->FixParameter(6, 0);
1379 fit_function_->FixParameter(7, 0);
1380 fit_function_->FixParameter(8, 0);
1381 fit_function_->FixParameter(9, 1);
1382 fit_function_->FixParameter(13, 0);
1383 fit_function_->FixParameter(14, 0);
1384 fit_function_->FixParameter(15, 1);
1385 fit_function_->FixParameter(16, 0);
1386 fit_function_->FixParameter(17, 0);
1387 fit_function_->FixParameter(18, 0);
1388 fit_function_->FixParameter(19, 1);
1389
1390 // Initial fit
1391 TFitResultPtr initial_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1392
1393 if (!initial_fit.Get() || !initial_fit->IsValid()) {
1394 std::cout << "ERROR: Initial double peak fit failed" << std::endl;
1395 return results;
1396 }
1397
1398 Double_t gaus_amp1 = TMath::Abs(fit_function_->GetParameter(2));
1399 Double_t gaus_amp2 = TMath::Abs(fit_function_->GetParameter(12));
1400
1401 Int_t npar = fit_function_->GetNpar();
1402 std::vector<Double_t> best_params(npar);
1403 std::vector<Double_t> best_errors(npar);
1404 for (Int_t i = 0; i < npar; i++) {
1405 best_params[i] = fit_function_->GetParameter(i);
1406 best_errors[i] = fit_function_->GetParError(i);
1407 }
1408
1409 Double_t best_chi2 = initial_fit->Chi2() / initial_fit->Ndf();
1410 std::cout << "Initial chi2/ndf = " << best_chi2 << std::endl;
1411
1412 // Low-side group for peak 1 (offset 0)
1413 {
1414 std::cout << "Testing low-side group for peak1..." << std::endl;
1415
1416 fit_function_->ReleaseParameter(3);
1417 fit_function_->SetParLimits(3, 0, 0.5);
1418 fit_function_->SetParameter(3, 0.15);
1419
1420 fit_function_->ReleaseParameter(4);
1421 fit_function_->ReleaseParameter(5);
1422 fit_function_->SetParLimits(4, 0, 0.5);
1423 fit_function_->SetParLimits(5, 1.0, tail_ratio_max_);
1424 fit_function_->SetParameter(4, 0.15);
1425 fit_function_->SetParameter(5, 1.5);
1426
1427 fit_function_->ReleaseParameter(6);
1428 fit_function_->ReleaseParameter(7);
1429 fit_function_->SetParLimits(6, 0, 0.5);
1430 fit_function_->SetParLimits(7, -0.1, 0.1);
1431 fit_function_->SetParameter(6, 0.15);
1432 fit_function_->SetParameter(7, 0);
1433
1434 TFitResultPtr group_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1435
1436 if (group_fit.Get() && group_fit->IsValid() &&
1437 group_fit->Chi2() / group_fit->Ndf() < best_chi2) {
1438 std::cout << "Low-side group peak1 ACCEPTED, pruning..." << std::endl;
1439 best_chi2 = group_fit->Chi2() / group_fit->Ndf();
1440 for (Int_t i = 0; i < npar; i++) {
1441 best_params[i] = fit_function_->GetParameter(i);
1442 best_errors[i] = fit_function_->GetParError(i);
1443 }
1444
1445 // Prune step1
1446 fit_function_->FixParameter(3, 0);
1447 TFitResultPtr p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1448 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
1449 std::cout << " Step1 pruned" << std::endl;
1450 best_chi2 = p->Chi2() / p->Ndf();
1451 for (Int_t i = 0; i < npar; i++) {
1452 best_params[i] = fit_function_->GetParameter(i);
1453 best_errors[i] = fit_function_->GetParError(i);
1454 }
1455 } else {
1456 fit_function_->ReleaseParameter(3);
1457 fit_function_->SetParLimits(3, 0, 0.5);
1458 for (Int_t i = 0; i < npar; i++) {
1459 fit_function_->SetParameter(i, best_params[i]);
1460 fit_function_->SetParError(i, best_errors[i]);
1461 }
1462 }
1463
1464 // Prune low exp tail1
1465 fit_function_->FixParameter(4, 0);
1466 fit_function_->FixParameter(5, 1);
1467 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1468 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
1469 std::cout << " LowExpTail1 pruned" << std::endl;
1470 best_chi2 = p->Chi2() / p->Ndf();
1471 for (Int_t i = 0; i < npar; i++) {
1472 best_params[i] = fit_function_->GetParameter(i);
1473 best_errors[i] = fit_function_->GetParError(i);
1474 }
1475 } else {
1476 fit_function_->ReleaseParameter(4);
1477 fit_function_->ReleaseParameter(5);
1478 fit_function_->SetParLimits(4, 0, 0.5);
1479 fit_function_->SetParLimits(5, 1.0, tail_ratio_max_);
1480 for (Int_t i = 0; i < npar; i++) {
1481 fit_function_->SetParameter(i, best_params[i]);
1482 fit_function_->SetParError(i, best_errors[i]);
1483 }
1484 }
1485
1486 // Prune low lin tail1
1487 fit_function_->FixParameter(6, 0);
1488 fit_function_->FixParameter(7, 0);
1489 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1490 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
1491 std::cout << " LowLinTail1 pruned" << std::endl;
1492 best_chi2 = p->Chi2() / p->Ndf();
1493 for (Int_t i = 0; i < npar; i++) {
1494 best_params[i] = fit_function_->GetParameter(i);
1495 best_errors[i] = fit_function_->GetParError(i);
1496 }
1497 } else {
1498 fit_function_->ReleaseParameter(6);
1499 fit_function_->ReleaseParameter(7);
1500 fit_function_->SetParLimits(6, 0, 0.5);
1501 fit_function_->SetParLimits(7, -0.1, 0.1);
1502 for (Int_t i = 0; i < npar; i++) {
1503 fit_function_->SetParameter(i, best_params[i]);
1504 fit_function_->SetParError(i, best_errors[i]);
1505 }
1506 }
1507 } else {
1508 std::cout << "Low-side group peak1 REJECTED" << std::endl;
1509 fit_function_->FixParameter(3, 0);
1510 fit_function_->FixParameter(4, 0);
1511 fit_function_->FixParameter(5, 1);
1512 fit_function_->FixParameter(6, 0);
1513 fit_function_->FixParameter(7, 0);
1514 for (Int_t i = 0; i < npar; i++) {
1515 fit_function_->SetParameter(i, best_params[i]);
1516 fit_function_->SetParError(i, best_errors[i]);
1517 }
1518 }
1519 }
1520
1521 // High tail for peak 2 (outer component, no inter-peak overlap)
1522 {
1523 std::cout << "Testing high tail for peak2..." << std::endl;
1524 fit_function_->ReleaseParameter(18);
1525 fit_function_->ReleaseParameter(19);
1526 fit_function_->SetParLimits(18, 0, 0.5);
1527 fit_function_->SetParLimits(19, 1.0, tail_ratio_max_);
1528 fit_function_->SetParameter(18, 0.15);
1529 fit_function_->SetParameter(19, 1.5);
1530
1531 TFitResultPtr ht_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1532 if (ht_fit.Get() && ht_fit->IsValid() &&
1533 ht_fit->Chi2() / ht_fit->Ndf() < best_chi2) {
1534 std::cout << "HighTail2 ACCEPTED" << std::endl;
1535 best_chi2 = ht_fit->Chi2() / ht_fit->Ndf();
1536 for (Int_t i = 0; i < npar; i++) {
1537 best_params[i] = fit_function_->GetParameter(i);
1538 best_errors[i] = fit_function_->GetParError(i);
1539 }
1540 } else {
1541 std::cout << "HighTail2 REJECTED" << std::endl;
1542 fit_function_->FixParameter(18, 0);
1543 fit_function_->FixParameter(19, 1);
1544 for (Int_t i = 0; i < npar; i++) {
1545 fit_function_->SetParameter(i, best_params[i]);
1546 fit_function_->SetParError(i, best_errors[i]);
1547 }
1548 }
1549 }
1550
1551 // Inter-peak group: peak1 high tail + peak2 low-side (both affect the
1552 // region between the two peaks, so they must be tested jointly)
1553 {
1554 std::cout
1555 << "Testing inter-peak group (peak1 high tail + peak2 low-side)..."
1556 << std::endl;
1557
1558 // Release peak1 high tail
1559 fit_function_->ReleaseParameter(8);
1560 fit_function_->ReleaseParameter(9);
1561 fit_function_->SetParLimits(8, 0, 0.5);
1562 fit_function_->SetParLimits(9, 1.0, tail_ratio_max_);
1563 fit_function_->SetParameter(8, 0.15);
1564 fit_function_->SetParameter(9, 1.5);
1565
1566 // Release peak2 low-side group
1567 fit_function_->ReleaseParameter(13);
1568 fit_function_->SetParLimits(13, 0, 0.5);
1569 fit_function_->SetParameter(13, 0.15);
1570
1571 fit_function_->ReleaseParameter(14);
1572 fit_function_->ReleaseParameter(15);
1573 fit_function_->SetParLimits(14, 0, 0.5);
1574 fit_function_->SetParLimits(15, 1.0, tail_ratio_max_);
1575 fit_function_->SetParameter(14, 0.15);
1576 fit_function_->SetParameter(15, 1.5);
1577
1578 fit_function_->ReleaseParameter(16);
1579 fit_function_->ReleaseParameter(17);
1580 fit_function_->SetParLimits(16, 0, 0.5);
1581 fit_function_->SetParLimits(17, -0.1, 0.1);
1582 fit_function_->SetParameter(16, 0.15);
1583 fit_function_->SetParameter(17, 0);
1584
1585 TFitResultPtr group_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1586
1587 if (group_fit.Get() && group_fit->IsValid() &&
1588 group_fit->Chi2() / group_fit->Ndf() < best_chi2) {
1589 std::cout << "Inter-peak group ACCEPTED, pruning..." << std::endl;
1590 best_chi2 = group_fit->Chi2() / group_fit->Ndf();
1591 for (Int_t i = 0; i < npar; i++) {
1592 best_params[i] = fit_function_->GetParameter(i);
1593 best_errors[i] = fit_function_->GetParError(i);
1594 }
1595
1596 // Prune peak1 high tail
1597 fit_function_->FixParameter(8, 0);
1598 fit_function_->FixParameter(9, 1);
1599 TFitResultPtr p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1600 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
1601 std::cout << " HighTail1 pruned" << std::endl;
1602 best_chi2 = p->Chi2() / p->Ndf();
1603 for (Int_t i = 0; i < npar; i++) {
1604 best_params[i] = fit_function_->GetParameter(i);
1605 best_errors[i] = fit_function_->GetParError(i);
1606 }
1607 } else {
1608 fit_function_->ReleaseParameter(8);
1609 fit_function_->ReleaseParameter(9);
1610 fit_function_->SetParLimits(8, 0, 0.5);
1611 fit_function_->SetParLimits(9, 1.0, tail_ratio_max_);
1612 for (Int_t i = 0; i < npar; i++) {
1613 fit_function_->SetParameter(i, best_params[i]);
1614 fit_function_->SetParError(i, best_errors[i]);
1615 }
1616 }
1617
1618 // Prune step2
1619 fit_function_->FixParameter(13, 0);
1620 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1621 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
1622 std::cout << " Step2 pruned" << std::endl;
1623 best_chi2 = p->Chi2() / p->Ndf();
1624 for (Int_t i = 0; i < npar; i++) {
1625 best_params[i] = fit_function_->GetParameter(i);
1626 best_errors[i] = fit_function_->GetParError(i);
1627 }
1628 } else {
1629 fit_function_->ReleaseParameter(13);
1630 fit_function_->SetParLimits(13, 0, 0.5);
1631 for (Int_t i = 0; i < npar; i++) {
1632 fit_function_->SetParameter(i, best_params[i]);
1633 fit_function_->SetParError(i, best_errors[i]);
1634 }
1635 }
1636
1637 // Prune low exp tail2
1638 fit_function_->FixParameter(14, 0);
1639 fit_function_->FixParameter(15, 1);
1640 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1641 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
1642 std::cout << " LowExpTail2 pruned" << std::endl;
1643 best_chi2 = p->Chi2() / p->Ndf();
1644 for (Int_t i = 0; i < npar; i++) {
1645 best_params[i] = fit_function_->GetParameter(i);
1646 best_errors[i] = fit_function_->GetParError(i);
1647 }
1648 } else {
1649 fit_function_->ReleaseParameter(14);
1650 fit_function_->ReleaseParameter(15);
1651 fit_function_->SetParLimits(14, 0, 0.5);
1652 fit_function_->SetParLimits(15, 1.0, tail_ratio_max_);
1653 for (Int_t i = 0; i < npar; i++) {
1654 fit_function_->SetParameter(i, best_params[i]);
1655 fit_function_->SetParError(i, best_errors[i]);
1656 }
1657 }
1658
1659 // Prune low lin tail2
1660 fit_function_->FixParameter(16, 0);
1661 fit_function_->FixParameter(17, 0);
1662 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1663 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
1664 std::cout << " LowLinTail2 pruned" << std::endl;
1665 best_chi2 = p->Chi2() / p->Ndf();
1666 for (Int_t i = 0; i < npar; i++) {
1667 best_params[i] = fit_function_->GetParameter(i);
1668 best_errors[i] = fit_function_->GetParError(i);
1669 }
1670 } else {
1671 fit_function_->ReleaseParameter(16);
1672 fit_function_->ReleaseParameter(17);
1673 fit_function_->SetParLimits(16, 0, 0.5);
1674 fit_function_->SetParLimits(17, -0.1, 0.1);
1675 for (Int_t i = 0; i < npar; i++) {
1676 fit_function_->SetParameter(i, best_params[i]);
1677 fit_function_->SetParError(i, best_errors[i]);
1678 }
1679 }
1680 } else {
1681 std::cout << "Inter-peak group REJECTED" << std::endl;
1682 fit_function_->FixParameter(8, 0);
1683 fit_function_->FixParameter(9, 1);
1684 fit_function_->FixParameter(13, 0);
1685 fit_function_->FixParameter(14, 0);
1686 fit_function_->FixParameter(15, 1);
1687 fit_function_->FixParameter(16, 0);
1688 fit_function_->FixParameter(17, 0);
1689 for (Int_t i = 0; i < npar; i++) {
1690 fit_function_->SetParameter(i, best_params[i]);
1691 fit_function_->SetParError(i, best_errors[i]);
1692 }
1693 }
1694 }
1695
1696 // Final fit
1697 std::cout << "Final fit with selected components..." << std::endl;
1698 for (Int_t i = 0; i < npar; i++) {
1699 fit_function_->SetParameter(i, best_params[i]);
1700 fit_function_->SetParError(i, best_errors[i]);
1701 }
1702 if (use_flat_background_) {
1703 fit_function_->FixParameter(21, 0);
1704 }
1705
1706 TFitResultPtr fit_result = working_hist_->Fit(fit_function_, "LSMRBENR+");
1707
1708 if (fit_result.Get() && fit_result->IsValid()) {
1709 final_chi2 = fit_result->Chi2() / fit_result->Ndf();
1710 std::cout << "Double peak fit converged successfully" << std::endl;
1711 std::cout << "Final chi2/ndf = " << final_chi2 << std::endl;
1712 fit_valid = kTRUE;
1713 } else {
1714 std::cout << "ERROR: Double peak fit failed to converge" << std::endl;
1715 }
1716 }
1717
1718 if (fit_valid) {
1719 SortPeaksByMu(2);
1720
1721 TString chi2label = Form("#chi^{2}/ndf = %.3f", final_chi2);
1722 PlotFitDoublePeak(input_name, peak_name, chi2label);
1723
1724 for (Int_t pk = 0; pk < 2; pk++) {
1725 Int_t o = pk * 10;
1726 PeakFitResult p;
1727 p.mu = fit_function_->GetParameter(o + 0);
1728 p.mu_error = fit_function_->GetParError(o + 0);
1729 p.sigma = fit_function_->GetParameter(o + 1);
1730 p.sigma_error = fit_function_->GetParError(o + 1);
1731 p.gaus_amplitude = fit_function_->GetParameter(o + 2);
1732 p.gaus_amplitude_error = fit_function_->GetParError(o + 2);
1733 // Convert ratios back to absolute amplitudes
1734 Double_t ga = p.gaus_amplitude;
1735 p.step_amplitude = fit_function_->GetParameter(o + 3) * ga;
1736 p.step_amplitude_error = fit_function_->GetParError(o + 3) * ga;
1737 p.low_exp_tail_amplitude = fit_function_->GetParameter(o + 4) * ga;
1738 p.low_exp_tail_amplitude_error = fit_function_->GetParError(o + 4) * ga;
1739 p.low_exp_tail_ratio = fit_function_->GetParameter(o + 5);
1740 p.low_exp_tail_ratio_error = fit_function_->GetParError(o + 5);
1741 p.low_lin_tail_amplitude = fit_function_->GetParameter(o + 6) * ga;
1742 p.low_lin_tail_amplitude_error = fit_function_->GetParError(o + 6) * ga;
1743 p.low_lin_tail_slope = fit_function_->GetParameter(o + 7);
1744 p.low_lin_tail_slope_error = fit_function_->GetParError(o + 7);
1745 p.high_exp_tail_amplitude = fit_function_->GetParameter(o + 8) * ga;
1746 p.high_exp_tail_amplitude_error = fit_function_->GetParError(o + 8) * ga;
1747 p.high_exp_tail_ratio = fit_function_->GetParameter(o + 9);
1748 p.high_exp_tail_ratio_error = fit_function_->GetParError(o + 9);
1749 results.peaks[pk] = p;
1750 }
1751
1752 results.bkg_constant = fit_function_->GetParameter(20);
1753 results.bkg_constant_error = fit_function_->GetParError(20);
1754 results.lin_bkg_slope = fit_function_->GetParameter(21);
1755 results.lin_bkg_slope_error = fit_function_->GetParError(21);
1756 results.reduced_chi2 = final_chi2;
1757 results.valid = kTRUE;
1758 } else {
1759 std::cout << "ERROR: Double peak fit failed" << std::endl;
1760 }
1761
1762 return results;
1763}
1764
1766 const TString peak_name,
1767 const PeakFitResult &constrained_peak,
1768 Double_t mu2_init) {
1769 FitResult results;
1770 results.peaks.emplace_back(); // peak 1, default -1
1771 results.peaks.emplace_back(); // peak 2, default -1
1772
1773 delete fit_function_;
1774 fit_function_ = new TF1("DoublePeak", &FittingFunctions::DoublePeakFunction,
1775 fit_range_low_, fit_range_high_, 22);
1776
1777 // Peak 1 param names (offset 0)
1778 fit_function_->SetParName(0, "Mu1");
1779 fit_function_->SetParName(1, "Sigma1");
1780 fit_function_->SetParName(2, "GausAmplitude1");
1781 fit_function_->SetParName(3, "StepAmplitude1");
1782 fit_function_->SetParName(4, "LowExpTailAmplitude1");
1783 fit_function_->SetParName(5, "LowExpTailRatio1");
1784 fit_function_->SetParName(6, "LowLinTailAmplitude1");
1785 fit_function_->SetParName(7, "LowLinTailSlope1");
1786 fit_function_->SetParName(8, "HighExpTailAmplitude1");
1787 fit_function_->SetParName(9, "HighExpTailRatio1");
1788
1789 // Peak 2 param names (offset 10)
1790 fit_function_->SetParName(10, "Mu2");
1791 fit_function_->SetParName(11, "Sigma2");
1792 fit_function_->SetParName(12, "GausAmplitude2");
1793 fit_function_->SetParName(13, "StepAmplitude2");
1794 fit_function_->SetParName(14, "LowExpTailAmplitude2");
1795 fit_function_->SetParName(15, "LowExpTailRatio2");
1796 fit_function_->SetParName(16, "LowLinTailAmplitude2");
1797 fit_function_->SetParName(17, "LowLinTailSlope2");
1798 fit_function_->SetParName(18, "HighExpTailAmplitude2");
1799 fit_function_->SetParName(19, "HighExpTailRatio2");
1800
1801 fit_function_->SetParName(20, "BkgConst");
1802 fit_function_->SetParName(21, "BkgSlope");
1803
1804 Double_t range_width = fit_range_high_ - fit_range_low_;
1805 Double_t sigma_init = range_width * 0.01;
1806 Double_t peak_height =
1807 working_hist_->GetBinContent(working_hist_->GetMaximumBin());
1808 Double_t bkg_estimate = EstimateBackground();
1809
1810 // Constrained peak 1: fix all shape params (mu, sigma, amplitude ratios,
1811 // decay, slope). Only gaussian amplitude is free — tail/step amplitudes
1812 // scale automatically via their fixed ratios.
1813 {
1814 const PeakFitResult &cp = constrained_peak;
1815
1816 fit_function_->FixParameter(0, cp.mu);
1817 fit_function_->FixParameter(1, cp.sigma);
1818
1819 fit_function_->SetParLimits(2, 0, peak_height * 2);
1820 fit_function_->SetParameter(2, cp.gaus_amplitude);
1821
1822 fit_function_->FixParameter(3, cp.step_amplitude / cp.gaus_amplitude);
1823 fit_function_->FixParameter(4,
1825 fit_function_->FixParameter(
1826 5, cp.low_exp_tail_ratio > 0 ? cp.low_exp_tail_ratio : 1);
1827 fit_function_->FixParameter(6,
1829 fit_function_->FixParameter(7, cp.low_lin_tail_slope);
1830
1831 fit_function_->FixParameter(8,
1833 fit_function_->FixParameter(
1834 9, cp.high_exp_tail_ratio > 0 ? cp.high_exp_tail_ratio : 1);
1835 }
1836
1837 // Free peak 2 (all optional components fixed to 0)
1838 fit_function_->SetParLimits(10, fit_range_low_, fit_range_high_);
1839 fit_function_->SetParLimits(11, range_width * 0.001, range_width * 0.5);
1840 fit_function_->SetParLimits(12, 0, peak_height * 0.999);
1841 fit_function_->SetParLimits(13, 0, 0.5);
1842 fit_function_->SetParLimits(14, 0, 0.5);
1843 fit_function_->SetParLimits(15, 1.0, tail_ratio_max_);
1844 fit_function_->SetParLimits(16, 0, 0.5);
1845 fit_function_->SetParLimits(17, -0.1, 0.1);
1846 fit_function_->SetParLimits(18, 0, 0.5);
1847 fit_function_->SetParLimits(19, 1.0, tail_ratio_max_);
1848
1849 fit_function_->SetParameter(10, mu2_init);
1850 fit_function_->SetParameter(11, sigma_init);
1851 fit_function_->SetParameter(12, peak_height * 0.999);
1852
1853 // Fix disabled optional components on free peak 2
1854 if (use_step_)
1855 fit_function_->SetParameter(13, 0);
1856 else
1857 fit_function_->FixParameter(13, 0);
1858
1859 if (use_low_exp_tail_) {
1860 fit_function_->SetParameter(14, 0);
1861 fit_function_->SetParameter(15, 1.5);
1862 } else {
1863 fit_function_->FixParameter(14, 0);
1864 fit_function_->FixParameter(15, 1);
1865 }
1866
1867 if (use_low_lin_tail_) {
1868 fit_function_->SetParameter(16, 0);
1869 fit_function_->SetParameter(17, 0);
1870 } else {
1871 fit_function_->FixParameter(16, 0);
1872 fit_function_->FixParameter(17, 0);
1873 }
1874
1875 if (use_high_exp_tail_) {
1876 fit_function_->SetParameter(18, 0);
1877 fit_function_->SetParameter(19, 1.5);
1878 } else {
1879 fit_function_->FixParameter(18, 0);
1880 fit_function_->FixParameter(19, 1);
1881 }
1882
1883 // Background
1884 fit_function_->SetParLimits(20, 0, peak_height * 0.999);
1885 if (!use_flat_background_) {
1886 fit_function_->SetParLimits(21, -0.1 * bkg_estimate / range_width,
1887 0.1 * bkg_estimate / range_width);
1888 }
1889 fit_function_->SetParameter(20, bkg_estimate);
1890 fit_function_->SetParameter(21, 0);
1891 if (use_flat_background_) {
1892 fit_function_->FixParameter(21, 0);
1893 }
1894
1895 Bool_t fit_valid = kFALSE;
1896 Double_t final_chi2 = 0;
1897
1898 if (interactive_) {
1899 if (LoadInteractiveParams(input_name, peak_name)) {
1900 TFitResultPtr refit = working_hist_->Fit(fit_function_, "LSMRBENR+");
1901 if (refit.Get() && refit->IsValid())
1902 final_chi2 = refit->Chi2() / refit->Ndf();
1903 else if (fit_function_->GetNDF() > 0)
1904 final_chi2 = fit_function_->GetChisquare() / fit_function_->GetNDF();
1905 std::cout << "Refit from saved params chi2/ndf = " << final_chi2
1906 << std::endl;
1907 fit_valid = kTRUE;
1908 } else {
1909 Bool_t was_batch = gROOT->IsBatch();
1910 gROOT->SetBatch(kFALSE);
1911 if (LaunchInteractiveFitEditor(working_hist_, fit_function_,
1912 fit_range_low_, fit_range_high_, 2,
1913 peak_name + " / " + input_name)) {
1914 Double_t rlo_tmp, rhi_tmp;
1915 fit_function_->GetRange(rlo_tmp, rhi_tmp);
1916 fit_range_low_ = rlo_tmp;
1917 fit_range_high_ = rhi_tmp;
1918 final_chi2 = fit_function_->GetChisquare() / fit_function_->GetNDF();
1919 std::cout << "Interactive chi2/ndf = " << final_chi2 << std::endl;
1920 SaveInteractiveParams(input_name, peak_name);
1921 fit_valid = kTRUE;
1922 }
1923 gROOT->SetBatch(was_batch);
1924 }
1925 } else {
1926 // Fix all optional components on peak 2 for baseline fit
1927 fit_function_->FixParameter(13, 0);
1928 fit_function_->FixParameter(14, 0);
1929 fit_function_->FixParameter(15, 1);
1930 fit_function_->FixParameter(16, 0);
1931 fit_function_->FixParameter(17, 0);
1932 fit_function_->FixParameter(18, 0);
1933 fit_function_->FixParameter(19, 1);
1934
1935 // Initial fit
1936 TFitResultPtr initial_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1937
1938 if (!initial_fit.Get() || !initial_fit->IsValid()) {
1939 std::cout << "ERROR: Initial double peak fit (constrained) failed"
1940 << std::endl;
1941 return results;
1942 }
1943
1944 Double_t gaus_amp2 = TMath::Abs(fit_function_->GetParameter(12));
1945
1946 Int_t npar = fit_function_->GetNpar();
1947 std::vector<Double_t> best_params(npar);
1948 std::vector<Double_t> best_errors(npar);
1949 for (Int_t i = 0; i < npar; i++) {
1950 best_params[i] = fit_function_->GetParameter(i);
1951 best_errors[i] = fit_function_->GetParError(i);
1952 }
1953
1954 Double_t best_chi2 = initial_fit->Chi2() / initial_fit->Ndf();
1955 std::cout << "Initial chi2/ndf = " << best_chi2 << std::endl;
1956
1957 // Low-side group for peak 2 only (offset 10)
1958 {
1959 std::cout << "Testing low-side group for peak2..." << std::endl;
1960
1961 fit_function_->ReleaseParameter(13);
1962 fit_function_->SetParLimits(13, 0, 0.5);
1963 fit_function_->SetParameter(13, 0.15);
1964
1965 fit_function_->ReleaseParameter(14);
1966 fit_function_->ReleaseParameter(15);
1967 fit_function_->SetParLimits(14, 0, 0.5);
1968 fit_function_->SetParLimits(15, 1.0, tail_ratio_max_);
1969 fit_function_->SetParameter(14, 0.15);
1970 fit_function_->SetParameter(15, 1.5);
1971
1972 fit_function_->ReleaseParameter(16);
1973 fit_function_->ReleaseParameter(17);
1974 fit_function_->SetParLimits(16, 0, 0.5);
1975 fit_function_->SetParLimits(17, -0.1, 0.1);
1976 fit_function_->SetParameter(16, 0.15);
1977 fit_function_->SetParameter(17, 0);
1978
1979 TFitResultPtr group_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1980
1981 if (group_fit.Get() && group_fit->IsValid() &&
1982 group_fit->Chi2() / group_fit->Ndf() < best_chi2) {
1983 std::cout << "Low-side group peak2 ACCEPTED, pruning..." << std::endl;
1984 best_chi2 = group_fit->Chi2() / group_fit->Ndf();
1985 for (Int_t i = 0; i < npar; i++) {
1986 best_params[i] = fit_function_->GetParameter(i);
1987 best_errors[i] = fit_function_->GetParError(i);
1988 }
1989
1990 // Prune step2
1991 fit_function_->FixParameter(13, 0);
1992 TFitResultPtr p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
1993 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
1994 std::cout << " Step2 pruned" << std::endl;
1995 best_chi2 = p->Chi2() / p->Ndf();
1996 for (Int_t i = 0; i < npar; i++) {
1997 best_params[i] = fit_function_->GetParameter(i);
1998 best_errors[i] = fit_function_->GetParError(i);
1999 }
2000 } else {
2001 fit_function_->ReleaseParameter(13);
2002 fit_function_->SetParLimits(13, 0, 0.5);
2003 for (Int_t i = 0; i < npar; i++) {
2004 fit_function_->SetParameter(i, best_params[i]);
2005 fit_function_->SetParError(i, best_errors[i]);
2006 }
2007 }
2008
2009 // Prune low exp tail2
2010 fit_function_->FixParameter(14, 0);
2011 fit_function_->FixParameter(15, 1);
2012 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2013 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
2014 std::cout << " LowExpTail2 pruned" << std::endl;
2015 best_chi2 = p->Chi2() / p->Ndf();
2016 for (Int_t i = 0; i < npar; i++) {
2017 best_params[i] = fit_function_->GetParameter(i);
2018 best_errors[i] = fit_function_->GetParError(i);
2019 }
2020 } else {
2021 fit_function_->ReleaseParameter(14);
2022 fit_function_->ReleaseParameter(15);
2023 fit_function_->SetParLimits(14, 0, 0.5);
2024 fit_function_->SetParLimits(15, 1.0, tail_ratio_max_);
2025 for (Int_t i = 0; i < npar; i++) {
2026 fit_function_->SetParameter(i, best_params[i]);
2027 fit_function_->SetParError(i, best_errors[i]);
2028 }
2029 }
2030
2031 // Prune low lin tail2
2032 fit_function_->FixParameter(16, 0);
2033 fit_function_->FixParameter(17, 0);
2034 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2035 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
2036 std::cout << " LowLinTail2 pruned" << std::endl;
2037 best_chi2 = p->Chi2() / p->Ndf();
2038 for (Int_t i = 0; i < npar; i++) {
2039 best_params[i] = fit_function_->GetParameter(i);
2040 best_errors[i] = fit_function_->GetParError(i);
2041 }
2042 } else {
2043 fit_function_->ReleaseParameter(16);
2044 fit_function_->ReleaseParameter(17);
2045 fit_function_->SetParLimits(16, 0, 0.5);
2046 fit_function_->SetParLimits(17, -0.1, 0.1);
2047 for (Int_t i = 0; i < npar; i++) {
2048 fit_function_->SetParameter(i, best_params[i]);
2049 fit_function_->SetParError(i, best_errors[i]);
2050 }
2051 }
2052 } else {
2053 std::cout << "Low-side group peak2 REJECTED" << std::endl;
2054 fit_function_->FixParameter(13, 0);
2055 fit_function_->FixParameter(14, 0);
2056 fit_function_->FixParameter(15, 1);
2057 fit_function_->FixParameter(16, 0);
2058 fit_function_->FixParameter(17, 0);
2059 for (Int_t i = 0; i < npar; i++) {
2060 fit_function_->SetParameter(i, best_params[i]);
2061 fit_function_->SetParError(i, best_errors[i]);
2062 }
2063 }
2064 }
2065
2066 // High tail for peak 2
2067 {
2068 std::cout << "Testing high tail for peak2..." << std::endl;
2069 fit_function_->ReleaseParameter(18);
2070 fit_function_->ReleaseParameter(19);
2071 fit_function_->SetParLimits(18, 0, 0.5);
2072 fit_function_->SetParLimits(19, 1.0, tail_ratio_max_);
2073 fit_function_->SetParameter(18, 0.15);
2074 fit_function_->SetParameter(19, 1.5);
2075
2076 TFitResultPtr ht_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2077 if (ht_fit.Get() && ht_fit->IsValid() &&
2078 ht_fit->Chi2() / ht_fit->Ndf() < best_chi2) {
2079 std::cout << "HighTail2 ACCEPTED" << std::endl;
2080 best_chi2 = ht_fit->Chi2() / ht_fit->Ndf();
2081 for (Int_t i = 0; i < npar; i++) {
2082 best_params[i] = fit_function_->GetParameter(i);
2083 best_errors[i] = fit_function_->GetParError(i);
2084 }
2085 } else {
2086 std::cout << "HighTail2 REJECTED" << std::endl;
2087 fit_function_->FixParameter(18, 0);
2088 fit_function_->FixParameter(19, 1);
2089 for (Int_t i = 0; i < npar; i++) {
2090 fit_function_->SetParameter(i, best_params[i]);
2091 fit_function_->SetParError(i, best_errors[i]);
2092 }
2093 }
2094 }
2095
2096 // Final fit
2097 std::cout << "Final fit with selected components..." << std::endl;
2098 for (Int_t i = 0; i < npar; i++) {
2099 fit_function_->SetParameter(i, best_params[i]);
2100 fit_function_->SetParError(i, best_errors[i]);
2101 }
2102 if (use_flat_background_) {
2103 fit_function_->FixParameter(21, 0);
2104 }
2105
2106 TFitResultPtr fit_result = working_hist_->Fit(fit_function_, "LSMRBENR+");
2107
2108 if (fit_result.Get() && fit_result->IsValid()) {
2109 final_chi2 = fit_result->Chi2() / fit_result->Ndf();
2110 std::cout << "Double peak fit (constrained) converged successfully"
2111 << std::endl;
2112 std::cout << "Final chi2/ndf = " << final_chi2 << std::endl;
2113 fit_valid = kTRUE;
2114 } else {
2115 std::cout << "ERROR: Double peak fit (constrained) failed to converge"
2116 << std::endl;
2117 }
2118 }
2119
2120 if (fit_valid) {
2121 SortPeaksByMu(2);
2122
2123 TString chi2label = Form("#chi^{2}/ndf = %.3f", final_chi2);
2124 PlotFitDoublePeak(input_name, peak_name, chi2label);
2125
2126 for (Int_t pk = 0; pk < 2; pk++) {
2127 Int_t o = pk * 10;
2128 PeakFitResult p;
2129 p.mu = fit_function_->GetParameter(o + 0);
2130 p.mu_error = fit_function_->GetParError(o + 0);
2131 p.sigma = fit_function_->GetParameter(o + 1);
2132 p.sigma_error = fit_function_->GetParError(o + 1);
2133 p.gaus_amplitude = fit_function_->GetParameter(o + 2);
2134 p.gaus_amplitude_error = fit_function_->GetParError(o + 2);
2135 // Convert ratios back to absolute amplitudes
2136 Double_t ga = p.gaus_amplitude;
2137 p.step_amplitude = fit_function_->GetParameter(o + 3) * ga;
2138 p.step_amplitude_error = fit_function_->GetParError(o + 3) * ga;
2139 p.low_exp_tail_amplitude = fit_function_->GetParameter(o + 4) * ga;
2140 p.low_exp_tail_amplitude_error = fit_function_->GetParError(o + 4) * ga;
2141 p.low_exp_tail_ratio = fit_function_->GetParameter(o + 5);
2142 p.low_exp_tail_ratio_error = fit_function_->GetParError(o + 5);
2143 p.low_lin_tail_amplitude = fit_function_->GetParameter(o + 6) * ga;
2144 p.low_lin_tail_amplitude_error = fit_function_->GetParError(o + 6) * ga;
2145 p.low_lin_tail_slope = fit_function_->GetParameter(o + 7);
2146 p.low_lin_tail_slope_error = fit_function_->GetParError(o + 7);
2147 p.high_exp_tail_amplitude = fit_function_->GetParameter(o + 8) * ga;
2148 p.high_exp_tail_amplitude_error = fit_function_->GetParError(o + 8) * ga;
2149 p.high_exp_tail_ratio = fit_function_->GetParameter(o + 9);
2150 p.high_exp_tail_ratio_error = fit_function_->GetParError(o + 9);
2151 results.peaks[pk] = p;
2152 }
2153
2154 results.bkg_constant = fit_function_->GetParameter(20);
2155 results.bkg_constant_error = fit_function_->GetParError(20);
2156 results.lin_bkg_slope = fit_function_->GetParameter(21);
2157 results.lin_bkg_slope_error = fit_function_->GetParError(21);
2158 results.reduced_chi2 = final_chi2;
2159 results.valid = kTRUE;
2160 } else {
2161 std::cout << "ERROR: Double peak fit (constrained) failed" << std::endl;
2162 }
2163
2164 return results;
2165}
2166
2168 const TString peak_name,
2169 const FitResult &constrained_peaks,
2170 Double_t mu3_init) {
2171 FitResult results;
2172 results.peaks.emplace_back(); // peak 1, default -1
2173 results.peaks.emplace_back(); // peak 2, default -1
2174 results.peaks.emplace_back(); // peak 3, default -1
2175
2176 delete fit_function_;
2177 fit_function_ = new TF1("TriplePeak", &FittingFunctions::TriplePeakFunction,
2178 fit_range_low_, fit_range_high_, 32);
2179
2180 // Peak 1 param names (offset 0)
2181 fit_function_->SetParName(0, "Mu1");
2182 fit_function_->SetParName(1, "Sigma1");
2183 fit_function_->SetParName(2, "GausAmplitude1");
2184 fit_function_->SetParName(3, "StepAmplitude1");
2185 fit_function_->SetParName(4, "LowExpTailAmplitude1");
2186 fit_function_->SetParName(5, "LowExpTailRatio1");
2187 fit_function_->SetParName(6, "LowLinTailAmplitude1");
2188 fit_function_->SetParName(7, "LowLinTailSlope1");
2189 fit_function_->SetParName(8, "HighExpTailAmplitude1");
2190 fit_function_->SetParName(9, "HighExpTailRatio1");
2191
2192 // Peak 2 param names (offset 10)
2193 fit_function_->SetParName(10, "Mu2");
2194 fit_function_->SetParName(11, "Sigma2");
2195 fit_function_->SetParName(12, "GausAmplitude2");
2196 fit_function_->SetParName(13, "StepAmplitude2");
2197 fit_function_->SetParName(14, "LowExpTailAmplitude2");
2198 fit_function_->SetParName(15, "LowExpTailRatio2");
2199 fit_function_->SetParName(16, "LowLinTailAmplitude2");
2200 fit_function_->SetParName(17, "LowLinTailSlope2");
2201 fit_function_->SetParName(18, "HighExpTailAmplitude2");
2202 fit_function_->SetParName(19, "HighExpTailRatio2");
2203
2204 // Peak 3 param names (offset 20)
2205 fit_function_->SetParName(20, "Mu3");
2206 fit_function_->SetParName(21, "Sigma3");
2207 fit_function_->SetParName(22, "GausAmplitude3");
2208 fit_function_->SetParName(23, "StepAmplitude3");
2209 fit_function_->SetParName(24, "LowExpTailAmplitude3");
2210 fit_function_->SetParName(25, "LowExpTailRatio3");
2211 fit_function_->SetParName(26, "LowLinTailAmplitude3");
2212 fit_function_->SetParName(27, "LowLinTailSlope3");
2213 fit_function_->SetParName(28, "HighExpTailAmplitude3");
2214 fit_function_->SetParName(29, "HighExpTailRatio3");
2215
2216 fit_function_->SetParName(30, "BkgConst");
2217 fit_function_->SetParName(31, "BkgSlope");
2218
2219 Double_t range_width = fit_range_high_ - fit_range_low_;
2220 Double_t sigma_init = range_width * 0.01;
2221 Double_t peak_height =
2222 working_hist_->GetBinContent(working_hist_->GetMaximumBin());
2223 Double_t bkg_estimate = EstimateBackground();
2224
2225 // Constrained peaks: fix shape (mu, sigma, decay, slope), allow amplitudes
2226 // to float with [0, ...] bounds. Disable components that were off in the
2227 // background fit.
2228 const PeakFitResult *cpeaks[2] = {&constrained_peaks.peaks[0],
2229 &constrained_peaks.peaks[1]};
2230 for (Int_t pk = 0; pk < 2; pk++) {
2231 Int_t o = pk * 10;
2232 const PeakFitResult &cp = *cpeaks[pk];
2233
2234 // Mu and sigma: fixed (shape)
2235 fit_function_->FixParameter(o + 0, cp.mu);
2236 fit_function_->FixParameter(o + 1, cp.sigma);
2237
2238 // Gaussian amplitude: free, positive
2239 fit_function_->SetParLimits(o + 2, 0, peak_height * 2);
2240 fit_function_->SetParameter(o + 2, cp.gaus_amplitude);
2241
2242 // Fix all amplitude ratios and shape params — they scale with
2243 // gaus_amplitude
2244 fit_function_->FixParameter(o + 3, cp.step_amplitude / cp.gaus_amplitude);
2245 fit_function_->FixParameter(o + 4,
2247 fit_function_->FixParameter(
2248 o + 5, cp.low_exp_tail_ratio > 0 ? cp.low_exp_tail_ratio : 1);
2249 fit_function_->FixParameter(o + 6,
2251 fit_function_->FixParameter(o + 7, cp.low_lin_tail_slope);
2252 fit_function_->FixParameter(o + 8,
2254 fit_function_->FixParameter(
2255 o + 9, cp.high_exp_tail_ratio > 0 ? cp.high_exp_tail_ratio : 1);
2256 }
2257
2258 // Free peak 3 (offset 20)
2259 fit_function_->SetParLimits(20, fit_range_low_, fit_range_high_);
2260 fit_function_->SetParLimits(21, range_width * 0.001, range_width * 0.5);
2261 fit_function_->SetParLimits(22, 0, peak_height * 0.999);
2262 fit_function_->SetParLimits(23, 0, 0.5);
2263 fit_function_->SetParLimits(24, 0, 0.5);
2264 fit_function_->SetParLimits(25, 1.0, tail_ratio_max_);
2265 fit_function_->SetParLimits(26, 0, 0.5);
2266 fit_function_->SetParLimits(27, -0.1, 0.1);
2267 fit_function_->SetParLimits(28, 0, 0.5);
2268 fit_function_->SetParLimits(29, 1.0, tail_ratio_max_);
2269
2270 fit_function_->SetParameter(20, mu3_init);
2271 fit_function_->SetParameter(21, sigma_init);
2272 fit_function_->SetParameter(22, peak_height * 0.999);
2273
2274 // Fix disabled optional components on free peak 3
2275 if (use_step_)
2276 fit_function_->SetParameter(23, 0);
2277 else
2278 fit_function_->FixParameter(23, 0);
2279
2280 if (use_low_exp_tail_) {
2281 fit_function_->SetParameter(24, 0);
2282 fit_function_->SetParameter(25, 1.5);
2283 } else {
2284 fit_function_->FixParameter(24, 0);
2285 fit_function_->FixParameter(25, 1);
2286 }
2287
2288 if (use_low_lin_tail_) {
2289 fit_function_->SetParameter(26, 0);
2290 fit_function_->SetParameter(27, 0);
2291 } else {
2292 fit_function_->FixParameter(26, 0);
2293 fit_function_->FixParameter(27, 0);
2294 }
2295
2296 if (use_high_exp_tail_) {
2297 fit_function_->SetParameter(28, 0);
2298 fit_function_->SetParameter(29, 1.5);
2299 } else {
2300 fit_function_->FixParameter(28, 0);
2301 fit_function_->FixParameter(29, 1);
2302 }
2303
2304 // Background
2305 fit_function_->SetParLimits(30, 0, peak_height * 0.999);
2306 if (!use_flat_background_) {
2307 fit_function_->SetParLimits(31, -0.1 * bkg_estimate / range_width,
2308 0.1 * bkg_estimate / range_width);
2309 }
2310 fit_function_->SetParameter(30, bkg_estimate);
2311 fit_function_->SetParameter(31, 0);
2312 if (use_flat_background_) {
2313 fit_function_->FixParameter(31, 0);
2314 }
2315
2316 Bool_t fit_valid = kFALSE;
2317 Double_t final_chi2 = 0;
2318
2319 if (interactive_) {
2320 if (LoadInteractiveParams(input_name, peak_name)) {
2321 TFitResultPtr refit = working_hist_->Fit(fit_function_, "LSMRBENR+");
2322 if (refit.Get() && refit->IsValid())
2323 final_chi2 = refit->Chi2() / refit->Ndf();
2324 else if (fit_function_->GetNDF() > 0)
2325 final_chi2 = fit_function_->GetChisquare() / fit_function_->GetNDF();
2326 std::cout << "Refit from saved params chi2/ndf = " << final_chi2
2327 << std::endl;
2328 fit_valid = kTRUE;
2329 } else {
2330 Bool_t was_batch = gROOT->IsBatch();
2331 gROOT->SetBatch(kFALSE);
2332 if (LaunchInteractiveFitEditor(working_hist_, fit_function_,
2333 fit_range_low_, fit_range_high_, 3,
2334 peak_name + " / " + input_name)) {
2335 Double_t rlo_tmp, rhi_tmp;
2336 fit_function_->GetRange(rlo_tmp, rhi_tmp);
2337 fit_range_low_ = rlo_tmp;
2338 fit_range_high_ = rhi_tmp;
2339 final_chi2 = fit_function_->GetChisquare() / fit_function_->GetNDF();
2340 std::cout << "Interactive chi2/ndf = " << final_chi2 << std::endl;
2341 SaveInteractiveParams(input_name, peak_name);
2342 fit_valid = kTRUE;
2343 }
2344 gROOT->SetBatch(was_batch);
2345 }
2346 } else {
2347 // Fix all optional components on peak 3 for baseline fit
2348 fit_function_->FixParameter(23, 0);
2349 fit_function_->FixParameter(24, 0);
2350 fit_function_->FixParameter(25, 1);
2351 fit_function_->FixParameter(26, 0);
2352 fit_function_->FixParameter(27, 0);
2353 fit_function_->FixParameter(28, 0);
2354 fit_function_->FixParameter(29, 1);
2355
2356 // Initial fit
2357 TFitResultPtr initial_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2358
2359 if (!initial_fit.Get() || !initial_fit->IsValid()) {
2360 std::cout << "ERROR: Initial triple peak fit failed" << std::endl;
2361 return results;
2362 }
2363
2364 Double_t gaus_amp3 = TMath::Abs(fit_function_->GetParameter(22));
2365
2366 Int_t npar = fit_function_->GetNpar();
2367 std::vector<Double_t> best_params(npar);
2368 std::vector<Double_t> best_errors(npar);
2369 for (Int_t i = 0; i < npar; i++) {
2370 best_params[i] = fit_function_->GetParameter(i);
2371 best_errors[i] = fit_function_->GetParError(i);
2372 }
2373
2374 Double_t best_chi2 = initial_fit->Chi2() / initial_fit->Ndf();
2375 std::cout << "Initial chi2/ndf = " << best_chi2 << std::endl;
2376
2377 // Low-side group for peak 3 (offset 20)
2378 {
2379 std::cout << "Testing low-side group for peak3..." << std::endl;
2380
2381 fit_function_->ReleaseParameter(23);
2382 fit_function_->SetParLimits(23, 0, 0.5);
2383 fit_function_->SetParameter(23, 0.15);
2384
2385 fit_function_->ReleaseParameter(24);
2386 fit_function_->ReleaseParameter(25);
2387 fit_function_->SetParLimits(24, 0, 0.5);
2388 fit_function_->SetParLimits(25, 1.0, tail_ratio_max_);
2389 fit_function_->SetParameter(24, 0.15);
2390 fit_function_->SetParameter(25, 1.5);
2391
2392 fit_function_->ReleaseParameter(26);
2393 fit_function_->ReleaseParameter(27);
2394 fit_function_->SetParLimits(26, 0, 0.5);
2395 fit_function_->SetParLimits(27, -0.1, 0.1);
2396 fit_function_->SetParameter(26, 0.15);
2397 fit_function_->SetParameter(27, 0);
2398
2399 TFitResultPtr group_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2400
2401 if (group_fit.Get() && group_fit->IsValid() &&
2402 group_fit->Chi2() / group_fit->Ndf() < best_chi2) {
2403 std::cout << "Low-side group peak3 ACCEPTED, pruning..." << std::endl;
2404 best_chi2 = group_fit->Chi2() / group_fit->Ndf();
2405 for (Int_t i = 0; i < npar; i++) {
2406 best_params[i] = fit_function_->GetParameter(i);
2407 best_errors[i] = fit_function_->GetParError(i);
2408 }
2409
2410 // Prune step3
2411 fit_function_->FixParameter(23, 0);
2412 TFitResultPtr p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2413 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
2414 std::cout << " Step3 pruned" << std::endl;
2415 best_chi2 = p->Chi2() / p->Ndf();
2416 for (Int_t i = 0; i < npar; i++) {
2417 best_params[i] = fit_function_->GetParameter(i);
2418 best_errors[i] = fit_function_->GetParError(i);
2419 }
2420 } else {
2421 fit_function_->ReleaseParameter(23);
2422 fit_function_->SetParLimits(23, 0, 0.5);
2423 for (Int_t i = 0; i < npar; i++) {
2424 fit_function_->SetParameter(i, best_params[i]);
2425 fit_function_->SetParError(i, best_errors[i]);
2426 }
2427 }
2428
2429 // Prune low exp tail3
2430 fit_function_->FixParameter(24, 0);
2431 fit_function_->FixParameter(25, 1);
2432 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2433 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
2434 std::cout << " LowExpTail3 pruned" << std::endl;
2435 best_chi2 = p->Chi2() / p->Ndf();
2436 for (Int_t i = 0; i < npar; i++) {
2437 best_params[i] = fit_function_->GetParameter(i);
2438 best_errors[i] = fit_function_->GetParError(i);
2439 }
2440 } else {
2441 fit_function_->ReleaseParameter(24);
2442 fit_function_->ReleaseParameter(25);
2443 fit_function_->SetParLimits(24, 0, 0.5);
2444 fit_function_->SetParLimits(25, 1.0, tail_ratio_max_);
2445 for (Int_t i = 0; i < npar; i++) {
2446 fit_function_->SetParameter(i, best_params[i]);
2447 fit_function_->SetParError(i, best_errors[i]);
2448 }
2449 }
2450
2451 // Prune low lin tail3
2452 fit_function_->FixParameter(26, 0);
2453 fit_function_->FixParameter(27, 0);
2454 p = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2455 if (p.Get() && p->IsValid() && p->Chi2() / p->Ndf() <= best_chi2) {
2456 std::cout << " LowLinTail3 pruned" << std::endl;
2457 best_chi2 = p->Chi2() / p->Ndf();
2458 for (Int_t i = 0; i < npar; i++) {
2459 best_params[i] = fit_function_->GetParameter(i);
2460 best_errors[i] = fit_function_->GetParError(i);
2461 }
2462 } else {
2463 fit_function_->ReleaseParameter(26);
2464 fit_function_->ReleaseParameter(27);
2465 fit_function_->SetParLimits(26, 0, 0.5);
2466 fit_function_->SetParLimits(27, -0.1, 0.1);
2467 for (Int_t i = 0; i < npar; i++) {
2468 fit_function_->SetParameter(i, best_params[i]);
2469 fit_function_->SetParError(i, best_errors[i]);
2470 }
2471 }
2472 } else {
2473 std::cout << "Low-side group peak3 REJECTED" << std::endl;
2474 fit_function_->FixParameter(23, 0);
2475 fit_function_->FixParameter(24, 0);
2476 fit_function_->FixParameter(25, 1);
2477 fit_function_->FixParameter(26, 0);
2478 fit_function_->FixParameter(27, 0);
2479 for (Int_t i = 0; i < npar; i++) {
2480 fit_function_->SetParameter(i, best_params[i]);
2481 fit_function_->SetParError(i, best_errors[i]);
2482 }
2483 }
2484 }
2485
2486 // High tail for peak 3
2487 {
2488 std::cout << "Testing high tail for peak3..." << std::endl;
2489 fit_function_->ReleaseParameter(28);
2490 fit_function_->ReleaseParameter(29);
2491 fit_function_->SetParLimits(28, 0, 0.5);
2492 fit_function_->SetParLimits(29, 1.0, tail_ratio_max_);
2493 fit_function_->SetParameter(28, 0.15);
2494 fit_function_->SetParameter(29, 1.5);
2495
2496 TFitResultPtr ht_fit = working_hist_->Fit(fit_function_, "LSMBNQ0R");
2497 if (ht_fit.Get() && ht_fit->IsValid() &&
2498 ht_fit->Chi2() / ht_fit->Ndf() < best_chi2) {
2499 std::cout << "HighTail3 ACCEPTED" << std::endl;
2500 best_chi2 = ht_fit->Chi2() / ht_fit->Ndf();
2501 for (Int_t i = 0; i < npar; i++) {
2502 best_params[i] = fit_function_->GetParameter(i);
2503 best_errors[i] = fit_function_->GetParError(i);
2504 }
2505 } else {
2506 std::cout << "HighTail3 REJECTED" << std::endl;
2507 fit_function_->FixParameter(28, 0);
2508 fit_function_->FixParameter(29, 1);
2509 for (Int_t i = 0; i < npar; i++) {
2510 fit_function_->SetParameter(i, best_params[i]);
2511 fit_function_->SetParError(i, best_errors[i]);
2512 }
2513 }
2514 }
2515
2516 // Final fit
2517 std::cout << "Final fit with selected components..." << std::endl;
2518 for (Int_t i = 0; i < npar; i++) {
2519 fit_function_->SetParameter(i, best_params[i]);
2520 fit_function_->SetParError(i, best_errors[i]);
2521 }
2522 if (use_flat_background_) {
2523 fit_function_->FixParameter(31, 0);
2524 }
2525
2526 TFitResultPtr fit_result = working_hist_->Fit(fit_function_, "LSMRBENR+");
2527
2528 if (fit_result.Get() && fit_result->IsValid()) {
2529 final_chi2 = fit_result->Chi2() / fit_result->Ndf();
2530 std::cout << "Triple peak fit converged successfully" << std::endl;
2531 std::cout << "Final chi2/ndf = " << final_chi2 << std::endl;
2532 fit_valid = kTRUE;
2533 } else {
2534 std::cout << "ERROR: Triple peak fit failed to converge" << std::endl;
2535 }
2536 }
2537
2538 if (fit_valid) {
2539 SortPeaksByMu(3);
2540
2541 TString chi2label = Form("#chi^{2}/ndf = %.3f", final_chi2);
2542 PlotFitTriplePeak(input_name, peak_name, chi2label);
2543
2544 for (Int_t pk = 0; pk < 3; pk++) {
2545 Int_t o = pk * 10;
2546 PeakFitResult p;
2547 p.mu = fit_function_->GetParameter(o + 0);
2548 p.mu_error = fit_function_->GetParError(o + 0);
2549 p.sigma = fit_function_->GetParameter(o + 1);
2550 p.sigma_error = fit_function_->GetParError(o + 1);
2551 p.gaus_amplitude = fit_function_->GetParameter(o + 2);
2552 p.gaus_amplitude_error = fit_function_->GetParError(o + 2);
2553 // Convert ratios back to absolute amplitudes
2554 Double_t ga = p.gaus_amplitude;
2555 p.step_amplitude = fit_function_->GetParameter(o + 3) * ga;
2556 p.step_amplitude_error = fit_function_->GetParError(o + 3) * ga;
2557 p.low_exp_tail_amplitude = fit_function_->GetParameter(o + 4) * ga;
2558 p.low_exp_tail_amplitude_error = fit_function_->GetParError(o + 4) * ga;
2559 p.low_exp_tail_ratio = fit_function_->GetParameter(o + 5);
2560 p.low_exp_tail_ratio_error = fit_function_->GetParError(o + 5);
2561 p.low_lin_tail_amplitude = fit_function_->GetParameter(o + 6) * ga;
2562 p.low_lin_tail_amplitude_error = fit_function_->GetParError(o + 6) * ga;
2563 p.low_lin_tail_slope = fit_function_->GetParameter(o + 7);
2564 p.low_lin_tail_slope_error = fit_function_->GetParError(o + 7);
2565 p.high_exp_tail_amplitude = fit_function_->GetParameter(o + 8) * ga;
2566 p.high_exp_tail_amplitude_error = fit_function_->GetParError(o + 8) * ga;
2567 p.high_exp_tail_ratio = fit_function_->GetParameter(o + 9);
2568 p.high_exp_tail_ratio_error = fit_function_->GetParError(o + 9);
2569 results.peaks[pk] = p;
2570 }
2571
2572 results.bkg_constant = fit_function_->GetParameter(30);
2573 results.bkg_constant_error = fit_function_->GetParError(30);
2574 results.lin_bkg_slope = fit_function_->GetParameter(31);
2575 results.lin_bkg_slope_error = fit_function_->GetParError(31);
2576 results.reduced_chi2 = final_chi2;
2577 results.valid = kTRUE;
2578 } else {
2579 std::cout << "ERROR: Triple peak fit failed" << std::endl;
2580 }
2581
2582 return results;
2583}
Binned chi-squared photopeak fitting on a TF1 + Minuit2 backend.
Bool_t LaunchInteractiveFitEditor(TH1 *hist, TF1 *fit_func, Double_t range_low, Double_t range_high, Int_t num_peaks=1, const TString &info_label="")
void SetManualParameter(Int_t index, Double_t value)
Override one starting value.
void PlotFitDoublePeak(const TString input_name, const TString peak_name, const TString label="")
Draw and save the double-peak fit with its residual panel.
void PlotFitSinglePeak(const TString input_name, const TString peak_name, const TString label="")
Draw and save the single-peak fit with its residual panel.
~FittingUtils()
Releases the fit function this object owns.
FitResult FitTriplePeak(const TString input_name, const TString peak_name, const FitResult &constrained_peaks, Double_t mu3_init)
Fit three peaks with the first two constrained by an earlier fit.
FitResult FitDoublePeak(const TString input_name, const TString peak_name, Double_t mu1_init, Double_t mu2_init)
Fit two peaks from centroid guesses.
FittingUtils(TH1 *working_hist, Float_t fit_range_low, Float_t fit_range_high, Bool_t use_flat_background=kFALSE, Bool_t use_step=kFALSE, Bool_t use_low_exp_tail=kFALSE, Bool_t use_low_lin_tail=kFALSE, Bool_t use_high_exp_tail=kFALSE)
Build a fitter for one histogram and range.
void PlotFitTriplePeak(const TString input_name, const TString peak_name, const TString label="")
Draw and save the triple-peak fit with its residual panel.
void SetManualParameters(const std::vector< Double_t > &params)
Supply explicit starting values for every parameter.
FitResult FitSinglePeak(const TString input_name, const TString peak_name)
Fit one peak, pruning components that do not earn their place.
static TString GetRandomName()
Generate a name unlikely to collide with existing ROOT objects.
static Width_t GetLineWidth()
Line width the Configure* methods apply.
static void PlotFitWithResiduals(TH1 *hist, TGraph *total_graph, const std::vector< TGraph * > &component_graphs, Float_t fit_range_low, Float_t fit_range_high, const TString &output_name, const TString &output_subdirectory="fits", const TString &label="", Bool_t logy=kTRUE)
Draw a fit over its data with a residual panel and save it.
static TString GetPlotsBaseDir()
Current base directory for saved figures, without trailing slash.
Double_t LinearBackground(Double_t *x, Double_t *par)
Straight-line background.
Double_t Step(Double_t *x, Double_t *par)
Resolution-smeared step shelf below the peak.
Double_t DoublePeakFunction(Double_t *x, Double_t *par)
Two peaks sharing one linear background.
Double_t PeakFunction(Double_t *x, Double_t *par)
One peak with every optional component, plus a linear background.
Double_t TriplePeakFunction(Double_t *x, Double_t *par)
Three peaks sharing one linear background.
Double_t Gaussian(Double_t *x, Double_t *par)
Gaussian peak.
Double_t LowTail(Double_t *x, Double_t *par)
Combined exponential and linear tail below the peak.
Double_t HighTail(Double_t *x, Double_t *par)
Exponential tail above the peak, from pileup.
Result of one fit: peaks, background, quality, and diagnostics.
Float_t lin_bkg_slope
std::vector< PeakFitResult > peaks
One entry per peak; 1 to 3 supported.
Bool_t valid
In the RooFit backend this is computed post hoc against the display binning, and drives component pru...
Float_t lin_bkg_slope_error
Background slope.
Float_t bkg_constant
Float_t reduced_chi2
Chi-squared per degree of freedom.
Float_t bkg_constant_error
Background offset.
Fitted parameters and errors for one peak.
Float_t mu_error
Centroid, in the histogram's x units.
Float_t high_exp_tail_amplitude
High-energy exponential tail scale.
Float_t low_lin_tail_amplitude
Low-energy linear tail scale.
Float_t high_exp_tail_ratio
High-energy exponential decay constant, in units of sigma.
Float_t low_exp_tail_amplitude
Low-energy exponential tail scale.
Float_t sigma_error
Gaussian resolution.
Float_t low_exp_tail_ratio
Low-energy exponential decay constant, in units of sigma.
Float_t low_lin_tail_slope
Slope of the linear tail factor.
Float_t gaus_amplitude
Float_t gaus_amplitude_error
Gaussian scale.
Float_t low_lin_tail_slope_error
Float_t low_exp_tail_ratio_error
Float_t step_amplitude_error
Step shelf scale.
Float_t low_lin_tail_amplitude_error
Float_t step_amplitude
Float_t high_exp_tail_amplitude_error
Float_t high_exp_tail_ratio_error
Float_t low_exp_tail_amplitude_error