Analysis-Utilities 26.9.9
C++/ROOT utilities for nuclear measurement data analysis
Loading...
Searching...
No Matches
RooFitUtils.cpp
Go to the documentation of this file.
1#include "RooFitUtils.hpp"
2
5
6#include <cmath>
7
8RooAbsPdf *RooFitFunctions::MakeGaussian(const TString &name, RooRealVar &x,
9 RooRealVar &mu, RooRealVar &sigma) {
10 return new RooGaussian(name.Data(), name.Data(), x, mu, sigma);
11}
12
13RooAbsPdf *RooFitFunctions::MakeStepShelf(const TString &name, RooRealVar &x,
14 RooRealVar &mu, RooRealVar &sigma) {
15 return new RooStepShelf(name.Data(), name.Data(), x, mu, sigma);
16}
17
18RooAbsPdf *RooFitFunctions::MakeLowExpTail(const TString &name, RooRealVar &x,
19 RooRealVar &mu, RooRealVar &sigma,
20 RooRealVar &tau_ratio) {
21 return new RooLowExpTail(name.Data(), name.Data(), x, mu, sigma, tau_ratio);
22}
23
24RooAbsPdf *RooFitFunctions::MakeLowLinTail(const TString &name, RooRealVar &x,
25 RooRealVar &mu, RooRealVar &sigma,
26 RooRealVar &slope) {
27 return new RooLowLinTail(name.Data(), name.Data(), x, mu, sigma, slope);
28}
29
30RooAbsPdf *RooFitFunctions::MakeHighExpTail(const TString &name, RooRealVar &x,
31 RooRealVar &mu, RooRealVar &sigma,
32 RooRealVar &tau_ratio) {
33 return new RooHighExpTail(name.Data(), name.Data(), x, mu, sigma, tau_ratio);
34}
35
36RooAbsPdf *RooFitFunctions::MakeLinearBackground(const TString &name,
37 RooRealVar &x,
38 RooRealVar &slope) {
39 return new RooPolynomial(name.Data(), name.Data(), x, RooArgList(slope));
40}
41
42void RooFitUtils::RegisterOwned(RooAbsArg *arg) { owned_args_.push_back(arg); }
43
44void RooFitUtils::InitState() {
45 working_hist_ = nullptr;
46 events_.clear();
47 fit_range_low_ = 0;
48 fit_range_high_ = 0;
49 display_bin_width_kev_ = 0;
50 use_flat_background_ = kFALSE;
51 use_step_ = kFALSE;
52 use_low_exp_tail_ = kFALSE;
53 use_low_lin_tail_ = kFALSE;
54 use_high_exp_tail_ = kFALSE;
55 use_manual_init_ = kFALSE;
56 interactive_ = kFALSE;
57 fit_debug_ = kFALSE;
58
59 x_ = nullptr;
60 unbinned_data_ = nullptr;
61 total_pdf_ = nullptr;
62 num_peaks_ = 0;
63
64 sim_category_ = nullptr;
65 sim_pdf_ = nullptr;
66 sim_combined_data_ = nullptr;
67 sim_mode_ = kFALSE;
68
69 RooMsgService::instance().setGlobalKillBelow(RooFit::WARNING);
70 for (Int_t i = 0; i < RooMsgService::instance().numStreams(); i++) {
71 RooMsgService::instance().getStream(i).removeTopic(RooFit::InputArguments);
72 }
73 RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::Ignore);
74 RooRealVar::enableSilentClipping();
75}
76
77std::vector<Double_t>
78RooFitUtils::LoadEventsFromTree(TTree *tree, const TString &branch_name) {
79 std::vector<Double_t> out;
80 if (!tree)
81 return out;
82 Float_t energy_f = 0;
83 Double_t energy_d = 0;
84 TBranch *br = tree->GetBranch(branch_name.Data());
85 if (!br) {
86 std::cerr << "ERROR: LoadEventsFromTree: branch '" << branch_name
87 << "' not found" << std::endl;
88 return out;
89 }
90 TLeaf *leaf = br->GetLeaf(branch_name.Data());
91 Bool_t is_double = (leaf && TString(leaf->GetTypeName()) == "Double_t");
92 if (is_double)
93 tree->SetBranchAddress(branch_name.Data(), &energy_d);
94 else
95 tree->SetBranchAddress(branch_name.Data(), &energy_f);
96
97 Long64_t n = tree->GetEntries();
98 out.reserve(n);
99 for (Long64_t i = 0; i < n; i++) {
100 tree->GetEntry(i);
101 out.push_back(is_double ? energy_d : (Double_t)energy_f);
102 }
103 tree->ResetBranchAddresses();
104 return out;
105}
106
108 const std::vector<Double_t> &events, Float_t fit_range_low,
109 Float_t fit_range_high, Float_t display_bin_width_kev) {
110 Float_t hist_lo = 0.85f * fit_range_low;
111 Float_t hist_hi = 1.15f * fit_range_high;
112 Int_t nbins = TMath::Max(
113 1, (Int_t)TMath::Nint((hist_hi - hist_lo) / display_bin_width_kev));
114 hist_hi = hist_lo + nbins * display_bin_width_kev;
115 TString hname = PlottingUtils::GetRandomName();
116 TH1F *hist = new TH1F(hname,
117 TString::Format("; Energy [keV]; Counts / %.0f eV",
118 display_bin_width_kev * 1000.0),
119 nbins, hist_lo, hist_hi);
120 hist->SetDirectory(0);
121 for (size_t i = 0; i < events.size(); i++) {
122 Double_t e = events[i];
123 if (e >= hist_lo && e < hist_hi)
124 hist->Fill(e);
125 }
126 return hist;
127}
128
130 const std::vector<Double_t> &events,
131 Float_t fit_range_low,
132 Float_t fit_range_high,
133 Float_t display_bin_width_kev) {
134 Float_t hist_lo = 0.85f * fit_range_low;
135 Float_t hist_hi = 1.15f * fit_range_high;
136 Int_t nbins = TMath::Max(
137 1, (Int_t)TMath::Nint((hist_hi - hist_lo) / display_bin_width_kev));
138 hist_hi = hist_lo + nbins * display_bin_width_kev;
139 hist->SetBins(nbins, hist_lo, hist_hi);
140 hist->Reset();
141 for (size_t i = 0; i < events.size(); i++) {
142 Double_t e = events[i];
143 if (e >= hist_lo && e < hist_hi)
144 hist->Fill(e);
145 }
146}
147
148RooDataSet *
149RooFitUtils::BuildUnbinnedDataFrom(const std::vector<Double_t> &events,
150 RooRealVar *x) {
151 RooArgSet vars(*x);
152 RooDataSet *ds = new RooDataSet("unbinned_data", "unbinned_data", vars);
153 Double_t xmin = x->getMin();
154 Double_t xmax = x->getMax();
155 for (size_t i = 0; i < events.size(); i++) {
156 Double_t e = events[i];
157 if (e < xmin || e > xmax)
158 continue;
159 x->setVal(e);
160 ds->add(vars);
161 }
162 return ds;
163}
164
165void RooFitUtils::BuildDisplayHistogram() {
166 delete working_hist_;
167 working_hist_ = BuildDisplayHistogramFrom(
168 events_, fit_range_low_, fit_range_high_, display_bin_width_kev_);
169}
170
171void RooFitUtils::BuildUnbinnedData() {
172 delete unbinned_data_;
173 unbinned_data_ = BuildUnbinnedDataFrom(events_, x_);
174}
175
177 InitState();
178 sim_mode_ = kTRUE;
179}
180
181RooFitUtils::RooFitUtils(const std::vector<Double_t> &events,
182 Float_t fit_range_low, Float_t fit_range_high,
183 Float_t display_bin_width_kev,
184 Bool_t use_flat_background, Bool_t use_step,
185 Bool_t use_low_exp_tail, Bool_t use_low_lin_tail,
186 Bool_t use_high_exp_tail) {
187 InitState();
188 events_ = events;
189 fit_range_low_ = fit_range_low;
190 fit_range_high_ = fit_range_high;
191 display_bin_width_kev_ = display_bin_width_kev;
192 use_flat_background_ = use_flat_background;
193 use_step_ = use_step;
194 use_low_exp_tail_ = use_low_exp_tail;
195 use_low_lin_tail_ = use_low_lin_tail;
196 use_high_exp_tail_ = use_high_exp_tail;
197
198 BuildDisplayHistogram();
199
200 std::cout << "Fit configuration:" << std::endl;
201 std::cout << std::endl;
202 if (use_flat_background_) {
203 std::cout << "Background: FLAT" << std::endl;
204 } else {
205 std::cout << "Background: LINEAR" << std::endl;
206 }
207 std::cout << "Step function: " << (use_step_ ? "ENABLED" : "DISABLED")
208 << std::endl;
209 std::cout << "Low exponential tail: "
210 << (use_low_exp_tail_ ? "ENABLED" : "DISABLED") << std::endl;
211 std::cout << "Low linear tail: "
212 << (use_low_lin_tail_ ? "ENABLED" : "DISABLED") << std::endl;
213 std::cout << "High exponential tail: "
214 << (use_high_exp_tail_ ? "ENABLED" : "DISABLED") << std::endl;
215 std::cout << "Unbinned events: " << events_.size() << std::endl;
216}
217
219 for (size_t i = 0; i < owned_args_.size(); i++) {
220 delete owned_args_[i];
221 }
222 owned_args_.clear();
223 delete unbinned_data_;
224 delete sim_combined_data_;
225 delete sim_pdf_;
226 delete sim_category_;
227 std::map<TString, RooDataSet *>::iterator dit;
228 for (dit = sim_channel_data_.begin(); dit != sim_channel_data_.end(); ++dit) {
229 delete dit->second;
230 }
231 sim_channel_data_.clear();
232 for (size_t i = 0; i < sim_channels_.size(); i++) {
233 delete sim_channels_[i].hist;
234 }
235 delete working_hist_;
236}
237
238void RooFitUtils::SetManualParameters(const std::vector<Double_t> &params) {
239 Int_t expected = num_peaks_ * 10 + 2;
240 if (num_peaks_ == 0 || params.size() != (size_t)expected) {
241 std::cerr << "ERROR: Manual parameters size (" << params.size()
242 << ") doesn't match expected (" << expected << ")" << std::endl;
243 return;
244 }
245
246 manual_params_ = params;
247 use_manual_init_ = kTRUE;
248
249 std::vector<RooRealVar *> all = CollectAllParams();
250 for (size_t i = 0; i < all.size(); i++) {
251 all[i]->setVal(params[i]);
252 all[i]->setConstant(kTRUE);
253 }
254
255 std::cout << "Manual parameters set:" << std::endl;
256 for (size_t i = 0; i < all.size(); i++) {
257 std::cout << " Par[" << i << "] " << all[i]->GetName() << " = "
258 << params[i] << std::endl;
259 }
260}
261
262void RooFitUtils::SetManualParameter(Int_t index, Double_t value) {
263 std::vector<RooRealVar *> all = CollectAllParams();
264 if (index < 0 || index >= (Int_t)all.size()) {
265 std::cerr << "ERROR: Parameter index " << index << " out of range [0, "
266 << (Int_t)all.size() - 1 << "]" << std::endl;
267 return;
268 }
269
270 if (!use_manual_init_) {
271 manual_params_.resize(all.size(), 0.0);
272 use_manual_init_ = kTRUE;
273 }
274
275 manual_params_[index] = value;
276 all[index]->setVal(value);
277 all[index]->setConstant(kTRUE);
278
279 std::cout << "Set Par[" << index << "] " << all[index]->GetName() << " = "
280 << value << std::endl;
281}
282
283Double_t RooFitUtils::EstimateBackground() {
284 Int_t left_bin = working_hist_->FindBin(fit_range_low_);
285 Int_t right_bin = working_hist_->FindBin(fit_range_high_);
286
287 Int_t n_sideband = (right_bin - left_bin) / 10;
288 Double_t left_avg = 0;
289 Double_t right_avg = 0;
290
291 for (Int_t i = 0; i < n_sideband; i++) {
292 left_avg += working_hist_->GetBinContent(left_bin + i);
293 right_avg += working_hist_->GetBinContent(right_bin - i);
294 }
295
296 return (left_avg + right_avg) / (2.0 * n_sideband);
297}
298
299void RooFitUtils::BuildPeak(Int_t peak_idx, Double_t mu_init,
300 Double_t sigma_init, Double_t peak_height,
301 Double_t range_width) {
302 RooFitPeakModel p;
303 TString suffix = TString::Format("%d", peak_idx + 1);
304
305 p.mu = new RooRealVar("Mu" + suffix, "Mu" + suffix, mu_init, fit_range_low_,
306 fit_range_high_);
307 Double_t sigma_lo = working_hist_->GetBinWidth(1);
308 Double_t sigma_hi = range_width * 0.5;
309 if (sigma_init < sigma_lo)
310 sigma_init = 2.0 * sigma_lo;
311 p.sigma = new RooRealVar("Sigma" + suffix, "Sigma" + suffix, sigma_init,
312 sigma_lo, sigma_hi);
313
314 Int_t mu_bin = working_hist_->FindBin(mu_init);
315 Double_t local_height = working_hist_->GetBinContent(mu_bin);
316 Double_t bkg_floor = EstimateBackground();
317 Double_t net_height = local_height - bkg_floor;
318 if (net_height < 0.1 * local_height)
319 net_height = 0.1 * local_height;
320 Double_t total_init =
321 net_height * sigma_init * TMath::Sqrt(2.0 * TMath::Pi());
322 p.gaus_yield =
323 new RooRealVar("GausAmplitude" + suffix, "GausAmplitude" + suffix,
324 total_init, 0, peak_height * range_width * 10.0);
325
326 p.ratio_step = new RooRealVar("StepAmplitude" + suffix,
327 "StepAmplitude" + suffix, 0.0, 0.0, 0.5);
328 p.ratio_low_exp =
329 new RooRealVar("LowExpTailAmplitude" + suffix,
330 "LowExpTailAmplitude" + suffix, 0.0, 0.0, 0.5);
332 new RooRealVar("LowExpTailRatio" + suffix, "LowExpTailRatio" + suffix,
333 1.5, 1.0, tail_ratio_max_);
334 p.ratio_low_lin =
335 new RooRealVar("LowLinTailAmplitude" + suffix,
336 "LowLinTailAmplitude" + suffix, 0.0, 0.0, 0.5);
337 p.slope_low_lin = new RooRealVar("LowLinTailSlope" + suffix,
338 "LowLinTailSlope" + suffix, 0.0, -0.1, 0.1);
340 new RooRealVar("HighExpTailAmplitude" + suffix,
341 "HighExpTailAmplitude" + suffix, 0.0, 0.0, 0.5);
343 new RooRealVar("HighExpTailRatio" + suffix, "HighExpTailRatio" + suffix,
344 1.5, 1.0, tail_ratio_max_);
345
346 RegisterOwned(p.mu);
347 RegisterOwned(p.sigma);
348 RegisterOwned(p.gaus_yield);
349 RegisterOwned(p.ratio_step);
350 RegisterOwned(p.ratio_low_exp);
351 RegisterOwned(p.tau_ratio_low_exp);
352 RegisterOwned(p.ratio_low_lin);
353 RegisterOwned(p.slope_low_lin);
354 RegisterOwned(p.ratio_high_exp);
355 RegisterOwned(p.tau_ratio_high_exp);
356
357 p.gauss_pdf =
358 RooFitFunctions::MakeGaussian("gauss_pdf" + suffix, *x_, *p.mu, *p.sigma);
359 p.step_pdf =
360 RooFitFunctions::MakeStepShelf("step_pdf" + suffix, *x_, *p.mu, *p.sigma);
362 "low_exp_pdf" + suffix, *x_, *p.mu, *p.sigma, *p.tau_ratio_low_exp);
364 "low_lin_pdf" + suffix, *x_, *p.mu, *p.sigma, *p.slope_low_lin);
366 "high_exp_pdf" + suffix, *x_, *p.mu, *p.sigma, *p.tau_ratio_high_exp);
367
368 RegisterOwned(p.gauss_pdf);
369 RegisterOwned(p.step_pdf);
370 RegisterOwned(p.low_exp_pdf);
371 RegisterOwned(p.low_lin_pdf);
372 RegisterOwned(p.high_exp_pdf);
373
374 p.step_yield = new RooFormulaVar("step_yield" + suffix, "@0*@1",
375 RooArgList(*p.gaus_yield, *p.ratio_step));
376 p.low_exp_yield =
377 new RooFormulaVar("low_exp_yield" + suffix, "@0*@1",
378 RooArgList(*p.gaus_yield, *p.ratio_low_exp));
379 p.low_lin_yield =
380 new RooFormulaVar("low_lin_yield" + suffix, "@0*@1",
381 RooArgList(*p.gaus_yield, *p.ratio_low_lin));
383 new RooFormulaVar("high_exp_yield" + suffix, "@0*@1",
384 RooArgList(*p.gaus_yield, *p.ratio_high_exp));
385
386 RegisterOwned(p.step_yield);
387 RegisterOwned(p.low_exp_yield);
388 RegisterOwned(p.low_lin_yield);
389 RegisterOwned(p.high_exp_yield);
390
391 peaks_.push_back(p);
392}
393
394void RooFitUtils::BuildBackground(Double_t bkg_estimate, Double_t peak_height,
395 Double_t range_width) {
396 bkg_.bkg_yield =
397 new RooRealVar("BkgConstant", "BkgConstant", bkg_estimate * range_width,
398 0, peak_height * range_width * 10.0);
399 // RooPolynomial evaluates as 1 + slope*x, so positivity over the fit range
400 // forces slope >= -1/fit_range_high_. No constraint on the positive side, so
401 // let it grow with the fit window instead of mirroring the tight neg bound.
402 Double_t slope_lo = -0.9 / fit_range_high_;
403 Double_t slope_hi = 5.0 / (fit_range_high_ - fit_range_low_);
404 bkg_.bkg_slope =
405 new RooRealVar("BkgSlope", "BkgSlope", 0.0, slope_lo, slope_hi);
406
407 RegisterOwned(bkg_.bkg_yield);
408 RegisterOwned(bkg_.bkg_slope);
409
410 bkg_.bkg_pdf =
411 RooFitFunctions::MakeLinearBackground("bkg_pdf", *x_, *bkg_.bkg_slope);
412 if (use_flat_background_) {
413 bkg_.bkg_slope->setVal(0.0);
414 bkg_.bkg_slope->setConstant(kTRUE);
415 }
416 RegisterOwned(bkg_.bkg_pdf);
417}
418
419void RooFitUtils::BuildTotalModel() {
420 RooArgList pdf_list;
421 RooArgList coef_list;
422 for (size_t pi = 0; pi < peaks_.size(); pi++) {
423 pdf_list.add(*peaks_[pi].gauss_pdf);
424 coef_list.add(*peaks_[pi].gaus_yield);
425 pdf_list.add(*peaks_[pi].step_pdf);
426 coef_list.add(*peaks_[pi].step_yield);
427 pdf_list.add(*peaks_[pi].low_exp_pdf);
428 coef_list.add(*peaks_[pi].low_exp_yield);
429 pdf_list.add(*peaks_[pi].low_lin_pdf);
430 coef_list.add(*peaks_[pi].low_lin_yield);
431 pdf_list.add(*peaks_[pi].high_exp_pdf);
432 coef_list.add(*peaks_[pi].high_exp_yield);
433 }
434 pdf_list.add(*bkg_.bkg_pdf);
435 coef_list.add(*bkg_.bkg_yield);
436
437 total_pdf_ = new RooAddPdf("total_pdf", "total_pdf", pdf_list, coef_list);
438 RegisterOwned(total_pdf_);
439}
440
441void RooFitUtils::ConfigureComponentFlagsForPeak(Int_t peak_idx) {
442 RooFitPeakModel &p = peaks_[peak_idx];
443
444 if (use_step_) {
445 p.ratio_step->setVal(0.0);
446 p.ratio_step->setConstant(kFALSE);
447 } else {
448 p.ratio_step->setVal(0.0);
449 p.ratio_step->setConstant(kTRUE);
450 }
451
452 if (use_low_exp_tail_) {
453 p.ratio_low_exp->setVal(0.1);
454 p.ratio_low_exp->setConstant(kFALSE);
455 p.tau_ratio_low_exp->setVal(1.5);
456 p.tau_ratio_low_exp->setConstant(kFALSE);
457 } else {
458 p.ratio_low_exp->setVal(0.0);
459 p.ratio_low_exp->setConstant(kTRUE);
460 p.tau_ratio_low_exp->setVal(1.0);
461 p.tau_ratio_low_exp->setConstant(kTRUE);
462 }
463
464 if (use_low_lin_tail_) {
465 p.ratio_low_lin->setVal(0.1);
466 p.ratio_low_lin->setConstant(kFALSE);
467 p.slope_low_lin->setVal(0.0);
468 p.slope_low_lin->setConstant(kFALSE);
469 } else {
470 p.ratio_low_lin->setVal(0.0);
471 p.ratio_low_lin->setConstant(kTRUE);
472 p.slope_low_lin->setVal(0.0);
473 p.slope_low_lin->setConstant(kTRUE);
474 }
475
476 if (use_high_exp_tail_) {
477 p.ratio_high_exp->setVal(0.1);
478 p.ratio_high_exp->setConstant(kFALSE);
479 p.tau_ratio_high_exp->setVal(1.5);
480 p.tau_ratio_high_exp->setConstant(kFALSE);
481 } else {
482 p.ratio_high_exp->setVal(0.0);
483 p.ratio_high_exp->setConstant(kTRUE);
484 p.tau_ratio_high_exp->setVal(1.0);
485 p.tau_ratio_high_exp->setConstant(kTRUE);
486 }
487}
488
489void RooFitUtils::FixComponent(Int_t peak_idx, const TString &component) {
490 RooFitPeakModel &p = peaks_[peak_idx];
491 if (component == "step") {
492 p.ratio_step->setVal(0.0);
493 p.ratio_step->setConstant(kTRUE);
494 } else if (component == "low_exp") {
495 p.ratio_low_exp->setVal(0.0);
496 p.ratio_low_exp->setConstant(kTRUE);
497 p.tau_ratio_low_exp->setVal(1.0);
498 p.tau_ratio_low_exp->setConstant(kTRUE);
499 } else if (component == "low_lin") {
500 p.ratio_low_lin->setVal(0.0);
501 p.ratio_low_lin->setConstant(kTRUE);
502 p.slope_low_lin->setVal(0.0);
503 p.slope_low_lin->setConstant(kTRUE);
504 } else if (component == "high_exp") {
505 p.ratio_high_exp->setVal(0.0);
506 p.ratio_high_exp->setConstant(kTRUE);
507 p.tau_ratio_high_exp->setVal(1.0);
508 p.tau_ratio_high_exp->setConstant(kTRUE);
509 }
510}
511
512void RooFitUtils::ReleaseComponent(Int_t peak_idx, const TString &component) {
513 RooFitPeakModel &p = peaks_[peak_idx];
514 if (component == "step") {
515 p.ratio_step->setConstant(kFALSE);
516 p.ratio_step->setVal(0.15);
517 } else if (component == "low_exp") {
518 p.ratio_low_exp->setConstant(kFALSE);
519 p.tau_ratio_low_exp->setConstant(kFALSE);
520 p.ratio_low_exp->setVal(0.15);
521 p.tau_ratio_low_exp->setVal(1.5);
522 } else if (component == "low_lin") {
523 p.ratio_low_lin->setConstant(kFALSE);
524 p.slope_low_lin->setConstant(kFALSE);
525 p.ratio_low_lin->setVal(0.15);
526 p.slope_low_lin->setVal(0.0);
527 } else if (component == "high_exp") {
528 p.ratio_high_exp->setConstant(kFALSE);
529 p.tau_ratio_high_exp->setConstant(kFALSE);
530 p.ratio_high_exp->setVal(0.15);
531 p.tau_ratio_high_exp->setVal(1.5);
532 }
533}
534
535std::vector<RooRealVar *> RooFitUtils::CollectAllParams() {
536 std::vector<RooRealVar *> out;
537 for (size_t pi = 0; pi < peaks_.size(); pi++) {
538 out.push_back(peaks_[pi].mu);
539 out.push_back(peaks_[pi].sigma);
540 out.push_back(peaks_[pi].gaus_yield);
541 out.push_back(peaks_[pi].ratio_step);
542 out.push_back(peaks_[pi].ratio_low_exp);
543 out.push_back(peaks_[pi].tau_ratio_low_exp);
544 out.push_back(peaks_[pi].ratio_low_lin);
545 out.push_back(peaks_[pi].slope_low_lin);
546 out.push_back(peaks_[pi].ratio_high_exp);
547 out.push_back(peaks_[pi].tau_ratio_high_exp);
548 }
549 out.push_back(bkg_.bkg_yield);
550 out.push_back(bkg_.bkg_slope);
551 return out;
552}
553
554std::vector<RooRealVar *> RooFitUtils::CollectFloatingParams() {
555 std::vector<RooRealVar *> all = CollectAllParams();
556 std::vector<RooRealVar *> out;
557 for (size_t i = 0; i < all.size(); i++) {
558 if (!all[i]->isConstant()) {
559 out.push_back(all[i]);
560 }
561 }
562 return out;
563}
564
565RooFitResult *RooFitUtils::RunFit(Bool_t quiet) {
566 Int_t print_level = quiet ? -1 : 0;
567 RooFitResult *result = total_pdf_->fitTo(
568 *unbinned_data_, RooFit::Save(kTRUE), RooFit::Extended(kTRUE),
569 RooFit::Range(kFitRangeName), RooFit::SumW2Error(kFALSE),
570 RooFit::PrintLevel(print_level), RooFit::PrintEvalErrors(-1),
571 RooFit::Strategy(2), RooFit::Minimizer("Minuit2", "migrad"),
573 return result;
574}
575
576Double_t RooFitUtils::ComputeReducedChi2(RooFitResult *fit_result,
577 Int_t &ndof) {
578 RooArgSet nset(*x_);
579 Double_t total_exp = total_pdf_->expectedEvents(&nset);
580 Double_t bin_width = working_hist_->GetBinWidth(1);
581 Double_t saved = x_->getVal();
582
583 Double_t chi2 = 0;
584 Int_t nbins_in_range = 0;
585 Int_t nbins_hist = working_hist_->GetNbinsX();
586 for (Int_t i = 1; i <= nbins_hist; i++) {
587 Double_t xv = working_hist_->GetBinCenter(i);
588 if (xv < fit_range_low_ || xv > fit_range_high_)
589 continue;
590 Double_t data = working_hist_->GetBinContent(i);
591 Double_t error = working_hist_->GetBinError(i);
592 if (error <= 0 || data <= 0)
593 continue;
594 x_->setVal(xv);
595 Double_t fit_val = total_exp * total_pdf_->getVal(&nset) * bin_width;
596 Double_t residual = (data - fit_val) / error;
597 chi2 += residual * residual;
598 nbins_in_range++;
599 }
600 x_->setVal(saved);
601
602 Int_t npars = fit_result ? fit_result->floatParsFinal().size()
603 : (Int_t)CollectFloatingParams().size();
604 ndof = nbins_in_range - npars;
605 if (ndof <= 0)
606 return -1;
607 return chi2 / ndof;
608}
609
610void RooFitUtils::SnapshotParams(std::vector<Double_t> &vals,
611 std::vector<Double_t> &errs,
612 std::vector<Bool_t> &consts) {
613 std::vector<RooRealVar *> all = CollectAllParams();
614 vals.resize(all.size());
615 errs.resize(all.size());
616 consts.resize(all.size());
617 for (size_t i = 0; i < all.size(); i++) {
618 vals[i] = all[i]->getVal();
619 errs[i] = all[i]->getError();
620 consts[i] = all[i]->isConstant();
621 }
622}
623
624void RooFitUtils::RestoreParams(const std::vector<Double_t> &vals,
625 const std::vector<Double_t> &errs,
626 const std::vector<Bool_t> &consts) {
627 std::vector<RooRealVar *> all = CollectAllParams();
628 for (size_t i = 0; i < all.size() && i < vals.size(); i++) {
629 all[i]->setVal(vals[i]);
630 all[i]->setError(errs[i]);
631 all[i]->setConstant(consts[i]);
632 }
633}
634
635void RooFitUtils::TestLowSideGroup(Int_t peak_idx, Double_t &best_chi2,
636 std::vector<Double_t> &best_vals,
637 std::vector<Double_t> &best_errs,
638 std::vector<Bool_t> &best_const) {
639 Bool_t any_low_side = use_step_ || use_low_exp_tail_ || use_low_lin_tail_;
640 if (!any_low_side)
641 return;
642
643 std::cout << "Testing low-side component group for peak " << peak_idx + 1
644 << "..." << std::endl;
645 if (use_step_)
646 ReleaseComponent(peak_idx, "step");
647 if (use_low_exp_tail_)
648 ReleaseComponent(peak_idx, "low_exp");
649 if (use_low_lin_tail_)
650 ReleaseComponent(peak_idx, "low_lin");
651
652 RooFitResult *group_fit = RunFit(kTRUE);
653 Bool_t group_ok = group_fit && group_fit->status() == 0;
654 Int_t tmp_ndof = 0;
655 Double_t chi2_group = group_ok ? ComputeReducedChi2(group_fit, tmp_ndof) : -1;
656 delete group_fit;
657
658 if (group_ok && chi2_group < best_chi2) {
659 std::cout << "Low-side group peak " << peak_idx + 1
660 << " ACCEPTED, pruning..." << std::endl;
661 best_chi2 = chi2_group;
662 SnapshotParams(best_vals, best_errs, best_const);
663
664 const TString comps[3] = {"step", "low_exp", "low_lin"};
665 Bool_t enabled[3] = {use_step_, use_low_exp_tail_, use_low_lin_tail_};
666 for (Int_t ci = 0; ci < 3; ci++) {
667 if (!enabled[ci])
668 continue;
669 FixComponent(peak_idx, comps[ci]);
670 RooFitResult *pf = RunFit(kTRUE);
671 Bool_t ok = pf && pf->status() == 0;
672 Int_t nd = 0;
673 Double_t c2 = ok ? ComputeReducedChi2(pf, nd) : -1;
674 delete pf;
675 if (ok && c2 <= best_chi2) {
676 std::cout << " " << comps[ci] << " peak " << peak_idx + 1 << " pruned"
677 << std::endl;
678 best_chi2 = c2;
679 SnapshotParams(best_vals, best_errs, best_const);
680 } else {
681 std::cout << " " << comps[ci] << " peak " << peak_idx + 1
682 << " retained" << std::endl;
683 ReleaseComponent(peak_idx, comps[ci]);
684 RestoreParams(best_vals, best_errs, best_const);
685 }
686 }
687 } else {
688 std::cout << "Low-side group peak " << peak_idx + 1 << " REJECTED"
689 << std::endl;
690 if (use_step_)
691 FixComponent(peak_idx, "step");
692 if (use_low_exp_tail_)
693 FixComponent(peak_idx, "low_exp");
694 if (use_low_lin_tail_)
695 FixComponent(peak_idx, "low_lin");
696 RestoreParams(best_vals, best_errs, best_const);
697 }
698}
699
700void RooFitUtils::TestHighTailIndependent(Int_t peak_idx, Double_t &best_chi2,
701 std::vector<Double_t> &best_vals,
702 std::vector<Double_t> &best_errs,
703 std::vector<Bool_t> &best_const) {
704 if (!use_high_exp_tail_)
705 return;
706
707 std::cout << "Testing high exponential tail for peak " << peak_idx + 1
708 << "..." << std::endl;
709 ReleaseComponent(peak_idx, "high_exp");
710 RooFitResult *htail_fit = RunFit(kTRUE);
711 Bool_t htail_ok = htail_fit && htail_fit->status() == 0;
712 Int_t tmp_ndof = 0;
713 Double_t chi2_htail = htail_ok ? ComputeReducedChi2(htail_fit, tmp_ndof) : -1;
714 delete htail_fit;
715 if (htail_ok && chi2_htail < best_chi2) {
716 std::cout << "High exp tail peak " << peak_idx + 1 << " ACCEPTED"
717 << std::endl;
718 best_chi2 = chi2_htail;
719 SnapshotParams(best_vals, best_errs, best_const);
720 } else {
721 std::cout << "High exp tail peak " << peak_idx + 1 << " REJECTED"
722 << std::endl;
723 FixComponent(peak_idx, "high_exp");
724 RestoreParams(best_vals, best_errs, best_const);
725 }
726}
727
728PeakFitResult RooFitUtils::ExtractPeakResult(Int_t peak_idx) {
729 RooFitPeakModel &p = peaks_[peak_idx];
730 PeakFitResult result;
731 result.mu = p.mu->getVal();
732 result.mu_error = p.mu->getError();
733 result.sigma = p.sigma->getVal();
734 result.sigma_error = p.sigma->getError();
735 result.gaus_amplitude = p.gaus_yield->getVal();
736 result.gaus_amplitude_error = p.gaus_yield->getError();
737
738 Double_t ga = result.gaus_amplitude;
739 result.step_amplitude = p.ratio_step->getVal() * ga;
740 result.step_amplitude_error = p.ratio_step->getError() * ga;
741 result.low_exp_tail_amplitude = p.ratio_low_exp->getVal() * ga;
742 result.low_exp_tail_amplitude_error = p.ratio_low_exp->getError() * ga;
743 result.low_exp_tail_ratio = p.tau_ratio_low_exp->getVal();
744 result.low_exp_tail_ratio_error = p.tau_ratio_low_exp->getError();
745 result.low_lin_tail_amplitude = p.ratio_low_lin->getVal() * ga;
746 result.low_lin_tail_amplitude_error = p.ratio_low_lin->getError() * ga;
747 result.low_lin_tail_slope = p.slope_low_lin->getVal();
748 result.low_lin_tail_slope_error = p.slope_low_lin->getError();
749 result.high_exp_tail_amplitude = p.ratio_high_exp->getVal() * ga;
750 result.high_exp_tail_amplitude_error = p.ratio_high_exp->getError() * ga;
751 result.high_exp_tail_ratio = p.tau_ratio_high_exp->getVal();
752 result.high_exp_tail_ratio_error = p.tau_ratio_high_exp->getError();
753 return result;
754}
755
756void RooFitUtils::SortPeaksByMu(Int_t num_peaks) {
757 for (Int_t i = 0; i < num_peaks - 1; i++) {
758 for (Int_t j = 0; j < num_peaks - i - 1; j++) {
759 Double_t mu_j = peaks_[j].mu->getVal();
760 Double_t mu_next = peaks_[j + 1].mu->getVal();
761 if (mu_j > mu_next) {
762 std::cout << "Sorting peaks: swapping peak " << j + 1 << " (mu=" << mu_j
763 << ") and peak " << j + 2 << " (mu=" << mu_next << ")"
764 << std::endl;
765 RooFitPeakModel tmp = peaks_[j];
766 peaks_[j] = peaks_[j + 1];
767 peaks_[j + 1] = tmp;
768 }
769 }
770 }
771}
772
773void RooFitUtils::AdoptSavedSimRange(const TString &input_name,
774 const TString &base_label) {
775 if (!interactive_)
776 return;
777 TString filename = PlottingUtils::GetPlotsBaseDir() + "/fits/" + base_label +
778 "_" + input_name + ".simroofits";
779 std::ifstream in(filename.Data());
780 if (!in.is_open())
781 return;
782 std::string token;
783 if (!(in >> token) || token != "RANGE")
784 return;
785 Double_t rlo, rhi;
786 if (!(in >> rlo >> rhi))
787 return;
788 if (!(rlo < rhi))
789 return;
790 for (size_t i = 0; i < sim_channels_.size(); i++) {
791 sim_channels_[i].fit_range_low = rlo;
792 sim_channels_[i].fit_range_high = rhi;
793 delete sim_channels_[i].hist;
794 sim_channels_[i].hist =
795 BuildDisplayHistogramFrom(sim_channels_[i].events, rlo, rhi,
796 sim_channels_[i].display_bin_width_kev);
797 }
798}
799
800void RooFitUtils::AdoptSavedRange(const TString &input_name,
801 const TString &peak_name) {
802 if (!interactive_)
803 return;
804 TString filename = PlottingUtils::GetPlotsBaseDir() + "/fits/" + peak_name +
805 "_" + input_name + ".roofits";
806 std::ifstream in(filename.Data());
807 if (!in.is_open())
808 return;
809 std::string token;
810 if (!(in >> token) || token != "RANGE")
811 return;
812 Double_t rlo, rhi;
813 if (!(in >> rlo >> rhi))
814 return;
815 if (!(rlo < rhi))
816 return;
817 fit_range_low_ = rlo;
818 fit_range_high_ = rhi;
819 BuildDisplayHistogram();
820}
821
822void RooFitUtils::SaveInteractiveParams(const TString &input_name,
823 const TString &peak_name) {
824 TString fits_dir = PlottingUtils::GetPlotsBaseDir() + "/fits";
825 gSystem->mkdir(fits_dir, kTRUE);
826 TString filename = fits_dir + "/" + peak_name + "_" + input_name + ".roofits";
827 std::ofstream out(filename.Data());
828 if (!out.is_open()) {
829 std::cerr << "WARNING: Could not save interactive params to " << filename
830 << std::endl;
831 return;
832 }
833 out << std::setprecision(17);
834 out << "RANGE " << fit_range_low_ << " " << fit_range_high_;
835 out << std::endl;
836 std::vector<RooRealVar *> all = CollectAllParams();
837 for (size_t i = 0; i < all.size(); i++) {
838 out << all[i]->GetName() << " " << all[i]->getVal() << " "
839 << all[i]->getError() << " " << (all[i]->isConstant() ? 1 : 0);
840 out << std::endl;
841 }
842 out.close();
843 std::cout << "Saved interactive params to " << filename << std::endl;
844}
845
846Bool_t RooFitUtils::LoadInteractiveParams(const TString &input_name,
847 const TString &peak_name) {
848 TString filename = PlottingUtils::GetPlotsBaseDir() + "/fits/" + peak_name +
849 "_" + input_name + ".roofits";
850 std::ifstream in(filename.Data());
851 if (!in.is_open())
852 return kFALSE;
853
854 std::vector<RooRealVar *> all = CollectAllParams();
855 std::string token;
856
857 in >> token;
858 if (token == "RANGE") {
859 Double_t rlo, rhi;
860 in >> rlo >> rhi;
861 fit_range_low_ = rlo;
862 fit_range_high_ = rhi;
863 x_->setRange(rlo, rhi);
864 x_->setRange(kFitRangeName, rlo, rhi);
865 BuildDisplayHistogram();
866 }
867
868 // Match by NAME, and never let saved state override MODEL CONFIGURATION
869 std::map<std::string, RooRealVar *> by_name;
870 for (size_t i = 0; i < all.size(); i++)
871 by_name[std::string(all[i]->GetName())] = all[i];
872
873 Double_t value, error;
874 Int_t fixed;
875 Int_t n_set = 0, n_skipped_fixed = 0, n_unknown = 0;
876 while (in >> token >> value >> error >> fixed) {
877 std::map<std::string, RooRealVar *>::iterator it = by_name.find(token);
878 if (it == by_name.end()) {
879 std::cerr << "WARNING: param " << token << " from " << filename
880 << " not found in model" << std::endl;
881 ++n_unknown;
882 continue;
883 }
884 // A parameter the model has already fixed is left alone entirely.
885 if (it->second->isConstant()) {
886 ++n_skipped_fixed;
887 continue;
888 }
889 // Value only. Constness is model configuration, not saved state.
890 (void)fixed;
891 it->second->setVal(value);
892 it->second->setError(error);
893 ++n_set;
894 }
895 in.close();
896
897 if (n_set == 0) {
898 std::cerr << "WARNING: no usable parameters in " << filename << std::endl;
899 return kFALSE;
900 }
901
902 std::cout << "Loaded interactive params from " << filename << std::endl;
903 if (n_skipped_fixed > 0)
904 std::cout << " (" << n_skipped_fixed
905 << " saved params ignored: fixed by model configuration)"
906 << std::endl;
907 if (n_unknown > 0)
908 std::cout << " (" << n_unknown << " saved params not in model)"
909 << std::endl;
910 return kTRUE;
911}
912
913void RooFitUtils::SaveSimInteractiveParams(const TString &input_name,
914 const TString &base_label) {
915 TString fits_dir = PlottingUtils::GetPlotsBaseDir() + "/fits";
916 gSystem->mkdir(fits_dir, kTRUE);
917 TString filename =
918 fits_dir + "/" + base_label + "_" + input_name + ".simroofits";
919 std::ofstream out(filename.Data());
920 if (!out.is_open()) {
921 std::cerr << "WARNING: Could not save sim interactive params to "
922 << filename << std::endl;
923 return;
924 }
925 out << std::setprecision(17);
926 out << "RANGE " << x_->getMin(kFitRangeName) << " "
927 << x_->getMax(kFitRangeName);
928 out << std::endl;
929
930 std::set<RooRealVar *> seen;
931 std::vector<RooRealVar *> ordered;
932 for (size_t ci = 0; ci < sim_channels_.size(); ci++) {
933 const TString &cname = sim_channels_[ci].name;
934 std::vector<RooFitPeakModel> &peaks = sim_channel_peaks_[cname];
935 for (size_t pi = 0; pi < peaks.size(); pi++) {
936 RooFitPeakModel &p = peaks[pi];
937 RooRealVar *vars[10] = {p.mu,
938 p.sigma,
939 p.gaus_yield,
940 p.ratio_step,
947 for (Int_t k = 0; k < 10; k++) {
948 if (seen.insert(vars[k]).second)
949 ordered.push_back(vars[k]);
950 }
951 }
952 RooFitBackgroundModel &bkg = sim_channel_bkg_[cname];
953 if (seen.insert(bkg.bkg_yield).second)
954 ordered.push_back(bkg.bkg_yield);
955 if (seen.insert(bkg.bkg_slope).second)
956 ordered.push_back(bkg.bkg_slope);
957 }
958
959 for (size_t i = 0; i < ordered.size(); i++) {
960 out << ordered[i]->GetName() << " " << ordered[i]->getVal() << " "
961 << ordered[i]->getError() << " " << (ordered[i]->isConstant() ? 1 : 0);
962 out << std::endl;
963 }
964 out.close();
965 std::cout << "Saved sim interactive params to " << filename << std::endl;
966}
967
968Bool_t RooFitUtils::LoadSimInteractiveParams(const TString &input_name,
969 const TString &base_label) {
970 TString filename = PlottingUtils::GetPlotsBaseDir() + "/fits/" + base_label +
971 "_" + input_name + ".simroofits";
972 std::ifstream in(filename.Data());
973 if (!in.is_open())
974 return kFALSE;
975
976 std::map<std::string, RooRealVar *> by_name;
977 for (size_t ci = 0; ci < sim_channels_.size(); ci++) {
978 const TString &cname = sim_channels_[ci].name;
979 std::vector<RooFitPeakModel> &peaks = sim_channel_peaks_[cname];
980 for (size_t pi = 0; pi < peaks.size(); pi++) {
981 RooFitPeakModel &p = peaks[pi];
982 RooRealVar *vars[10] = {p.mu,
983 p.sigma,
984 p.gaus_yield,
985 p.ratio_step,
992 for (Int_t k = 0; k < 10; k++) {
993 by_name[vars[k]->GetName()] = vars[k];
994 }
995 }
996 RooFitBackgroundModel &bkg = sim_channel_bkg_[cname];
997 by_name[bkg.bkg_yield->GetName()] = bkg.bkg_yield;
998 by_name[bkg.bkg_slope->GetName()] = bkg.bkg_slope;
999 }
1000
1001 std::string token;
1002 in >> token;
1003 if (token == "RANGE") {
1004 Double_t rlo, rhi;
1005 in >> rlo >> rhi;
1006 x_->setRange(rlo, rhi);
1007 x_->setRange(kFitRangeName, rlo, rhi);
1008 }
1009
1010 Double_t value, error;
1011 Int_t fixed;
1012 Int_t n_set = 0, n_skipped_fixed = 0;
1013 while (in >> token >> value >> error >> fixed) {
1014 std::map<std::string, RooRealVar *>::iterator it = by_name.find(token);
1015 if (it == by_name.end()) {
1016 std::cerr << "WARNING: param " << token
1017 << " from .simroofits not found in model" << std::endl;
1018 continue;
1019 }
1020 // Never let saved state override MODEL CONFIGURATION.
1021 if (it->second->isConstant()) {
1022 ++n_skipped_fixed;
1023 continue;
1024 }
1025 // Take the VALUE only. Constness is model configuration, not saved state.
1026 (void)fixed;
1027 it->second->setVal(value);
1028 it->second->setError(error);
1029 ++n_set;
1030 }
1031 in.close();
1032
1033 if (n_set == 0) {
1034 std::cerr << "WARNING: no usable parameters in " << filename << std::endl;
1035 return kFALSE;
1036 }
1037
1038 std::cout << "Loaded sim interactive params from " << filename << std::endl;
1039 if (n_skipped_fixed > 0)
1040 std::cout << " (" << n_skipped_fixed
1041 << " saved params ignored: fixed by model configuration)"
1042 << std::endl;
1043 return kTRUE;
1044}
1045
1046void RooFitUtils::AppendPeakGraphs(std::vector<TGraph *> &components,
1047 Int_t peak_idx, Style_t line_style,
1048 RooAbsPdf *background_pdf,
1049 Double_t bkg_yield_val, Int_t npts,
1050 Double_t x_step, Double_t bin_width) {
1051 RooFitPeakModel &p = peaks_[peak_idx];
1052 Width_t line_width = PlottingUtils::GetLineWidth();
1053 RooArgSet nset(*x_);
1054
1055 TGraph *peak_graph = new TGraph(npts);
1056 Double_t gy = p.gaus_yield->getVal();
1057 for (Int_t i = 0; i < npts; i++) {
1058 Double_t xv = fit_range_low_ + i * x_step;
1059 x_->setVal(xv);
1060 Double_t y = gy * p.gauss_pdf->getVal(&nset) * bin_width;
1061 Double_t bkg_v = bkg_yield_val * background_pdf->getVal(&nset) * bin_width;
1062 peak_graph->SetPoint(i, xv, y + bkg_v);
1063 }
1064 peak_graph->SetLineColor(kBlack);
1065 peak_graph->SetLineStyle(line_style);
1066 peak_graph->SetLineWidth(line_width);
1067 components.push_back(peak_graph);
1068
1069 if (TMath::Abs(p.ratio_step->getVal()) > 1e-6) {
1070 TGraph *step_graph = new TGraph(npts);
1071 Double_t sy = p.step_yield->getVal();
1072 for (Int_t i = 0; i < npts; i++) {
1073 Double_t xv = fit_range_low_ + i * x_step;
1074 x_->setVal(xv);
1075 Double_t y = sy * p.step_pdf->getVal(&nset) * bin_width;
1076 Double_t bkg_v =
1077 bkg_yield_val * background_pdf->getVal(&nset) * bin_width;
1078 step_graph->SetPoint(i, xv, y + bkg_v);
1079 }
1080 step_graph->SetLineColor(kGray);
1081 step_graph->SetLineStyle(line_style);
1082 step_graph->SetLineWidth(line_width);
1083 components.push_back(step_graph);
1084 }
1085
1086 if (TMath::Abs(p.ratio_low_exp->getVal()) > 1e-6 ||
1087 TMath::Abs(p.ratio_low_lin->getVal()) > 1e-6) {
1088 TGraph *low_tail_graph = new TGraph(npts);
1089 Double_t lexp_y = p.low_exp_yield->getVal();
1090 Double_t llin_y = p.low_lin_yield->getVal();
1091 for (Int_t i = 0; i < npts; i++) {
1092 Double_t xv = fit_range_low_ + i * x_step;
1093 x_->setVal(xv);
1094 Double_t y_exp = lexp_y * p.low_exp_pdf->getVal(&nset) * bin_width;
1095 Double_t y_lin = llin_y * p.low_lin_pdf->getVal(&nset) * bin_width;
1096 Double_t bkg_v =
1097 bkg_yield_val * background_pdf->getVal(&nset) * bin_width;
1098 low_tail_graph->SetPoint(i, xv, y_exp + y_lin + bkg_v);
1099 }
1100 low_tail_graph->SetLineColor(kRed);
1101 low_tail_graph->SetLineStyle(line_style);
1102 low_tail_graph->SetLineWidth(line_width);
1103 components.push_back(low_tail_graph);
1104 }
1105
1106 if (TMath::Abs(p.ratio_high_exp->getVal()) > 1e-6) {
1107 TGraph *high_tail_graph = new TGraph(npts);
1108 Double_t hexp_y = p.high_exp_yield->getVal();
1109 for (Int_t i = 0; i < npts; i++) {
1110 Double_t xv = fit_range_low_ + i * x_step;
1111 x_->setVal(xv);
1112 Double_t y = hexp_y * p.high_exp_pdf->getVal(&nset) * bin_width;
1113 Double_t bkg_v =
1114 bkg_yield_val * background_pdf->getVal(&nset) * bin_width;
1115 high_tail_graph->SetPoint(i, xv, y + bkg_v);
1116 }
1117 high_tail_graph->SetLineColor(kOrange);
1118 high_tail_graph->SetLineStyle(line_style);
1119 high_tail_graph->SetLineWidth(line_width);
1120 components.push_back(high_tail_graph);
1121 }
1122}
1123
1124void RooFitUtils::PlotFitSinglePeak(const TString input_name,
1125 const TString peak_name,
1126 const TString label) {
1127 Int_t npts = 1000;
1128 Double_t x_step = (fit_range_high_ - fit_range_low_) / (npts - 1);
1129 Width_t line_width = PlottingUtils::GetLineWidth();
1130 Double_t bin_width = working_hist_->GetBinWidth(1);
1131 RooArgSet nset(*x_);
1132
1133 TGraph *total_graph = new TGraph(npts);
1134 Double_t total_exp = total_pdf_->expectedEvents(&nset);
1135 for (Int_t i = 0; i < npts; i++) {
1136 Double_t xv = fit_range_low_ + i * x_step;
1137 x_->setVal(xv);
1138 Double_t y = total_exp * total_pdf_->getVal(&nset) * bin_width;
1139 total_graph->SetPoint(i, xv, y);
1140 }
1141 total_graph->SetLineColor(kAzure);
1142 total_graph->SetLineWidth(line_width);
1143
1144 Double_t bkg_yield_val = bkg_.bkg_yield->getVal();
1145 TGraph *background_graph = new TGraph(npts);
1146 for (Int_t i = 0; i < npts; i++) {
1147 Double_t xv = fit_range_low_ + i * x_step;
1148 x_->setVal(xv);
1149 Double_t y = bkg_yield_val * bkg_.bkg_pdf->getVal(&nset) * bin_width;
1150 background_graph->SetPoint(i, xv, y);
1151 }
1152 background_graph->SetLineColor(kGreen);
1153 background_graph->SetLineWidth(line_width);
1154
1155 std::vector<TGraph *> components;
1156 components.push_back(background_graph);
1157 AppendPeakGraphs(components, 0, 1, bkg_.bkg_pdf, bkg_yield_val, npts, x_step,
1158 bin_width);
1159
1161 working_hist_, total_graph, components, fit_range_low_, fit_range_high_,
1162 peak_name + "_" + input_name, "fits", label, kTRUE);
1163
1164 delete total_graph;
1165 for (Int_t i = 0; i < (Int_t)components.size(); i++) {
1166 delete components[i];
1167 }
1168}
1169
1170void RooFitUtils::PlotFitDoublePeak(const TString input_name,
1171 const TString peak_name,
1172 const TString label) {
1173 Int_t npts = 1000;
1174 Double_t x_step = (fit_range_high_ - fit_range_low_) / (npts - 1);
1175 Width_t line_width = PlottingUtils::GetLineWidth();
1176 Double_t bin_width = working_hist_->GetBinWidth(1);
1177 RooArgSet nset(*x_);
1178
1179 TGraph *total_graph = new TGraph(npts);
1180 Double_t total_exp = total_pdf_->expectedEvents(&nset);
1181 for (Int_t i = 0; i < npts; i++) {
1182 Double_t xv = fit_range_low_ + i * x_step;
1183 x_->setVal(xv);
1184 Double_t y = total_exp * total_pdf_->getVal(&nset) * bin_width;
1185 total_graph->SetPoint(i, xv, y);
1186 }
1187 total_graph->SetLineColor(kAzure);
1188 total_graph->SetLineWidth(line_width);
1189
1190 Double_t bkg_yield_val = bkg_.bkg_yield->getVal();
1191 TGraph *background_graph = new TGraph(npts);
1192 for (Int_t i = 0; i < npts; i++) {
1193 Double_t xv = fit_range_low_ + i * x_step;
1194 x_->setVal(xv);
1195 Double_t y = bkg_yield_val * bkg_.bkg_pdf->getVal(&nset) * bin_width;
1196 background_graph->SetPoint(i, xv, y);
1197 }
1198 background_graph->SetLineColor(kGreen);
1199 background_graph->SetLineWidth(line_width);
1200
1201 std::vector<TGraph *> components;
1202 components.push_back(background_graph);
1203 AppendPeakGraphs(components, 0, 1, bkg_.bkg_pdf, bkg_yield_val, npts, x_step,
1204 bin_width);
1205 AppendPeakGraphs(components, 1, 3, bkg_.bkg_pdf, bkg_yield_val, npts, x_step,
1206 bin_width);
1207
1209 working_hist_, total_graph, components, fit_range_low_, fit_range_high_,
1210 peak_name + "_" + input_name, "fits", label, kTRUE);
1211
1212 delete total_graph;
1213 for (Int_t i = 0; i < (Int_t)components.size(); i++) {
1214 delete components[i];
1215 }
1216}
1217
1218void RooFitUtils::PlotFitTriplePeak(const TString input_name,
1219 const TString peak_name,
1220 const TString label) {
1221 Int_t npts = 1000;
1222 Double_t x_step = (fit_range_high_ - fit_range_low_) / (npts - 1);
1223 Width_t line_width = PlottingUtils::GetLineWidth();
1224 Double_t bin_width = working_hist_->GetBinWidth(1);
1225 RooArgSet nset(*x_);
1226
1227 TGraph *total_graph = new TGraph(npts);
1228 Double_t total_exp = total_pdf_->expectedEvents(&nset);
1229 for (Int_t i = 0; i < npts; i++) {
1230 Double_t xv = fit_range_low_ + i * x_step;
1231 x_->setVal(xv);
1232 Double_t y = total_exp * total_pdf_->getVal(&nset) * bin_width;
1233 total_graph->SetPoint(i, xv, y);
1234 }
1235 total_graph->SetLineColor(kAzure);
1236 total_graph->SetLineWidth(line_width);
1237
1238 Double_t bkg_yield_val = bkg_.bkg_yield->getVal();
1239 TGraph *background_graph = new TGraph(npts);
1240 for (Int_t i = 0; i < npts; i++) {
1241 Double_t xv = fit_range_low_ + i * x_step;
1242 x_->setVal(xv);
1243 Double_t y = bkg_yield_val * bkg_.bkg_pdf->getVal(&nset) * bin_width;
1244 background_graph->SetPoint(i, xv, y);
1245 }
1246 background_graph->SetLineColor(kGreen);
1247 background_graph->SetLineWidth(line_width);
1248
1249 std::vector<TGraph *> components;
1250 components.push_back(background_graph);
1251 AppendPeakGraphs(components, 0, 1, bkg_.bkg_pdf, bkg_yield_val, npts, x_step,
1252 bin_width);
1253 AppendPeakGraphs(components, 1, 3, bkg_.bkg_pdf, bkg_yield_val, npts, x_step,
1254 bin_width);
1255 AppendPeakGraphs(components, 2, 4, bkg_.bkg_pdf, bkg_yield_val, npts, x_step,
1256 bin_width);
1257
1259 working_hist_, total_graph, components, fit_range_low_, fit_range_high_,
1260 peak_name + "_" + input_name, "fits", label, kTRUE);
1261
1262 delete total_graph;
1263 for (Int_t i = 0; i < (Int_t)components.size(); i++) {
1264 delete components[i];
1265 }
1266}
1267
1268FitResult RooFitUtils::FitSinglePeak(const TString input_name,
1269 const TString peak_name) {
1270 FitResult results;
1271 results.peaks.emplace_back();
1272
1273 AdoptSavedRange(input_name, peak_name);
1274
1275 num_peaks_ = 1;
1276 Double_t range_width = fit_range_high_ - fit_range_low_;
1277 Double_t mu_init = (fit_range_low_ + fit_range_high_) / 2;
1278 Double_t sigma_init = range_width * 0.01;
1279 Double_t peak_height =
1280 working_hist_->GetBinContent(working_hist_->GetMaximumBin());
1281 Double_t bkg_estimate = EstimateBackground();
1282
1283 Double_t hist_xmin = working_hist_->GetXaxis()->GetXmin();
1284 Double_t hist_xmax = working_hist_->GetXaxis()->GetXmax();
1285 x_ = new RooRealVar("x", "x", hist_xmin, hist_xmax);
1286 x_->setRange(kFitRangeName, fit_range_low_, fit_range_high_);
1287 RegisterOwned(x_);
1288
1289 BuildPeak(0, mu_init, sigma_init, peak_height, range_width);
1290 BuildBackground(bkg_estimate, peak_height, range_width);
1291 BuildTotalModel();
1292
1293 BuildUnbinnedData();
1294 x_->setRange(fit_range_low_, fit_range_high_);
1295
1296 ConfigureComponentFlagsForPeak(0);
1297
1298 Bool_t fit_valid = kFALSE;
1299 Double_t final_chi2 = 0;
1300 Int_t final_ndof = 0;
1301
1302 if (interactive_) {
1303 if (LoadInteractiveParams(input_name, peak_name)) {
1304 RooFitResult *refit = RunFit(kTRUE);
1305 final_chi2 = ComputeReducedChi2(refit, final_ndof);
1306 std::cout << "Refit from saved params chi2/ndf = " << final_chi2
1307 << std::endl;
1308 fit_valid = kTRUE;
1309 delete refit;
1310 } else {
1311 Bool_t was_batch = gROOT->IsBatch();
1312 gROOT->SetBatch(kFALSE);
1314 working_hist_, &events_, display_bin_width_kev_, total_pdf_, x_,
1315 unbinned_data_, &peaks_, &bkg_, fit_range_low_, fit_range_high_,
1316 peak_name + " / " + input_name)) {
1317 fit_range_low_ = x_->getMin(kFitRangeName);
1318 fit_range_high_ = x_->getMax(kFitRangeName);
1319 BuildDisplayHistogram();
1320 final_chi2 = ComputeReducedChi2(nullptr, final_ndof);
1321 std::cout << "Interactive chi2/ndf = " << final_chi2 << std::endl;
1322 SaveInteractiveParams(input_name, peak_name);
1323 fit_valid = kTRUE;
1324 }
1325 gROOT->SetBatch(was_batch);
1326 }
1327 } else {
1328 FixComponent(0, "step");
1329 FixComponent(0, "low_exp");
1330 FixComponent(0, "low_lin");
1331 FixComponent(0, "high_exp");
1332
1333 if (use_manual_init_) {
1334 std::cout << "Using manually initialized parameters" << std::endl;
1335 std::vector<RooRealVar *> all = CollectAllParams();
1336 for (size_t i = 0; i < manual_params_.size() && i < all.size(); i++) {
1337 all[i]->setVal(manual_params_[i]);
1338 }
1339 }
1340
1341 RooFitResult *initial_fit = RunFit(kTRUE);
1342 if (!initial_fit || initial_fit->status() != 0) {
1343 std::cout << "ERROR: Initial fit failed" << std::endl;
1344 delete initial_fit;
1345 return results;
1346 }
1347
1348 Int_t tmp_ndof = 0;
1349 Double_t best_chi2 = ComputeReducedChi2(initial_fit, tmp_ndof);
1350 std::cout << "Initial chi2/ndf = " << best_chi2 << std::endl;
1351 delete initial_fit;
1352
1353 std::vector<Double_t> best_vals;
1354 std::vector<Double_t> best_errs;
1355 std::vector<Bool_t> best_const;
1356 SnapshotParams(best_vals, best_errs, best_const);
1357
1358 TestLowSideGroup(0, best_chi2, best_vals, best_errs, best_const);
1359 TestHighTailIndependent(0, best_chi2, best_vals, best_errs, best_const);
1360
1361 std::cout << "Final fit with selected components..." << std::endl;
1362 RestoreParams(best_vals, best_errs, best_const);
1363 RooFitResult *final_fit = RunFit(kFALSE);
1364 if (final_fit && final_fit->status() == 0) {
1365 final_chi2 = ComputeReducedChi2(final_fit, final_ndof);
1366 fit_valid = kTRUE;
1367 std::cout << "Final chi2/ndf = " << final_chi2 << std::endl;
1368 }
1369 delete final_fit;
1370 }
1371
1372 if (fit_valid) {
1373 TString chi2label = Form("#chi^{2}/ndf = %.3f", final_chi2);
1374 PlotFitSinglePeak(input_name, peak_name, chi2label);
1375
1376 results.peaks[0] = ExtractPeakResult(0);
1377 results.bkg_constant = bkg_.bkg_yield->getVal();
1378 results.bkg_constant_error = bkg_.bkg_yield->getError();
1379 results.lin_bkg_slope = bkg_.bkg_slope->getVal();
1380 results.lin_bkg_slope_error = bkg_.bkg_slope->getError();
1381 results.reduced_chi2 = final_chi2;
1382 results.valid = kTRUE;
1383 } else {
1384 std::cout << "ERROR: Fit did not converge" << std::endl;
1385 }
1386
1387 return results;
1388}
1389
1390FitResult RooFitUtils::FitDoublePeak(const TString input_name,
1391 const TString peak_name, Double_t mu1_init,
1392 Double_t mu2_init, Bool_t link_sigma) {
1393 FitResult results;
1394 results.peaks.emplace_back();
1395 results.peaks.emplace_back();
1396
1397 AdoptSavedRange(input_name, peak_name);
1398
1399 if (mu1_init > mu2_init) {
1400 std::cout << "WARNING: mu1_init > mu2_init, swapping initial values"
1401 << std::endl;
1402 Double_t tmp = mu1_init;
1403 mu1_init = mu2_init;
1404 mu2_init = tmp;
1405 }
1406
1407 num_peaks_ = 2;
1408 Double_t range_width = fit_range_high_ - fit_range_low_;
1409 Double_t sigma_init = range_width * 0.01;
1410 Double_t peak_height =
1411 working_hist_->GetBinContent(working_hist_->GetMaximumBin());
1412 Double_t bkg_estimate = EstimateBackground();
1413
1414 Double_t hist_xmin = working_hist_->GetXaxis()->GetXmin();
1415 Double_t hist_xmax = working_hist_->GetXaxis()->GetXmax();
1416 x_ = new RooRealVar("x", "x", hist_xmin, hist_xmax);
1417 x_->setRange(kFitRangeName, fit_range_low_, fit_range_high_);
1418 RegisterOwned(x_);
1419
1420 BuildPeak(0, mu1_init, sigma_init, peak_height, range_width);
1421 BuildPeak(1, mu2_init, sigma_init, peak_height, range_width);
1422
1423 // Share one Gaussian width across the doublet: rebuild peak 2's PDFs to use
1424 // peak 1's sigma var, then freeze Sigma2 so it neither floats nor is saved as
1425 // an independent width. Sigma2 stays in the peak struct (param count, save/
1426 // load, and CollectAllParams contracts unchanged) but is inert.
1427 if (link_sigma) {
1428 RooRealVar *s = peaks_[0].sigma;
1429 RooFitPeakModel &p = peaks_[1];
1430 p.sigma->setVal(s->getVal());
1431 p.sigma->setConstant(kTRUE);
1432 p.gauss_pdf =
1433 RooFitFunctions::MakeGaussian("gauss_pdf2_linked", *x_, *p.mu, *s);
1434 p.step_pdf =
1435 RooFitFunctions::MakeStepShelf("step_pdf2_linked", *x_, *p.mu, *s);
1437 "low_exp_pdf2_linked", *x_, *p.mu, *s, *p.tau_ratio_low_exp);
1439 "low_lin_pdf2_linked", *x_, *p.mu, *s, *p.slope_low_lin);
1441 "high_exp_pdf2_linked", *x_, *p.mu, *s, *p.tau_ratio_high_exp);
1442 RegisterOwned(p.gauss_pdf);
1443 RegisterOwned(p.step_pdf);
1444 RegisterOwned(p.low_exp_pdf);
1445 RegisterOwned(p.low_lin_pdf);
1446 RegisterOwned(p.high_exp_pdf);
1447 }
1448
1449 BuildBackground(bkg_estimate, peak_height, range_width);
1450 BuildTotalModel();
1451
1452 Double_t mu_midpoint = 0.5 * (mu1_init + mu2_init);
1453 peaks_[0].mu->setMax(mu_midpoint);
1454 peaks_[1].mu->setMin(mu_midpoint);
1455
1456 BuildUnbinnedData();
1457 x_->setRange(fit_range_low_, fit_range_high_);
1458
1459 ConfigureComponentFlagsForPeak(0);
1460 ConfigureComponentFlagsForPeak(1);
1461
1462 Bool_t fit_valid = kFALSE;
1463 Double_t final_chi2 = 0;
1464 Int_t final_ndof = 0;
1465
1466 if (interactive_) {
1467 if (LoadInteractiveParams(input_name, peak_name)) {
1468 RooFitResult *refit = RunFit(kTRUE);
1469 final_chi2 = ComputeReducedChi2(refit, final_ndof);
1470 std::cout << "Refit from saved params chi2/ndf = " << final_chi2
1471 << std::endl;
1472 fit_valid = kTRUE;
1473 delete refit;
1474 } else {
1475 Bool_t was_batch = gROOT->IsBatch();
1476 gROOT->SetBatch(kFALSE);
1478 working_hist_, &events_, display_bin_width_kev_, total_pdf_, x_,
1479 unbinned_data_, &peaks_, &bkg_, fit_range_low_, fit_range_high_,
1480 peak_name + " / " + input_name)) {
1481 fit_range_low_ = x_->getMin(kFitRangeName);
1482 fit_range_high_ = x_->getMax(kFitRangeName);
1483 BuildDisplayHistogram();
1484 final_chi2 = ComputeReducedChi2(nullptr, final_ndof);
1485 std::cout << "Interactive chi2/ndf = " << final_chi2 << std::endl;
1486 SaveInteractiveParams(input_name, peak_name);
1487 fit_valid = kTRUE;
1488 }
1489 gROOT->SetBatch(was_batch);
1490 }
1491 } else {
1492 FixComponent(0, "step");
1493 FixComponent(0, "low_exp");
1494 FixComponent(0, "low_lin");
1495 FixComponent(0, "high_exp");
1496 FixComponent(1, "step");
1497 FixComponent(1, "low_exp");
1498 FixComponent(1, "low_lin");
1499 FixComponent(1, "high_exp");
1500
1501 RooFitResult *initial_fit = RunFit(kTRUE);
1502 if (!initial_fit || initial_fit->status() != 0) {
1503 std::cout << "ERROR: Initial double peak fit failed" << std::endl;
1504 delete initial_fit;
1505 return results;
1506 }
1507
1508 Int_t tmp_ndof = 0;
1509 Double_t best_chi2 = ComputeReducedChi2(initial_fit, tmp_ndof);
1510 std::cout << "Initial chi2/ndf = " << best_chi2 << std::endl;
1511 delete initial_fit;
1512
1513 std::vector<Double_t> best_vals;
1514 std::vector<Double_t> best_errs;
1515 std::vector<Bool_t> best_const;
1516 SnapshotParams(best_vals, best_errs, best_const);
1517
1518 TestLowSideGroup(0, best_chi2, best_vals, best_errs, best_const);
1519 TestHighTailIndependent(1, best_chi2, best_vals, best_errs, best_const);
1520
1521 {
1522 std::cout
1523 << "Testing inter-peak group (peak1 high tail + peak2 low-side)..."
1524 << std::endl;
1525 if (use_high_exp_tail_)
1526 ReleaseComponent(0, "high_exp");
1527 if (use_step_)
1528 ReleaseComponent(1, "step");
1529 if (use_low_exp_tail_)
1530 ReleaseComponent(1, "low_exp");
1531 if (use_low_lin_tail_)
1532 ReleaseComponent(1, "low_lin");
1533
1534 RooFitResult *group_fit = RunFit(kTRUE);
1535 Bool_t ok = group_fit && group_fit->status() == 0;
1536 Int_t nd = 0;
1537 Double_t c2 = ok ? ComputeReducedChi2(group_fit, nd) : -1;
1538 delete group_fit;
1539
1540 if (ok && c2 < best_chi2) {
1541 std::cout << "Inter-peak group ACCEPTED, pruning..." << std::endl;
1542 best_chi2 = c2;
1543 SnapshotParams(best_vals, best_errs, best_const);
1544
1545 struct CompRef {
1546 Int_t peak_idx;
1547 TString comp;
1548 Bool_t enabled;
1549 };
1550 CompRef refs[4] = {
1551 {0, "high_exp", use_high_exp_tail_},
1552 {1, "step", use_step_},
1553 {1, "low_exp", use_low_exp_tail_},
1554 {1, "low_lin", use_low_lin_tail_},
1555 };
1556 for (Int_t ri = 0; ri < 4; ri++) {
1557 if (!refs[ri].enabled)
1558 continue;
1559 FixComponent(refs[ri].peak_idx, refs[ri].comp);
1560 RooFitResult *pf = RunFit(kTRUE);
1561 Bool_t pok = pf && pf->status() == 0;
1562 Int_t pnd = 0;
1563 Double_t pc2 = pok ? ComputeReducedChi2(pf, pnd) : -1;
1564 delete pf;
1565 if (pok && pc2 <= best_chi2) {
1566 std::cout << " " << refs[ri].comp << " peak "
1567 << refs[ri].peak_idx + 1 << " pruned" << std::endl;
1568 best_chi2 = pc2;
1569 SnapshotParams(best_vals, best_errs, best_const);
1570 } else {
1571 std::cout << " " << refs[ri].comp << " peak "
1572 << refs[ri].peak_idx + 1 << " retained" << std::endl;
1573 ReleaseComponent(refs[ri].peak_idx, refs[ri].comp);
1574 RestoreParams(best_vals, best_errs, best_const);
1575 }
1576 }
1577 } else {
1578 std::cout << "Inter-peak group REJECTED" << std::endl;
1579 if (use_high_exp_tail_)
1580 FixComponent(0, "high_exp");
1581 if (use_step_)
1582 FixComponent(1, "step");
1583 if (use_low_exp_tail_)
1584 FixComponent(1, "low_exp");
1585 if (use_low_lin_tail_)
1586 FixComponent(1, "low_lin");
1587 RestoreParams(best_vals, best_errs, best_const);
1588 }
1589 }
1590
1591 std::cout << "Final fit with selected components..." << std::endl;
1592 RestoreParams(best_vals, best_errs, best_const);
1593 RooFitResult *final_fit = RunFit(kFALSE);
1594 if (final_fit && final_fit->status() == 0) {
1595 final_chi2 = ComputeReducedChi2(final_fit, final_ndof);
1596 fit_valid = kTRUE;
1597 std::cout << "Double peak fit converged successfully" << std::endl;
1598 std::cout << "Final chi2/ndf = " << final_chi2 << std::endl;
1599 } else {
1600 std::cout << "ERROR: Double peak fit failed to converge" << std::endl;
1601 }
1602 delete final_fit;
1603 }
1604
1605 if (fit_valid) {
1606 SortPeaksByMu(2);
1607 TString chi2label = Form("#chi^{2}/ndf = %.3f", final_chi2);
1608 PlotFitDoublePeak(input_name, peak_name, chi2label);
1609
1610 results.peaks[0] = ExtractPeakResult(0);
1611 results.peaks[1] = ExtractPeakResult(1);
1612 results.bkg_constant = bkg_.bkg_yield->getVal();
1613 results.bkg_constant_error = bkg_.bkg_yield->getError();
1614 results.lin_bkg_slope = bkg_.bkg_slope->getVal();
1615 results.lin_bkg_slope_error = bkg_.bkg_slope->getError();
1616 results.reduced_chi2 = final_chi2;
1617 results.valid = kTRUE;
1618 } else {
1619 std::cout << "ERROR: Double peak fit failed" << std::endl;
1620 }
1621
1622 return results;
1623}
1624
1625FitResult RooFitUtils::FitDoublePeak(const TString input_name,
1626 const TString peak_name,
1627 const PeakFitResult &constrained_peak,
1628 Double_t mu2_init) {
1629 FitResult results;
1630 results.peaks.emplace_back();
1631 results.peaks.emplace_back();
1632
1633 AdoptSavedRange(input_name, peak_name);
1634
1635 num_peaks_ = 2;
1636 Double_t range_width = fit_range_high_ - fit_range_low_;
1637 Double_t sigma_init = range_width * 0.01;
1638 Double_t peak_height =
1639 working_hist_->GetBinContent(working_hist_->GetMaximumBin());
1640 Double_t bkg_estimate = EstimateBackground();
1641
1642 Double_t hist_xmin = working_hist_->GetXaxis()->GetXmin();
1643 Double_t hist_xmax = working_hist_->GetXaxis()->GetXmax();
1644 x_ = new RooRealVar("x", "x", hist_xmin, hist_xmax);
1645 x_->setRange(kFitRangeName, fit_range_low_, fit_range_high_);
1646 RegisterOwned(x_);
1647
1648 BuildPeak(0, constrained_peak.mu, constrained_peak.sigma, peak_height,
1649 range_width);
1650 BuildPeak(1, mu2_init, sigma_init, peak_height, range_width);
1651 BuildBackground(bkg_estimate, peak_height, range_width);
1652 BuildTotalModel();
1653
1654 BuildUnbinnedData();
1655 x_->setRange(fit_range_low_, fit_range_high_);
1656
1657 {
1658 RooFitPeakModel &p = peaks_[0];
1659 Double_t cga = constrained_peak.gaus_amplitude;
1660 p.mu->setVal(constrained_peak.mu);
1661 p.mu->setConstant(kTRUE);
1662 p.sigma->setVal(constrained_peak.sigma);
1663 p.sigma->setConstant(kTRUE);
1664 p.gaus_yield->setVal(cga);
1665 p.gaus_yield->setRange(0, peak_height * range_width * 10.0);
1666 p.gaus_yield->setConstant(kFALSE);
1667 p.ratio_step->setVal(constrained_peak.step_amplitude / cga);
1668 p.ratio_step->setConstant(kTRUE);
1669 p.ratio_low_exp->setVal(constrained_peak.low_exp_tail_amplitude / cga);
1670 p.ratio_low_exp->setConstant(kTRUE);
1671 p.tau_ratio_low_exp->setVal(constrained_peak.low_exp_tail_ratio > 0
1672 ? constrained_peak.low_exp_tail_ratio
1673 : 1.0);
1674 p.tau_ratio_low_exp->setConstant(kTRUE);
1675 p.ratio_low_lin->setVal(constrained_peak.low_lin_tail_amplitude / cga);
1676 p.ratio_low_lin->setConstant(kTRUE);
1677 p.slope_low_lin->setVal(constrained_peak.low_lin_tail_slope);
1678 p.slope_low_lin->setConstant(kTRUE);
1679 p.ratio_high_exp->setVal(constrained_peak.high_exp_tail_amplitude / cga);
1680 p.ratio_high_exp->setConstant(kTRUE);
1681 p.tau_ratio_high_exp->setVal(constrained_peak.high_exp_tail_ratio > 0
1682 ? constrained_peak.high_exp_tail_ratio
1683 : 1.0);
1684 p.tau_ratio_high_exp->setConstant(kTRUE);
1685 }
1686 ConfigureComponentFlagsForPeak(1);
1687
1688 Bool_t fit_valid = kFALSE;
1689 Double_t final_chi2 = 0;
1690 Int_t final_ndof = 0;
1691
1692 if (interactive_) {
1693 if (LoadInteractiveParams(input_name, peak_name)) {
1694 RooFitResult *refit = RunFit(kTRUE);
1695 final_chi2 = ComputeReducedChi2(refit, final_ndof);
1696 fit_valid = kTRUE;
1697 delete refit;
1698 } else {
1699 Bool_t was_batch = gROOT->IsBatch();
1700 gROOT->SetBatch(kFALSE);
1702 working_hist_, &events_, display_bin_width_kev_, total_pdf_, x_,
1703 unbinned_data_, &peaks_, &bkg_, fit_range_low_, fit_range_high_,
1704 peak_name + " / " + input_name)) {
1705 fit_range_low_ = x_->getMin(kFitRangeName);
1706 fit_range_high_ = x_->getMax(kFitRangeName);
1707 BuildDisplayHistogram();
1708 final_chi2 = ComputeReducedChi2(nullptr, final_ndof);
1709 SaveInteractiveParams(input_name, peak_name);
1710 fit_valid = kTRUE;
1711 }
1712 gROOT->SetBatch(was_batch);
1713 }
1714 } else {
1715 FixComponent(1, "step");
1716 FixComponent(1, "low_exp");
1717 FixComponent(1, "low_lin");
1718 FixComponent(1, "high_exp");
1719
1720 RooFitResult *initial_fit = RunFit(kTRUE);
1721 if (!initial_fit || initial_fit->status() != 0) {
1722 std::cout << "ERROR: Initial constrained double peak fit failed"
1723 << std::endl;
1724 delete initial_fit;
1725 return results;
1726 }
1727
1728 Int_t tmp_ndof = 0;
1729 Double_t best_chi2 = ComputeReducedChi2(initial_fit, tmp_ndof);
1730 std::cout << "Initial chi2/ndf = " << best_chi2 << std::endl;
1731 delete initial_fit;
1732
1733 std::vector<Double_t> best_vals;
1734 std::vector<Double_t> best_errs;
1735 std::vector<Bool_t> best_const;
1736 SnapshotParams(best_vals, best_errs, best_const);
1737
1738 TestLowSideGroup(1, best_chi2, best_vals, best_errs, best_const);
1739 TestHighTailIndependent(1, best_chi2, best_vals, best_errs, best_const);
1740
1741 std::cout << "Final fit with selected components..." << std::endl;
1742 RestoreParams(best_vals, best_errs, best_const);
1743 RooFitResult *final_fit = RunFit(kFALSE);
1744 if (final_fit && final_fit->status() == 0) {
1745 final_chi2 = ComputeReducedChi2(final_fit, final_ndof);
1746 fit_valid = kTRUE;
1747 std::cout << "Final chi2/ndf = " << final_chi2 << std::endl;
1748 } else {
1749 std::cout << "ERROR: Constrained double peak fit failed" << std::endl;
1750 }
1751 delete final_fit;
1752 }
1753
1754 if (fit_valid) {
1755 SortPeaksByMu(2);
1756 TString chi2label = Form("#chi^{2}/ndf = %.3f", final_chi2);
1757 PlotFitDoublePeak(input_name, peak_name, chi2label);
1758
1759 results.peaks[0] = ExtractPeakResult(0);
1760 results.peaks[1] = ExtractPeakResult(1);
1761 results.bkg_constant = bkg_.bkg_yield->getVal();
1762 results.bkg_constant_error = bkg_.bkg_yield->getError();
1763 results.lin_bkg_slope = bkg_.bkg_slope->getVal();
1764 results.lin_bkg_slope_error = bkg_.bkg_slope->getError();
1765 results.reduced_chi2 = final_chi2;
1766 results.valid = kTRUE;
1767 }
1768
1769 return results;
1770}
1771
1772FitResult RooFitUtils::FitTriplePeak(const TString input_name,
1773 const TString peak_name,
1774 const FitResult &constrained_peaks,
1775 Double_t mu3_init) {
1776 FitResult results;
1777 results.peaks.emplace_back();
1778 results.peaks.emplace_back();
1779 results.peaks.emplace_back();
1780
1781 AdoptSavedRange(input_name, peak_name);
1782
1783 num_peaks_ = 3;
1784 Double_t range_width = fit_range_high_ - fit_range_low_;
1785 Double_t sigma_init = range_width * 0.01;
1786 Double_t peak_height =
1787 working_hist_->GetBinContent(working_hist_->GetMaximumBin());
1788 Double_t bkg_estimate = EstimateBackground();
1789
1790 Double_t hist_xmin = working_hist_->GetXaxis()->GetXmin();
1791 Double_t hist_xmax = working_hist_->GetXaxis()->GetXmax();
1792 x_ = new RooRealVar("x", "x", hist_xmin, hist_xmax);
1793 x_->setRange(kFitRangeName, fit_range_low_, fit_range_high_);
1794 RegisterOwned(x_);
1795
1796 for (Int_t pi = 0; pi < 2; pi++) {
1797 const PeakFitResult &cp = constrained_peaks.peaks[pi];
1798 BuildPeak(pi, cp.mu, cp.sigma, peak_height, range_width);
1799 }
1800 BuildPeak(2, mu3_init, sigma_init, peak_height, range_width);
1801 BuildBackground(bkg_estimate, peak_height, range_width);
1802 BuildTotalModel();
1803
1804 BuildUnbinnedData();
1805 x_->setRange(fit_range_low_, fit_range_high_);
1806
1807 for (Int_t pi = 0; pi < 2; pi++) {
1808 const PeakFitResult &cp = constrained_peaks.peaks[pi];
1809 RooFitPeakModel &p = peaks_[pi];
1810 Double_t cga = cp.gaus_amplitude;
1811 p.mu->setVal(cp.mu);
1812 p.mu->setConstant(kTRUE);
1813 p.sigma->setVal(cp.sigma);
1814 p.sigma->setConstant(kTRUE);
1815 p.gaus_yield->setVal(cga);
1816 p.gaus_yield->setRange(0, peak_height * range_width * 10.0);
1817 p.gaus_yield->setConstant(kFALSE);
1818 p.ratio_step->setVal(cp.step_amplitude / cga);
1819 p.ratio_step->setConstant(kTRUE);
1820 p.ratio_low_exp->setVal(cp.low_exp_tail_amplitude / cga);
1821 p.ratio_low_exp->setConstant(kTRUE);
1822 p.tau_ratio_low_exp->setVal(
1823 cp.low_exp_tail_ratio > 0 ? cp.low_exp_tail_ratio : 1.0);
1824 p.tau_ratio_low_exp->setConstant(kTRUE);
1825 p.ratio_low_lin->setVal(cp.low_lin_tail_amplitude / cga);
1826 p.ratio_low_lin->setConstant(kTRUE);
1827 p.slope_low_lin->setVal(cp.low_lin_tail_slope);
1828 p.slope_low_lin->setConstant(kTRUE);
1829 p.ratio_high_exp->setVal(cp.high_exp_tail_amplitude / cga);
1830 p.ratio_high_exp->setConstant(kTRUE);
1831 p.tau_ratio_high_exp->setVal(
1832 cp.high_exp_tail_ratio > 0 ? cp.high_exp_tail_ratio : 1.0);
1833 p.tau_ratio_high_exp->setConstant(kTRUE);
1834 }
1835 ConfigureComponentFlagsForPeak(2);
1836
1837 Bool_t fit_valid = kFALSE;
1838 Double_t final_chi2 = 0;
1839 Int_t final_ndof = 0;
1840
1841 if (interactive_) {
1842 if (LoadInteractiveParams(input_name, peak_name)) {
1843 RooFitResult *refit = RunFit(kTRUE);
1844 final_chi2 = ComputeReducedChi2(refit, final_ndof);
1845 fit_valid = kTRUE;
1846 delete refit;
1847 } else {
1848 Bool_t was_batch = gROOT->IsBatch();
1849 gROOT->SetBatch(kFALSE);
1851 working_hist_, &events_, display_bin_width_kev_, total_pdf_, x_,
1852 unbinned_data_, &peaks_, &bkg_, fit_range_low_, fit_range_high_,
1853 peak_name + " / " + input_name)) {
1854 fit_range_low_ = x_->getMin(kFitRangeName);
1855 fit_range_high_ = x_->getMax(kFitRangeName);
1856 BuildDisplayHistogram();
1857 final_chi2 = ComputeReducedChi2(nullptr, final_ndof);
1858 SaveInteractiveParams(input_name, peak_name);
1859 fit_valid = kTRUE;
1860 }
1861 gROOT->SetBatch(was_batch);
1862 }
1863 } else {
1864 FixComponent(2, "step");
1865 FixComponent(2, "low_exp");
1866 FixComponent(2, "low_lin");
1867 FixComponent(2, "high_exp");
1868
1869 RooFitResult *initial_fit = RunFit(kTRUE);
1870 if (!initial_fit || initial_fit->status() != 0) {
1871 std::cout << "ERROR: Initial triple peak fit failed" << std::endl;
1872 delete initial_fit;
1873 return results;
1874 }
1875
1876 Int_t tmp_ndof = 0;
1877 Double_t best_chi2 = ComputeReducedChi2(initial_fit, tmp_ndof);
1878 std::cout << "Initial chi2/ndf = " << best_chi2 << std::endl;
1879 delete initial_fit;
1880
1881 std::vector<Double_t> best_vals;
1882 std::vector<Double_t> best_errs;
1883 std::vector<Bool_t> best_const;
1884 SnapshotParams(best_vals, best_errs, best_const);
1885
1886 TestLowSideGroup(2, best_chi2, best_vals, best_errs, best_const);
1887 TestHighTailIndependent(2, best_chi2, best_vals, best_errs, best_const);
1888
1889 std::cout << "Final fit with selected components..." << std::endl;
1890 RestoreParams(best_vals, best_errs, best_const);
1891 RooFitResult *final_fit = RunFit(kFALSE);
1892 if (final_fit && final_fit->status() == 0) {
1893 final_chi2 = ComputeReducedChi2(final_fit, final_ndof);
1894 fit_valid = kTRUE;
1895 std::cout << "Triple peak fit converged successfully" << std::endl;
1896 std::cout << "Final chi2/ndf = " << final_chi2 << std::endl;
1897 } else {
1898 std::cout << "ERROR: Triple peak fit failed to converge" << std::endl;
1899 }
1900 delete final_fit;
1901 }
1902
1903 if (fit_valid) {
1904 SortPeaksByMu(3);
1905 TString chi2label = Form("#chi^{2}/ndf = %.3f", final_chi2);
1906 PlotFitTriplePeak(input_name, peak_name, chi2label);
1907
1908 results.peaks[0] = ExtractPeakResult(0);
1909 results.peaks[1] = ExtractPeakResult(1);
1910 results.peaks[2] = ExtractPeakResult(2);
1911 results.bkg_constant = bkg_.bkg_yield->getVal();
1912 results.bkg_constant_error = bkg_.bkg_yield->getError();
1913 results.lin_bkg_slope = bkg_.bkg_slope->getVal();
1914 results.lin_bkg_slope_error = bkg_.bkg_slope->getError();
1915 results.reduced_chi2 = final_chi2;
1916 results.valid = kTRUE;
1917 }
1918
1919 return results;
1920}
1921
1922void RooFitUtils::ConstrainPeakSeparation(const TString &channel, Int_t peak_hi,
1923 Int_t peak_lo, Double_t delta,
1924 Double_t sigma) {
1925 if (!(sigma > 0)) {
1926 std::cerr << "ERROR: ConstrainPeakSeparation needs sigma > 0 (got " << sigma
1927 << "); a hard equality would have to fix a centroid instead."
1928 << std::endl;
1929 return;
1930 }
1932 c.channel = channel;
1933 c.peak_hi = peak_hi;
1934 c.peak_lo = peak_lo;
1935 c.delta = delta;
1936 c.sigma = sigma;
1937 sim_sep_constraints_.push_back(c);
1938}
1939
1940// Realise the requested separation constraints against the built channels. Must
1941// run after BuildChannelModel has populated sim_channel_peaks_, since the mu
1942// RooRealVars do not exist before that.
1943void RooFitUtils::BuildSeparationConstraints() {
1944 sim_constraint_set_.removeAll();
1945 for (size_t i = 0; i < sim_sep_constraints_.size(); i++) {
1946 const RooFitSeparationConstraint &c = sim_sep_constraints_[i];
1947 std::map<TString, std::vector<RooFitPeakModel>>::iterator it =
1948 sim_channel_peaks_.find(c.channel);
1949 if (it == sim_channel_peaks_.end()) {
1950 std::cerr << "WARNING: separation constraint names unknown channel '"
1951 << c.channel << "'; ignored." << std::endl;
1952 continue;
1953 }
1954 const std::vector<RooFitPeakModel> &pk = it->second;
1955 if (c.peak_hi < 0 || c.peak_lo < 0 || c.peak_hi >= (Int_t)pk.size() ||
1956 c.peak_lo >= (Int_t)pk.size()) {
1957 std::cerr << "WARNING: separation constraint on '" << c.channel
1958 << "' names peaks " << c.peak_lo << "," << c.peak_hi
1959 << " but the channel has " << pk.size() << "; ignored."
1960 << std::endl;
1961 continue;
1962 }
1963 RooRealVar *mu_hi = pk[c.peak_hi].mu;
1964 RooRealVar *mu_lo = pk[c.peak_lo].mu;
1965 if (!mu_hi || !mu_lo)
1966 continue;
1967 // A linked doublet can resolve both mus to the SAME RooRealVar, in which
1968 // case the separation is identically zero and no constraint is meaningful.
1969 if (mu_hi == mu_lo) {
1970 std::cerr << "WARNING: separation constraint on '" << c.channel
1971 << "' targets two peaks sharing one mu; ignored." << std::endl;
1972 continue;
1973 }
1974 TString base =
1975 c.channel + TString::Format("_sep%d%d", c.peak_hi, c.peak_lo);
1976 // Constrain mu_hi to (mu_lo + delta) rather than building a pdf in the
1977 // DIFFERENCE. RooFit normalises an external constraint over its observable,
1978 // and a RooFormulaVar is not something it can integrate: a Gaussian in
1979 // (mu_hi - mu_lo) leaves the normalisation ill-defined and the minimiser
1980 // returns covQual 0 with edm exactly 0, so every fit is then discarded by
1981 // the validity gate. Putting the formula in the MEAN keeps the observable a
1982 // genuine RooRealVar while stating identical physics.
1983 RooFormulaVar *target = new RooFormulaVar(
1984 base + "_target", TString::Format("@0 + %.10g", c.delta),
1985 RooArgList(*mu_lo));
1986 RooRealVar *width = new RooRealVar(base + "_sigma", "", c.sigma);
1987 width->setConstant(kTRUE);
1988 RooGaussian *pen =
1989 new RooGaussian(base + "_pdf", "", *mu_hi, *target, *width);
1990 RegisterOwned(target);
1991 RegisterOwned(width);
1992 RegisterOwned(pen);
1993 sim_constraint_set_.add(*pen);
1994 std::cout << "Separation constraint on " << c.channel << ": mu" << c.peak_hi
1995 << " - mu" << c.peak_lo << " = " << c.delta << " +/- " << c.sigma
1996 << std::endl;
1997 }
1998}
1999
2000TString RooFitUtils::ParamFullName(const TString &channel,
2001 const TString &param) {
2002 return channel + ":" + param;
2003}
2004
2005TString RooFitUtils::SourceForTarget(const TString &target) {
2006 for (size_t i = 0; i < sim_links_.size(); i++) {
2007 TString full_target =
2008 ParamFullName(sim_links_[i].target_channel, sim_links_[i].target_param);
2009 if (full_target == target) {
2010 return ParamFullName(sim_links_[i].source_channel,
2011 sim_links_[i].source_param);
2012 }
2013 }
2014 return "";
2015}
2016
2017RooRealVar *
2018RooFitUtils::ResolveOrCreate(const TString &channel, const TString &param_name,
2019 std::map<TString, RooRealVar *> &registry,
2020 Double_t init_val, Double_t lo, Double_t hi) {
2021 TString full = ParamFullName(channel, param_name);
2022 TString source = SourceForTarget(full);
2023 if (source.Length() > 0) {
2024 std::map<TString, RooRealVar *>::iterator it = registry.find(source);
2025 if (it == registry.end()) {
2026 std::cerr << "ERROR: source param " << source << " for link target "
2027 << full << " not yet built; check channel order." << std::endl;
2028 return nullptr;
2029 }
2030 registry[full] = it->second;
2031 return it->second;
2032 }
2033 RooRealVar *v = new RooRealVar(full.Data(), full.Data(), init_val, lo, hi);
2034 RegisterOwned(v);
2035 registry[full] = v;
2036 return v;
2037}
2038
2040 const TString &name, const std::vector<Double_t> &events,
2041 Float_t fit_range_low, Float_t fit_range_high,
2042 Float_t display_bin_width_kev, Int_t num_peaks,
2043 const std::vector<Double_t> &mu_inits, Bool_t use_flat_background,
2044 Bool_t use_step, Bool_t use_low_exp_tail, Bool_t use_low_lin_tail,
2045 Bool_t use_high_exp_tail, const std::vector<Bool_t> &mu_fixed,
2046 Bool_t bkg_yield_fixed, Bool_t bkg_slope_fixed,
2047 Bool_t lock_shape_after_seed, const std::vector<Bool_t> &use_step_per_peak,
2048 const std::vector<Bool_t> &shape_lock_per_peak) {
2049 if (!sim_mode_) {
2050 std::cerr << "ERROR: AddChannel called on a single-channel RooFitUtils "
2051 "instance; construct with the default ctor for sim mode."
2052 << std::endl;
2053 return;
2054 }
2055 if ((Int_t)mu_inits.size() != num_peaks) {
2056 std::cerr << "ERROR: mu_inits size (" << mu_inits.size()
2057 << ") must match num_peaks (" << num_peaks << ")" << std::endl;
2058 return;
2059 }
2060 if (!mu_fixed.empty() && (Int_t)mu_fixed.size() != num_peaks) {
2061 std::cerr << "ERROR: mu_fixed size (" << mu_fixed.size()
2062 << ") must match num_peaks (" << num_peaks << ")" << std::endl;
2063 return;
2064 }
2065 if (!use_step_per_peak.empty() &&
2066 (Int_t)use_step_per_peak.size() != num_peaks) {
2067 std::cerr << "ERROR: use_step_per_peak size (" << use_step_per_peak.size()
2068 << ") must match num_peaks (" << num_peaks << ")" << std::endl;
2069 return;
2070 }
2072 cfg.name = name;
2073 cfg.events = events;
2074 cfg.hist = BuildDisplayHistogramFrom(events, fit_range_low, fit_range_high,
2075 display_bin_width_kev);
2076 cfg.fit_range_low = fit_range_low;
2077 cfg.fit_range_high = fit_range_high;
2078 cfg.display_bin_width_kev = display_bin_width_kev;
2079 cfg.num_peaks = num_peaks;
2080 cfg.mu_inits = mu_inits;
2081 cfg.mu_fixed =
2082 mu_fixed.empty() ? std::vector<Bool_t>(num_peaks, kFALSE) : mu_fixed;
2083 cfg.use_step_per_peak = use_step_per_peak;
2084 cfg.bkg_yield_fixed = bkg_yield_fixed;
2085 cfg.bkg_slope_fixed = bkg_slope_fixed;
2086 cfg.lock_shape_after_seed = lock_shape_after_seed;
2087 cfg.shape_lock_per_peak = shape_lock_per_peak;
2088 cfg.use_flat_background = use_flat_background;
2089 cfg.use_step = use_step;
2090 cfg.use_low_exp_tail = use_low_exp_tail;
2091 cfg.use_low_lin_tail = use_low_lin_tail;
2092 cfg.use_high_exp_tail = use_high_exp_tail;
2093 sim_channels_.push_back(cfg);
2094}
2095
2096void RooFitUtils::LinkParameter(const TString &target, const TString &source) {
2097 Ssiz_t t_sep = target.Index(":");
2098 Ssiz_t s_sep = source.Index(":");
2099 if (t_sep < 0 || s_sep < 0) {
2100 std::cerr << "ERROR: LinkParameter expects 'channel:Param' format"
2101 << std::endl;
2102 return;
2103 }
2104 RooFitParamLink lk;
2105 lk.target_channel = TString(target(0, t_sep));
2106 lk.target_param = TString(target(t_sep + 1, target.Length() - t_sep - 1));
2107 lk.source_channel = TString(source(0, s_sep));
2108 lk.source_param = TString(source(s_sep + 1, source.Length() - s_sep - 1));
2109 sim_links_.push_back(lk);
2110}
2111
2112void RooFitUtils::LinkPeakShape(const TString &target_channel,
2113 Int_t target_peak,
2114 const TString &source_channel,
2115 Int_t source_peak) {
2116 TString t_suffix = TString::Format("%d", target_peak + 1);
2117 TString s_suffix = TString::Format("%d", source_peak + 1);
2118 const char *shape_params[9] = {"Mu",
2119 "Sigma",
2120 "StepAmplitude",
2121 "LowExpTailAmplitude",
2122 "LowExpTailRatio",
2123 "LowLinTailAmplitude",
2124 "LowLinTailSlope",
2125 "HighExpTailAmplitude",
2126 "HighExpTailRatio"};
2127 for (Int_t i = 0; i < 9; i++) {
2128 LinkParameter(target_channel + ":" + shape_params[i] + t_suffix,
2129 source_channel + ":" + shape_params[i] + s_suffix);
2130 }
2131}
2132
2133void RooFitUtils::SeedChannel(const TString &channel_name,
2134 const FitResult &result) {
2135 sim_seeds_[channel_name] = result;
2136}
2137
2138Bool_t
2139RooFitUtils::BuildChannelModel(const RooFitChannelConfig &cfg,
2140 std::map<TString, RooRealVar *> &registry) {
2141 Double_t range_width = cfg.fit_range_high - cfg.fit_range_low;
2142 Double_t sigma_lo = cfg.hist->GetBinWidth(1);
2143 Double_t sigma_hi = range_width * 0.1;
2144 Double_t sigma_init = range_width * 0.05;
2145 if (sigma_init < sigma_lo)
2146 sigma_init = 2.0 * sigma_lo;
2147 if (sigma_init > sigma_hi)
2148 sigma_init = 0.5 * sigma_hi;
2149 Double_t peak_height = cfg.hist->GetBinContent(cfg.hist->GetMaximumBin());
2150 Double_t hist_xmin = cfg.hist->GetXaxis()->GetXmin();
2151 Double_t hist_xmax = cfg.hist->GetXaxis()->GetXmax();
2152
2153 if (x_ == nullptr) {
2154 Double_t global_xmin = hist_xmin;
2155 Double_t global_xmax = hist_xmax;
2156 for (size_t i = 0; i < sim_channels_.size(); i++) {
2157 Double_t cmin = sim_channels_[i].hist->GetXaxis()->GetXmin();
2158 Double_t cmax = sim_channels_[i].hist->GetXaxis()->GetXmax();
2159 if (cmin < global_xmin)
2160 global_xmin = cmin;
2161 if (cmax > global_xmax)
2162 global_xmax = cmax;
2163 }
2164 x_ = new RooRealVar("x", "x", global_xmin, global_xmax);
2165 RegisterOwned(x_);
2166 }
2167
2168 TString range_name = TString("fitrange_") + cfg.name;
2169 x_->setRange(range_name.Data(), cfg.fit_range_low, cfg.fit_range_high);
2170 sim_channel_range_names_[cfg.name] = range_name;
2171
2172 std::vector<RooFitPeakModel> peaks;
2173 for (Int_t pi = 0; pi < cfg.num_peaks; pi++) {
2174 RooFitPeakModel p;
2175 TString suffix = TString::Format("%d", pi + 1);
2176 Double_t mu_init = cfg.mu_inits[pi];
2177
2178 Int_t mu_bin = cfg.hist->FindBin(mu_init);
2179 Double_t local_height = cfg.hist->GetBinContent(mu_bin);
2180 Double_t bkg_left = 0;
2181 Double_t bkg_right = 0;
2182 Int_t lbin = cfg.hist->FindBin(cfg.fit_range_low);
2183 Int_t rbin = cfg.hist->FindBin(cfg.fit_range_high);
2184 Int_t nside = (rbin - lbin) / 10;
2185 if (nside < 1)
2186 nside = 1;
2187 for (Int_t i = 0; i < nside; i++) {
2188 bkg_left += cfg.hist->GetBinContent(lbin + i);
2189 bkg_right += cfg.hist->GetBinContent(rbin - i);
2190 }
2191 Double_t bkg_floor = (bkg_left + bkg_right) / (2.0 * nside);
2192 Double_t net_height = local_height - bkg_floor;
2193 if (net_height < 0.1 * local_height)
2194 net_height = 0.1 * local_height;
2195 Double_t total_init =
2196 net_height * sigma_init * TMath::Sqrt(2.0 * TMath::Pi());
2197
2198 p.mu = ResolveOrCreate(cfg.name, "Mu" + suffix, registry, mu_init,
2199 cfg.fit_range_low, cfg.fit_range_high);
2200 p.sigma = ResolveOrCreate(cfg.name, "Sigma" + suffix, registry, sigma_init,
2201 sigma_lo, sigma_hi);
2202 p.gaus_yield =
2203 ResolveOrCreate(cfg.name, "GausAmplitude" + suffix, registry,
2204 total_init, 0, peak_height * range_width * 10.0);
2205 p.ratio_step = ResolveOrCreate(cfg.name, "StepAmplitude" + suffix, registry,
2206 0.0, 0.0, 0.5);
2207 p.ratio_low_exp = ResolveOrCreate(cfg.name, "LowExpTailAmplitude" + suffix,
2208 registry, 0.0, 0.0, 0.5);
2209 p.tau_ratio_low_exp = ResolveOrCreate(cfg.name, "LowExpTailRatio" + suffix,
2210 registry, 1.5, 1.0, tail_ratio_max_);
2211 p.ratio_low_lin = ResolveOrCreate(cfg.name, "LowLinTailAmplitude" + suffix,
2212 registry, 0.0, 0.0, 0.5);
2213 p.slope_low_lin = ResolveOrCreate(cfg.name, "LowLinTailSlope" + suffix,
2214 registry, 0.0, -0.1, 0.1);
2215 p.ratio_high_exp = ResolveOrCreate(
2216 cfg.name, "HighExpTailAmplitude" + suffix, registry, 0.0, 0.0, 0.5);
2218 ResolveOrCreate(cfg.name, "HighExpTailRatio" + suffix, registry, 1.5,
2219 1.0, tail_ratio_max_);
2220
2221 // ResolveOrCreate returns nullptr when a LinkParameter target names a
2222 // source that has not been built yet. Links are resolved in construction
2223 // order, so "peak 2 follows peak 1" is legal and "peak 1 follows peak 2" is
2224 // not -- an easy mistake, since the physically natural phrasing ("tie the
2225 // weak line to the strong one") is often the illegal direction.
2226 //
2227 // ResolveOrCreate already prints a precise diagnostic naming both
2228 // parameters. Without this guard that nullptr is dereferenced by the PDF
2229 // constructors immediately below, and the process dies in a Cling stack
2230 // trace that scrolls the actual message off screen.
2231 RooRealVar *required[] = {p.mu,
2232 p.sigma,
2233 p.gaus_yield,
2234 p.ratio_step,
2235 p.ratio_low_exp,
2237 p.ratio_low_lin,
2238 p.slope_low_lin,
2241 const char *required_names[] = {"Mu",
2242 "Sigma",
2243 "GausAmplitude",
2244 "StepAmplitude",
2245 "LowExpTailAmplitude",
2246 "LowExpTailRatio",
2247 "LowLinTailAmplitude",
2248 "LowLinTailSlope",
2249 "HighExpTailAmplitude",
2250 "HighExpTailRatio"};
2251 for (size_t ri = 0; ri < sizeof(required) / sizeof(required[0]); ri++) {
2252 if (!required[ri]) {
2253 std::cerr << "ERROR: channel '" << cfg.name << "' peak " << suffix
2254 << ": could not resolve " << required_names[ri] << suffix
2255 << " (see the link error above). Aborting the channel build "
2256 "rather than dereferencing null."
2257 << std::endl;
2258 return kFALSE;
2259 }
2260 }
2261
2262 TString pdf_suffix = "_" + cfg.name + "_" + suffix;
2263 p.gauss_pdf = RooFitFunctions::MakeGaussian("gauss_pdf" + pdf_suffix, *x_,
2264 *p.mu, *p.sigma);
2265 p.step_pdf = RooFitFunctions::MakeStepShelf("step_pdf" + pdf_suffix, *x_,
2266 *p.mu, *p.sigma);
2268 "low_exp_pdf" + pdf_suffix, *x_, *p.mu, *p.sigma, *p.tau_ratio_low_exp);
2270 "low_lin_pdf" + pdf_suffix, *x_, *p.mu, *p.sigma, *p.slope_low_lin);
2272 "high_exp_pdf" + pdf_suffix, *x_, *p.mu, *p.sigma,
2274 RegisterOwned(p.gauss_pdf);
2275 RegisterOwned(p.step_pdf);
2276 RegisterOwned(p.low_exp_pdf);
2277 RegisterOwned(p.low_lin_pdf);
2278 RegisterOwned(p.high_exp_pdf);
2279
2280 p.step_yield =
2281 new RooFormulaVar(("step_yield" + pdf_suffix).Data(), "@0*@1",
2282 RooArgList(*p.gaus_yield, *p.ratio_step));
2283 p.low_exp_yield =
2284 new RooFormulaVar(("low_exp_yield" + pdf_suffix).Data(), "@0*@1",
2285 RooArgList(*p.gaus_yield, *p.ratio_low_exp));
2286 p.low_lin_yield =
2287 new RooFormulaVar(("low_lin_yield" + pdf_suffix).Data(), "@0*@1",
2288 RooArgList(*p.gaus_yield, *p.ratio_low_lin));
2289 p.high_exp_yield =
2290 new RooFormulaVar(("high_exp_yield" + pdf_suffix).Data(), "@0*@1",
2291 RooArgList(*p.gaus_yield, *p.ratio_high_exp));
2292 RegisterOwned(p.step_yield);
2293 RegisterOwned(p.low_exp_yield);
2294 RegisterOwned(p.low_lin_yield);
2295 RegisterOwned(p.high_exp_yield);
2296
2297 Bool_t peak_use_step = cfg.use_step;
2298 if (pi < (Int_t)cfg.use_step_per_peak.size())
2299 peak_use_step = cfg.use_step_per_peak[pi];
2300 if (!peak_use_step) {
2301 p.ratio_step->setVal(0.0);
2302 p.ratio_step->setConstant(kTRUE);
2303 }
2304 if (!cfg.use_low_exp_tail) {
2305 p.ratio_low_exp->setVal(0.0);
2306 p.ratio_low_exp->setConstant(kTRUE);
2307 p.tau_ratio_low_exp->setVal(1.0);
2308 p.tau_ratio_low_exp->setConstant(kTRUE);
2309 }
2310 if (!cfg.use_low_lin_tail) {
2311 p.ratio_low_lin->setVal(0.0);
2312 p.ratio_low_lin->setConstant(kTRUE);
2313 p.slope_low_lin->setVal(0.0);
2314 p.slope_low_lin->setConstant(kTRUE);
2315 }
2316 if (!cfg.use_high_exp_tail) {
2317 p.ratio_high_exp->setVal(0.0);
2318 p.ratio_high_exp->setConstant(kTRUE);
2319 p.tau_ratio_high_exp->setVal(1.0);
2320 p.tau_ratio_high_exp->setConstant(kTRUE);
2321 }
2322
2323 peaks.push_back(p);
2324 }
2325
2326 if (cfg.num_peaks > 1) {
2327 std::vector<Int_t> sorted_idx(cfg.num_peaks);
2328 for (Int_t i = 0; i < cfg.num_peaks; i++)
2329 sorted_idx[i] = i;
2330 std::sort(sorted_idx.begin(), sorted_idx.end(), [&](Int_t a, Int_t b) {
2331 return cfg.mu_inits[a] < cfg.mu_inits[b];
2332 });
2333 for (Int_t k = 0; k < cfg.num_peaks - 1; k++) {
2334 Int_t left = sorted_idx[k];
2335 Int_t right = sorted_idx[k + 1];
2336 Double_t midpoint = 0.5 * (cfg.mu_inits[left] + cfg.mu_inits[right]);
2337 peaks[left].mu->setMax(midpoint);
2338 peaks[right].mu->setMin(midpoint);
2339 }
2340 }
2341
2342 RooFitBackgroundModel bkg;
2343 Double_t bkg_estimate = 0;
2344 Int_t lbin = cfg.hist->FindBin(cfg.fit_range_low);
2345 Int_t rbin = cfg.hist->FindBin(cfg.fit_range_high);
2346 Int_t nside = (rbin - lbin) / 10;
2347 if (nside < 1)
2348 nside = 1;
2349 Double_t bkg_left = 0;
2350 Double_t bkg_right = 0;
2351 for (Int_t i = 0; i < nside; i++) {
2352 bkg_left += cfg.hist->GetBinContent(lbin + i);
2353 bkg_right += cfg.hist->GetBinContent(rbin - i);
2354 }
2355 bkg_estimate = (bkg_left + bkg_right) / (2.0 * nside);
2356
2357 bkg.bkg_yield = ResolveOrCreate(cfg.name, "BkgConstant", registry,
2358 bkg_estimate * range_width, 0,
2359 peak_height * range_width * 10.0);
2360 Double_t slope_lo = -0.9 / cfg.fit_range_high;
2361 Double_t slope_hi = 5.0 / range_width;
2362 bkg.bkg_slope =
2363 ResolveOrCreate(cfg.name, "BkgSlope", registry, 0.0, slope_lo, slope_hi);
2364 if (!bkg.bkg_yield || !bkg.bkg_slope) {
2365 std::cerr << "ERROR: channel '" << cfg.name
2366 << "': could not resolve background parameters (see the link "
2367 "error above). Aborting the channel build rather than "
2368 "dereferencing null."
2369 << std::endl;
2370 return kFALSE;
2371 }
2372 TString bkg_pdf_name = "bkg_pdf_" + cfg.name;
2373 bkg.bkg_pdf =
2374 RooFitFunctions::MakeLinearBackground(bkg_pdf_name, *x_, *bkg.bkg_slope);
2375 if (cfg.use_flat_background) {
2376 bkg.bkg_slope->setVal(0.0);
2377 bkg.bkg_slope->setConstant(kTRUE);
2378 }
2379 RegisterOwned(bkg.bkg_pdf);
2380
2381 RooArgList pdf_list;
2382 RooArgList coef_list;
2383 for (size_t pi = 0; pi < peaks.size(); pi++) {
2384 pdf_list.add(*peaks[pi].gauss_pdf);
2385 coef_list.add(*peaks[pi].gaus_yield);
2386 pdf_list.add(*peaks[pi].step_pdf);
2387 coef_list.add(*peaks[pi].step_yield);
2388 pdf_list.add(*peaks[pi].low_exp_pdf);
2389 coef_list.add(*peaks[pi].low_exp_yield);
2390 pdf_list.add(*peaks[pi].low_lin_pdf);
2391 coef_list.add(*peaks[pi].low_lin_yield);
2392 pdf_list.add(*peaks[pi].high_exp_pdf);
2393 coef_list.add(*peaks[pi].high_exp_yield);
2394 }
2395 pdf_list.add(*bkg.bkg_pdf);
2396 coef_list.add(*bkg.bkg_yield);
2397
2398 TString sum_name = "total_pdf_" + cfg.name;
2399 RooAddPdf *sum =
2400 new RooAddPdf(sum_name.Data(), sum_name.Data(), pdf_list, coef_list);
2401 RegisterOwned(sum);
2402 sim_channel_pdfs_[cfg.name] = sum;
2403 sim_channel_peaks_[cfg.name] = peaks;
2404 sim_channel_bkg_[cfg.name] = bkg;
2405
2406 RooArgSet vars(*x_);
2407 RooDataSet *ds = new RooDataSet(("data_" + cfg.name).Data(),
2408 ("data_" + cfg.name).Data(), vars);
2409 Double_t xmin = x_->getMin();
2410 Double_t xmax = x_->getMax();
2411 for (size_t i = 0; i < cfg.events.size(); i++) {
2412 Double_t e = cfg.events[i];
2413 if (e < xmin || e > xmax)
2414 continue;
2415 x_->setVal(e);
2416 ds->add(vars);
2417 }
2418 sim_channel_data_[cfg.name] = ds;
2419 return kTRUE;
2420}
2421
2422void RooFitUtils::ApplySeedToChannel(const TString &channel) {
2423 std::map<TString, FitResult>::iterator it = sim_seeds_.find(channel);
2424 if (it == sim_seeds_.end())
2425 return;
2426 const FitResult &seed = it->second;
2427 std::vector<RooFitPeakModel> &peaks = sim_channel_peaks_[channel];
2428 RooFitBackgroundModel &bkg = sim_channel_bkg_[channel];
2429 for (size_t pi = 0; pi < peaks.size() && pi < seed.peaks.size(); pi++) {
2430 const PeakFitResult &cp = seed.peaks[pi];
2431 RooFitPeakModel &p = peaks[pi];
2432 Double_t cga = cp.gaus_amplitude;
2433 if (cp.mu >= 0)
2434 p.mu->setVal(cp.mu);
2435 if (cp.sigma >= 0)
2436 p.sigma->setVal(cp.sigma);
2437 if (cga >= 0)
2438 p.gaus_yield->setVal(cga);
2439 if (cp.step_amplitude >= 0 && cga > 0)
2440 p.ratio_step->setVal(cp.step_amplitude / cga);
2441 if (cp.low_exp_tail_amplitude >= 0 && cga > 0)
2442 p.ratio_low_exp->setVal(cp.low_exp_tail_amplitude / cga);
2443 if (cp.low_exp_tail_ratio > 0)
2445 if (cp.low_lin_tail_amplitude >= 0 && cga > 0)
2446 p.ratio_low_lin->setVal(cp.low_lin_tail_amplitude / cga);
2447 if (cp.low_lin_tail_slope > -1)
2448 p.slope_low_lin->setVal(cp.low_lin_tail_slope);
2449 if (cp.high_exp_tail_amplitude >= 0 && cga > 0)
2450 p.ratio_high_exp->setVal(cp.high_exp_tail_amplitude / cga);
2451 if (cp.high_exp_tail_ratio > 0)
2453 }
2454 if (seed.bkg_constant >= 0)
2455 bkg.bkg_yield->setVal(seed.bkg_constant);
2456 if (seed.lin_bkg_slope > -1e6)
2457 bkg.bkg_slope->setVal(seed.lin_bkg_slope);
2458}
2459
2460void RooFitUtils::ApplyChannelMuLocks() {
2461 for (size_t ci = 0; ci < sim_channels_.size(); ci++) {
2462 const RooFitChannelConfig &cfg = sim_channels_[ci];
2463 std::vector<RooFitPeakModel> &peaks = sim_channel_peaks_[cfg.name];
2464 Int_t n_lock = TMath::Min((Int_t)peaks.size(), (Int_t)cfg.mu_fixed.size());
2465 for (Int_t pi = 0; pi < n_lock; pi++) {
2466 if (!cfg.mu_fixed[pi])
2467 continue;
2468 Double_t target = cfg.mu_inits[pi];
2469 peaks[pi].mu->setVal(target);
2470 peaks[pi].mu->setConstant(kTRUE);
2471 }
2472 }
2473}
2474
2475void RooFitUtils::ApplyChannelBkgLocks() {
2476 for (size_t ci = 0; ci < sim_channels_.size(); ci++) {
2477 const RooFitChannelConfig &cfg = sim_channels_[ci];
2478 RooFitBackgroundModel &bkg = sim_channel_bkg_[cfg.name];
2479 if (cfg.bkg_yield_fixed && bkg.bkg_yield)
2480 bkg.bkg_yield->setConstant(kTRUE);
2481 if (cfg.bkg_slope_fixed && bkg.bkg_slope)
2482 bkg.bkg_slope->setConstant(kTRUE);
2483 }
2484}
2485
2486void RooFitUtils::ApplyChannelShapeLocks() {
2487 for (size_t ci = 0; ci < sim_channels_.size(); ci++) {
2488 const RooFitChannelConfig &cfg = sim_channels_[ci];
2489 if (!cfg.lock_shape_after_seed)
2490 continue;
2491 std::vector<RooFitPeakModel> &peaks = sim_channel_peaks_[cfg.name];
2492 for (size_t pi = 0; pi < peaks.size(); pi++) {
2493 // Per-peak override: if shape_lock_per_peak is non-empty, use it.
2494 // Otherwise lock all peaks (backward-compatible).
2495 Bool_t lock_peak = kTRUE;
2496 if (!cfg.shape_lock_per_peak.empty()) {
2497 if (pi < (Int_t)cfg.shape_lock_per_peak.size())
2498 lock_peak = cfg.shape_lock_per_peak[pi];
2499 else
2500 lock_peak = kFALSE; // default: don't lock if index out of range
2501 }
2502 if (!lock_peak)
2503 continue;
2504 RooFitPeakModel &p = peaks[pi];
2505 if (p.sigma)
2506 p.sigma->setConstant(kTRUE);
2507 if (p.gaus_yield)
2508 p.gaus_yield->setConstant(kTRUE);
2509 if (p.ratio_step)
2510 p.ratio_step->setConstant(kTRUE);
2511 if (p.ratio_low_exp)
2512 p.ratio_low_exp->setConstant(kTRUE);
2513 if (p.tau_ratio_low_exp)
2514 p.tau_ratio_low_exp->setConstant(kTRUE);
2515 if (p.ratio_low_lin)
2516 p.ratio_low_lin->setConstant(kTRUE);
2517 if (p.slope_low_lin)
2518 p.slope_low_lin->setConstant(kTRUE);
2519 if (p.ratio_high_exp)
2520 p.ratio_high_exp->setConstant(kTRUE);
2521 if (p.tau_ratio_high_exp)
2522 p.tau_ratio_high_exp->setConstant(kTRUE);
2523 }
2524 }
2525}
2526
2527Double_t RooFitUtils::ComputeChannelChi2(
2528 const TString &channel, const std::vector<RooFitPeakModel> & /*peaks*/,
2529 const RooFitBackgroundModel & /*bkg*/, Int_t &ndof) {
2530 RooAbsPdf *pdf = sim_channel_pdfs_[channel];
2531 RooFitChannelConfig const *cfg = nullptr;
2532 for (size_t i = 0; i < sim_channels_.size(); i++) {
2533 if (sim_channels_[i].name == channel) {
2534 cfg = &sim_channels_[i];
2535 break;
2536 }
2537 }
2538 if (!cfg) {
2539 ndof = 0;
2540 return -1;
2541 }
2542 Double_t saved_val = x_->getVal();
2543
2544 RooArgSet nset(*x_);
2545 Double_t total_exp = pdf->expectedEvents(&nset);
2546 Double_t bin_width = cfg->hist->GetBinWidth(1);
2547
2548 Double_t chi2 = 0;
2549 Int_t nbins_in_range = 0;
2550 Int_t nbins_hist = cfg->hist->GetNbinsX();
2551 for (Int_t i = 1; i <= nbins_hist; i++) {
2552 Double_t xv = cfg->hist->GetBinCenter(i);
2553 if (xv < cfg->fit_range_low || xv > cfg->fit_range_high)
2554 continue;
2555 Double_t data = cfg->hist->GetBinContent(i);
2556 Double_t error = cfg->hist->GetBinError(i);
2557 if (error <= 0 || data <= 0)
2558 continue;
2559 x_->setVal(xv);
2560 Double_t fit_val = total_exp * pdf->getVal(&nset) * bin_width;
2561 Double_t residual = (data - fit_val) / error;
2562 chi2 += residual * residual;
2563 nbins_in_range++;
2564 }
2565 x_->setVal(saved_val);
2566
2567 Int_t npars = 0;
2568 RooArgSet *params = pdf->getParameters(RooArgSet(*x_));
2569 for (Int_t i = 0; i < (Int_t)params->size(); i++) {
2570 RooRealVar *v = dynamic_cast<RooRealVar *>((*params)[i]);
2571 if (v && !v->isConstant())
2572 npars++;
2573 }
2574 delete params;
2575 ndof = nbins_in_range - npars;
2576 if (ndof <= 0)
2577 return -1;
2578 return chi2 / ndof;
2579}
2580
2581PeakFitResult RooFitUtils::ExtractPeakResultFor(const RooFitPeakModel &p) {
2582 PeakFitResult r;
2583 r.mu = p.mu->getVal();
2584 r.mu_error = p.mu->getError();
2585 r.sigma = p.sigma->getVal();
2586 r.sigma_error = p.sigma->getError();
2587 r.gaus_amplitude = p.gaus_yield->getVal();
2588 r.gaus_amplitude_error = p.gaus_yield->getError();
2589 Double_t ga = r.gaus_amplitude;
2590 r.step_amplitude = p.ratio_step->getVal() * ga;
2591 r.step_amplitude_error = p.ratio_step->getError() * ga;
2592 r.low_exp_tail_amplitude = p.ratio_low_exp->getVal() * ga;
2593 r.low_exp_tail_amplitude_error = p.ratio_low_exp->getError() * ga;
2594 r.low_exp_tail_ratio = p.tau_ratio_low_exp->getVal();
2596 r.low_lin_tail_amplitude = p.ratio_low_lin->getVal() * ga;
2597 r.low_lin_tail_amplitude_error = p.ratio_low_lin->getError() * ga;
2598 r.low_lin_tail_slope = p.slope_low_lin->getVal();
2599 r.low_lin_tail_slope_error = p.slope_low_lin->getError();
2600 r.high_exp_tail_amplitude = p.ratio_high_exp->getVal() * ga;
2601 r.high_exp_tail_amplitude_error = p.ratio_high_exp->getError() * ga;
2604 return r;
2605}
2606
2607// ---------------------------------------------------------------------------
2608// Per-parameter diagnostics
2609// ---------------------------------------------------------------------------
2610
2611// Populate one FitParameterDiagnostic from a RooRealVar. The variable is
2612// nullptr for a component that was not enabled (its model object was never
2613// built), so the diagnostic records only its name. The near-limit flags fire
2614// when the value sits within a margin of its bound; the margin is the larger
2615// of a small absolute tolerance and a fraction of the full range, so tight
2616// limits don't get skipped.
2617static void
2618RooFitExtractParamDiagnostic(RooRealVar *rv, const std::string &name,
2619 std::vector<FitParameterDiagnostic> &diags) {
2621 d.name = name;
2622 if (rv) {
2623 d.value = rv->getVal();
2624 d.error = rv->getError();
2625 d.lo = rv->getMin();
2626 d.hi = rv->getMax();
2627 d.has_limits = kTRUE;
2628 Double_t range = d.hi - d.lo;
2629 if (range > 0) {
2630 Double_t abs_tol = 1e-4;
2631 Double_t frac_tol = 1e-3;
2632 Double_t margin = TMath::Max(abs_tol, frac_tol * range);
2633 if (d.value - d.lo < margin)
2634 d.near_lower = kTRUE;
2635 if (d.hi - d.value < margin)
2636 d.near_upper = kTRUE;
2637 d.near_limit = d.near_lower || d.near_upper;
2638 }
2639 }
2640 diags.push_back(d);
2641}
2642
2643std::vector<FitParameterDiagnostic>
2644RooFitUtils::ExtractParameterDiagnostics(const TString &channel) {
2645 std::vector<FitParameterDiagnostic> diags;
2646
2647 // Lookup channel config to get peak count
2648 const RooFitChannelConfig *cfg = nullptr;
2649 for (size_t i = 0; i < sim_channels_.size(); i++) {
2650 if (sim_channels_[i].name == channel) {
2651 cfg = &sim_channels_[i];
2652 break;
2653 }
2654 }
2655 if (!cfg)
2656 return diags;
2657
2658 const std::vector<RooFitPeakModel> &peaks = sim_channel_peaks_[channel];
2659 const RooFitBackgroundModel &bkg = sim_channel_bkg_[channel];
2660
2661 // Peak parameters
2662 for (size_t pi = 0; pi < peaks.size(); pi++) {
2663 TString suffix = TString::Format("%d", pi + 1);
2664 TString cname = channel + ":";
2665 RooFitExtractParamDiagnostic(peaks[pi].mu, (cname + "Mu" + suffix).Data(),
2666 diags);
2667 RooFitExtractParamDiagnostic(peaks[pi].sigma,
2668 (cname + "Sigma" + suffix).Data(), diags);
2669 RooFitExtractParamDiagnostic(
2670 peaks[pi].gaus_yield, (cname + "GausAmplitude" + suffix).Data(), diags);
2671 RooFitExtractParamDiagnostic(
2672 peaks[pi].ratio_step, (cname + "StepAmplitude" + suffix).Data(), diags);
2673 RooFitExtractParamDiagnostic(
2674 peaks[pi].ratio_low_exp,
2675 (cname + "LowExpTailAmplitude" + suffix).Data(), diags);
2676 RooFitExtractParamDiagnostic(peaks[pi].tau_ratio_low_exp,
2677 (cname + "LowExpTailRatio" + suffix).Data(),
2678 diags);
2679 RooFitExtractParamDiagnostic(
2680 peaks[pi].ratio_low_lin,
2681 (cname + "LowLinTailAmplitude" + suffix).Data(), diags);
2682 RooFitExtractParamDiagnostic(peaks[pi].slope_low_lin,
2683 (cname + "LowLinTailSlope" + suffix).Data(),
2684 diags);
2685 RooFitExtractParamDiagnostic(
2686 peaks[pi].ratio_high_exp,
2687 (cname + "HighExpTailAmplitude" + suffix).Data(), diags);
2688 RooFitExtractParamDiagnostic(peaks[pi].tau_ratio_high_exp,
2689 (cname + "HighExpTailRatio" + suffix).Data(),
2690 diags);
2691 }
2692
2693 // Background parameters
2694 RooFitExtractParamDiagnostic(bkg.bkg_yield,
2695 TString(channel + ":BkgConstant").Data(), diags);
2696 RooFitExtractParamDiagnostic(bkg.bkg_slope,
2697 TString(channel + ":BkgSlope").Data(), diags);
2698
2699 return diags;
2700}
2701
2702std::vector<FitParameterDiagnostic>
2703RooFitUtils::ExtractParameterDiagnosticsSingle() {
2704 std::vector<FitParameterDiagnostic> diags;
2705
2706 for (size_t pi = 0; pi < peaks_.size(); pi++) {
2707 TString suffix = TString::Format("%d", pi + 1);
2708 RooFitExtractParamDiagnostic(peaks_[pi].mu, ("Mu" + suffix).Data(), diags);
2709 RooFitExtractParamDiagnostic(peaks_[pi].sigma, ("Sigma" + suffix).Data(),
2710 diags);
2711 RooFitExtractParamDiagnostic(peaks_[pi].gaus_yield,
2712 ("GausAmplitude" + suffix).Data(), diags);
2713 RooFitExtractParamDiagnostic(peaks_[pi].ratio_step,
2714 ("StepAmplitude" + suffix).Data(), diags);
2715 RooFitExtractParamDiagnostic(peaks_[pi].ratio_low_exp,
2716 ("LowExpTailAmplitude" + suffix).Data(),
2717 diags);
2718 RooFitExtractParamDiagnostic(peaks_[pi].tau_ratio_low_exp,
2719 ("LowExpTailRatio" + suffix).Data(), diags);
2720 RooFitExtractParamDiagnostic(peaks_[pi].ratio_low_lin,
2721 ("LowLinTailAmplitude" + suffix).Data(),
2722 diags);
2723 RooFitExtractParamDiagnostic(peaks_[pi].slope_low_lin,
2724 ("LowLinTailSlope" + suffix).Data(), diags);
2725 RooFitExtractParamDiagnostic(peaks_[pi].ratio_high_exp,
2726 ("HighExpTailAmplitude" + suffix).Data(),
2727 diags);
2728 RooFitExtractParamDiagnostic(peaks_[pi].tau_ratio_high_exp,
2729 ("HighExpTailRatio" + suffix).Data(), diags);
2730 }
2731 RooFitExtractParamDiagnostic(bkg_.bkg_yield, "BkgConstant", diags);
2732 RooFitExtractParamDiagnostic(bkg_.bkg_slope, "BkgSlope", diags);
2733
2734 return diags;
2735}
2736
2737void RooFitUtils::PlotChannel(const TString &channel, Int_t num_peaks,
2738 const std::vector<RooFitPeakModel> &peaks,
2739 const RooFitBackgroundModel &bkg,
2740 const TString &input_name,
2741 const TString &base_label,
2742 const TString &chi2_label) {
2743 RooFitChannelConfig const *cfg = nullptr;
2744 for (size_t i = 0; i < sim_channels_.size(); i++) {
2745 if (sim_channels_[i].name == channel) {
2746 cfg = &sim_channels_[i];
2747 break;
2748 }
2749 }
2750 if (!cfg)
2751 return;
2752
2753 TH1 *saved_hist = working_hist_;
2754 Float_t saved_lo = fit_range_low_;
2755 Float_t saved_hi = fit_range_high_;
2756 std::vector<RooFitPeakModel> saved_peaks = peaks_;
2757 RooFitBackgroundModel saved_bkg = bkg_;
2758 Int_t saved_np = num_peaks_;
2759 RooAddPdf *saved_total = total_pdf_;
2760
2761 working_hist_ = cfg->hist;
2762 fit_range_low_ = cfg->fit_range_low;
2763 fit_range_high_ = cfg->fit_range_high;
2764 peaks_ = peaks;
2765 bkg_ = bkg;
2766 num_peaks_ = num_peaks;
2767 total_pdf_ = static_cast<RooAddPdf *>(sim_channel_pdfs_[channel]);
2768
2769 TString plot_name = base_label + "_" + channel;
2770 if (num_peaks == 1)
2771 PlotFitSinglePeak(input_name, plot_name, chi2_label);
2772 else if (num_peaks == 2)
2773 PlotFitDoublePeak(input_name, plot_name, chi2_label);
2774 else if (num_peaks == 3)
2775 PlotFitTriplePeak(input_name, plot_name, chi2_label);
2776
2777 working_hist_ = saved_hist;
2778 fit_range_low_ = saved_lo;
2779 fit_range_high_ = saved_hi;
2780 peaks_ = saved_peaks;
2781 bkg_ = saved_bkg;
2782 num_peaks_ = saved_np;
2783 total_pdf_ = saved_total;
2784}
2785
2786void RooFitUtils::DumpChannelCSV(const TString &channel,
2787 const TString &csv_path, Int_t npts) {
2788 std::map<TString, RooAbsPdf *>::iterator pit =
2789 sim_channel_pdfs_.find(channel);
2790 std::map<TString, RooFitBackgroundModel>::iterator bit =
2791 sim_channel_bkg_.find(channel);
2792 RooFitChannelConfig const *cfg = nullptr;
2793 for (size_t i = 0; i < sim_channels_.size(); i++)
2794 if (sim_channels_[i].name == channel) {
2795 cfg = &sim_channels_[i];
2796 break;
2797 }
2798 if (pit == sim_channel_pdfs_.end() || bit == sim_channel_bkg_.end() || !cfg ||
2799 !cfg->hist) {
2800 std::cerr << "ERROR: DumpChannelCSV: channel '" << channel
2801 << "' not built; call after FitSimultaneous." << std::endl;
2802 return;
2803 }
2804 RooAddPdf *total = static_cast<RooAddPdf *>(pit->second);
2805 RooFitBackgroundModel &bkg = bit->second;
2806 TH1 *hist = cfg->hist;
2807 Double_t bin_width = hist->GetBinWidth(1);
2808 Float_t lo = cfg->fit_range_low;
2809 Float_t hi = cfg->fit_range_high;
2810 RooArgSet nset(*x_);
2811 Double_t total_exp = total->expectedEvents(&nset);
2812 Double_t bkg_yield = bkg.bkg_yield ? bkg.bkg_yield->getVal() : 0.0;
2813
2814 // csv_path is a BASE; write two tidy, read_csv-able files:
2815 // <base>_spectrum.csv : per-bin data + fit + residual (the data table)
2816 // <base>_fitcurve.csv : smooth fit curve (the overlay line)
2817 TString base = csv_path;
2818 if (base.EndsWith(".csv"))
2819 base.Remove(base.Length() - 4);
2820
2821 // 1) Per-bin spectrum: raw data, fit, background, residual pull. All
2822 // counts/bin.
2823 TString spec_path = base + "_spectrum.csv";
2824 std::ofstream spec(spec_path.Data());
2825 if (!spec.is_open()) {
2826 std::cerr << "ERROR: DumpChannelCSV: cannot open " << spec_path
2827 << std::endl;
2828 return;
2829 }
2830 spec << "energy_keV,data_counts,fit_total,fit_background,residual_pull"
2831 << std::endl;
2832 Int_t lbin = hist->FindBin(lo);
2833 Int_t rbin = hist->FindBin(hi);
2834 for (Int_t b = lbin; b <= rbin; b++) {
2835 Double_t xc = hist->GetBinCenter(b);
2836 if (xc < lo || xc > hi)
2837 continue;
2838 Double_t data = hist->GetBinContent(b);
2839 x_->setVal(xc);
2840 Double_t fit = total_exp * total->getVal(&nset) * bin_width;
2841 Double_t fb =
2842 bkg.bkg_pdf ? bkg_yield * bkg.bkg_pdf->getVal(&nset) * bin_width : 0.0;
2843 Double_t pull = (fit > 0) ? (data - fit) / std::sqrt(fit) : 0.0;
2844 spec << std::fixed << std::setprecision(6) << xc << "," << data << ","
2845 << fit << "," << fb << "," << pull << std::endl;
2846 }
2847 spec.close();
2848 std::cout << "Wrote CSV: " << spec_path << std::endl;
2849
2850 // 2) Smooth fit curve (counts/bin) for the overlaid line.
2851 TString curve_path = base + "_fitcurve.csv";
2852 std::ofstream curve(curve_path.Data());
2853 if (!curve.is_open()) {
2854 std::cerr << "ERROR: DumpChannelCSV: cannot open " << curve_path
2855 << std::endl;
2856 return;
2857 }
2858 curve << "energy_keV,fit_total,fit_background" << std::endl;
2859 Double_t x_step = (hi - lo) / (npts - 1);
2860 for (Int_t i = 0; i < npts; i++) {
2861 Double_t xv = lo + i * x_step;
2862 x_->setVal(xv);
2863 Double_t yt = total_exp * total->getVal(&nset) * bin_width;
2864 Double_t yb =
2865 bkg.bkg_pdf ? bkg_yield * bkg.bkg_pdf->getVal(&nset) * bin_width : 0.0;
2866 curve << std::fixed << std::setprecision(6) << xv << "," << yt << "," << yb
2867 << std::endl;
2868 }
2869 curve.close();
2870 std::cout << "Wrote CSV: " << curve_path << std::endl;
2871}
2872
2873std::vector<FitResult> RooFitUtils::FitSimultaneous(const TString &input_name,
2874 const TString &base_label) {
2875 std::vector<FitResult> results;
2876 if (sim_channels_.empty()) {
2877 std::cerr << "ERROR: FitSimultaneous called with no channels" << std::endl;
2878 return results;
2879 }
2880
2881 AdoptSavedSimRange(input_name, base_label);
2882
2883 std::map<TString, RooRealVar *> registry;
2884 for (size_t i = 0; i < sim_channels_.size(); i++) {
2885 // A failed channel build leaves half-constructed PDFs behind; continuing
2886 // dereferences them and crashes several frames from the actual cause. Bail
2887 // with an empty result so the caller sees a clean failure and the
2888 // diagnostics printed above stay on screen.
2889 if (!BuildChannelModel(sim_channels_[i], registry)) {
2890 std::cerr << "ERROR: aborting simultaneous fit '" << base_label
2891 << "': channel '" << sim_channels_[i].name
2892 << "' could not be built." << std::endl;
2893 return std::vector<FitResult>();
2894 }
2895 }
2896
2897 Float_t union_lo = sim_channels_[0].fit_range_low;
2898 Float_t union_hi = sim_channels_[0].fit_range_high;
2899 for (size_t i = 1; i < sim_channels_.size(); i++) {
2900 if (sim_channels_[i].fit_range_low < union_lo)
2901 union_lo = sim_channels_[i].fit_range_low;
2902 if (sim_channels_[i].fit_range_high > union_hi)
2903 union_hi = sim_channels_[i].fit_range_high;
2904 }
2905 x_->setRange(union_lo, union_hi);
2906 x_->setRange(kFitRangeName, union_lo, union_hi);
2907
2908 for (size_t i = 0; i < sim_channels_.size(); i++) {
2909 ApplySeedToChannel(sim_channels_[i].name);
2910 }
2911 ApplyChannelMuLocks();
2912 ApplyChannelBkgLocks();
2913 ApplyChannelShapeLocks();
2914 BuildSeparationConstraints();
2915
2916 sim_category_ = new RooCategory("channel", "channel");
2917 for (size_t i = 0; i < sim_channels_.size(); i++) {
2918 sim_category_->defineType(sim_channels_[i].name.Data());
2919 }
2920
2921 std::map<std::string, RooDataSet *> data_map;
2922 for (size_t i = 0; i < sim_channels_.size(); i++) {
2923 data_map[sim_channels_[i].name.Data()] =
2924 sim_channel_data_[sim_channels_[i].name];
2925 }
2926 sim_combined_data_ =
2927 new RooDataSet("combined_data", "combined_data", RooArgSet(*x_),
2928 RooFit::Index(*sim_category_), RooFit::Import(data_map));
2929
2930 sim_pdf_ = new RooSimultaneous("sim_pdf", "sim_pdf", *sim_category_);
2931 for (size_t i = 0; i < sim_channels_.size(); i++) {
2932 sim_pdf_->addPdf(*sim_channel_pdfs_[sim_channels_[i].name],
2933 sim_channels_[i].name.Data());
2934 }
2935
2936 std::cout << "Running simultaneous fit over " << sim_channels_.size()
2937 << " channels" << std::endl;
2938
2939 RooFitResult *fit_result = nullptr;
2940 Bool_t sim_valid = kFALSE;
2941
2942 if (interactive_) {
2943 if (LoadSimInteractiveParams(input_name, base_label)) {
2944 sim_valid = kTRUE;
2945 Float_t loaded_lo = x_->getMin(kFitRangeName);
2946 Float_t loaded_hi = x_->getMax(kFitRangeName);
2947 for (size_t i = 0; i < sim_channels_.size(); i++) {
2948 sim_channels_[i].fit_range_low = loaded_lo;
2949 sim_channels_[i].fit_range_high = loaded_hi;
2950 delete sim_channels_[i].hist;
2951 sim_channels_[i].hist = BuildDisplayHistogramFrom(
2952 sim_channels_[i].events, loaded_lo, loaded_hi,
2953 sim_channels_[i].display_bin_width_kev);
2954 }
2955 ApplyChannelMuLocks();
2956 ApplyChannelBkgLocks();
2957 ApplyChannelShapeLocks();
2958
2959 // Optionally treat the loaded parameters as a SEED rather than as the
2960 // answer, and actually minimise from there. See SetRefitAfterLoad().
2961 // Identical fitTo() configuration to the non-interactive branch below, so
2962 // the only difference between the two paths is the starting point.
2963 if (refit_after_load_) {
2964 Int_t load_print_level = fit_debug_ ? 1 : 0;
2965 Int_t load_eval_errors = fit_debug_ ? 10 : -1;
2966 fit_result = sim_pdf_->fitTo(
2967 *sim_combined_data_, RooFit::Save(kTRUE), RooFit::Extended(kTRUE),
2968 RooFit::Range(kFitRangeName), RooFit::SplitRange(kTRUE),
2969 RooFit::SumW2Error(kFALSE), RooFit::PrintLevel(load_print_level),
2970 RooFit::PrintEvalErrors(load_eval_errors), RooFit::Strategy(1),
2971 RooFit::Minimizer("Minuit2", "migrad"),
2972 RooFit::ExternalConstraints(sim_constraint_set_),
2974 // Gate on EDM, not on the status code. Minuit2 routinely returns a
2975 // nonzero status (covariance forced positive-definite, or a post-migrad
2976 // Hesse quirk) on fits that have genuinely converged.
2977 // chi2/ndf per channel is the measure to actually judge quality on, and
2978 // it is printed for every channel below.
2979 Double_t refit_edm = fit_result ? fit_result->edm() : -1.0;
2980 Int_t refit_covq = fit_result ? fit_result->covQual() : -1;
2981 Bool_t edm_converged =
2982 (refit_edm >= 0.0 && refit_edm < 10.0 && refit_covq >= 2);
2983 sim_valid =
2984 (fit_result && (fit_result->status() == 0 || edm_converged));
2985 if (!sim_valid)
2986 std::cout << "WARNING: simultaneous refit from saved params did not "
2987 "converge cleanly (edm = "
2988 << refit_edm << ")" << std::endl;
2989 }
2990 } else {
2991 std::vector<SimEditorChannelView> views;
2992 for (size_t i = 0; i < sim_channels_.size(); i++) {
2993 const RooFitChannelConfig &cfg = sim_channels_[i];
2995 v.name = cfg.name;
2996 v.hist = cfg.hist;
2997 v.events = &sim_channels_[i].events;
2999 v.pdf = sim_channel_pdfs_[cfg.name];
3000 v.data = sim_channel_data_[cfg.name];
3001 v.peaks = &sim_channel_peaks_[cfg.name];
3002 v.bkg = &sim_channel_bkg_[cfg.name];
3003 v.num_peaks = cfg.num_peaks;
3004 views.push_back(v);
3005 }
3006 Bool_t was_batch = gROOT->IsBatch();
3007 gROOT->SetBatch(kFALSE);
3008 TString info = base_label + " / " + input_name;
3010 sim_pdf_, sim_combined_data_, x_, views, union_lo, union_hi, info,
3011 fit_debug_);
3012 gROOT->SetBatch(was_batch);
3013 sim_valid = accepted;
3014 if (accepted) {
3015 Float_t edited_lo = x_->getMin(kFitRangeName);
3016 Float_t edited_hi = x_->getMax(kFitRangeName);
3017 for (size_t i = 0; i < sim_channels_.size(); i++) {
3018 sim_channels_[i].fit_range_low = edited_lo;
3019 sim_channels_[i].fit_range_high = edited_hi;
3020 delete sim_channels_[i].hist;
3021 sim_channels_[i].hist = BuildDisplayHistogramFrom(
3022 sim_channels_[i].events, edited_lo, edited_hi,
3023 sim_channels_[i].display_bin_width_kev);
3024 }
3025 ApplyChannelMuLocks();
3026 ApplyChannelBkgLocks();
3027 ApplyChannelShapeLocks();
3028 SaveSimInteractiveParams(input_name, base_label);
3029 } else {
3030 std::cout << "Interactive sim fit cancelled" << std::endl;
3031 }
3032 }
3033 } else {
3034 if (fit_debug_) {
3035 // Un-suppress RooFit eval errors (the fit normally forces -1) so the
3036 // offending pdf is named, and report the seed NLL up front.
3037 RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors);
3038 RooAbsReal *nll = sim_pdf_->createNLL(
3039 *sim_combined_data_, RooFit::Extended(kTRUE),
3040 RooFit::Range(kFitRangeName), RooFit::SplitRange(kTRUE));
3041 Double_t nll_seed = (nll != 0) ? nll->getVal() : 0.0;
3042 std::cout << "=== AU_ROOFIT_FIT_DEBUG: seed NLL = " << nll_seed
3043 << (std::isfinite(nll_seed) ? "" : " <== NON-FINITE")
3044 << " ===" << std::endl;
3045 if (nll != 0)
3046 delete nll;
3047 }
3048
3049 // SplitRange normalizes each channel over its own "fitrange_<name>" window
3050 // (set in BuildChannelModel) instead of the union range. Without it, a
3051 // channel whose peak sits below union_hi has its linear background and
3052 // exponential tails evaluated far past their own fit range: the background
3053 // polynomial 1+slope*x can go negative and the tails overflow, poisoning
3054 // the NLL once high statistics populate the out-of-range bins.
3055 Int_t print_level = fit_debug_ ? 1 : 0;
3056 Int_t eval_errors = fit_debug_ ? 10 : -1;
3057 fit_result = sim_pdf_->fitTo(
3058 *sim_combined_data_, RooFit::Save(kTRUE), RooFit::Extended(kTRUE),
3059 RooFit::Range(kFitRangeName), RooFit::SplitRange(kTRUE),
3060 RooFit::SumW2Error(kFALSE), RooFit::PrintLevel(print_level),
3061 RooFit::PrintEvalErrors(eval_errors), RooFit::Strategy(1),
3062 RooFit::Minimizer("Minuit2", "migrad"),
3063 RooFit::ExternalConstraints(sim_constraint_set_),
3065 // Same EDM-based criterion as the refit-after-load path above
3066 Double_t cold_edm = fit_result ? fit_result->edm() : -1.0;
3067 Int_t cold_covq = fit_result ? fit_result->covQual() : -1;
3068 sim_valid = (fit_result &&
3069 (fit_result->status() == 0 ||
3070 (cold_edm >= 0.0 && cold_edm < 10.0 && cold_covq >= 2)));
3071 if (!sim_valid) {
3072 std::cout << "WARNING: simultaneous fit did not converge cleanly"
3073 << std::endl;
3074 }
3075 }
3076
3077 // Capture global simultaneous-fit diagnostics from RooFitResult.
3078 // These are the same for every channel since they come from the single
3079 // combined minimization. Interactive path leaves them at sentinel values.
3080 Bool_t has_diag = kFALSE;
3081 Int_t diag_status = -999;
3082 Int_t diag_cov_qual = -999;
3083 Double_t diag_edm = -1;
3084 Double_t diag_min_nll = -1;
3085
3086 if (fit_result) {
3087 has_diag = kTRUE;
3088 diag_status = fit_result->status();
3089 diag_cov_qual = fit_result->covQual();
3090 diag_edm = fit_result->edm();
3091 diag_min_nll = fit_result->minNll();
3092 }
3093
3094 if (has_diag) {
3095 std::cout << "=== Simultaneous fit diagnostics ===" << std::endl;
3096 std::cout << " status = " << diag_status << std::endl;
3097 std::cout << " covQual = " << diag_cov_qual << std::endl;
3098 std::cout << " edm = " << diag_edm << std::endl;
3099 std::cout << " minNLL = " << diag_min_nll
3100 << (std::isfinite(diag_min_nll) ? "" : " <== NON-FINITE")
3101 << " ===" << std::endl;
3102 }
3103
3104 for (size_t i = 0; i < sim_channels_.size(); i++) {
3105 const RooFitChannelConfig &cfg = sim_channels_[i];
3106 Int_t ndof = 0;
3107 Double_t chi2 = ComputeChannelChi2(cfg.name, sim_channel_peaks_[cfg.name],
3108 sim_channel_bkg_[cfg.name], ndof);
3109 std::cout << "Channel '" << cfg.name << "' chi2/ndf = " << chi2
3110 << " (ndof=" << ndof << ")" << std::endl;
3111
3112 FitResult cr;
3113 for (Int_t pi = 0; pi < cfg.num_peaks; pi++) {
3114 cr.peaks.push_back(
3115 ExtractPeakResultFor(sim_channel_peaks_[cfg.name][pi]));
3116 }
3117 cr.bkg_constant = sim_channel_bkg_[cfg.name].bkg_yield->getVal();
3118 cr.bkg_constant_error = sim_channel_bkg_[cfg.name].bkg_yield->getError();
3119 cr.lin_bkg_slope = sim_channel_bkg_[cfg.name].bkg_slope->getVal();
3120 cr.lin_bkg_slope_error = sim_channel_bkg_[cfg.name].bkg_slope->getError();
3121 cr.reduced_chi2 = chi2;
3122 cr.valid = sim_valid;
3123 cr.has_fit_diagnostics = has_diag;
3124 cr.fit_status = diag_status;
3125 cr.cov_qual = diag_cov_qual;
3126 cr.edm = diag_edm;
3127 cr.min_nll = diag_min_nll;
3129 ExtractParameterDiagnostics(sim_channels_[i].name);
3130 results.push_back(cr);
3131
3132 TString chi2_label = Form("#chi^{2}/ndf = %.3f", chi2);
3133 PlotChannel(cfg.name, cfg.num_peaks, sim_channel_peaks_[cfg.name],
3134 sim_channel_bkg_[cfg.name], input_name, base_label, chi2_label);
3135 }
3136
3137 delete fit_result;
3138 return results;
3139}
Bool_t LaunchInteractiveRooFitEditor(TH1 *hist, const std::vector< Double_t > *events, Float_t display_bin_width_kev, RooAbsPdf *total_pdf, RooRealVar *x, RooAbsData *data, std::vector< RooFitPeakModel > *peaks, RooFitBackgroundModel *bkg, Double_t range_low, Double_t range_high, const TString &info_label)
Open the RooFit editor and pump its event loop until the user is done.
Bool_t LaunchInteractiveSimultaneousFitEditor(RooSimultaneous *sim_pdf, RooAbsData *combined_data, RooRealVar *x, std::vector< SimEditorChannelView > &channel_views, Double_t range_low, Double_t range_high, const TString &info_label, Bool_t fit_debug=kFALSE)
Open the simultaneous editor and pump its event loop.
RooCmdArg BestAvailableBackend()
The best RooFit evaluation backend this build supports.
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.
void LinkParameter(const TString &target, const TString &source)
Tie one parameter to another, fitting them as one degree of freedom.
RooFitUtils()
Construct in simultaneous mode.
void ConstrainPeakSeparation(const TString &channel, Int_t peak_hi, Int_t peak_lo, Double_t delta, Double_t sigma)
Constrain the spacing between two peaks to a known value.
void PlotFitSinglePeak(const TString input_name, const TString peak_name, const TString label="")
Draw and save the single-peak fit with its residual panel.
void LinkPeakShape(const TString &target_channel, Int_t target_peak, const TString &source_channel, Int_t source_peak)
Tie a peak's whole shape to another peak's.
void SeedChannel(const TString &channel_name, const FitResult &result)
Use a prior single-channel fit as starting values for a channel.
void DumpChannelCSV(const TString &channel, const TString &csv_path, Int_t npts=1000)
Export a converged channel to CSV for plotting elsewhere.
void PlotFitTriplePeak(const TString input_name, const TString peak_name, const TString label="")
Draw and save the triple-peak fit with its residual panel.
static std::vector< Double_t > LoadEventsFromTree(TTree *tree, const TString &branch_name)
Read a branch into the event vector the constructor expects.
FitResult FitSinglePeak(const TString input_name, const TString peak_name)
Fit one peak, pruning components that do not earn their place.
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.
void PlotFitDoublePeak(const TString input_name, const TString peak_name, const TString label="")
Draw and save the double-peak fit with its residual panel.
static void RefillDisplayHistogram(TH1 *hist, const std::vector< Double_t > &events, Float_t fit_range_low, Float_t fit_range_high, Float_t display_bin_width_kev)
Rebin an existing display histogram in place.
static TH1F * BuildDisplayHistogramFrom(const std::vector< Double_t > &events, Float_t fit_range_low, Float_t fit_range_high, Float_t display_bin_width_kev)
Bin events into a display histogram.
std::vector< FitResult > FitSimultaneous(const TString &input_name, const TString &base_label)
Run one joint extended-likelihood fit across every channel.
void AddChannel(const TString &name, const std::vector< Double_t > &events, Float_t fit_range_low, Float_t fit_range_high, Float_t display_bin_width_kev, Int_t num_peaks, const std::vector< Double_t > &mu_inits, 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, const std::vector< Bool_t > &mu_fixed=std::vector< Bool_t >(), Bool_t bkg_yield_fixed=kFALSE, Bool_t bkg_slope_fixed=kFALSE, Bool_t lock_shape_after_seed=kFALSE, const std::vector< Bool_t > &use_step_per_peak=std::vector< Bool_t >(), const std::vector< Bool_t > &shape_lock_per_peak=std::vector< Bool_t >())
Register one spectrum as a channel of the simultaneous fit.
FitResult FitDoublePeak(const TString input_name, const TString peak_name, Double_t mu1_init, Double_t mu2_init, Bool_t link_sigma=kFALSE)
Fit two peaks from centroid guesses.
static constexpr const char * kFitRangeName
Name of the RooFit range this class fits over.
~RooFitUtils()
Destroys every RooFit object this instance created.
void SetManualParameter(Int_t index, Double_t value)
Override one starting value.
void SetManualParameters(const std::vector< Double_t > &params)
Supply explicit starting values for every parameter.
Exponential tail above the photopeak, convolved with the resolution.
Exponential tail below the photopeak, convolved with the resolution.
Linear tail below the photopeak, convolved with the resolution.
Resolution-smeared step on the low side of the photopeak.
RooAbsPdf * MakeLowExpTail(const TString &name, RooRealVar &x, RooRealVar &mu, RooRealVar &sigma, RooRealVar &tau_ratio)
Build a LowExpTail component.
RooAbsPdf * MakeLowLinTail(const TString &name, RooRealVar &x, RooRealVar &mu, RooRealVar &sigma, RooRealVar &slope)
Build a LowLinTail component.
RooAbsPdf * MakeGaussian(const TString &name, RooRealVar &x, RooRealVar &mu, RooRealVar &sigma)
Build a Gaussian component.
RooAbsPdf * MakeLinearBackground(const TString &name, RooRealVar &x, RooRealVar &slope)
Build a linear background component.
RooAbsPdf * MakeHighExpTail(const TString &name, RooRealVar &x, RooRealVar &mu, RooRealVar &sigma, RooRealVar &tau_ratio)
Build a HighExpTail component.
RooAbsPdf * MakeStepShelf(const TString &name, RooRealVar &x, RooRealVar &mu, RooRealVar &sigma)
Build a StepShelf component.
One fitted parameter's value, error and proximity to its limits.
Bool_t has_limits
Whether the parameter was bounded.
Double_t error
Fitted uncertainty.
std::string name
Parameter name as RooFit knows it.
Bool_t near_lower
Sits within a small margin of lo.
Bool_t near_limit
Either of the two above.
Bool_t near_upper
Sits within a small margin of hi.
Double_t lo
Lower bound, if has_limits.
Double_t value
Fitted value.
Double_t hi
Upper bound, if has_limits.
Result of one fit: peaks, background, quality, and diagnostics.
Float_t lin_bkg_slope
Int_t cov_qual
Covariance quality: 0 Exact, 1 NotPositiveDefinite, 2 Approximate, 3 External.
Int_t fit_status
Minuit migrad status; 0 means success.
Double_t edm
Estimated distance to the minimum.
Double_t min_nll
Minimised negative log-likelihood.
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...
std::vector< FitParameterDiagnostic > parameter_diagnostics
One entry per fitted RooRealVar, peak and background alike.
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.
Bool_t has_fit_diagnostics
Whether a RooFitResult was available, and hence whether the fields in this group mean anything.
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
The RooFit objects making up one channel's background.
One channel of a simultaneous fit: its data, model and locks.
std::vector< Bool_t > use_step_per_peak
std::vector< Double_t > mu_inits
std::vector< Bool_t > shape_lock_per_peak
std::vector< Bool_t > mu_fixed
std::vector< Double_t > events
Every RooFit object making up one peak.
RooRealVar * sigma
RooFormulaVar * high_exp_yield
RooRealVar * ratio_low_exp
RooAbsPdf * low_exp_pdf
RooRealVar * ratio_low_lin
RooRealVar * slope_low_lin
RooRealVar * tau_ratio_low_exp
RooFormulaVar * low_exp_yield
RooAbsPdf * low_lin_pdf
RooRealVar * ratio_step
RooAbsPdf * gauss_pdf
RooRealVar * mu
RooAbsPdf * step_pdf
RooRealVar * tau_ratio_high_exp
RooAbsPdf * high_exp_pdf
RooFormulaVar * low_lin_yield
RooRealVar * ratio_high_exp
RooFormulaVar * step_yield
RooRealVar * gaus_yield
A known spacing between two peaks, imposed as a Gaussian penalty.
Int_t peak_hi
Index of the upper peak.
Double_t delta
Known separation, in the observable's units.
Int_t peak_lo
Index of the lower peak.
Double_t sigma
Uncertainty on delta; must be positive.
TString channel
Channel the two peaks belong to.
Everything the simultaneous editor needs to show one channel.
Float_t display_bin_width_kev
Display bin width.
const std::vector< Double_t > * events
Event-level values behind hist, so the display can be rebinned live as the fit range changes.
RooAbsData * data
This channel's unbinned dataset.
RooFitBackgroundModel * bkg
Background model.
TH1 * hist
Display histogram for this channel.
RooAbsPdf * pdf
This channel's summed model.
Int_t num_peaks
Peaks in this channel.
std::vector< RooFitPeakModel > * peaks
Per-peak parameter models.
TString name
Channel name, shown on the tab.