Analysis-Utilities 26.9.9
C++/ROOT utilities for nuclear measurement data analysis
Loading...
Searching...
No Matches
InteractiveSimultaneousFitEditor.cpp
Go to the documentation of this file.
2
4#include "PlottingUtils.hpp"
5
6#include <RooAbsReal.h>
7#include <RooArgSet.h>
8#include <RooFitResult.h>
9#include <RooGlobalFunc.h>
10#include <RooMsgService.h>
11#include <TROOT.h>
12#include <cmath>
13#include <iostream>
14
16 const TGWindow *parent, RooSimultaneous *sim_pdf, RooAbsData *combined_data,
17 RooRealVar *x, const std::vector<SimEditorChannelView> &channel_views,
18 Double_t range_low, Double_t range_high, const TString &info_label,
19 Bool_t fit_debug)
20 : TGMainFrame(parent, 1500, 950) {
21
22 sim_pdf_ = sim_pdf;
23 combined_data_ = combined_data;
24 x_ = x;
25 channels_ = channel_views;
26 info_label_text_ = info_label;
27 fit_debug_ = fit_debug;
28 range_low_ = range_low;
29 range_high_ = range_high;
30 original_range_low_ = range_low;
31 original_range_high_ = range_high;
32
33 hist_x_min_ = channels_[0].hist->GetXaxis()->GetXmin();
34 hist_x_max_ = channels_[0].hist->GetXaxis()->GetXmax();
35 for (size_t i = 1; i < channels_.size(); i++) {
36 Double_t cmin = channels_[i].hist->GetXaxis()->GetXmin();
37 Double_t cmax = channels_[i].hist->GetXaxis()->GetXmax();
38 if (cmin < hist_x_min_)
39 hist_x_min_ = cmin;
40 if (cmax > hist_x_max_)
41 hist_x_max_ = cmax;
42 }
43
44 accepted_ = kFALSE;
45 done_ = kFALSE;
46 needs_redraw_ = kFALSE;
47 syncing_ = kFALSE;
48
49 for (size_t ci = 0; ci < channels_.size(); ci++) {
50 SimEditorChannelView &cv = channels_[ci];
51 cv.params.clear();
52 for (Int_t pi = 0; pi < cv.num_peaks; pi++) {
53 RooFitPeakModel &p = (*cv.peaks)[pi];
54 cv.params.push_back(p.mu);
55 cv.params.push_back(p.sigma);
56 cv.params.push_back(p.gaus_yield);
57 cv.params.push_back(p.ratio_step);
58 cv.params.push_back(p.ratio_low_exp);
59 cv.params.push_back(p.tau_ratio_low_exp);
60 cv.params.push_back(p.ratio_low_lin);
61 cv.params.push_back(p.slope_low_lin);
62 cv.params.push_back(p.ratio_high_exp);
63 cv.params.push_back(p.tau_ratio_high_exp);
64 }
65 cv.params.push_back(cv.bkg->bkg_yield);
66 cv.params.push_back(cv.bkg->bkg_slope);
67
68 Int_t np = (Int_t)cv.params.size();
69 cv.original_params.resize(np);
70 cv.original_bounds_low.resize(np);
71 cv.original_bounds_high.resize(np);
72 cv.original_fixed.resize(np);
73 cv.current_bounds_low.resize(np);
74 cv.current_bounds_high.resize(np);
75 cv.sliders.resize(np);
76 cv.value_entries.resize(np);
77 cv.fix_checks.resize(np);
78 cv.lo_bound_entries.resize(np);
79 cv.hi_bound_entries.resize(np);
80
81 for (Int_t i = 0; i < np; i++) {
82 cv.original_params[i] = cv.params[i]->getVal();
83 cv.original_fixed[i] = cv.params[i]->isConstant();
84 Double_t lo, hi;
85 if (cv.params[i]->hasMin() && cv.params[i]->hasMax()) {
86 lo = cv.params[i]->getMin();
87 hi = cv.params[i]->getMax();
88 } else {
89 GetDefaultBounds(ci, i, lo, hi);
90 }
91 cv.original_bounds_low[i] = lo;
92 cv.original_bounds_high[i] = hi;
93 cv.current_bounds_low[i] = lo;
94 cv.current_bounds_high[i] = hi;
95 }
96
97 cv.embedded_canvas = nullptr;
98 cv.main_pad = nullptr;
99 cv.residual_pad = nullptr;
100 cv.hist_draw = nullptr;
101 cv.total_graph = nullptr;
102 cv.bkg_graph = nullptr;
103 cv.res_graph = nullptr;
104 cv.zero_line = nullptr;
105 cv.plus3_line = nullptr;
106 cv.minus3_line = nullptr;
107 cv.chi2_label = nullptr;
108 cv.n_res_points = 0;
109 for (Int_t p = 0; p < 3; p++) {
110 for (Int_t c = 0; c < 4; c++) {
111 cv.comp_graphs[p][c] = nullptr;
112 }
113 }
114 }
115
116 ApplyBackgroundSlopeBounds();
117
118 BuildGUI();
119 InitDrawing();
120
121 redraw_timer_ = new TTimer(this, 50);
122 redraw_timer_->TurnOn();
123
124 SetWindowName("Interactive Simultaneous Fit Editor");
125 MapSubwindows();
126 Resize(GetDefaultSize());
127 MapWindow();
128
129 UpdateCanvases();
130}
131
133 if (redraw_timer_) {
134 redraw_timer_->TurnOff();
135 delete redraw_timer_;
136 }
137
138 // Drawing primitives drawn on each channel's embedded canvas — TPad does
139 // not auto-delete user primitives, so they have to go by hand.
140 //
141 // Pre-clear each pad's primitives list with "nodelete" before destroying
142 // the primitives themselves. The launcher removes the embedded canvases
143 // from gROOT->GetListOfCanvases() to suppress X11 errors during teardown,
144 // which also disables RecursiveRemove's cleanup of pad lists; without
145 // this manual Clear, ~TPad later walks stale pointers and segfaults.
146 for (Int_t ci = 0; ci < (Int_t)channels_.size(); ci++) {
147 SimEditorChannelView &cv = channels_[ci];
148 if (cv.main_pad && cv.main_pad->GetListOfPrimitives()) {
149 cv.main_pad->GetListOfPrimitives()->Clear("nodelete");
150 }
151 if (cv.residual_pad && cv.residual_pad->GetListOfPrimitives()) {
152 cv.residual_pad->GetListOfPrimitives()->Clear("nodelete");
153 }
154 for (Int_t p = 0; p < 3; p++) {
155 for (Int_t c = 0; c < 4; c++) {
156 delete cv.comp_graphs[p][c];
157 }
158 }
159 delete cv.chi2_label;
160 delete cv.zero_line;
161 delete cv.plus3_line;
162 delete cv.minus3_line;
163 delete cv.res_graph;
164 delete cv.bkg_graph;
165 delete cv.total_graph;
166 delete cv.hist_draw;
167 delete cv.residual_pad;
168 delete cv.main_pad;
169 }
170}
171
172Int_t InteractiveSimultaneousFitEditor::ChannelIndexFromWidgetId(
173 Int_t base, Int_t parm1, Int_t &local_idx) {
174 Int_t offset = parm1 - base;
175 Int_t ch_idx = offset / kSliderStride;
176 local_idx = offset % kSliderStride;
177 if (ch_idx < 0 || ch_idx >= (Int_t)channels_.size())
178 return -1;
179 if (local_idx < 0 || local_idx >= (Int_t)channels_[ch_idx].params.size())
180 return -1;
181 return ch_idx;
182}
183
184void InteractiveSimultaneousFitEditor::BuildGUI() {
185 TGHorizontalFrame *main_frame = new TGHorizontalFrame(this, 1500, 900);
186 AddFrame(main_frame,
187 new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2));
188
189 TGTab *outer_tabs = new TGTab(main_frame, 1500, 900);
190 main_frame->AddFrame(
191 outer_tabs,
192 new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2));
193
194 for (size_t ci = 0; ci < channels_.size(); ci++) {
195 TGCompositeFrame *channel_tab =
196 outer_tabs->AddTab(channels_[ci].name.Data());
197 BuildChannelTab(channel_tab, (Int_t)ci);
198 }
199
200 TGHorizontalFrame *bottom = new TGHorizontalFrame(this, 1500, 80);
201 AddFrame(bottom,
202 new TGLayoutHints(kLHintsExpandX | kLHintsBottom, 2, 2, 2, 5));
203
204 if (info_label_text_.Length() > 0) {
205 TGLabel *info_label = new TGLabel(bottom, info_label_text_);
206 bottom->AddFrame(info_label, new TGLayoutHints(kLHintsLeft | kLHintsCenterY,
207 8, 8, 4, 4));
208 }
209
210 TGGroupFrame *range_grp =
211 new TGGroupFrame(bottom, "Fit Range (shared)", kHorizontalFrame);
212 bottom->AddFrame(range_grp,
213 new TGLayoutHints(kLHintsLeft | kLHintsCenterY, 4, 4, 0, 0));
214
215 range_slider_ =
216 new TGDoubleHSlider(range_grp, 220, kDoubleScaleNo, kRangeSlider);
217 range_slider_->SetRange(hist_x_min_, hist_x_max_);
218 range_slider_->SetPosition(range_low_, range_high_);
219 range_slider_->Associate(this);
220 range_grp->AddFrame(range_slider_,
221 new TGLayoutHints(kLHintsCenterY, 2, 2, 2, 2));
222
223 range_lo_entry_ = new TGNumberEntry(
224 range_grp, range_low_, 7, kRangeLoEntry, TGNumberFormat::kNESReal,
225 TGNumberFormat::kNEAAnyNumber, TGNumberFormat::kNELNoLimits);
226 range_lo_entry_->GetNumberEntry()->Associate(this);
227 range_grp->AddFrame(range_lo_entry_,
228 new TGLayoutHints(kLHintsCenterY, 2, 2, 2, 2));
229
230 range_hi_entry_ = new TGNumberEntry(
231 range_grp, range_high_, 7, kRangeHiEntry, TGNumberFormat::kNESReal,
232 TGNumberFormat::kNEAAnyNumber, TGNumberFormat::kNELNoLimits);
233 range_hi_entry_->GetNumberEntry()->Associate(this);
234 range_grp->AddFrame(range_hi_entry_,
235 new TGLayoutHints(kLHintsCenterY, 2, 2, 2, 2));
236
237 TGTextButton *refit_btn = new TGTextButton(bottom, "Refit", kBtnRefit);
238 refit_btn->Associate(this);
239 TGTextButton *accept_btn = new TGTextButton(bottom, "Accept", kBtnAccept);
240 accept_btn->Associate(this);
241 TGTextButton *cancel_btn = new TGTextButton(bottom, "Cancel", kBtnCancel);
242 cancel_btn->Associate(this);
243 TGTextButton *reset_btn = new TGTextButton(bottom, "Reset", kBtnReset);
244 reset_btn->Associate(this);
245
246 TGLayoutHints *btn_hints =
247 new TGLayoutHints(kLHintsRight | kLHintsCenterY, 4, 4, 4, 4);
248 bottom->AddFrame(reset_btn, btn_hints);
249 bottom->AddFrame(cancel_btn, btn_hints);
250 bottom->AddFrame(accept_btn, btn_hints);
251 bottom->AddFrame(refit_btn, btn_hints);
252}
253
254void InteractiveSimultaneousFitEditor::BuildChannelTab(TGCompositeFrame *parent,
255 Int_t ch_idx) {
256 SimEditorChannelView &cv = channels_[ch_idx];
257
258 TGHorizontalFrame *row = new TGHorizontalFrame(parent, 1490, 850);
259 parent->AddFrame(
260 row, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2));
261
262 TString canvas_name = "SimEditorCanvas_" + cv.name;
263 cv.embedded_canvas =
264 new TRootEmbeddedCanvas(canvas_name.Data(), row, 850, 800);
265 row->AddFrame(cv.embedded_canvas,
266 new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2));
267
268 TGVerticalFrame *controls = new TGVerticalFrame(row, 540, 800);
269 row->AddFrame(controls,
270 new TGLayoutHints(kLHintsExpandY | kLHintsRight, 2, 2, 2, 2));
271
272 TGTab *peak_tabs = new TGTab(controls, 530, 800);
273 controls->AddFrame(
274 peak_tabs,
275 new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2));
276
277 for (Int_t pi = 0; pi < cv.num_peaks; pi++) {
278 TString tab_name = TString::Format("Peak %d", pi + 1);
279 TGCompositeFrame *pf = peak_tabs->AddTab(tab_name);
280 BuildPeakSubTab(pf, ch_idx, pi);
281 }
282 TGCompositeFrame *bf = peak_tabs->AddTab("Background");
283 BuildBackgroundSubTab(bf, ch_idx);
284}
285
286void InteractiveSimultaneousFitEditor::BuildPeakSubTab(TGCompositeFrame *parent,
287 Int_t ch_idx,
288 Int_t peak_idx) {
289 Int_t offset = peak_idx * 10;
290
291 TGGroupFrame *gaus_grp = new TGGroupFrame(parent, "Gaussian", kVerticalFrame);
292 parent->AddFrame(gaus_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 3, 1));
293 AddParamRow(gaus_grp, ch_idx, offset + 0, "Mu");
294 AddParamRow(gaus_grp, ch_idx, offset + 1, "Sigma");
295 AddParamRow(gaus_grp, ch_idx, offset + 2, "Yield");
296
297 TGGroupFrame *step_grp = new TGGroupFrame(parent, "Step", kVerticalFrame);
298 parent->AddFrame(step_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 1, 1));
299 AddParamRow(step_grp, ch_idx, offset + 3, "Step Ratio");
300
301 TGGroupFrame *ltail_grp =
302 new TGGroupFrame(parent, "Low-Side Tails", kVerticalFrame);
303 parent->AddFrame(ltail_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 1, 1));
304 AddParamRow(ltail_grp, ch_idx, offset + 4, "Exp Amp");
305 AddParamRow(ltail_grp, ch_idx, offset + 5, "Exp Decay/Sigma");
306 AddParamRow(ltail_grp, ch_idx, offset + 6, "Lin Ratio");
307 AddParamRow(ltail_grp, ch_idx, offset + 7, "Lin Slope");
308
309 TGGroupFrame *htail_grp =
310 new TGGroupFrame(parent, "High-Side Tail", kVerticalFrame);
311 parent->AddFrame(htail_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 1, 3));
312 AddParamRow(htail_grp, ch_idx, offset + 8, "Exp Amp");
313 AddParamRow(htail_grp, ch_idx, offset + 9, "Exp Decay/Sigma");
314}
315
316void InteractiveSimultaneousFitEditor::BuildBackgroundSubTab(
317 TGCompositeFrame *parent, Int_t ch_idx) {
318 TGGroupFrame *bkg_grp =
319 new TGGroupFrame(parent, "Background", kVerticalFrame);
320 parent->AddFrame(bkg_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 3, 3));
321 AddParamRow(bkg_grp, ch_idx, BkgConstIdx(ch_idx), "Yield");
322 AddParamRow(bkg_grp, ch_idx, BkgSlopeIdx(ch_idx), "Slope");
323}
324
325void InteractiveSimultaneousFitEditor::AddParamRow(TGCompositeFrame *parent,
326 Int_t ch_idx,
327 Int_t param_idx,
328 const char *name) {
329 SimEditorChannelView &cv = channels_[ch_idx];
330 Int_t global_id = ch_idx * kSliderStride + param_idx;
331
332 TGHorizontalFrame *row = new TGHorizontalFrame(parent, 510, 28);
333 parent->AddFrame(row, new TGLayoutHints(kLHintsExpandX, 1, 1, 1, 1));
334
335 Double_t val = cv.params[param_idx]->getVal();
336 Bool_t fixed = cv.original_fixed[param_idx];
337
338 TGLabel *label = new TGLabel(row, name);
339 row->AddFrame(label, new TGLayoutHints(kLHintsCenterY, 2, 4, 2, 2));
340 label->Resize(90, 20);
341
342 TGHSlider *slider =
343 new TGHSlider(row, 140, kSlider1, kSliderBase + global_id);
344 slider->SetRange(0, kSliderRes);
345 slider->SetPosition(ValToSlider(ch_idx, param_idx, val));
346 slider->Associate(this);
347 row->AddFrame(slider,
348 new TGLayoutHints(kLHintsExpandX | kLHintsCenterY, 2, 2, 2, 2));
349 cv.sliders[param_idx] = slider;
350
351 TGNumberEntry *entry = new TGNumberEntry(
352 row, val, 8, kEntryBase + global_id, TGNumberFormat::kNESReal,
353 TGNumberFormat::kNEAAnyNumber, TGNumberFormat::kNELNoLimits);
354 entry->GetNumberEntry()->Associate(this);
355 row->AddFrame(entry, new TGLayoutHints(kLHintsCenterY, 2, 2, 2, 2));
356 cv.value_entries[param_idx] = entry;
357
358 TGCheckButton *fix_cb = new TGCheckButton(row, "Fix", kFixBase + global_id);
359 fix_cb->Associate(this);
360 if (fixed)
361 fix_cb->SetState(kButtonDown);
362 row->AddFrame(fix_cb, new TGLayoutHints(kLHintsCenterY, 2, 2, 2, 2));
363 cv.fix_checks[param_idx] = fix_cb;
364
365 TGNumberEntry *lo_entry = new TGNumberEntry(
366 row, cv.current_bounds_low[param_idx], 6, kLoBoundBase + global_id,
367 TGNumberFormat::kNESReal, TGNumberFormat::kNEAAnyNumber,
368 TGNumberFormat::kNELNoLimits);
369 lo_entry->GetNumberEntry()->Associate(this);
370 row->AddFrame(lo_entry, new TGLayoutHints(kLHintsCenterY, 1, 1, 2, 2));
371 cv.lo_bound_entries[param_idx] = lo_entry;
372
373 TGNumberEntry *hi_entry = new TGNumberEntry(
374 row, cv.current_bounds_high[param_idx], 6, kHiBoundBase + global_id,
375 TGNumberFormat::kNESReal, TGNumberFormat::kNEAAnyNumber,
376 TGNumberFormat::kNELNoLimits);
377 hi_entry->GetNumberEntry()->Associate(this);
378 row->AddFrame(hi_entry, new TGLayoutHints(kLHintsCenterY, 1, 1, 2, 2));
379 cv.hi_bound_entries[param_idx] = hi_entry;
380
381 if (fixed) {
382 slider->SetEnabled(kFALSE);
383 entry->GetNumberEntry()->SetEnabled(kFALSE);
384 }
385}
386
387void InteractiveSimultaneousFitEditor::InitDrawing() {
388 for (size_t ci = 0; ci < channels_.size(); ci++) {
389 InitChannelDrawing(channels_[ci]);
390 }
391}
392
393void InteractiveSimultaneousFitEditor::InitChannelDrawing(
395 TCanvas *canvas = cv.embedded_canvas->GetCanvas();
396 canvas->cd();
397
398 TString main_name = "simed_main_" + cv.name;
399 cv.main_pad = new TPad(main_name.Data(), main_name.Data(), 0, 0.3, 1, 1.0);
400 cv.main_pad->SetBottomMargin(0.04);
401 cv.main_pad->SetGridx(1);
402 cv.main_pad->SetGridy(1);
403 cv.main_pad->SetTopMargin(0.08);
404 cv.main_pad->Draw();
405
406 TString res_name = "simed_res_" + cv.name;
407 cv.residual_pad = new TPad(res_name.Data(), res_name.Data(), 0, 0, 1, 0.3);
408 cv.residual_pad->SetTopMargin(0.04);
409 cv.residual_pad->SetBottomMargin(0.35);
410 cv.residual_pad->SetGridx(1);
411 cv.residual_pad->SetGridy(1);
412 cv.residual_pad->Draw();
413
414 cv.main_pad->cd();
415 TString draw_name = "hist_simed_" + cv.name;
416 cv.hist_draw = static_cast<TH1 *>(cv.hist->Clone(draw_name.Data()));
417 cv.hist_draw->SetDirectory(0);
418 cv.hist_draw->GetXaxis()->SetRangeUser(0.9 * range_low_, 1.1 * range_high_);
419 cv.hist_draw->GetXaxis()->SetLabelSize(0);
420 cv.hist_draw->GetXaxis()->SetTitleSize(0);
421 cv.hist_draw->SetLineColor(kViolet);
422 cv.hist_draw->SetLineWidth(2);
423 cv.hist_draw->Draw();
424
425 cv.total_graph = new TGraph(kNDrawPts);
426 cv.total_graph->SetLineColor(kAzure);
427 cv.total_graph->SetLineWidth(2);
428 cv.total_graph->Draw("L same");
429
430 cv.bkg_graph = new TGraph(kNDrawPts);
431 cv.bkg_graph->SetLineColor(kGreen);
432 cv.bkg_graph->SetLineWidth(2);
433 cv.bkg_graph->Draw("L same");
434
435 Int_t comp_colors[4] = {kBlack, kGray, kRed, kOrange};
436 for (Int_t p = 0; p < cv.num_peaks; p++) {
437 for (Int_t c = 0; c < 4; c++) {
438 cv.comp_graphs[p][c] = new TGraph(kNDrawPts);
439 cv.comp_graphs[p][c]->SetLineColor(comp_colors[c]);
440 cv.comp_graphs[p][c]->SetLineStyle(PeakStyle(p));
441 cv.comp_graphs[p][c]->SetLineWidth(2);
442 cv.comp_graphs[p][c]->Draw("L same");
443 }
444 }
445
446 cv.chi2_label = new TLatex();
447 cv.chi2_label->SetNDC();
448 cv.chi2_label->SetTextSize(0.045);
449 cv.chi2_label->SetTextAlign(31);
450 cv.chi2_label->Draw();
451
452 cv.main_pad->SetLogy(kTRUE);
453
454 cv.residual_pad->cd();
455 Int_t nbh = cv.hist->GetNbinsX();
456 cv.n_res_points = 0;
457 for (Int_t i = 1; i <= nbh; i++) {
458 Double_t xv = cv.hist->GetBinCenter(i);
459 if (xv < range_low_ || xv > range_high_)
460 continue;
461 Double_t d = cv.hist->GetBinContent(i);
462 Double_t e = cv.hist->GetBinError(i);
463 if (e > 0 && d > 0)
464 cv.n_res_points++;
465 }
466 if (cv.n_res_points < 1)
467 cv.n_res_points = 1;
468
469 cv.res_graph = new TGraph(cv.n_res_points);
470 cv.res_graph->SetMarkerStyle(20);
471 cv.res_graph->SetMarkerSize(0.8);
472 cv.res_graph->SetMarkerColor(kAzure);
473 cv.res_graph->SetLineColor(kAzure);
474 cv.res_graph->SetTitle("");
475
476 Double_t ax_min = 0.9 * range_low_;
477 Double_t ax_max = 1.1 * range_high_;
478 cv.res_graph->GetXaxis()->SetLimits(ax_min, ax_max);
479 cv.res_graph->GetYaxis()->SetTitle("#delta/#sigma");
480 cv.res_graph->GetXaxis()->SetTitle(cv.hist->GetXaxis()->GetTitle());
481 cv.res_graph->GetXaxis()->SetTitleSize(0.13);
482 cv.res_graph->GetYaxis()->SetTitleSize(0.13);
483 cv.res_graph->GetXaxis()->SetLabelSize(0.12);
484 cv.res_graph->GetYaxis()->SetLabelSize(0.12);
485 cv.res_graph->GetXaxis()->SetTitleOffset(1.0);
486 cv.res_graph->GetYaxis()->SetTitleOffset(0.3);
487 cv.res_graph->GetYaxis()->SetNdivisions(505);
488 cv.res_graph->GetXaxis()->SetNdivisions(510);
489 cv.res_graph->GetYaxis()->CenterTitle(kTRUE);
490 cv.res_graph->GetYaxis()->SetRangeUser(-5.5, 5.5);
491 cv.res_graph->Draw("AP");
492
493 TString zero_name = "zero_simed_" + cv.name;
494 cv.zero_line = new TF1(zero_name.Data(), "0", ax_min, ax_max);
495 cv.zero_line->SetLineColor(kBlack);
496 cv.zero_line->SetLineStyle(2);
497 cv.zero_line->SetLineWidth(2);
498 cv.zero_line->Draw("same");
499
500 TString plus3_name = "plus3_simed_" + cv.name;
501 cv.plus3_line = new TF1(plus3_name.Data(), "3", ax_min, ax_max);
502 cv.plus3_line->SetLineColor(kGray + 2);
503 cv.plus3_line->SetLineStyle(3);
504 cv.plus3_line->SetLineWidth(2);
505 cv.plus3_line->Draw("same");
506
507 TString minus3_name = "minus3_simed_" + cv.name;
508 cv.minus3_line = new TF1(minus3_name.Data(), "-3", ax_min, ax_max);
509 cv.minus3_line->SetLineColor(kGray + 2);
510 cv.minus3_line->SetLineStyle(3);
511 cv.minus3_line->SetLineWidth(2);
512 cv.minus3_line->Draw("same");
513}
514
515void InteractiveSimultaneousFitEditor::UpdateCanvases() {
516 for (size_t ci = 0; ci < channels_.size(); ci++) {
517 UpdateChannelGraphs(channels_[ci]);
518 UpdateChannelResiduals(channels_[ci]);
519 UpdateChannelChi2(channels_[ci]);
520
521 TCanvas *canvas = channels_[ci].embedded_canvas->GetCanvas();
522 channels_[ci].main_pad->Modified();
523 channels_[ci].residual_pad->Modified();
524 canvas->Modified();
525 canvas->Update();
526 }
527 needs_redraw_ = kFALSE;
528}
529
530void InteractiveSimultaneousFitEditor::UpdateChannelGraphs(
532 Double_t x_step = (range_high_ - range_low_) / (kNDrawPts - 1);
533 Double_t bin_width = cv.hist->GetBinWidth(1);
534 Double_t saved = x_->getVal();
535 RooArgSet nset(*x_);
536
537 Double_t total_exp = cv.pdf->expectedEvents(&nset);
538 for (Int_t i = 0; i < kNDrawPts; i++) {
539 Double_t xv = range_low_ + i * x_step;
540 x_->setVal(xv);
541 cv.total_graph->SetPoint(i, xv,
542 total_exp * cv.pdf->getVal(&nset) * bin_width);
543 }
544
545 Double_t bkg_yield = cv.bkg->bkg_yield->getVal();
546 for (Int_t i = 0; i < kNDrawPts; i++) {
547 Double_t xv = range_low_ + i * x_step;
548 x_->setVal(xv);
549 cv.bkg_graph->SetPoint(
550 i, xv, bkg_yield * cv.bkg->bkg_pdf->getVal(&nset) * bin_width);
551 }
552
553 for (Int_t pi = 0; pi < cv.num_peaks; pi++) {
554 RooFitPeakModel &p = (*cv.peaks)[pi];
555 Double_t gy = p.gaus_yield->getVal();
556 Double_t sy = p.step_yield->getVal();
557 Double_t lexp_y = p.low_exp_yield->getVal();
558 Double_t llin_y = p.low_lin_yield->getVal();
559 Double_t hexp_y = p.high_exp_yield->getVal();
560
561 for (Int_t i = 0; i < kNDrawPts; i++) {
562 Double_t xv = range_low_ + i * x_step;
563 x_->setVal(xv);
564 Double_t bkg_v = bkg_yield * cv.bkg->bkg_pdf->getVal(&nset) * bin_width;
565
566 cv.comp_graphs[pi][0]->SetPoint(
567 i, xv, gy * p.gauss_pdf->getVal(&nset) * bin_width + bkg_v);
568 cv.comp_graphs[pi][1]->SetPoint(
569 i, xv, sy * p.step_pdf->getVal(&nset) * bin_width + bkg_v);
570 Double_t y_low = lexp_y * p.low_exp_pdf->getVal(&nset) * bin_width +
571 llin_y * p.low_lin_pdf->getVal(&nset) * bin_width;
572 cv.comp_graphs[pi][2]->SetPoint(i, xv, y_low + bkg_v);
573 cv.comp_graphs[pi][3]->SetPoint(
574 i, xv, hexp_y * p.high_exp_pdf->getVal(&nset) * bin_width + bkg_v);
575 }
576 }
577
578 x_->setVal(saved);
579}
580
581void InteractiveSimultaneousFitEditor::UpdateChannelResiduals(
583 Int_t nbh = cv.hist->GetNbinsX();
584 Double_t bin_width = cv.hist->GetBinWidth(1);
585 RooArgSet nset(*x_);
586 Double_t total_exp = cv.pdf->expectedEvents(&nset);
587 Double_t saved = x_->getVal();
588
589 Int_t pt = 0;
590 for (Int_t i = 1; i <= nbh; i++) {
591 Double_t xv = cv.hist->GetBinCenter(i);
592 if (xv < range_low_ || xv > range_high_)
593 continue;
594 Double_t d = cv.hist->GetBinContent(i);
595 Double_t e = cv.hist->GetBinError(i);
596 if (e > 0 && d > 0) {
597 x_->setVal(xv);
598 Double_t fit_val = total_exp * cv.pdf->getVal(&nset) * bin_width;
599 Double_t pull = (d - fit_val) / e;
600 cv.res_graph->SetPoint(pt, xv, pull);
601 pt++;
602 }
603 }
604 cv.res_graph->Set(pt);
605 cv.n_res_points = pt;
606
607 Double_t ax_min = 0.9 * range_low_;
608 Double_t ax_max = 1.1 * range_high_;
609 cv.res_graph->GetXaxis()->SetLimits(ax_min, ax_max);
610 cv.zero_line->SetRange(ax_min, ax_max);
611 cv.plus3_line->SetRange(ax_min, ax_max);
612 cv.minus3_line->SetRange(ax_min, ax_max);
613 cv.res_graph->GetYaxis()->SetRangeUser(-5.5, 5.5);
614
615 x_->setVal(saved);
616}
617
618void InteractiveSimultaneousFitEditor::UpdateChannelChi2(
620 Double_t chi2_sum = 0;
621 Int_t ndf_bins = 0;
622 Int_t nbh = cv.hist->GetNbinsX();
623 Double_t bin_width = cv.hist->GetBinWidth(1);
624 RooArgSet nset(*x_);
625 Double_t total_exp = cv.pdf->expectedEvents(&nset);
626 Double_t saved = x_->getVal();
627 for (Int_t i = 1; i <= nbh; i++) {
628 Double_t xv = cv.hist->GetBinCenter(i);
629 if (xv < range_low_ || xv > range_high_)
630 continue;
631 Double_t d = cv.hist->GetBinContent(i);
632 Double_t e = cv.hist->GetBinError(i);
633 if (e > 0 && d > 0) {
634 x_->setVal(xv);
635 Double_t fit_val = total_exp * cv.pdf->getVal(&nset) * bin_width;
636 Double_t r = (d - fit_val) / e;
637 chi2_sum += r * r;
638 ndf_bins++;
639 }
640 }
641 x_->setVal(saved);
642
643 Int_t n_free = 0;
644 for (Int_t i = 0; i < (Int_t)cv.params.size(); i++) {
645 if (!IsFixed((Int_t)(&cv - channels_.data()), i))
646 n_free++;
647 }
648 Int_t ndf = ndf_bins - n_free;
649 if (ndf > 0)
650 cv.chi2_label->SetText(0.85, 0.85,
651 Form("#chi^{2}/ndf = %.3f", chi2_sum / ndf));
652 else
653 cv.chi2_label->SetText(0.85, 0.85, "#chi^{2}/ndf = N/A");
654}
655
656void InteractiveSimultaneousFitEditor::SyncAllWidgets() {
657 syncing_ = kTRUE;
658 for (size_t ci = 0; ci < channels_.size(); ci++) {
659 for (Int_t i = 0; i < (Int_t)channels_[ci].params.size(); i++) {
660 SyncChannelWidget((Int_t)ci, i);
661 }
662 }
663 syncing_ = kFALSE;
664}
665
666void InteractiveSimultaneousFitEditor::SyncChannelWidget(Int_t ch_idx,
667 Int_t param_idx) {
668 SimEditorChannelView &cv = channels_[ch_idx];
669 Double_t val = cv.params[param_idx]->getVal();
670 Bool_t fixed = cv.params[param_idx]->isConstant();
671
672 if (!fixed && cv.params[param_idx]->hasMin() &&
673 cv.params[param_idx]->hasMax()) {
674 cv.current_bounds_low[param_idx] = cv.params[param_idx]->getMin();
675 cv.current_bounds_high[param_idx] = cv.params[param_idx]->getMax();
676 }
677
678 Double_t clamped = val;
679 if (clamped < cv.current_bounds_low[param_idx])
680 clamped = cv.current_bounds_low[param_idx];
681 if (clamped > cv.current_bounds_high[param_idx])
682 clamped = cv.current_bounds_high[param_idx];
683
684 cv.sliders[param_idx]->SetPosition(ValToSlider(ch_idx, param_idx, clamped));
685 cv.value_entries[param_idx]->SetNumber(val);
686
687 if (fixed) {
688 cv.fix_checks[param_idx]->SetState(kButtonDown);
689 cv.sliders[param_idx]->SetEnabled(kFALSE);
690 cv.value_entries[param_idx]->GetNumberEntry()->SetEnabled(kFALSE);
691 } else {
692 cv.fix_checks[param_idx]->SetState(kButtonUp);
693 cv.sliders[param_idx]->SetEnabled(kTRUE);
694 cv.value_entries[param_idx]->GetNumberEntry()->SetEnabled(kTRUE);
695 }
696
697 cv.lo_bound_entries[param_idx]->SetNumber(cv.current_bounds_low[param_idx]);
698 cv.hi_bound_entries[param_idx]->SetNumber(cv.current_bounds_high[param_idx]);
699}
700
701void InteractiveSimultaneousFitEditor::OnSliderMoved(Int_t ch_idx,
702 Int_t param_idx) {
703 if (syncing_)
704 return;
705 if (IsFixed(ch_idx, param_idx))
706 return;
707
708 SimEditorChannelView &cv = channels_[ch_idx];
709 Int_t pos = cv.sliders[param_idx]->GetPosition();
710 Double_t val = SliderToVal(ch_idx, param_idx, pos);
711 cv.params[param_idx]->setVal(val);
712
713 syncing_ = kTRUE;
714 cv.value_entries[param_idx]->SetNumber(val);
715 syncing_ = kFALSE;
716
717 needs_redraw_ = kTRUE;
718}
719
720void InteractiveSimultaneousFitEditor::OnEntryChanged(Int_t ch_idx,
721 Int_t param_idx) {
722 if (syncing_)
723 return;
724 if (IsFixed(ch_idx, param_idx))
725 return;
726
727 SimEditorChannelView &cv = channels_[ch_idx];
728 Double_t val = cv.value_entries[param_idx]->GetNumber();
729
730 Bool_t bounds_changed = kFALSE;
731 if (val < cv.current_bounds_low[param_idx]) {
732 cv.current_bounds_low[param_idx] = val;
733 bounds_changed = kTRUE;
734 }
735 if (val > cv.current_bounds_high[param_idx]) {
736 cv.current_bounds_high[param_idx] = val;
737 bounds_changed = kTRUE;
738 }
739
740 cv.params[param_idx]->setRange(cv.current_bounds_low[param_idx],
741 cv.current_bounds_high[param_idx]);
742 cv.params[param_idx]->setVal(val);
743
744 syncing_ = kTRUE;
745 cv.sliders[param_idx]->SetPosition(ValToSlider(ch_idx, param_idx, val));
746 if (bounds_changed) {
747 cv.lo_bound_entries[param_idx]->SetNumber(cv.current_bounds_low[param_idx]);
748 cv.hi_bound_entries[param_idx]->SetNumber(
749 cv.current_bounds_high[param_idx]);
750 }
751 syncing_ = kFALSE;
752
753 needs_redraw_ = kTRUE;
754}
755
756void InteractiveSimultaneousFitEditor::OnBoundsChanged(Int_t ch_idx,
757 Int_t param_idx) {
758 if (syncing_)
759 return;
760
761 SimEditorChannelView &cv = channels_[ch_idx];
762 Double_t new_lo = cv.lo_bound_entries[param_idx]->GetNumber();
763 Double_t new_hi = cv.hi_bound_entries[param_idx]->GetNumber();
764 if (new_lo >= new_hi)
765 return;
766
767 cv.current_bounds_low[param_idx] = new_lo;
768 cv.current_bounds_high[param_idx] = new_hi;
769
770 Double_t val = cv.params[param_idx]->getVal();
771 if (val < new_lo)
772 val = new_lo;
773 if (val > new_hi)
774 val = new_hi;
775
776 if (!IsFixed(ch_idx, param_idx)) {
777 cv.params[param_idx]->setRange(new_lo, new_hi);
778 }
779 cv.params[param_idx]->setVal(val);
780
781 syncing_ = kTRUE;
782 cv.sliders[param_idx]->SetPosition(ValToSlider(ch_idx, param_idx, val));
783 cv.value_entries[param_idx]->SetNumber(val);
784 syncing_ = kFALSE;
785
786 needs_redraw_ = kTRUE;
787}
788
789void InteractiveSimultaneousFitEditor::OnFixToggled(Int_t ch_idx,
790 Int_t param_idx) {
791 SimEditorChannelView &cv = channels_[ch_idx];
792 Bool_t now_fixed = (cv.fix_checks[param_idx]->GetState() == kButtonDown);
793
794 if (now_fixed) {
795 cv.params[param_idx]->setConstant(kTRUE);
796 cv.sliders[param_idx]->SetEnabled(kFALSE);
797 cv.value_entries[param_idx]->GetNumberEntry()->SetEnabled(kFALSE);
798 } else {
799 cv.params[param_idx]->setRange(cv.current_bounds_low[param_idx],
800 cv.current_bounds_high[param_idx]);
801 cv.params[param_idx]->setConstant(kFALSE);
802 cv.sliders[param_idx]->SetEnabled(kTRUE);
803 cv.value_entries[param_idx]->GetNumberEntry()->SetEnabled(kTRUE);
804 }
805 needs_redraw_ = kTRUE;
806}
807
808void InteractiveSimultaneousFitEditor::OnRangeChanged() {
809 if (syncing_)
810 return;
811 Double_t new_lo = range_slider_->GetMinPosition();
812 Double_t new_hi = range_slider_->GetMaxPosition();
813 if (new_lo >= new_hi)
814 return;
815
816 range_low_ = new_lo;
817 range_high_ = new_hi;
818
819 x_->setRange(range_low_, range_high_);
820 x_->setRange(RooFitUtils::kFitRangeName, range_low_, range_high_);
821 ApplyBackgroundSlopeBounds();
822
823 for (size_t ci = 0; ci < channels_.size(); ci++) {
824 SimEditorChannelView &cv = channels_[ci];
825 if (cv.events) {
827 cv.hist_draw, *cv.events, (Float_t)range_low_, (Float_t)range_high_,
829 }
830 cv.hist_draw->GetXaxis()->SetRangeUser(0.9 * range_low_, 1.1 * range_high_);
831 }
832
833 syncing_ = kTRUE;
834 range_lo_entry_->SetNumber(range_low_);
835 range_hi_entry_->SetNumber(range_high_);
836 syncing_ = kFALSE;
837
838 needs_redraw_ = kTRUE;
839}
840
841void InteractiveSimultaneousFitEditor::DoRefit() {
842 for (size_t ci = 0; ci < channels_.size(); ci++) {
843 SimEditorChannelView &cv = channels_[ci];
844 for (Int_t i = 0; i < (Int_t)cv.params.size(); i++) {
845 if (!IsFixed((Int_t)ci, i)) {
846 cv.params[i]->setRange(cv.current_bounds_low[i],
847 cv.current_bounds_high[i]);
848 }
849 }
850 }
851
852 if (fit_debug_) {
853 // Un-suppress RooFit eval errors so the pdf producing an invalid NLL is
854 // named, and report the pre-fit NLL at the current parameter values.
855 RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors);
856 RooAbsReal *nll =
857 sim_pdf_->createNLL(*combined_data_, RooFit::Extended(kTRUE),
858 RooFit::Range(RooFitUtils::kFitRangeName));
859 Double_t nll_seed = (nll != 0) ? nll->getVal() : 0.0;
860 std::cout << "=== AU_ROOFIT_FIT_DEBUG: pre-refit NLL = " << nll_seed
861 << (std::isfinite(nll_seed) ? "" : " <== NON-FINITE")
862 << " ===" << std::endl;
863 if (nll != 0)
864 delete nll;
865 DiagnoseInvalidComponents();
866 }
867
868 Int_t print_level = fit_debug_ ? 1 : -1;
869 Int_t eval_errors = fit_debug_ ? 10 : -1;
870 RooFitResult *res = sim_pdf_->fitTo(
871 *combined_data_, RooFit::Save(kTRUE), RooFit::Extended(kTRUE),
872 RooFit::Range(RooFitUtils::kFitRangeName), RooFit::SumW2Error(kFALSE),
873 RooFit::PrintLevel(print_level), RooFit::PrintEvalErrors(eval_errors),
874 RooFit::Strategy(1), RooFit::Minimizer("Minuit2", "migrad"),
876 if (res)
877 delete res;
878
879 SyncAllWidgets();
880 needs_redraw_ = kTRUE;
881 UpdateCanvases();
882}
883
884void InteractiveSimultaneousFitEditor::DoAccept() {
885 accepted_ = kTRUE;
886 done_ = kTRUE;
887}
888
889void InteractiveSimultaneousFitEditor::DoCancel() {
890 for (size_t ci = 0; ci < channels_.size(); ci++) {
891 SimEditorChannelView &cv = channels_[ci];
892 for (Int_t i = 0; i < (Int_t)cv.params.size(); i++) {
893 cv.params[i]->setVal(cv.original_params[i]);
894 if (cv.original_fixed[i]) {
895 cv.params[i]->setConstant(kTRUE);
896 } else {
897 cv.params[i]->setRange(cv.original_bounds_low[i],
899 cv.params[i]->setConstant(kFALSE);
900 }
901 }
902 }
903 x_->setRange(original_range_low_, original_range_high_);
904 x_->setRange(RooFitUtils::kFitRangeName, original_range_low_,
905 original_range_high_);
906 accepted_ = kFALSE;
907 done_ = kTRUE;
908}
909
910void InteractiveSimultaneousFitEditor::DoReset() {
911 for (size_t ci = 0; ci < channels_.size(); ci++) {
912 SimEditorChannelView &cv = channels_[ci];
913 for (Int_t i = 0; i < (Int_t)cv.params.size(); i++) {
914 cv.params[i]->setVal(cv.original_params[i]);
917 if (cv.original_fixed[i]) {
918 cv.params[i]->setConstant(kTRUE);
919 } else {
920 cv.params[i]->setRange(cv.original_bounds_low[i],
922 cv.params[i]->setConstant(kFALSE);
923 }
924 }
925 }
926 range_low_ = original_range_low_;
927 range_high_ = original_range_high_;
928 x_->setRange(range_low_, range_high_);
929 x_->setRange(RooFitUtils::kFitRangeName, range_low_, range_high_);
930 ApplyBackgroundSlopeBounds();
931 for (size_t ci = 0; ci < channels_.size(); ci++) {
932 channels_[ci].hist_draw->GetXaxis()->SetRangeUser(0.9 * range_low_,
933 1.1 * range_high_);
934 }
935 syncing_ = kTRUE;
936 range_slider_->SetPosition(range_low_, range_high_);
937 range_lo_entry_->SetNumber(range_low_);
938 range_hi_entry_->SetNumber(range_high_);
939 syncing_ = kFALSE;
940
941 SyncAllWidgets();
942 needs_redraw_ = kTRUE;
943 UpdateCanvases();
944}
945
946void InteractiveSimultaneousFitEditor::ApplyBackgroundSlopeBounds() {
947 // The linear background 1+slope*x is only non-negative across [0, x_hi] when
948 // slope >= -1/x_hi. The build-time bound used each channel's original fit
949 // high edge; once the shared range is dragged wider, that bound lets the
950 // polynomial dip negative inside the normalization window and poison the NLL.
951 // Re-tie the lower bound to the current range high (with the same 0.9 margin
952 // BuildChannelModel uses).
953 if (range_high_ <= 0)
954 return;
955 Double_t slope_lo = -0.9 / range_high_;
956 for (size_t ci = 0; ci < channels_.size(); ci++) {
957 SimEditorChannelView &cv = channels_[ci];
958 Int_t si = BkgSlopeIdx((Int_t)ci);
959 if (si < 0 || si >= (Int_t)cv.params.size())
960 continue;
961 RooRealVar *slope = cv.params[si];
962 if (slope->isConstant())
963 continue;
964 Double_t hi = cv.current_bounds_high[si];
965 if (slope_lo >= hi)
966 continue;
967 cv.current_bounds_low[si] = slope_lo;
968 if (slope->getVal() < slope_lo)
969 slope->setVal(slope_lo);
970 slope->setRange(slope_lo, hi);
971 }
972}
973
974void InteractiveSimultaneousFitEditor::DiagnoseInvalidComponents() {
975 RooArgSet nset(*x_);
976 for (size_t ci = 0; ci < channels_.size(); ci++) {
977 SimEditorChannelView &cv = channels_[ci];
978 std::cout << " channel '" << cv.name << "' components:" << std::endl;
979 // Per-component normalization integral (one value per parameter set): a NaN
980 // or collapsed integral makes pdf/Int non-finite for the whole channel.
981 for (size_t pi = 0; pi < cv.peaks->size(); pi++) {
982 RooFitPeakModel &p = (*cv.peaks)[pi];
983 RooAbsPdf *comps[5] = {p.gauss_pdf, p.step_pdf, p.low_exp_pdf,
985 const char *names[5] = {"gauss", "step", "lowExp", "lowLin", "highExp"};
986 for (Int_t k = 0; k < 5; k++) {
987 if (comps[k] == 0)
988 continue;
989 RooAbsReal *integ =
990 comps[k]->createIntegral(nset, RooFit::NormSet(nset),
991 RooFit::Range(RooFitUtils::kFitRangeName));
992 Double_t nrm = (integ != 0) ? integ->getVal() : 0.0;
993 if (integ != 0)
994 delete integ;
995 if (!std::isfinite(nrm) || nrm <= 0.0)
996 std::cout << " [BAD NORM] peak" << (pi + 1) << "." << names[k]
997 << " integral=" << nrm << std::endl;
998 }
999 }
1000 // Scan the range for any raw component value that is non-finite (e.g. a
1001 // tail overflowing to Inf*0=NaN), reporting the first few hits.
1002 const Int_t n_samp = 4000;
1003 Double_t step_x = (range_high_ - range_low_) / (Double_t)(n_samp - 1);
1004 Int_t reported = 0;
1005 for (Int_t i = 0; i < n_samp && reported < 8; i++) {
1006 Double_t xv = range_low_ + i * step_x;
1007 x_->setVal(xv);
1008 for (size_t pi = 0; pi < cv.peaks->size() && reported < 8; pi++) {
1009 RooFitPeakModel &p = (*cv.peaks)[pi];
1010 RooAbsPdf *comps[5] = {p.gauss_pdf, p.step_pdf, p.low_exp_pdf,
1012 const char *names[5] = {"gauss", "step", "lowExp", "lowLin", "highExp"};
1013 for (Int_t k = 0; k < 5; k++) {
1014 if (comps[k] == 0)
1015 continue;
1016 Double_t r = comps[k]->getVal();
1017 if (!std::isfinite(r)) {
1018 std::cout << " [BAD RAW] peak" << (pi + 1) << "." << names[k]
1019 << " raw=" << r << " at x=" << xv << std::endl;
1020 reported++;
1021 }
1022 }
1023 }
1024 }
1025 }
1026}
1027
1029 Long_t parm1,
1030 Long_t parm2) {
1031 if (syncing_)
1032 return kTRUE;
1033 (void)parm2;
1034
1035 switch (GET_MSG(msg)) {
1036 case kC_COMMAND:
1037 switch (GET_SUBMSG(msg)) {
1038 case kCM_BUTTON:
1039 if (parm1 == kBtnRefit)
1040 DoRefit();
1041 else if (parm1 == kBtnAccept)
1042 DoAccept();
1043 else if (parm1 == kBtnCancel)
1044 DoCancel();
1045 else if (parm1 == kBtnReset)
1046 DoReset();
1047 break;
1048 case kCM_CHECKBUTTON: {
1049 Int_t local;
1050 Int_t ch = ChannelIndexFromWidgetId(kFixBase, parm1, local);
1051 if (ch >= 0)
1052 OnFixToggled(ch, local);
1053 break;
1054 }
1055 }
1056 break;
1057 case kC_HSLIDER:
1058 if (parm1 == kRangeSlider) {
1059 OnRangeChanged();
1060 } else {
1061 Int_t local;
1062 Int_t ch = ChannelIndexFromWidgetId(kSliderBase, parm1, local);
1063 if (ch >= 0)
1064 OnSliderMoved(ch, local);
1065 }
1066 break;
1067 case kC_TEXTENTRY:
1068 if (GET_SUBMSG(msg) == kTE_ENTER || GET_SUBMSG(msg) == kTE_TAB) {
1069 if (parm1 == kRangeLoEntry || parm1 == kRangeHiEntry) {
1070 Double_t new_lo = range_lo_entry_->GetNumber();
1071 Double_t new_hi = range_hi_entry_->GetNumber();
1072 if (new_lo < new_hi) {
1073 syncing_ = kTRUE;
1074 range_slider_->SetPosition(new_lo, new_hi);
1075 syncing_ = kFALSE;
1076 OnRangeChanged();
1077 }
1078 } else {
1079 Int_t local;
1080 Int_t ch = ChannelIndexFromWidgetId(kEntryBase, parm1, local);
1081 if (ch >= 0) {
1082 OnEntryChanged(ch, local);
1083 } else {
1084 ch = ChannelIndexFromWidgetId(kLoBoundBase, parm1, local);
1085 if (ch >= 0) {
1086 OnBoundsChanged(ch, local);
1087 } else {
1088 ch = ChannelIndexFromWidgetId(kHiBoundBase, parm1, local);
1089 if (ch >= 0)
1090 OnBoundsChanged(ch, local);
1091 }
1092 }
1093 }
1094 }
1095 break;
1096 }
1097 return kTRUE;
1098}
1099
1101 if (timer == redraw_timer_ && needs_redraw_) {
1102 UpdateCanvases();
1103 }
1104 return kTRUE;
1105}
1106
1108
1109Int_t InteractiveSimultaneousFitEditor::ValToSlider(Int_t ch_idx,
1110 Int_t param_idx,
1111 Double_t val) {
1112 SimEditorChannelView &cv = channels_[ch_idx];
1113 Double_t lo = cv.current_bounds_low[param_idx];
1114 Double_t hi = cv.current_bounds_high[param_idx];
1115 if (hi <= lo)
1116 return 0;
1117 Double_t frac = (val - lo) / (hi - lo);
1118 if (frac < 0.0)
1119 frac = 0.0;
1120 if (frac > 1.0)
1121 frac = 1.0;
1122 return static_cast<Int_t>(frac * kSliderRes);
1123}
1124
1125Double_t InteractiveSimultaneousFitEditor::SliderToVal(Int_t ch_idx,
1126 Int_t param_idx,
1127 Int_t pos) {
1128 SimEditorChannelView &cv = channels_[ch_idx];
1129 Double_t lo = cv.current_bounds_low[param_idx];
1130 Double_t hi = cv.current_bounds_high[param_idx];
1131 Double_t frac = static_cast<Double_t>(pos) / kSliderRes;
1132 return lo + frac * (hi - lo);
1133}
1134
1135Bool_t InteractiveSimultaneousFitEditor::IsFixed(Int_t ch_idx,
1136 Int_t param_idx) {
1137 return (channels_[ch_idx].fix_checks[param_idx]->GetState() == kButtonDown);
1138}
1139
1140void InteractiveSimultaneousFitEditor::GetDefaultBounds(Int_t ch_idx,
1141 Int_t param_idx,
1142 Double_t &lo,
1143 Double_t &hi) {
1144 SimEditorChannelView &cv = channels_[ch_idx];
1145 Double_t peak_height = cv.hist->GetMaximum();
1146 Double_t range_width = range_high_ - range_low_;
1147
1148 if (param_idx == BkgConstIdx(ch_idx)) {
1149 lo = 0;
1150 hi = peak_height * range_width * 10.0;
1151 return;
1152 }
1153 if (param_idx == BkgSlopeIdx(ch_idx)) {
1154 lo = -1000.0;
1155 hi = 1000.0;
1156 return;
1157 }
1158
1159 Int_t local = param_idx % 10;
1160 switch (local) {
1161 case 0:
1162 lo = range_low_;
1163 hi = range_high_;
1164 break;
1165 case 1:
1166 lo = cv.hist->GetBinWidth(1);
1167 hi = range_width * 0.1;
1168 break;
1169 case 2:
1170 lo = 0;
1171 hi = peak_height * range_width * 10.0;
1172 break;
1173 case 3:
1174 case 4:
1175 case 6:
1176 case 8:
1177 lo = 0;
1178 hi = 0.5;
1179 break;
1180 case 5:
1181 case 9:
1182 lo = 1.0;
1183 hi = 100.0;
1184 break;
1185 case 7:
1186 lo = -1.0;
1187 hi = 1.0;
1188 break;
1189 default:
1190 lo = -1000;
1191 hi = 1000;
1192 break;
1193 }
1194}
1195
1196Int_t InteractiveSimultaneousFitEditor::PeakStyle(Int_t peak_idx) {
1197 if (peak_idx == 0)
1198 return 1;
1199 if (peak_idx == 1)
1200 return 3;
1201 return 4;
1202}
1203
1205 RooSimultaneous *sim_pdf, RooAbsData *combined_data, RooRealVar *x,
1206 std::vector<SimEditorChannelView> &channel_views, Double_t range_low,
1207 Double_t range_high, const TString &info_label, Bool_t fit_debug) {
1208 if (!gClient) {
1209 std::cerr << "InteractiveSimultaneousFitEditor: GUI not available"
1210 << std::endl;
1211 return kFALSE;
1212 }
1213
1214 // Swallow recoverable X protocol errors for the editor's lifetime so a
1215 // transient bad redraw doesn't trip ROOT's crashing default handler.
1217
1220 gClient->GetRoot(), sim_pdf, combined_data, x, channel_views,
1221 range_low, range_high, info_label, fit_debug);
1222
1223 while (!editor->IsDone()) {
1224 gSystem->ProcessEvents();
1225 gSystem->Sleep(10);
1226 }
1227
1228 Bool_t result = editor->WasAccepted();
1229 editor->GetRedrawTimer()->TurnOff();
1230
1231 // Prevent TCanvas::Close() from doing X11 operations during teardown.
1232 // Removing the canvases from the canvases list also suppresses X11
1233 // events for them on the next ProcessEvents (otherwise the next editor's
1234 // first ProcessEvents picks up stale paint events and crashes in
1235 // DrawString). Batch mode disables remaining X11 drawing calls.
1236 // Note: removing the canvas from this list disables RecursiveRemove's
1237 // pad-primitive cleanup, so the editor destructor explicitly clears
1238 // pad primitive lists before deleting drawn objects.
1239 std::vector<SimEditorChannelView> *cvs = editor->GetChannels();
1240 for (size_t ci = 0; ci < cvs->size(); ci++) {
1241 TCanvas *c = (*cvs)[ci].embedded_canvas->GetCanvas();
1242 if (c) {
1243 gROOT->GetListOfCanvases()->Remove(c);
1244 c->SetBatch(kTRUE);
1245 }
1246 }
1247
1248 editor->DontCallClose();
1249 editor->UnmapWindow();
1250 gSystem->ProcessEvents();
1251 delete editor;
1252
1253 AURestoreXErrorHandler(xerr_save);
1254 return result;
1255}
Stops a recoverable X protocol error from becoming a fatal SIGSEGV.
AUXErrorHandlerSave AUInstallTolerantXErrorHandler()
Install the tolerant X error handler for the duration of an event loop.
void AURestoreXErrorHandler(AUXErrorHandlerSave s)
Put back the handler that was in place before the tolerant one.
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)
Open the simultaneous editor and pump its event loop.
RooCmdArg BestAvailableBackend()
The best RooFit evaluation backend this build supports.
Interactive editor for a multi-channel simultaneous RooFit fit.
virtual void CloseWindow()
Window-manager close. Treated as a cancel, not an accept.
Bool_t WasAccepted() const
Whether the user accepted rather than cancelled.
std::vector< SimEditorChannelView > * GetChannels()
The per-channel views, carrying the edited parameters.
virtual Bool_t HandleTimer(TTimer *timer)
Redraw tick.
TTimer * GetRedrawTimer()
The coalescing redraw timer, for the driving event loop.
InteractiveSimultaneousFitEditor(const TGWindow *parent, RooSimultaneous *sim_pdf, RooAbsData *combined_data, RooRealVar *x, const std::vector< SimEditorChannelView > &channel_views, Double_t range_low, Double_t range_high, const TString &info_label="", Bool_t fit_debug=kFALSE)
Build the editor around a converged simultaneous model.
virtual ~InteractiveSimultaneousFitEditor()
Destroys the widgets and drawing objects the editor created.
virtual Bool_t ProcessMessage(Long_t msg, Long_t parm1, Long_t parm2)
ROOT GUI message dispatch for every widget in the editor.
Bool_t IsDone() const
Whether the editor has finished and the loop may exit.
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 constexpr const char * kFitRangeName
Name of the RooFit range this class fits over.
What AUInstallTolerantXErrorHandler() needs in order to undo itself.
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
Everything the simultaneous editor needs to show one channel.
std::vector< Double_t > original_bounds_high
std::vector< TGNumberEntry * > lo_bound_entries
std::vector< TGCheckButton * > fix_checks
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.
RooFitBackgroundModel * bkg
Background model.
std::vector< Double_t > current_bounds_low
std::vector< Double_t > original_bounds_low
std::vector< Double_t > current_bounds_high
std::vector< TGNumberEntry * > hi_bound_entries
TH1 * hist
Display histogram for this channel.
std::vector< TGNumberEntry * > value_entries
RooAbsPdf * pdf
This channel's summed model.
std::vector< RooRealVar * > params
Int_t num_peaks
Peaks in this channel.
std::vector< RooFitPeakModel > * peaks
Per-peak parameter models.
TString name
Channel name, shown on the tab.