Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
xRooNode.cxx
Go to the documentation of this file.
1/*
2 * Project: xRooFit
3 * Author:
4 * Will Buttinger, RAL 2022
5 *
6 * Copyright (c) 2022, CERN
7 *
8 * Redistribution and use in source and binary forms,
9 * with or without modification, are permitted according to the terms
10 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
11 */
12
13/** \class ROOT::Experimental::XRooFit::xRooNode
14\ingroup xroofit
15
16The xRooNode class is designed to wrap over a TObject and provide functionality to aid with interacting with that
17object, particularly in the case where the object is a RooFit class instance. It is a smart pointer to the object, so
18you have access to all the methods of the object too.
19 */
20
21#include "RVersion.h"
22
23#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
24
25#define protected public
26#include "TRootBrowser.h"
28#define private public
29#include "RooAbsArg.h"
30#include "RooWorkspace.h"
33#include "RooProdPdf.h"
34#include "TGFileBrowser.h"
35#include "RooFitResult.h"
36#include "TPad.h"
37#undef private
38#include "RooAddPdf.h"
39#include "RooRealSumPdf.h"
40#include "RooProduct.h"
41#include "RooHistFunc.h"
42#include "RooConstVar.h"
43#include "RooSimultaneous.h"
44#undef protected
45
46#define GETWS(a) a->_myws
47#define GETWSSETS(w) w->_namedSets
48#define GETWSSNAPSHOTS(w) w->_snapshots
49#define GETACTBROWSER(b) b->fActBrowser
50#define GETROOTDIR(b) b->fRootDir
51#define GETLISTTREE(b) b->fListTree
52#define GETDMP(o, m) o->m
53
54#else
55
56#include "RooAbsArg.h"
57#include "RooWorkspace.h"
58#include "RooFitResult.h"
59#include "RooConstVar.h"
60#include "RooHistFunc.h"
61#include "RooRealSumPdf.h"
62#include "RooSimultaneous.h"
63#include "RooAddPdf.h"
64#include "RooProduct.h"
65#include "TPad.h"
69#include "RooProdPdf.h"
70#include "TRootBrowser.h"
71#include "TGFileBrowser.h"
72
74{
75 return a->workspace();
76}
78{
79 return w->sets();
80}
82{
83 return w->getSnapshots();
84}
86{
87 return b->GetActBrowser();
88}
90{
91 return b->GetRootDir();
92}
94{
95 return b->GetListTree();
96}
97#define GETDMP(o, m) \
98 *reinterpret_cast<void **>(reinterpret_cast<unsigned char *>(o) + o->Class()->GetDataMemberOffset(#m))
99
100#endif
101
102#include "RooAddition.h"
103
104#include "RooCategory.h"
105#include "RooRealVar.h"
106#include "RooStringVar.h"
107#include "RooBinning.h"
108#include "RooUniformBinning.h"
109
110#include "RooAbsData.h"
111#include "RooDataHist.h"
112#include "RooDataSet.h"
113
114#include "xRooFit/xRooNode.h"
115#include "xRooFit/xRooFit.h"
116
117#include "TH1.h"
118#include "TBrowser.h"
119#include "TROOT.h"
120#include "TQObject.h"
121#include "TAxis.h"
122#include "TGraphAsymmErrors.h"
123#include "TMath.h"
124#include "TPRegexp.h"
125#include "TRegexp.h"
126#include "TExec.h"
127#include "TPaveText.h"
128
129#include "TGListTree.h"
130#include "TGMsgBox.h"
131#include "TGedEditor.h"
132#include "TGMimeTypes.h"
133#include "TH2.h"
134#include "RooExtendPdf.h"
135#include "RooExtendedBinding.h"
136
138
139#include "coutCapture.h"
140
141// #include "RooFitTrees/RooFitResultTree.h"
142// #include "RooFitTrees/RooDataTree.h"
143#include "TFile.h"
144#include "TSystem.h"
145#include "TKey.h"
146#include "TEnv.h"
147#include "TStyle.h"
148
149#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 26, 00)
151#endif
152
153#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 24, 00)
154#include "RooBinSamplingPdf.h"
155#endif
156
157#include "RooPoisson.h"
158#include "RooGaussian.h"
159#include "RooFormulaVar.h"
160#include "RooGenericPdf.h"
161#include "TVectorD.h"
162#include "TStopwatch.h"
163#include "TTimeStamp.h"
164
165#include <csignal>
166
167#include "TCanvas.h"
168#include "THStack.h"
169
170#include "TLegend.h"
171#include "TLegendEntry.h"
172#include "TGraphErrors.h"
173#include "TMultiGraph.h"
174#include "TFrame.h"
175#include "RooProjectedPdf.h"
176#include "TMemFile.h"
177#include "TGaxis.h"
178#include "TPie.h"
179// #include <thread>
180// #include <future>
181
182#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
183#include "RooNaNPacker.h"
184#endif
185
187
188xRooNode::InteractiveObject *xRooNode::gIntObj = nullptr;
189std::map<std::string, std::tuple<std::function<double(double, double, double)>, bool>> xRooNode::auxFunctions;
190void xRooNode::SetAuxFunction(const char *title, const std::function<double(double, double, double)> &func,
191 bool symmetrize)
192{
193 auxFunctions[title] = std::make_tuple(func, symmetrize);
194}
195
196template <typename T>
197const T &_or_func(const T &a, const T &b)
198{
199 if (a)
200 return a;
201 return b;
202}
203
204xRooNode::xRooNode(const char *classname, const char *name, const char *title)
205 : xRooNode(name, std::shared_ptr<TObject>(TClass::GetClass(classname)
206 ? reinterpret_cast<TObject *>(TClass::GetClass(classname)->New())
207 : nullptr,
208 [](TObject *o) {
209 if (auto w = dynamic_cast<RooWorkspace *>(o); w) {
210#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
211 w->_embeddedDataList.Delete();
212#endif
213 xRooNode(*w, std::make_shared<xRooNode>()).sterilize();
214 }
215 if (o)
216 delete o;
217 }))
218{
219 if (auto a = get<TNamed>(); a)
220 a->SetName(name);
221 SetTitle(title);
222}
223
224xRooNode::xRooNode(const char *name, const std::shared_ptr<TObject> &comp, const std::shared_ptr<xRooNode> &parent)
225 : TNamed(name, ""), fComp(comp), fParent(parent)
226{
227
228 if (!fComp && !fParent && name && strlen(name) > 0) {
229 char *_path = gSystem->ExpandPathName(name);
230 TString pathName = TString(_path);
231 delete[] _path;
232 if (!gSystem->AccessPathName(pathName)) {
233 // if file is json can try to read
234 if (pathName.EndsWith(".json")) {
235#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 26, 00)
236 fComp = std::make_shared<RooWorkspace>("workspace", name);
237 RooJSONFactoryWSTool tool(*get<RooWorkspace>());
240 if (!tool.importJSON(pathName.Data())) {
241 Error("xRooNode", "Error reading json workspace %s", name);
242 fComp.reset();
243 }
245#else
246 Error("xRooNode", "json format workspaces available only in ROOT 6.26 onwards");
247#endif
248 } else {
249
250 // using acquire in the constructor seems to cause a mem leak according to valgrind ... possibly because
251 // (*this) gets called on it before the node is fully constructed
252 auto _file = std::make_shared<TFile>(
253 pathName); // acquire<TFile>(name); // acquire file to ensure stays open while we have the workspace
254 // actually it appears we don't need to keep the file open once we've loaded the workspace, but should be
255 // no harm doing so
256 // otherwise the workspace doesn't saveas
257 auto keys = _file->GetListOfKeys();
258 if (keys) {
259 for (auto &&k : *keys) {
260 auto cl = TClass::GetClass((static_cast<TKey *>(k))->GetClassName());
261 if (cl == RooWorkspace::Class() || cl->InheritsFrom("RooWorkspace")) {
262 fComp.reset(_file->Get<RooWorkspace>(k->GetName()), [](TObject *ws) {
263 // memory leak in workspace, some RooLinkedLists aren't cleared, fixed in ROOT 6.28
264 if (ws) {
265#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
266 dynamic_cast<RooWorkspace *>(ws)->_embeddedDataList.Delete();
267#endif
268 xRooNode(*ws, std::make_shared<xRooNode>()).sterilize();
269 delete ws;
270 }
271 });
272 if (fComp) {
273 TNamed::SetNameTitle(fComp->GetName(), fComp->GetTitle());
274 fParent = std::make_shared<xRooNode>(
275 _file); // keep file alive - seems necessary to save workspace again in some cases
276 break;
277 }
278 }
279 }
280 }
281 }
282 } else if (pathName.EndsWith(".root") || pathName.EndsWith(".json")) {
283 throw std::runtime_error(TString::Format("%s does not exist", name));
284 }
285 }
286
287 if (auto _ws = get<RooWorkspace>(); _ws && (!parent || parent->get<TFile>())) {
290 .removeTopic(RooFit::NumIntegration); // stop info message every time
291
292 // check if any of the open files have version numbers greater than our major version
293 // may not read correctly
294 for (auto f : *gROOT->GetListOfFiles()) {
295 if ((dynamic_cast<TFile *>(f)->GetVersion() / 100) > (gROOT->GetVersionInt() / 100)) {
296 Warning("xRooNode", "There is file open with version %d > current version %d ... results may be wrong",
297 dynamic_cast<TFile *>(f)->GetVersion(), gROOT->GetVersionInt());
298 }
299 }
300
301 // use the datasets if any to 'mark' observables
302 int checkCount = 0;
303 for (auto &d : _ws->allData()) {
304 for (auto &a : *d->get()) {
305 if (auto v = _ws->var(a->GetName()); v) {
306 v->setAttribute("obs");
307 } else if (auto c = _ws->cat(a->GetName()); c) {
308 c->setAttribute("obs");
309 }
310 }
311 // count how many ds are checked ... if none are checked will check the first
312 checkCount += d->TestBit(1 << 20);
313 }
314
315 if (checkCount == 0 && !_ws->allData().empty())
316 _ws->allData().back()->SetBit(1 << 20, true);
317
318 if (auto _set = dynamic_cast<RooArgSet *>(GETWSSNAPSHOTS(_ws).find("NominalParamValues")); _set) {
319 for (auto s : *_set) {
320 if (auto v = dynamic_cast<RooRealVar *>(s); v) {
321 _ws->var(s->GetName())->setStringAttribute("nominal", TString::Format("%f", v->getVal()));
322 }
323 }
324 }
325
326 // also flag global observables ... relies on ModelConfig existences
327 RooArgSet _allGlobs;
328 for (auto &[k, v] : GETWSSETS(_ws)) {
329 if (k == "globalObservables" || TString(k).EndsWith("_GlobalObservables")) {
330 for (auto &s : v) {
331 _allGlobs.add(*s);
332 s->setAttribute("obs");
333 s->setAttribute("global");
334 }
335 } else if (TString(k).EndsWith("_Observables")) {
336 const_cast<RooArgSet &>(v).setAttribAll("obs");
337 } else if (TString(k).EndsWith("_POI")) {
338 for (auto &s : v) {
339 s->setAttribute("poi");
340 auto _v = dynamic_cast<RooRealVar *>(s);
341 if (!_v)
342 continue;
343 // if (!_v->hasRange("physical")) {
344 // _v->setRange("physical", 0, std::numeric_limits<double>::infinity());
345 // // ensure range of poi is also straddling 0
346 // if (_v->getMin() >= 0)
347 // _v->setMin(-1e-5);
348 // }
349 }
350 } else if (TString(k).EndsWith("_NuisParams")) {
351 const_cast<RooArgSet &>(v).setAttribAll("np");
352 }
353 }
354 if (!_allGlobs.empty() && GETWSSETS(_ws).count("globalObservables") == 0) {
355 _ws->defineSet("globalObservables", _allGlobs);
356 }
357
358 // now check if any pars don't have errors defined (not same as error=0) ... if so, use the first pdf (if there is
359 // one) to try setting values from
360 if (!_ws->allPdfs().empty()) {
361 std::set<RooRealVar *> noErrorPars;
362 std::string parNames;
363 for (auto &p : np()) { // infer errors on all floating non-poi parameters
364 auto v = p->get<RooRealVar>();
365 if (!v)
366 continue;
367 if (!v->hasError()) {
368 noErrorPars.insert(v);
369 if (!parNames.empty())
370 parNames += ",";
371 parNames += v->GetName();
372 }
373 }
374 if (!noErrorPars.empty()) {
375 Warning(
376 "xRooNode",
377 "Inferring initial errors of %d parameters (give all nuisance parameters an error to avoid this msg)",
378 int(noErrorPars.size()));
379 // get the first top-level pdf
380 browse();
381 for (auto &a : *this) {
382 if (a->fFolder == "!models") {
383 try {
384 auto fr = a->floats().reduced(parNames).fitResult("prefit");
385 if (auto _fr = fr.get<RooFitResult>(); _fr) {
386 for (auto &v : noErrorPars) {
387 if (auto arg = dynamic_cast<RooRealVar *>(_fr->floatParsFinal().find(v->GetName()));
388 arg && arg->hasError()) {
389 v->setError(arg->getError());
390 }
391 }
392 }
393 } catch (...) {
394 }
395 }
396 }
397 }
398 }
399 }
400
401 if (strlen(GetTitle()) == 0) {
402 if (fComp) {
403 TNamed::SetTitle(fComp->GetTitle());
404 } else {
405 TNamed::SetTitle(GetName());
406 }
407 }
408}
409
410xRooNode::xRooNode(const TObject &comp, const std::shared_ptr<xRooNode> &parent)
411 : xRooNode(/*[](const TObject& c) {
412c.InheritsFrom("RooAbsArg");
413if (s) {
414return (s->getStringAttribute("alias")) ? s->getStringAttribute("alias") : c.GetName();
415}
416return c.GetName();
417}(comp)*/
418 (comp.InheritsFrom("RooAbsArg") && dynamic_cast<const RooAbsArg *>(&comp)->getStringAttribute("alias"))
419 ? dynamic_cast<const RooAbsArg *>(&comp)->getStringAttribute("alias")
420 : comp.GetName(),
421 std::shared_ptr<TObject>(const_cast<TObject *>(&comp), [](TObject *) {}), parent)
422{
423}
424
425xRooNode::xRooNode(const std::shared_ptr<TObject> &comp, const std::shared_ptr<xRooNode> &parent)
426 : xRooNode(
427 [&]() {
428 if (auto a = std::dynamic_pointer_cast<RooAbsArg>(comp); a && a->getStringAttribute("alias"))
429 return a->getStringAttribute("alias");
430 if (comp)
431 return comp->GetName();
432 return "";
433 }(),
434 comp, parent)
435{
436}
437
439
440void xRooNode::Checked(TObject *obj, bool val)
441{
442 if (obj != this)
443 return;
444
445 // cycle through states:
446 // unhidden and selected: tick, no uline
447 // hidden and unselected: notick, uline
448 // unhidden and unselected: tick, uline
449 if (auto o = get<RooAbsReal>(); o) {
450 if (o->isSelectedComp() && !val) {
451 // deselecting and hiding
452 o->selectComp(val);
453 o->setAttribute("hidden");
454 } else if (!o->isSelectedComp() && !val) {
455 // selecting
456 o->selectComp(!val);
457 } else if (val) {
458 // unhiding but keeping unselected
459 o->setAttribute("hidden", false);
460 }
461 auto item = GetTreeItem(nullptr);
462 item->CheckItem(!o->getAttribute("hidden"));
463 if (o->isSelectedComp()) {
464 item->ClearColor();
465 } else {
466 item->SetColor(kGray);
467 }
468 return;
469 }
470
471 if (auto o = get(); o) {
472 // if (o->TestBit(1<<20)==val) return; // do nothing
473 o->SetBit(1 << 20, val); // TODO: check is 20th bit ok to play with?
474 if (auto fr = get<RooFitResult>(); fr) {
475 if (auto _ws = ws(); _ws) {
476 if (val) {
477 // ensure fit result is in genericObjects list ... if not, add a copy ...
478 if (!_ws->genobj(fr->GetName())) {
479 _ws->import(*fr);
480 if (auto wfr = dynamic_cast<RooFitResult *>(_ws->genobj(fr->GetName()))) {
481 fr = wfr;
482 }
483 }
484 RooArgSet _allVars = _ws->allVars();
485 _allVars = fr->floatParsFinal();
486 _allVars = fr->constPars();
487 for (auto &i : fr->floatParsInit()) {
488 auto v = dynamic_cast<RooRealVar *>(_allVars.find(i->GetName()));
489 if (v)
490 v->setStringAttribute("initVal", TString::Format("%f", dynamic_cast<RooRealVar *>(i)->getVal()));
491 }
492 // uncheck all other fit results
493 for (auto oo : _ws->allGenericObjects()) {
494 if (auto ffr = dynamic_cast<RooFitResult *>(oo); ffr && ffr != fr) {
495 ffr->ResetBit(1 << 20);
496 }
497 }
498 } else
499 _ws->allVars() = fr->floatParsInit();
500 }
501 if (auto item = GetTreeItem(nullptr); item) {
502 // update check marks on siblings
503 if (auto first = item->GetParent()->GetFirstChild()) {
504 do {
505 if (first->HasCheckBox()) {
506 auto _obj = static_cast<xRooNode *>(first->GetUserData());
507 first->CheckItem(_obj->get() && _obj->get()->TestBit(1 << 20));
508 }
509 } while ((first = first->GetNextSibling()));
510 }
511 }
512 }
513 }
514}
515
517{
518 static bool blockBrowse = false;
519 if (blockBrowse)
520 return;
521 if (b == nullptr) {
522 auto b2 = dynamic_cast<TBrowser *>(gROOT->GetListOfBrowsers()->Last());
523 if (!b2 || !b2->GetBrowserImp()) { // no browser imp if browser was closed
524 blockBrowse = true;
525 gEnv->SetValue("X11.UseXft", "no"); // for faster x11
526 gEnv->SetValue("X11.Sync", "no");
527 gEnv->SetValue("X11.FindBestVisual", "no");
528 gEnv->SetValue("Browser.Name", "TRootBrowser"); // forces classic root browser (in 6.26 onwards)
529 gEnv->SetValue("Canvas.Name", "TRootCanvas");
530 b2 = new TBrowser("nodeBrowser", this, "RooFit Browser");
531 blockBrowse = false;
532 } else if (strcmp(b2->GetName(), "nodeBrowser") == 0) {
533 blockBrowse = true;
534 b2->BrowseObject(this);
535 blockBrowse = false;
536 } else {
537 auto _b = dynamic_cast<TGFileBrowser *>(GETACTBROWSER(dynamic_cast<TRootBrowser *>(b2->GetBrowserImp())));
538 if (_b)
539 _b->AddFSDirectory("Workspaces", nullptr, "SetRootDir");
540 /*auto l = Node2::Class()->GetMenuList();
541 auto o = new CustomClassMenuItem(TClassMenuItem::kPopupUserFunction,Node2::Class(),
542 "blah blah blah","BlahBlah",0,"Option_t*",-1,true);
543 //o->SetCall(o,"BlahBlah","Option_t*",-1);
544 l->AddFirst(o);*/
545 // b->BrowseObject(this);
546 _b->GotoDir(nullptr);
547 _b->Add(this, GetName());
548 // b->Add(this);
549 }
550 return;
551 }
552
553 if (auto item = GetTreeItem(b); item) {
554 if (!item->IsOpen() && IsFolder())
555 return; // no need to rebrowse if closing
556 // update check marks on any child items
557 if (auto first = item->GetFirstChild()) {
558 do {
559 if (first->HasCheckBox()) {
560 auto _obj = static_cast<xRooNode *>(first->GetUserData());
561 first->CheckItem(_obj->get() &&
562 (_obj->get()->TestBit(1 << 20) ||
563 (_obj->get<RooAbsArg>() && !_obj->get<RooAbsArg>()->getAttribute("hidden"))));
564 }
565 } while ((first = first->GetNextSibling()));
566 }
567 }
568
569 browse();
570
571 // for top-level pdfs default to having the .vars browsable too
572 if (get<RooAbsPdf>() && fFolder == "!models" && !_IsShowVars_()) {
573 fBrowsables.push_back(std::make_shared<xRooNode>(vars()));
574 }
575
576 if (auto _fr = get<RooFitResult>(); _fr && fBrowsables.empty()) {
577 // have some common drawing options
578 fBrowsables.push_back(std::make_shared<xRooNode>(".Draw(\"pull\")", nullptr, *this));
579 fBrowsables.push_back(std::make_shared<xRooNode>(".Draw(\"corrcolztext\")", nullptr, *this));
580 if (std::unique_ptr<RooAbsCollection>(_fr->floatParsFinal().selectByAttrib("poi", true))->size() == 1) {
581 fBrowsables.push_back(std::make_shared<xRooNode>(".Draw(\"impact\")", nullptr, *this));
582 }
583 }
584
585 if (empty() && fBrowsables.empty()) {
586 try {
587 if (auto s = get<TStyle>()) {
588 s->SetFillAttributes();
589 if (auto ed = dynamic_cast<TGedEditor *>(TVirtualPadEditor::GetPadEditor())) {
590 ed->SetModel(gPad, s, kButton1Down, true);
591 }
592 } else if (TString(GetName()).BeginsWith(".Draw(\"") && fParent) {
593 fParent->Draw(TString(TString(GetName())(7, strlen(GetName()) - 9)) + b->GetDrawOption());
594 } else {
595 Draw(b->GetDrawOption());
596 }
597 } catch (const std::exception &e) {
598 new TGMsgBox(
599 gClient->GetRoot(),
600 (gROOT->GetListOfBrowsers()->At(0))
601 ? dynamic_cast<TGWindow *>(static_cast<TBrowser *>(gROOT->GetListOfBrowsers()->At(0))->GetBrowserImp())
602 : gClient->GetRoot(),
603 "Exception", e.what(),
604 kMBIconExclamation); // deletes self on dismiss?
605 }
606 }
607
608 bool hasFolders = false;
609 if (strlen(GetName()) > 0 && GetName()[0] != '!') { // folders don't have folders
610 for (auto &c : *this) {
611 if (!c->fFolder.empty()) {
612 hasFolders = true;
613 break;
614 }
615 }
616 }
617 // auto _ws = get<RooWorkspace>();
618 if (/*_ws*/ hasFolders) {
619 // organize in folders
620 auto _folders = find(".folders");
621 if (!_folders) {
622 _folders = emplace_back(std::make_shared<xRooNode>(".folders", nullptr, *this));
623 }
624 // ensure entry in folders for every folder type ...
625 for (auto &v : *this) {
626 if (!v->fFolder.empty() && !_folders->find(v->fFolder, false)) {
627 _folders->emplace_back(std::make_shared<xRooNode>(v->fFolder.c_str(), nullptr, *this));
628 }
629 }
630 // now just add all the folders
631 for (auto &v : *_folders) {
632 TString _name = v->GetName();
633 if (_name.BeginsWith('!'))
634 _name = _name(1, _name.Length()); // strip ! from display
635 b->Add(v.get(), _name);
636 }
637 }
638
639 for (auto &v : *this) {
640 if (hasFolders && !v->fFolder.empty())
641 continue; // in the folders
642 if (strcmp(v->GetName(), ".folders") == 0)
643 continue; // never 'browse' the folders property
644 auto _fr = v->get<RooFitResult>();
645 int _checked = (v->get<RooAbsData>() || _fr) ? v->get()->TestBit(1 << 20) : -1;
646 if (_fr && ((_fr->status() == 0 && _fr->numStatusHistory() == 0) || (_fr->floatParsFinal().empty()))) {
647 // this is a "PARTIAL" fit result ... don't allow it to be selected
648 _checked = -1;
649 }
650 if (v->get<RooAbsPdf>() && get<RooSimultaneous>())
651 _checked = !v->get<RooAbsArg>()->getAttribute("hidden");
652 TString _name = v->GetName();
653 if (v->get() && _name.BeginsWith(TString(v->get()->ClassName()) + "::")) {
654 _name = _name(strlen(v->get()->ClassName()) + 2, _name.Length());
655 }
656 if (_name.BeginsWith(".")) {
657 // property node -- display the name of the contained object
658 if (v->get()) {
659 _name = TString::Format("%s: %s::%s", _name.Data(), v->get()->ClassName(),
660 (v->get<RooAbsArg>() && v->get<RooAbsArg>()->getStringAttribute("alias"))
661 ? v->get<RooAbsArg>()->getStringAttribute("alias")
662 : v->get()->GetName());
663 }
664 } else if (v->get() && !v->get<TFile>() && !TString(v->GetName()).BeginsWith('/'))
665 _name = TString::Format("%s::%s", v->get()->ClassName(), _name.Data());
666 if (auto _type = v->GetNodeType(); strlen(_type)) {
667 // decided not to show const values until figure out how to update if value changes
668 /*if (TString(_type)=="Const") _name += TString::Format(" [%s=%g]",_type,v->get<RooConstVar>()->getVal());
669 else*/
670 _name += TString::Format(" [%s]", _type);
671 }
672 if (auto fv = v->get<RooFormulaVar>()) {
673 TString formu = TString::Format(" [%s]", fv->expression());
674 for (size_t i = 0; i < fv->dependents().size(); i++) {
675 formu.ReplaceAll(TString::Format("x[%zu]", i), fv->dependents()[i].GetName());
676 }
677 _name += formu;
678 } else if (auto gv = v->get<RooGenericPdf>()) {
679 TString formu = TString::Format(" [%s]", gv->expression());
680 for (size_t i = 0; i < gv->dependents().size(); i++) {
681 formu.ReplaceAll(TString::Format("x[%zu]", i), gv->dependents()[i].GetName());
682 }
683 _name += formu;
684 }
685 // tool tip defaults to displaying name and title, so temporarily set name to obj name if has one
686 // and set title to the object type
687 TString nameSave(v->TNamed::GetName());
688 TString titleSave(v->TNamed::GetTitle());
689 if (auto o = v->get(); o)
690 v->TNamed::SetNameTitle(o->GetName(), o->ClassName());
691 b->Add(v.get(), _name, _checked);
692 if (auto o = v->get(); o)
693 v->TNamed::SetNameTitle(nameSave, titleSave);
694 if (_checked != -1) {
695 dynamic_cast<TQObject *>(b->GetBrowserImp())
696 ->Connect("Checked(TObject *, bool)", ClassName(), v.get(), "Checked(TObject *, bool)");
697 }
698 if (_fr) {
699 if (_fr->status() || _fr->covQual() != 3) { // snapshots or bad fits
700 v->GetTreeItem(b)->SetColor((_fr->numStatusHistory() || _fr->floatParsFinal().empty()) ? kRed : kBlue);
701 } else if (_fr->numStatusHistory() == 0) { // partial fit result ..
702 v->GetTreeItem(b)->SetColor(kGray);
703 }
704 }
705 if ((v->fFolder == "!np" || v->fFolder == "!poi")) {
706 if (v->get<RooAbsArg>()->getAttribute("Constant")) {
707 v->GetTreeItem(b)->SetColor(kGray);
708 } else
709 v->GetTreeItem(b)->ClearColor();
710 }
711 if (auto _htr = v->get<RooStats::HypoTestResult>(); _htr) {
712 // check for fit statuses
713 if (auto fits = _htr->GetFitInfo()) {
714 for (int i = 0; i < fits->numEntries(); i++) {
715 // if any fit (other than a genFit) is bad, flag point as bad
716 if (fits->get(i)->getCatIndex("type") != 5 && fits->get(i)->getRealValue("status") != 0) {
717 v->GetTreeItem(b)->SetColor(kRed);
718 break;
719 }
720 }
721 } else {
722 v->GetTreeItem(b)->SetColor(kBlue); // unknown fit status
723 }
724 }
725
726 // v.fBrowsers.insert(b);
727 }
728
729 // for pdfs, check for datasets too and add to list
730 /*if (get<RooAbsPdf>()) {
731 auto dsets = datasets();
732 if (!dsets.empty()) {
733 // check if already have .datasets() in browsables
734 bool found(false);
735 for(auto& p : fBrowsables) {
736 if (TString(p->GetName())==".datasets()") {found=true;
737 // add
738 break;
739 }
740 }
741 if (!found) {
742 fBrowsables.push_back(std::make_shared<xRooNode>(dsets));
743 }
744 }
745 }*/
746 // browse the browsables too
747 for (auto &v : fBrowsables) {
748 TString _name = v->GetName();
749 if (_name == ".memory")
750 continue; // hide the memory from browsing, if put in browsables
751 TString nameSave(v->TNamed::GetName());
752 TString titleSave(v->TNamed::GetTitle());
753 if (auto o = v->get(); o)
754 v->TNamed::SetNameTitle(o->GetName(), o->ClassName());
755 b->Add(v.get(), _name, -1);
756 if (auto o = v->get(); o)
757 v->TNamed::SetNameTitle(nameSave, titleSave);
758 }
759
760 b->SetSelected(this);
761}
762
764{
765 if (!set) {
766 // can't remove as causes a crash, need to remove from the browser first
767 /*for(auto itr = fBrowsables.begin(); itr != fBrowsables.end(); ++itr) {
768 if (strcmp((*itr)->GetName(),".vars")==0) {
769 fBrowsables.erase(itr);
770 }
771 }*/
772 } else {
773 auto v = std::make_shared<xRooNode>(vars());
774 fBrowsables.push_back(v);
775 if (auto l = GetListTree(nullptr)) {
776 l->AddItem(GetTreeItem(nullptr), v->GetName(), v.get());
777 }
778 }
779}
780
782{
783 for (auto &b : fBrowsables) {
784 if (strcmp(b->GetName(), ".vars") == 0)
785 return true;
786 }
787 return false;
788}
789
791{
792 if (strlen(GetName()) > 0 && GetName()[0] == '!')
793 return true;
794 if (strlen(GetName()) > 0 && GetName()[0] == '.' && !(TString(GetName()).BeginsWith(".Draw(\"")))
795 return true;
796 if (empty())
797 const_cast<xRooNode *>(this)->browse();
798 return !empty();
799}
800
801class Axis2 : public TAxis {
802
803public:
804 using TAxis::TAxis;
805 double GetBinWidth(Int_t bin) const override
806 {
807 if (auto v = var(); v)
808 return v->getBinWidth(bin - 1, GetName());
809 return 1;
810 }
811 double GetBinLowEdge(Int_t bin) const override
812 {
813 if (auto v = rvar(); v) {
814 return (bin == v->getBinning(GetName()).numBins() + 1) ? v->getBinning(GetName()).binHigh(bin - 2)
815 : v->getBinning(GetName()).binLow(bin - 1);
816 }
817 return bin - 1;
818 }
819 double GetBinUpEdge(Int_t bin) const override
820 {
821 if (auto v = rvar(); v)
822 return (bin == 0) ? v->getBinning(GetName()).binLow(bin) : v->getBinning(GetName()).binHigh(bin - 1);
823 return bin;
824 }
825
826 const char *GetTitle() const override
827 {
828 return (binning() && strlen(binning()->GetTitle())) ? binning()->GetTitle() : GetParent()->GetTitle();
829 }
830 void SetTitle(const char *title) override
831 {
832 if (binning()) {
833 const_cast<RooAbsBinning *>(binning())->SetTitle(title);
834 } else {
835 dynamic_cast<TNamed *>(GetParent())->SetTitle(title);
836 }
837 }
838
839 void Set(Int_t nbins, const double *xbins) override
840 {
841 if (auto v = dynamic_cast<RooRealVar *>(rvar()))
842 v->setBinning(RooBinning(nbins, xbins), GetName());
843 TAxis::Set(nbins, xbins);
844 }
845 void Set(Int_t nbins, const float *xbins) override
846 {
847 std::vector<double> bins(nbins + 1);
848 for (int i = 0; i <= nbins; i++)
849 bins.at(i) = xbins[i];
850 return Set(nbins, &bins[0]);
851 }
852 void Set(Int_t nbins, double xmin, double xmax) override
853 {
854 if (auto v = dynamic_cast<RooRealVar *>(rvar()))
855 v->setBinning(RooUniformBinning(xmin, xmax, nbins), GetName());
856 TAxis::Set(nbins, xmin, xmax);
857 }
858
859 const RooAbsBinning *binning() const { return var()->getBinningPtr(GetName()); }
860
861 Int_t FindFixBin(const char *label) const override { return TAxis::FindFixBin(label); }
862 Int_t FindFixBin(double x) const override { return (binning()) ? (binning()->binNumber(x) + 1) : x; }
863
864private:
865 RooAbsLValue *var() const { return dynamic_cast<RooAbsLValue *>(GetParent()); }
866 RooAbsRealLValue *rvar() const { return dynamic_cast<RooAbsRealLValue *>(GetParent()); }
867};
868
869std::shared_ptr<TObject> xRooNode::getObject(const std::string &name, const std::string &type) const
870{
871 // if (fParent) return fParent->getObject(name);
872
873 if (auto _owned = find(".memory"); _owned) {
874 for (auto &o : *_owned) {
875 if (name == o->GetName()) {
876 if (type.empty() || o->get()->InheritsFrom(type.c_str()))
877 return o->fComp;
878 }
879 }
880 }
881
882 // see if have a provider
883 auto _provider = fProvider;
884 auto _parent = fParent;
885 while (!_provider && _parent) {
886 _provider = _parent->fProvider;
887 _parent = _parent->fParent;
888 }
889 if (_provider)
890 return _provider->getObject(name, type);
891
892 if (ws()) {
893 std::shared_ptr<TObject> out;
894 if (auto arg = ws()->arg(name.c_str()); arg) {
895 auto _tmp = std::shared_ptr<TObject>(arg, [](TObject *) {});
896 if (!type.empty() && arg->InheritsFrom(type.c_str()))
897 return _tmp;
898 if (!out)
899 out = _tmp;
900 }
901 if (auto arg = ws()->data(name.c_str()); arg) {
902 auto _tmp = std::shared_ptr<TObject>(arg, [](TObject *) {});
903 if (!type.empty() && arg->InheritsFrom(type.c_str()))
904 return _tmp;
905 if (!out)
906 out = _tmp;
907 }
908 if (auto arg = ws()->genobj(name.c_str()); arg) {
909 auto _tmp = std::shared_ptr<TObject>(arg, [](TObject *) {});
910 if (!type.empty() && arg->InheritsFrom(type.c_str()))
911 return _tmp;
912 if (!out)
913 out = _tmp;
914 }
915 if (auto arg = ws()->embeddedData(name.c_str()); arg) {
916 auto _tmp = std::shared_ptr<TObject>(arg, [](TObject *) {});
917 if (!type.empty() && arg->InheritsFrom(type.c_str()))
918 return _tmp;
919 if (!out)
920 out = _tmp;
921 }
922 if (auto arg = GETWSSNAPSHOTS(ws()).find(name.c_str()); arg) {
923 auto _tmp = std::shared_ptr<TObject>(arg, [](TObject *) {});
924 if (!type.empty() && arg->InheritsFrom(type.c_str()))
925 return _tmp;
926 if (!out)
927 out = _tmp;
928 }
929 return out;
930 }
931 return nullptr;
932}
933
935{
936 if (fXAxis) {
937 // check if num bins needs update or not
938 if (auto cat = dynamic_cast<RooAbsCategory *>(fXAxis->GetParent());
939 cat && cat->numTypes() != fXAxis->GetNbins()) {
940 fXAxis.reset();
941 } else {
942 return fXAxis.get();
943 }
944 }
945 RooAbsLValue *x = nullptr;
946 if (auto a = get<RooAbsArg>(); a && a->isFundamental())
947 x = dynamic_cast<RooAbsLValue *>(a); // self-axis
948
949 auto _parentX = (!x && fParent && !fParent->get<RooSimultaneous>()) ? fParent->GetXaxis() : nullptr;
950
951 auto o = get<RooAbsReal>();
952 if (!o)
953 return _parentX;
954
955 if (auto xName = o->getStringAttribute("xvar"); xName) {
956 x = dynamic_cast<RooAbsLValue *>(getObject(xName).get());
957 }
958
959 // if xvar has become set equal to an arg and this is a pdf, we will allow a do-over
960 if (!x) {
961 // need to choose from dependent fundamentals, in following order:
962 // parentX (if not a glob), robs, globs, vars, args
963
964 if (_parentX && !dynamic_cast<RooAbsArg *>(_parentX->GetParent())->getAttribute("global") &&
965 (o->dependsOn(*dynamic_cast<RooAbsArg *>(_parentX->GetParent())) || vars().empty())) {
966 x = dynamic_cast<RooAbsLValue *>(_parentX->GetParent());
967 } else if (auto _obs = obs(); !_obs.empty()) {
968 for (auto &v : _obs) {
969 if (!v->get<RooAbsArg>()->getAttribute("global")) {
970 x = v->get<RooAbsLValue>();
971 if (x)
972 break;
973 } else if (!x) {
974 x = v->get<RooAbsLValue>();
975 }
976 }
977 } else if (auto _pars = pars(); !_pars.empty()) {
978 for (auto &v : _pars) {
979 if (!v->get<RooAbsArg>()->getAttribute("Constant")) {
980 x = v->get<RooAbsLValue>();
981 if (x)
982 break;
983 } else if (!x) {
984 x = v->get<RooAbsLValue>();
985 }
986 }
987 }
988
989 if (!x) {
990 return nullptr;
991 }
992 }
993
994 if (o != dynamic_cast<TObject *>(x)) {
995 o->setStringAttribute("xvar", dynamic_cast<TObject *>(x)->GetName());
996 }
997
998 // decide binning to use
999 TString binningName = o->getStringAttribute("binning");
1000 auto _bnames = x->getBinningNames();
1001 bool hasBinning = false;
1002 for (auto &b : _bnames) {
1003 if (b == binningName) {
1004 hasBinning = true;
1005 break;
1006 }
1007 }
1008 if (!hasBinning) {
1009 // doesn't have binning, so clear binning attribute
1010 // this can happen after Combine of models because binning don't get combined yet (should fix this)
1011 Warning("GetXaxis", "Binning %s not defined on %s - clearing", binningName.Data(),
1012 dynamic_cast<TObject *>(x)->GetName());
1013 o->setStringAttribute("binning", nullptr);
1014 binningName = "";
1015 }
1016
1017 if (binningName == "" && o != dynamic_cast<TObject *>(x)) {
1018 // has var has a binning matching this nodes name then use that
1019 auto __bnames = x->getBinningNames();
1020 for (auto &b : __bnames) {
1021 if (b == GetName())
1022 binningName = GetName();
1023 if (b == o->GetName()) {
1024 binningName = o->GetName();
1025 break;
1026 } // best match
1027 }
1028 if (binningName == "") {
1029 // if we are binned in this var then will define that as a binning
1030 if (/*o->isBinnedDistribution(*dynamic_cast<RooAbsArg *>(x))*/
1031 auto bins = _or_func(
1032 /*o->plotSamplingHint(*dynamic_cast<RooAbsRealLValue
1033 *>(x),-std::numeric_limits<double>::infinity(),std::numeric_limits<double>::infinity())*/
1034 (std::list<double> *)(nullptr),
1035 o->binBoundaries(*dynamic_cast<RooAbsRealLValue *>(x), -std::numeric_limits<double>::infinity(),
1036 std::numeric_limits<double>::infinity()));
1037 bins) {
1038 std::vector<double> _bins;
1039 for (auto &b : *bins) {
1040 if (_bins.empty() || std::abs(_bins.back() - b) > 1e-5 * _bins.back())
1041 _bins.push_back(b);
1042 }
1043 fXAxis = std::make_shared<Axis2>(_bins.size() - 1, &_bins[0]);
1044 // add this binning to the var to avoid recalling ...
1045 if (auto _v = dynamic_cast<RooRealVar *>(x); _v) {
1046 _v->setBinning(RooBinning(_bins.size() - 1, &_bins[0], o->GetName()), o->GetName());
1047 _v->getBinning(o->GetName())
1048 .SetTitle(""); // indicates to use the current var title when building histograms etc
1049 //_v->getBinning(o->GetName()).SetTitle(strlen(dynamic_cast<TObject*>(x)->GetTitle()) ?
1050 // dynamic_cast<TObject*>(x)->GetTitle() : dynamic_cast<TObject*>(x)->GetName());
1051 }
1052 binningName = o->GetName();
1053 delete bins;
1054 } else if (_parentX) {
1055 // use parent axis binning if defined, otherwise we will default
1056 binningName = _parentX->GetName();
1057 }
1058 }
1059 }
1060
1061 if (!fXAxis) {
1062 if (auto r = dynamic_cast<RooAbsRealLValue *>(x); r) {
1063 if (r->getBinning(binningName).isUniform()) {
1064 fXAxis = std::make_shared<Axis2>(x->numBins(binningName), r->getMin(binningName), r->getMax(binningName));
1065 } else {
1066 fXAxis = std::make_shared<Axis2>(x->numBins(binningName), r->getBinning(binningName).array());
1067 }
1068 } else if (dynamic_cast<RooAbsCategory *>(x)) {
1069 std::vector<double> bins = {};
1070 for (int i = 0; i <= x->numBins(binningName); i++)
1071 bins.push_back(i);
1072 fXAxis = std::make_shared<Axis2>(x->numBins(binningName), &bins[0]);
1073 // TODO have to load current state of bin labels if was a category (sadly not a virtual method)
1074 for (int i = 0; i < x->numBins(binningName); i++) {
1075 fXAxis->SetBinLabel(i + 1, dynamic_cast<RooAbsCategory *>(x)->lookupName(i).c_str());
1076 }
1077 }
1078 }
1079
1080 fXAxis->SetName(binningName);
1081 fXAxis->SetParent(dynamic_cast<TObject *>(x));
1082 return fXAxis.get();
1083}
1084
1085const char *xRooNode::GetIconName() const
1086{
1087 if (auto o = get(); o) {
1088 if (o->InheritsFrom("RooWorkspace"))
1089 return "TFile";
1090 if (o->InheritsFrom("RooAbsData"))
1091 return "TProfile";
1092 if (o->InheritsFrom("RooSimultaneous"))
1093 return "TH3D";
1094
1095 if (o->InheritsFrom("RooProdPdf"))
1096 return "a.C"; // or nullptr for folder
1097 if (o->InheritsFrom("RooRealSumPdf") || o->InheritsFrom("RooAddPdf"))
1098 return "TH2D";
1099 // if(o->InheritsFrom("RooProduct")) return "TH1D";
1100 if (o->InheritsFrom("RooFitResult")) {
1101 if (!gClient->GetMimeTypeList()->GetIcon("xRooFitRooFitResult", true)) {
1102 gClient->GetMimeTypeList()->AddType("xRooFitRooFitResult", "xRooFitRooFitResult", "package.xpm",
1103 "package.xpm", "->Browse()");
1104 }
1105 return "xRooFitRooFitResult";
1106 }
1107 if (o->InheritsFrom("RooRealVar") || o->InheritsFrom("RooCategory")) {
1108 if (get<RooAbsArg>()->getAttribute("obs")) {
1109 if (!gClient->GetMimeTypeList()->GetIcon("xRooFitObs", true)) {
1110 gClient->GetMimeTypeList()->AddType("xRooFitObs", "xRooFitObs", "x_pic.xpm", "x_pic.xpm", "->Browse()");
1111 }
1112 if (!gClient->GetMimeTypeList()->GetIcon("xRooFitGlobs", true)) {
1113 gClient->GetMimeTypeList()->AddType("xRooFitGlobs", "xRooFitGlobs", "z_pic.xpm", "z_pic.xpm",
1114 "->Browse()");
1115 }
1116 return (get<RooAbsArg>()->getAttribute("global") ? "xRooFitGlobs" : "xRooFitObs");
1117 }
1118 return "TLeaf";
1119 }
1120 if (o->InheritsFrom("TStyle")) {
1121 if (!gClient->GetMimeTypeList()->GetIcon("xRooFitTStyle", true)) {
1122 gClient->GetMimeTypeList()->AddType("xRooFitTStyle", "xRooFitTStyle", "bld_colorselect.xpm",
1123 "bld_colorselect.xpm", "->Browse()");
1124 }
1125 return "xRooFitTStyle";
1126 }
1127 if (o->InheritsFrom("RooConstVar")) {
1128 /*if (!gClient->GetMimeTypeList()->GetIcon("xRooFitRooConstVar",true)) {
1129 gClient->GetMimeTypeList()->AddType("xRooFitRooConstVar", "xRooFitRooConstVar", "stop_t.xpm", "stop_t.xpm",
1130 "->Browse()");
1131 }
1132 return "xRooFitRooConstVar";*/
1133 return "TMethodBrowsable-leaf";
1134 }
1135 if (o->InheritsFrom("RooStats::HypoTestInverterResult")) {
1136 if (!gClient->GetMimeTypeList()->GetIcon("xRooFitScanStyle", true)) {
1137 gClient->GetMimeTypeList()->AddType("xRooFitScanStyle", "xRooFitScanStyle", "f2_s.xpm", "f2_s.xpm",
1138 "->Browse()");
1139 }
1140 return "xRooFitScanStyle";
1141 }
1142 if (o->InheritsFrom("RooStats::HypoTestResult")) {
1143 if (!gClient->GetMimeTypeList()->GetIcon("xRooFitTestStyle", true)) {
1144 gClient->GetMimeTypeList()->AddType("xRooFitTestStyle", "xRooFitTestStyle", "diamond.xpm", "diamond.xpm",
1145 "->Browse()");
1146 }
1147 return "xRooFitTestStyle";
1148 }
1149 if (o->InheritsFrom("RooStats::HistFactory::FlexibleInterpVar"))
1150 return "TBranchElement-folder";
1151 if (o->InheritsFrom("RooAbsPdf")) {
1152 if (!gClient->GetMimeTypeList()->GetIcon("xRooFitPDFStyle", true)) {
1153 gClient->GetMimeTypeList()->AddType("xRooFitPDFStyle", "xRooFitPDFStyle", "pdf.xpm", "pdf.xpm",
1154 "->Browse()");
1155 }
1156 return "xRooFitPDFStyle";
1157 }
1158 if (auto a = dynamic_cast<RooAbsReal *>(o); a) {
1159 if (auto _ax = GetXaxis();
1160 _ax && (a->isBinnedDistribution(*dynamic_cast<RooAbsArg *>(_ax->GetParent())) ||
1161 (dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()) &&
1162 std::unique_ptr<std::list<double>>(a->binBoundaries(
1163 *dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()), -std::numeric_limits<double>::infinity(),
1164 std::numeric_limits<double>::infinity()))))) {
1165 return "TH1D";
1166 }
1167 return "TF1";
1168 }
1169 return o->ClassName();
1170 }
1171 if (!IsFolder()) {
1172 return "Unknown";
1173 }
1174 return nullptr;
1175}
1176
1177const char *xRooNode::GetNodeType() const
1178{
1179 if (auto o = get(); o && fParent && (fParent->get<RooProduct>() || fParent->get<RooRealSumPdf>())) {
1180 if (o->InheritsFrom("RooStats::HistFactory::FlexibleInterpVar"))
1181 return "Overall";
1182 if (o->InheritsFrom("PiecewiseInterpolation"))
1183 return (dynamic_cast<RooAbsArg *>(o)->getAttribute("density")) ? "DensityHisto" : "Histo";
1184 if (o->InheritsFrom("RooHistFunc"))
1185 return (dynamic_cast<RooAbsArg *>(o)->getAttribute("density")) ? "ConstDensityHisto" : "ConstHisto";
1186 if (o->InheritsFrom("RooBinWidthFunction"))
1187 return "Density";
1188 if (o->InheritsFrom("ParamHistFunc"))
1189 return "Shape";
1190 if (o->InheritsFrom("RooRealVar"))
1191 return "Norm";
1192 if (o->InheritsFrom("RooConstVar"))
1193 return "Const";
1194 }
1195 return "";
1196}
1197
1198xRooNode xRooNode::coords(bool setVals) const
1199{
1200 xRooNode out(".coords", nullptr, *this);
1201 // go up through parents looking for slice obs
1202 auto _p = std::shared_ptr<xRooNode>(const_cast<xRooNode *>(this), [](xRooNode *) {});
1203 while (_p) {
1204 TString pName(_p->GetName());
1205 if (auto pos = pName.Index('='); pos != -1) {
1206 if (pos > 0 && pName(pos - 1) == '<') {
1207 // should be a range on a real lvalue, of form low<=name<high
1208 double low = TString(pName(0, pos - 1)).Atof();
1209 pName = pName(pos + 1, pName.Length());
1210 double high = TString(pName(pName.Index('<') + 1, pName.Length())).Atof();
1211 pName = pName(0, pName.Index('<'));
1212 if (auto _obs = _p->getObject<RooAbsRealLValue>(pName.Data()); _obs) {
1213 if (setVals) {
1214 _obs->setVal((high + low) / 2.);
1215 static_cast<RooRealVar *>(_obs.get())->setRange("coordRange", low, high);
1216 _obs->setStringAttribute(
1217 "coordRange", "coordRange"); // will need if we allow multi disconnected regions, need comma list
1218 }
1219 out.emplace_back(std::make_shared<xRooNode>(_obs->GetName(), _obs, _p));
1220 } else {
1221 throw std::runtime_error(TString::Format("Unknown observable: %s", pName.Data()));
1222 }
1223
1224 } else if (auto _obs = _p->getObject<RooAbsArg>(pName(0, pos)); _obs) {
1225 if (setVals) {
1226 if (auto _cat = dynamic_cast<RooAbsCategoryLValue *>(_obs.get()); _cat) {
1227 _cat->setLabel(pName(pos + 1, pName.Length()));
1228 } else if (auto _var = dynamic_cast<RooAbsRealLValue *>(_obs.get()); _var) {
1229 _var->setVal(TString(pName(pos + 1, pName.Length())).Atof());
1230 }
1231 }
1232 out.emplace_back(std::make_shared<xRooNode>(_obs->GetName(), _obs, _p));
1233 } else {
1234 throw std::runtime_error("Unknown observable, could not find");
1235 }
1236 }
1237 _p = _p->fParent;
1238 }
1239 return out;
1240}
1241
1242void xRooNode::_Add_(const char *name, const char *opt)
1243{
1244 try {
1245 Add(name, opt);
1246 } catch (const std::exception &e) {
1247 new TGMsgBox(gClient->GetRoot(), gClient->GetRoot(), "Exception", e.what(),
1248 kMBIconExclamation); // deletes self on dismiss?
1249 }
1250}
1251void xRooNode::_Vary_(const char *what)
1252{
1253 try {
1254 Vary(what);
1255 } catch (const std::exception &e) {
1256 new TGMsgBox(gClient->GetRoot(), gClient->GetRoot(), "Exception", e.what(),
1257 kMBIconExclamation); // deletes self on dismiss?
1258 }
1259}
1260
1262{
1263
1264 if (strcmp(GetName(), ".poi") == 0) {
1265 // demote a parameter from being a poi
1266 auto toRemove =
1267 (child.get<RooAbsArg>() || !find(child.GetName())) ? child : xRooNode(find(child.GetName())->fComp);
1268 if (toRemove) {
1269 if (!toRemove.get<RooAbsArg>()->getAttribute("poi")) {
1270 throw std::runtime_error(TString::Format("%s is not a poi", toRemove.GetName()));
1271 }
1272 toRemove.get<RooAbsArg>()->setAttribute("poi", false);
1273 return toRemove;
1274 }
1275 } else if (strcmp(GetName(), ".factors") == 0 || strcmp(GetName(), ".constraints") == 0 ||
1276 strcmp(GetName(), ".components") == 0) {
1277 auto toRemove =
1278 (child.get<RooAbsArg>() || !find(child.GetName())) ? child : xRooNode(find(child.GetName())->fComp);
1279 if (auto p = fParent->get<RooProdPdf>(); p) {
1280 auto pdf = toRemove.get<RooAbsArg>();
1281 if (!pdf)
1282 pdf = p->pdfList().find(child.GetName());
1283 if (!pdf)
1284 throw std::runtime_error(TString::Format("Cannot find %s in %s", child.GetName(), fParent->GetName()));
1285 auto i = p->pdfList().index(*pdf);
1286 if (i >= 0) {
1287#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
1288 const_cast<RooArgList &>(p->pdfList()).remove(*pdf);
1289#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 26, 00)
1290 p->_pdfNSetList.erase(p->_pdfNSetList.begin() + i);
1291#else
1292 auto nset = p->_pdfNSetList.At(i);
1293 p->_pdfNSetList.Remove(nset);
1294 delete nset; // I don't think the RooLinkedList owned it so must delete ourself
1295#endif
1296 if (p->_extendedIndex == i)
1297 p->_extendedIndex = -1;
1298 else if (p->_extendedIndex > i)
1299 p->_extendedIndex--;
1300#else
1301 p->removePdfs(RooArgSet(*pdf));
1302#endif
1303 sterilize();
1304 return xRooNode(*pdf);
1305 } else {
1306 throw std::runtime_error(TString::Format("Cannot find %s in %s", child.GetName(), fParent->GetName()));
1307 }
1308 } else if (auto p2 = fParent->get<RooProduct>(); p2) {
1309 auto arg = toRemove.get<RooAbsArg>();
1310 if (!arg)
1311 arg = p2->components().find(child.GetName());
1312 if (!arg)
1313 throw std::runtime_error(TString::Format("Cannot find %s in %s", child.GetName(), fParent->GetName()));
1314 // remove server ... doesn't seem to trigger removal from proxy
1315#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
1316 p2->_compRSet.remove(*arg);
1317#else
1318 const_cast<RooArgList &>(p2->realComponents()).remove(*arg);
1319#endif
1320 p2->removeServer(*arg, true);
1321 sterilize();
1322 return xRooNode(*arg);
1323 } else if (fParent->get<RooSimultaneous>()) {
1324 // remove from all channels
1325 bool removed = false;
1326 for (auto &c : fParent->bins()) {
1327 try {
1328 c->constraints().Remove(toRemove);
1329 removed = true;
1330 } catch (std::runtime_error &) { /* wasn't a constraint in channel */
1331 }
1332 }
1333 sterilize();
1334 if (!removed)
1335 throw std::runtime_error(TString::Format("Cannot find %s in %s", child.GetName(), fParent->GetName()));
1336 return toRemove;
1337 } else if (auto p4 = fParent->get<RooRealSumPdf>(); p4) {
1338 auto arg = toRemove.get<RooAbsArg>();
1339 if (!arg)
1340 arg = p4->funcList().find(child.GetName());
1341 if (!arg)
1342 throw std::runtime_error(TString::Format("Cannot find %s in %s", child.GetName(), fParent->GetName()));
1343 // remove, including coef removal ....
1344 auto idx = p4->funcList().index(arg);
1345
1346 if (idx != -1) {
1347
1348 const_cast<RooArgList &>(p4->funcList()).remove(*arg);
1349 p4->removeServer(*arg, true);
1350 // have to be careful removing coef because if shared will end up removing them all!!
1351 std::vector<RooAbsArg *> _coefs;
1352 for (size_t ii = 0; ii < const_cast<RooArgList &>(p4->coefList()).size(); ii++) {
1353 if (ii != size_t(idx))
1354 _coefs.push_back(const_cast<RooArgList &>(p4->coefList()).at(ii));
1355 }
1356 const_cast<RooArgList &>(p4->coefList()).removeAll();
1357 for (auto &a : _coefs)
1358 const_cast<RooArgList &>(p4->coefList()).add(*a);
1359
1360 sterilize();
1361 } else {
1362 throw std::runtime_error(TString::Format("Cannot find %s in %s", child.GetName(), fParent->GetName()));
1363 }
1364 return xRooNode(*arg);
1365 } // todo: add support for RooAddPdf and RooAddition
1366 }
1367
1368 if (auto w = get<RooWorkspace>(); w) {
1369 xRooNode out(child.GetName());
1370 auto arg = w->components().find(child.GetName());
1371 if (!arg)
1372 arg = operator[](child.GetName())->get<RooAbsArg>();
1373 if (!arg) {
1374 throw std::runtime_error(TString::Format("Cannot find %s in workspace %s", child.GetName(), GetName()));
1375 }
1376 // check has no clients ... if so, cannot delete
1377 if (arg->hasClients()) {
1378 throw std::runtime_error(
1379 TString::Format("Cannot remove %s from workspace %s, because it has dependencies - first remove from those",
1380 child.GetName(), GetName()));
1381 }
1382 const_cast<RooArgSet &>(w->components()).remove(*arg); // deletes arg
1383 Info("Remove", "Deleted %s from workspace %s", out.GetName(), GetName());
1384 return out;
1385 } else if (get<RooProduct>() || get<RooProdPdf>()) {
1386 return factors().Remove(child);
1387 } else if (get<RooRealSumPdf>()) {
1388 return components().Remove(child);
1389 }
1390
1391 throw std::runtime_error("Removal not implemented for this type of object");
1392}
1393
1395{
1396
1397 class AutoUpdater {
1398 public:
1399 AutoUpdater(xRooNode &_n) : n(_n) {}
1400 ~AutoUpdater() { n.browse(); }
1401 xRooNode &n;
1402 };
1403 AutoUpdater xxx(*this);
1404
1405 TString sOpt(opt);
1406 bool considerType(sOpt == "+");
1407
1408 if (strlen(GetName()) > 0 && GetName()[0] == '!' && fParent) {
1409 // folder .. pass onto parent and add folder to child folder list
1410 const_cast<xRooNode &>(child).fFolder += GetName();
1411 return fParent->Add(child, opt);
1412 }
1413 // this is how to get the first real parent ... may be useful at some point?
1414 /*auto realParent = fParent;
1415 while(!realParent->get()) {
1416 realParent = realParent->fParent;
1417 if (!realParent) throw std::runtime_error("No parentage");
1418 }*/
1419
1420 // adding to a collection node will incorporate the child into the parent of the collection
1421 // in the appropriate way
1422 if (strcmp(GetName(), ".factors") == 0) {
1423 // multiply the parent
1424 return fParent->Multiply(child, opt);
1425 } else if (strcmp(GetName(), ".components") == 0) {
1426 // add to the parent
1427 return fParent->Add(child, opt);
1428 } else if (strcmp(GetName(), ".variations") == 0) {
1429 // vary the parent
1430 return fParent->Vary(child);
1431 } else if (strcmp(GetName(), ".constraints") == 0) {
1432 // constrain the parent
1433 return fParent->Constrain(child);
1434 } else if (strcmp(GetName(), ".bins") == 0 && fParent->get<RooSimultaneous>()) {
1435 // adding a channel (should adding a 'bin' be an 'Extend' operation?)
1436 return fParent->Vary(child);
1437 } else if ((strcmp(GetName(), ".globs") == 0)) {
1438 if (child.get<RooAbsArg>() || (!child.fComp && getObject<RooAbsArg>(child.GetName()))) {
1439 auto out = (child.get<RooAbsArg>()) ? child.get<RooAbsArg>() : getObject<RooAbsArg>(child.GetName()).get();
1440 out->setAttribute("obs");
1441 out->setAttribute("global");
1442 return xRooNode(*out, *this);
1443 }
1444 throw std::runtime_error("Failed to add global observable");
1445 } else if ((strcmp(GetName(), ".poi") == 0)) {
1446 if (child.get<RooAbsLValue>() || (!child.fComp && getObject<RooAbsLValue>(child.GetName()))) {
1447 auto out = (child.get<RooAbsArg>()) ? child.get<RooAbsArg>() : getObject<RooAbsArg>(child.GetName()).get();
1448 out->setAttribute("poi");
1449 return xRooNode(*out, *this);
1450 }
1451 throw std::runtime_error("Failed to add parameter of interest");
1452 } else if ((strcmp(GetName(), ".pars") == 0 || strcmp(GetName(), ".vars") == 0) && fParent->get<RooWorkspace>()) {
1453 // adding a parameter, interpret as factory string unless no "[" then create RooRealVar
1454 TString fac(child.GetName());
1455 if (!fac.Contains("["))
1456 fac += "[1]";
1457 return xRooNode(*fParent->get<RooWorkspace>()->factory(fac), fParent);
1458 } else if (strcmp(GetName(), ".datasets()") == 0) {
1459 // create a dataset - only allowed for pdfs or workspaces
1460 if (auto _ws = ws(); _ws && fParent) {
1461 sOpt.ToLower();
1462 if (!fParent->get<RooAbsPdf>() && (!fParent->get<RooWorkspace>() || sOpt == "asimov")) {
1463 throw std::runtime_error(
1464 "Datasets can only be created for pdfs or workspaces (except if generated dataset, then must be pdf)");
1465 }
1466
1467 if (sOpt == "asimov" || sOpt == "toy") {
1468 // generate expected dataset - note that globs will be frozen at this time
1469 auto _fr = std::dynamic_pointer_cast<const RooFitResult>(fParent->fitResult().fComp);
1470 if (strlen(_fr->GetName()) == 0)
1471 std::const_pointer_cast<RooFitResult>(_fr)->SetName(TUUID().AsString());
1472 auto asi = xRooFit::generateFrom(*fParent->get<RooAbsPdf>(), *_fr, sOpt == "asimov");
1473 if (strlen(child.GetName()))
1474 asi.first->SetName(child.GetName());
1475 if (asi.first) {
1476 _ws->import(*asi.first);
1477 }
1478 if (_fr->numStatusHistory() == 0) {
1479 if (!GETWSSNAPSHOTS(_ws).find(_fr->GetName())) {
1480 const_cast<RooLinkedList &>(GETWSSNAPSHOTS(_ws)).Add(_fr->Clone());
1481 }
1482 } else if (!_ws->obj(_fr->GetName())) {
1483 _ws->import(const_cast<RooFitResult &>(*_fr));
1484 } // save fr to workspace, for later retrieval
1485 if (asi.second) {
1486#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 26, 00)
1487 _ws->saveSnapshot(asi.first->GetName(), *asi.second,
1488 true); // TODO: Migrate to using globs inside datasets
1489#else
1490 RooArgSet _tmp;
1491 _tmp.add(*asi.second);
1492 _ws->saveSnapshot(asi.first->GetName(), _tmp, true);
1493#endif
1494 }
1495 return xRooNode(*_ws->data(asi.first->GetName()), fParent);
1496 }
1497
1498 auto _obs = fParent->obs().argList();
1499 // put globs in a snapshot
1500 std::unique_ptr<RooAbsCollection> _globs(_obs.selectByAttrib("global", true));
1501 // RooArgSet _tmp; _tmp.add(*_globs);_ws->saveSnapshot(child.GetName(),_tmp);
1502 _obs.remove(*_globs);
1503
1504 // include any coords
1505 _obs.add(coords(false).argList(), true);
1506 // include axis var too, provided it's an observable
1507 if (auto ax = GetXaxis(); ax && dynamic_cast<RooAbsArg *>(ax->GetParent())->getAttribute("obs")) {
1508 _obs.add(*dynamic_cast<RooAbsArg *>(ax->GetParent()));
1509 }
1510 // check if ws already has a dataset with this name, if it does we may need to extend columns
1511 if (auto _d = _ws->data(child.GetName()); _d) {
1512 // add any missing obs
1513 RooArgSet l(_obs);
1514 l.remove(*_d->get(), true, true);
1515 if (!l.empty()) {
1516 auto _dd = dynamic_cast<RooDataSet *>(_d);
1517 if (!_dd)
1518 throw std::runtime_error("Cannot extend dataset with new columns");
1519 for (auto &x : l) {
1520 _dd->addColumn(*x);
1521 }
1522 }
1523 } else {
1524 RooRealVar w("weightVar", "weightVar", 1);
1525 _obs.add(w);
1526 RooDataSet d(child.GetName(), child.GetTitle(), _obs, RooFit::WeightVar("weightVar"));
1527 _ws->import(d);
1528 // seems have to set bits after importing, not before
1529 if (auto __d = _ws->data(child.GetName()))
1530 __d->SetBit(1 << 20, _ws->allData().size() == 1); // sets as selected if is only ds
1531 }
1532 /*if(!_ws->data(child.GetName())) {
1533 RooRealVar w("weightVar", "weightVar", 1);
1534 RooArgSet _obs; _obs.add(w);
1535 RooDataSet d(child.GetName(), child.GetTitle(), _obs, "weightVar");
1536 _ws->import(d);
1537 }*/
1538 auto out = std::shared_ptr<TObject>(_ws->data(child.GetName()), [](TObject *) {});
1539
1540 if (out) {
1541 xRooNode o(out, fParent);
1542 if (child.get<TH1>())
1543 o = *child.get();
1544 return o;
1545 }
1546 }
1547 throw std::runtime_error("Cannot create dataset");
1548 }
1549
1550 if (!get()) {
1551 if (!fParent)
1552 throw std::runtime_error("Cannot add to null object with no parentage");
1553
1554 auto _ref = emplace_back(std::shared_ptr<xRooNode>(&const_cast<xRooNode &>(child), [](TObject *) {}));
1555 try {
1556 fComp = fParent->Add(*this, "+").fComp;
1557 } catch (...) {
1558 resize(size() - 1);
1559 std::rethrow_exception(std::current_exception());
1560 }
1561 resize(size() - 1); // remove the temporarily added node
1562
1563 if (!fComp) {
1564 throw std::runtime_error("No object");
1565 }
1566 }
1567
1568 if (auto p = get<RooAbsData>(); p) {
1569 if (auto bb = getBrowsable(".sourceds"))
1570 bb->Add(child, opt);
1571 if (auto _data = child.get<RooDataSet>()) {
1572 auto ds = dynamic_cast<RooDataSet *>(p);
1573 if (!ds) {
1574 throw std::runtime_error("Can only add datasets to a dataset");
1575 }
1576
1577 // append any missing globs, and check any existing globs have matching values
1578 RooArgList globsToAdd;
1579 auto _globs = globs();
1580 for (auto &glob : child.globs()) {
1581 if (auto g = _globs.find(glob->GetName()); !g) {
1582 globsToAdd.addClone(*glob->get<RooAbsArg>());
1583 } else if (g->GetContent() != glob->GetContent()) {
1584 Warning("Add", "Global observable %s=%g in dataset mismatches child value %g ... ignoring child",
1585 g->GetName(), g->GetContent(), glob->GetContent());
1586 }
1587 }
1588 // add any existing globs to list then set the list
1589 if (auto _dglobs = p->getGlobalObservables()) {
1590 globsToAdd.addClone(*_dglobs);
1591 } else {
1592 for (auto g : _globs)
1593 globsToAdd.addClone(*g->get<RooAbsArg>());
1594 }
1595 p->setGlobalObservables(globsToAdd);
1596
1597 // append any missing observables to our dataset, then append the dataset
1598
1599 for (auto col : *_data->get()) {
1600 if (!p->get()->contains(*col)) {
1601 ds->addColumn(*col);
1602 }
1603 }
1604 ds->append(*_data);
1605 return *this;
1606 }
1607 auto _h = child.get<TH1>();
1608 if (!_h) {
1609 throw std::runtime_error("Can only add histogram or dataset to data");
1610 }
1611 auto _pdf = parentPdf();
1612 if (!_pdf)
1613 throw std::runtime_error("Could not find pdf");
1614 auto _ax = _pdf->GetXaxis();
1615 if (!_ax) {
1616 throw std::runtime_error("Cannot determine binning to add data");
1617 }
1618
1619 RooArgSet obs;
1620 obs.add(*dynamic_cast<RooAbsArg *>(_ax->GetParent()));
1621 obs.add(coords().argList()); // will also move obs to coords
1622
1623 // add any missing obs
1624 RooArgSet l(obs);
1625 l.remove(*p->get(), true, true);
1626 if (!l.empty()) {
1627 auto _d = dynamic_cast<RooDataSet *>(p);
1628 if (!_d)
1629 throw std::runtime_error("Cannot extend dataset with new columns");
1630 for (auto &x : l) {
1631 _d->addColumn(*x);
1632 }
1633 }
1634
1635 // before adding, ensure range is good to cover
1636 for (auto &o : obs) {
1637 if (auto v = dynamic_cast<RooRealVar *>(o); v) {
1638 if (auto dv = dynamic_cast<RooRealVar *>(p->get()->find(v->GetName())); dv) {
1639 if (v->getMin() < dv->getMin())
1640 dv->setMin(v->getMin());
1641 if (v->getMax() > dv->getMax())
1642 dv->setMax(v->getMax());
1643 }
1644 } else if (auto c = dynamic_cast<RooCategory *>(o); c) {
1645 if (auto dc = dynamic_cast<RooCategory *>(p->get()->find(c->GetName())); dc) {
1646 if (!dc->hasLabel(c->getCurrentLabel())) {
1647 dc->defineType(c->getCurrentLabel(), c->getCurrentIndex());
1648 }
1649 }
1650 }
1651 }
1652
1653 for (int i = 1; i <= _h->GetNbinsX(); i++) {
1654 if (auto cat = dynamic_cast<RooAbsCategoryLValue *>(_ax->GetParent())) {
1655 if (!_h->GetXaxis()->GetBinLabel(i)) {
1656 throw std::runtime_error(
1657 TString::Format("Categorical observable %s requires bin labels", _ax->GetParent()->GetName()));
1658 } else if (!cat->hasLabel(_h->GetXaxis()->GetBinLabel(i))) {
1659 throw std::runtime_error(TString::Format("Categorical observable %s does not have label %s",
1660 _ax->GetParent()->GetName(), _h->GetXaxis()->GetBinLabel(i)));
1661 } else {
1662 cat->setLabel(_h->GetXaxis()->GetBinLabel(i));
1663 }
1664 } else {
1665 dynamic_cast<RooAbsRealLValue *>(_ax->GetParent())->setVal(_h->GetBinCenter(i));
1666 }
1667 p->add(obs, _h->GetBinContent(i));
1668 }
1669
1670 return *this;
1671 }
1672
1673 if (auto p = get<RooAddPdf>(); p) {
1674 if ((child.get<RooAbsPdf>() || (!child.fComp && getObject<RooAbsPdf>(child.GetName())))) {
1675 auto out = (child.fComp) ? acquire(child.fComp) : getObject<RooAbsArg>(child.GetName());
1676 // don't add a coef if in 'all-extended' mode and this pdf is extendable
1677 auto _pdf = std::dynamic_pointer_cast<RooAbsPdf>(out);
1678 if (!_pdf) {
1679 throw std::runtime_error("Something went wrong with pdf acquisition");
1680 }
1681
1682 if (auto _ax = GetXaxis(); _ax && dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()) &&
1683 _pdf->dependsOn(*static_cast<RooAbsArg *>(_ax->GetParent()))) {
1684 auto _p = _pdf;
1685
1686 if (auto _boundaries = std::unique_ptr<std::list<double>>(_p->binBoundaries(
1687 *dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()), -std::numeric_limits<double>::infinity(),
1688 std::numeric_limits<double>::infinity()));
1689 !_boundaries && _ax->GetNbins() > 0) {
1690#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 24, 00)
1691 Warning("Add", "Adding unbinned pdf %s to binned %s - will wrap with RooBinSamplingPdf(...)",
1692 _p->GetName(), GetName());
1693 _p = acquireNew<RooBinSamplingPdf>(TString::Format("%s_binned", _p->GetName()), _p->GetTitle(),
1694 *dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()), *_p);
1695 _p->setStringAttribute("alias", std::dynamic_pointer_cast<RooAbsArg>(out)->getStringAttribute("alias"));
1696 if (!_p->getStringAttribute("alias"))
1697 _p->setStringAttribute("alias", out->GetName());
1698#else
1699 throw std::runtime_error(
1700 "unsupported addition of unbinned pdf to binned model - please upgrade to at least ROOT 6.24");
1701#endif
1702 _pdf = _p;
1703 }
1704 }
1705
1706 if (!(_pdf->canBeExtended() && p->coefList().empty())) {
1707 // if extended, use an extended binding as the coef
1708 // otherwise e.g. if adding a RooRealSumPdf the stacked histograms will be above the
1709 // actual pdf histogram because the pdf histogram is just normalized down
1710 if (_pdf->canBeExtended()) {
1711 // FIXME: ExtendedBinding needs the obs list passing to it ... should be fixed in RooFit
1712 // until then, this will return "1" and so the pdf's histograms wont be normalized properly in relation
1713 // to stacks of its comps
1714 const_cast<RooArgList &>(p->coefList())
1715 .add(*acquireNew<RooExtendedBinding>(TString::Format("%s_extBind", _pdf->GetName()),
1716 TString::Format("Expected Events of %s", _pdf->GetTitle()),
1717 *_pdf));
1718 } else {
1719 const_cast<RooArgList &>(p->coefList()).add(*acquire2<RooAbsArg, RooRealVar>("1", "1", 1));
1720 }
1721 }
1722 const_cast<RooArgList &>(p->pdfList()).add(*_pdf);
1723 sterilize();
1724 return xRooNode(*_pdf, *this);
1725 } else if ((child.get<TH1>() || child.get<RooAbsReal>() ||
1726 (!child.get() && getObject<RooAbsReal>(child.GetName()))) &&
1727 !child.get<RooAbsPdf>()) {
1728 RooRealSumPdf *_pdf = nullptr;
1729 bool tooMany(false);
1730 for (auto &pp : factors()) {
1731 if (auto _p = pp->get<RooRealSumPdf>(); _p) {
1732 if (_pdf) {
1733 _pdf = nullptr;
1734 tooMany = true;
1735 break;
1736 } // more than one!
1737 _pdf = _p;
1738 }
1739 }
1740 if (_pdf) {
1741 return xRooNode(*_pdf, *this).Add(child);
1742 } else if (!tooMany) {
1743 // create a RooRealSumPdf to hold the child
1744 auto _sumpdf = Add(*acquireNew<RooRealSumPdf>(TString::Format("%s_samples", p->GetName()),
1745 TString::Format("%s samples", GetTitle()), RooArgList(),
1746 RooArgList(), true));
1747 _sumpdf.get<RooAbsArg>()->setStringAttribute("alias", "samples");
1748 return _sumpdf.Add(child);
1749 }
1750 }
1751 }
1752
1753 if (auto p = get<RooRealSumPdf>(); p) {
1754 std::shared_ptr<TObject> out;
1755 auto cc = child.fComp;
1756 bool isConverted = (cc != child.convertForAcquisition(*this, sOpt));
1757 if (child.get<RooAbsReal>())
1758 out = acquire(child.fComp);
1759 if (!child.fComp && getObject<RooAbsReal>(child.GetName())) {
1760 Info("Add", "Adding existing function %s to %s", child.GetName(), p->GetName());
1761 out = getObject<RooAbsReal>(child.GetName());
1762 }
1763
1764 if (!out && !child.fComp) {
1765 std::shared_ptr<RooAbsArg> _func;
1766 // a null node .. so create either a new RooProduct or RooHistFunc if has observables (or no deps but has
1767 // x-axis)
1768 auto _obs = robs();
1769 if (!_obs.empty() || GetXaxis()) {
1770 if (_obs.empty()) {
1771 // using X axis to construct hist
1772 auto _ax = dynamic_cast<Axis2 *>(GetXaxis());
1773 auto t = TH1::AddDirectoryStatus();
1774 TH1::AddDirectory(false);
1775 auto h =
1776 std::make_unique<TH1D>(child.GetName(), child.GetTitle(), _ax->GetNbins(), _ax->binning()->array());
1778 h->GetXaxis()->SetName(TString::Format("%s;%s", _ax->GetParent()->GetName(), _ax->GetName()));
1779 // technically convertForAcquisition has already acquired so no need to re-acquire but should be harmless
1780 _func = std::dynamic_pointer_cast<RooAbsArg>(acquire(xRooNode(*h).convertForAcquisition(*this)));
1781 } else if (_obs.size() == 1) {
1782 // use the single obs to make a TH1D
1783 auto _x = _obs.at(0)->get<RooAbsLValue>();
1784 auto _bnames = _x->getBinningNames();
1785 TString binningName = p->getStringAttribute("binning");
1786 for (auto &b : _bnames) {
1787 if (b == p->GetName()) {
1788 binningName = p->GetName();
1789 break;
1790 }
1791 }
1792 auto t = TH1::AddDirectoryStatus();
1793 TH1::AddDirectory(false);
1794 auto h = std::make_unique<TH1D>(child.GetName(), child.GetTitle(), _x->numBins(binningName),
1795 _x->getBinningPtr(binningName)->array());
1797 h->GetXaxis()->SetName(
1798 TString::Format("%s;%s", dynamic_cast<TObject *>(_x)->GetName(), binningName.Data()));
1799 // technically convertForAcquisition has already acquired so no need to re-acquire but should be harmless
1800 _func = std::dynamic_pointer_cast<RooAbsArg>(acquire(xRooNode(*h).convertForAcquisition(*this)));
1801 Info("Add", "Created densityhisto factor %s (xaxis=%s) for %s", _func->GetName(), _obs.at(0)->GetName(),
1802 p->GetName());
1803 } else {
1804 throw std::runtime_error("Unsupported creation of new component in SumPdf for this many obs");
1805 }
1806 } else {
1807 _func = acquireNew<RooProduct>(TString::Format("%s_%s", p->GetName(), child.GetName()), child.GetTitle(),
1808 RooArgList());
1809 }
1810 _func->setStringAttribute("alias", child.GetName());
1811 out = _func;
1812 }
1813
1814 if (auto _f = std::dynamic_pointer_cast<RooHistFunc>(
1815 (child.get<RooProduct>()) ? child.factors()[child.GetName()]->fComp : out);
1816 _f) {
1817 // adding a histfunc directly to a sumpdf, should be a density
1818 _f->setAttribute("density");
1819 if (_f->getAttribute("autodensity")) {
1820 // need to divide by bin widths first
1821 for (int i = 0; i < _f->dataHist().numEntries(); i++) {
1822 auto bin_pars = _f->dataHist().get(i);
1823 _f->dataHist().set(*bin_pars, _f->dataHist().weight() / _f->dataHist().binVolume(*bin_pars));
1824 }
1825 _f->setAttribute("autodensity", false);
1826 _f->setValueDirty();
1827 }
1828
1829 // promote the axis vars to observables
1830 // can't use original child as might refer to unacquired deps
1831 for (auto &x : xRooNode("tmp", _f).vars()) {
1832 x->get<RooAbsArg>()->setAttribute("obs");
1833 }
1834 if (isConverted) {
1835 Info("Add", "Created %s factor RooHistFunc::%s for %s",
1836 _f->getAttribute("density") ? "densityhisto" : "histo", _f->GetName(), p->GetName());
1837 }
1838 }
1839
1840 if (auto _p = std::dynamic_pointer_cast<RooAbsPdf>(out); _p) {
1841 // adding a pdf to a RooRealSumPdf will replace it with a RooAddPdf and put the RooRealSumPdf inside that
1842 // if pdf is extended will use in the "no coefficients" state, where the expectedEvents are taking from
1843 // the pdf integrals
1844 TString newName(_p->GetName());
1845 newName.ReplaceAll("_samples", "");
1846 newName += "_components";
1847 Warning("Add", "converting samples to components");
1848
1849 if (auto _ax = GetXaxis(); _ax && dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()) &&
1850 _p->dependsOn(*static_cast<RooAbsArg *>(_ax->GetParent()))) {
1851
1852 if (auto _boundaries = std::unique_ptr<std::list<double>>(_p->binBoundaries(
1853 *dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()), -std::numeric_limits<double>::infinity(),
1854 std::numeric_limits<double>::infinity()));
1855 !_boundaries && _ax->GetNbins() > 0) {
1856#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 24, 00)
1857 Warning("Add", "Adding unbinned pdf %s to binned %s - will wrap with RooBinSamplingPdf(...)",
1858 _p->GetName(), GetName());
1859 _p = acquireNew<RooBinSamplingPdf>(TString::Format("%s_binned", _p->GetName()), _p->GetTitle(),
1860 *dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()), *_p);
1861 _p->setStringAttribute("alias", std::dynamic_pointer_cast<RooAbsArg>(out)->getStringAttribute("alias"));
1862 if (!_p->getStringAttribute("alias"))
1863 _p->setStringAttribute("alias", out->GetName());
1864#else
1865 throw std::runtime_error(
1866 "unsupported addition of unbinned pdf to binned model - please upgrade to at least ROOT 6.24");
1867#endif
1868 }
1869 }
1870
1871 // require to be extended to be in coefficient-free mode ...
1872 // otherwise would lose the integral of the sumPdf (can't think of way to have a coef be the integral)
1873 if (!_p->canBeExtended()) {
1874 _p = acquireNew<RooExtendPdf>(TString::Format("%s_extended", _p->GetName()), _p->GetTitle(), *_p,
1875 *acquire2<RooAbsReal, RooRealVar>("1", "1", 1));
1876 }
1877
1878 return *(Replace(*acquireNew<RooAddPdf>(newName, _p->GetTitle(), RooArgList(*p, *_p)))
1879 .browse()[1]); // returns second node.
1880 }
1881
1882 if (auto _f = std::dynamic_pointer_cast<RooAbsReal>(out); _f) {
1883
1884 // todo: if adding a pdf, should actually replace RooRealSumPdf with a RooAddPdf and put
1885 // the sumPdf and *this* pdf inside that pdf
1886 // only exception is the binSamplingPdf below to integrate unbinned functions across bins
1887
1888 if (auto _ax = GetXaxis(); _ax && dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()) &&
1889 _f->dependsOn(*static_cast<RooAbsArg *>(_ax->GetParent()))) {
1890
1891 if (auto _boundaries = std::unique_ptr<std::list<double>>(_f->binBoundaries(
1892 *dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()), -std::numeric_limits<double>::infinity(),
1893 std::numeric_limits<double>::infinity()));
1894 !_boundaries && _ax->GetNbins() > 0) {
1895#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 24, 00)
1896 Warning(
1897 "Add",
1898 "Adding unbinned function %s to binned %s - will wrap with RooRealSumPdf(RooBinSamplingPdf(...))",
1899 _f->GetName(), GetName());
1900 auto sumPdf = acquireNew<RooRealSumPdf>(TString::Format("%s_pdfWrapper", _f->GetName()), _f->GetTitle(),
1901 *_f, *acquire2<RooAbsArg, RooRealVar>("1", "1", 1), true);
1902 sumPdf->setStringAttribute("alias", _f->getStringAttribute("alias"));
1903 if (!sumPdf->getStringAttribute("alias"))
1904 sumPdf->setStringAttribute("alias", out->GetName());
1905 _f = acquireNew<RooBinSamplingPdf>(TString::Format("%s_binned", _f->GetName()), _f->GetTitle(),
1906 *dynamic_cast<RooAbsRealLValue *>(_ax->GetParent()), *sumPdf);
1907 _f->setStringAttribute("alias", std::dynamic_pointer_cast<RooAbsArg>(out)->getStringAttribute("alias"));
1908 if (!_f->getStringAttribute("alias"))
1909 _f->setStringAttribute("alias", out->GetName());
1910#else
1911 throw std::runtime_error(
1912 "unsupported addition of unbinned function to binned model - please upgrade to at least ROOT 6.24");
1913#endif
1914 }
1915 }
1916
1917 const_cast<RooArgList &>(p->coefList()).add(*acquire2<RooAbsArg, RooRealVar>("1", "1", 1));
1918 const_cast<RooArgList &>(p->funcList()).add(*_f);
1919 // inherit binning if we dont have one yet
1920 if (!p->getStringAttribute("binning"))
1921 p->setStringAttribute("binning", _f->getStringAttribute("binning"));
1922
1923 xRooNode _out(_f, *this);
1924 if (auto gf = p->getStringAttribute("global_factors"); gf) {
1925 TStringToken pattern(gf, ";");
1926 while (pattern.NextToken()) {
1927 auto fac = getObject<RooAbsReal>(pattern.Data());
1928 if (!fac) {
1929 throw std::runtime_error(TString::Format("Could not find global factor %s", pattern.Data()));
1930 }
1931 _out.Multiply(fac);
1932 }
1933 }
1934 sterilize();
1935 // clear children for reload and update shared axis
1936 clear();
1937 fXAxis.reset();
1938 p->setStringAttribute("xvar", nullptr);
1939 browse();
1940 return _out;
1941 }
1942 } else if (auto p2 = get<RooProdPdf>(); p2) {
1943 // can "add" to a RooProdPdf provided trying to add a RooAbsReal not a RooAbsPdf and have a zero or 1
1944 // RooRealSumPdf child.convertForAcquisition(*this); - don't convert here because want generated objects named
1945 // after roorealsumpdf
1946 if (child.get<RooAbsPdf>() || (!child.get() && getObject<RooAbsPdf>(child.GetName()))) {
1947 // can add if 0 or 1 RooAddPdf ....
1948 RooAddPdf *_pdf = nullptr;
1949 bool tooMany(false);
1950 for (auto &pp : factors()) {
1951 if (auto _p = pp->get<RooAddPdf>(); _p) {
1952 if (_pdf) {
1953 _pdf = nullptr;
1954 tooMany = true;
1955 break;
1956 } // more than one!
1957 _pdf = _p;
1958 }
1959 }
1960 if (_pdf) {
1961 return xRooNode(*_pdf, *this).Add(child);
1962 } else if (!tooMany) {
1963 auto out = this->operator[]("components")->Add(child);
1964 return out;
1965 }
1966 } else if ((child.get<TH1>() || child.get<RooAbsReal>() ||
1967 (!child.get() && getObject<RooAbsReal>(child.GetName()))) &&
1968 !child.get<RooAbsPdf>()) {
1969 RooRealSumPdf *_pdf = nullptr;
1970 RooAddPdf *_backup = nullptr;
1971 bool tooMany(false);
1972 for (auto &pp : factors()) {
1973 if (auto _p = pp->get<RooRealSumPdf>(); _p) {
1974 if (_pdf) {
1975 _pdf = nullptr;
1976 tooMany = true;
1977 break;
1978 } // more than one!
1979 _pdf = _p;
1980 } else if (auto _p2 = pp->get<RooAddPdf>(); _p2) {
1981 _backup = _p2;
1982 for (auto &_pdfa : pp->components()) {
1983 if (auto _p3 = _pdfa->get<RooRealSumPdf>(); _p3) {
1984 if (_pdf) {
1985 _pdf = nullptr;
1986 tooMany = true;
1987 break;
1988 } // more than one!
1989 _pdf = _p3;
1990 }
1991 }
1992 }
1993 }
1994 if (_pdf) {
1995 return xRooNode(*_pdf, *this).Add(child);
1996 } else if (_backup) {
1997 // added *INSIDE* the addPdf -- will create a RooRealSumPdf to hold it
1998 return xRooNode(*_backup, *this).Add(child);
1999 } else if (!tooMany) {
2000 auto out = this->operator[]("samples")->Add(child);
2001 // clear our x-axis to re-evaluate
2002 fXAxis.reset();
2003 p2->setStringAttribute("xvar", nullptr);
2004 return out;
2005 }
2006 }
2007 } else if (auto s = get<RooSimultaneous>(); s) {
2008
2009 // adding to a simultaneous means adding a bin
2010 return bins().Add(child);
2011
2012 // if the child is a RooAbsPdf can just add it as a new channel using name of pdf as the channel name
2013 // if child is a histogram, will create a RooProdPdf
2014
2015 } else if (auto w = get<RooWorkspace>(); w) {
2016 child.convertForAcquisition(*this);
2017 if (child.get()) {
2018 auto out = acquire(child.fComp);
2019 if (out)
2020 return xRooNode(child.GetName(), out, *this);
2021 }
2022
2023 if (!child.empty() || child.fFolder == "!models") {
2024 // create a RooSimultaneous using the children as the channels
2025 // children either have "=" in name if specifying channel cat name or otherwise assume
2026 std::string catName = "channelCat";
2027 if (!child.empty()) {
2028 if (TString ss = child.at(0)->GetName(); ss.Contains("=")) {
2029 catName = ss(0, ss.Index('='));
2030 }
2031 }
2032 auto _cat = acquire<RooCategory>(catName.c_str(), catName.c_str());
2033 _cat->setAttribute("obs");
2034 auto out = acquireNew<RooSimultaneous>(child.GetName(), child.GetTitle(), *_cat);
2035 Info("Add", "Created model RooSimultaneous::%s in workspace %s", out->GetName(), w->GetName());
2036 return xRooNode(out, *this);
2037 }
2038 }
2039
2040 if (sOpt == "model") {
2041 // can only add a model to a workspace
2042 if (get<RooWorkspace>()) {
2043 const_cast<xRooNode &>(child).fFolder = "!models";
2044 return Add(child);
2045 }
2046 } else if (sOpt == "channel") {
2047 // can add to a model or to a workspace (creates a RooProdPdf either way)
2048 if (get<RooSimultaneous>()) {
2049 return Vary(child);
2050 } else if (get<RooWorkspace>()) {
2051 std::shared_ptr<TObject> out;
2052 child.convertForAcquisition(*this);
2053 if (child.get<RooAbsPdf>()) {
2054 out = acquire(child.fComp);
2055 } else if (!child.fComp) {
2056 out = acquireNew<RooProdPdf>(child.GetName(),
2057 (strlen(child.GetTitle())) ? child.GetTitle() : child.GetName(), RooArgList());
2058 Info("Add", "Created channel RooProdPdf::%s in workspace %s", out->GetName(), get()->GetName());
2059 }
2060 return xRooNode(out, *this);
2061 }
2062 } else if (sOpt == "sample" || sOpt == "func") {
2063 if (get<RooProdPdf>()) {
2064 auto _mainChild = mainChild();
2065 if (_mainChild.get<RooRealSumPdf>()) {
2066 return _mainChild.Add(child, sOpt == "func" ? "func" : "");
2067 } else {
2068 return (*this)["samples"]->Add(child, sOpt == "func" ? "func" : "");
2069 }
2070 }
2071 } else if (sOpt == "dataset") {
2072 if (get<RooWorkspace>()) {
2073 // const_cast<xRooNode&>(child).fFolder = "!datasets";return Add(child);
2074 return (*this).datasets().Add(child);
2075 }
2076 }
2077
2078 if (considerType) {
2079
2080 // interpret 'adding' here as dependent on the object type ...
2081 if (get<RooSimultaneous>()) {
2082 return bins().Add(child);
2083 } else if (TString(child.GetName()).Contains('=')) {
2084 return variations().Add(child);
2085 } else if (get<RooProduct>() || get<RooProdPdf>()) {
2086 return factors().Add(child);
2087 }
2088 }
2089
2090 // Nov 2022 - removed ability to add placeholders ... could bring back if rediscover need for them
2091 // if (!child.get() && child.empty() && strlen(child.GetName())) {
2092 // // can add a 'placeholder' node, note it will be deleted at the next browse
2093 // xRooNode out(child.GetName(),nullptr,*this);
2094 // out.SetTitle(child.GetTitle());
2095 // emplace_back(std::make_shared<xRooNode>(out));
2096 // // update the parent in the out node so that it's copy of the parent knows it has itself in it
2097 // // actually maybe not want this :-/
2098 // //out.fParent = std::make_shared<Node2>(*this);
2099 // for(auto o : *gROOT->GetListOfBrowsers()) {
2100 // if(auto b = dynamic_cast<TBrowser*>(o); b && b->GetBrowserImp()){
2101 // if(auto _b = dynamic_cast<TGFileBrowser*>(
2102 // dynamic_cast<TRootBrowser*>(b->GetBrowserImp())->fActBrowser ); _b) {
2103 // auto _root = _b->fRootDir;
2104 // if (!_root) _root = _b->fListTree->GetFirstItem();
2105 // if (auto item = _b->fListTree->FindItemByObj(_root,this); item) {
2106 // _b->fListTree->AddItem(item,back()->GetName(),back().get());
2107 // }
2108 // }
2109 // }
2110 // }
2111 // return out;
2112 // }
2113
2114 throw std::runtime_error(TString::Format("Cannot add %s to %s", child.GetName(), GetName()));
2115}
2116
2117std::string xRooNode::GetPath() const
2118{
2119 if (!fParent)
2120 return GetName();
2121 return fParent->GetPath() + "/" + GetName();
2122}
2123
2125{
2126 // std::cout << "deleting " << GetPath() << std::endl;
2127}
2128
2130{
2131 if (auto a = get<RooAbsArg>()) {
2132 a->setAttribute("hidden", set);
2133 // if(auto item = GetTreeItem(nullptr); item) {
2134 // if(set) item->SetColor(kRed);
2135 // else item->ClearColor();
2136 // }
2137 }
2138}
2140{
2141 auto a = get<RooAbsArg>();
2142 if (a)
2143 return a->getAttribute("hidden");
2144 return false;
2145}
2146
2148{
2149
2150 if (get() == rhs.get()) {
2151 // nothing to do because objects are identical
2152 return *this;
2153 }
2154
2155 // Info("Combine","Combining %s into %s",rhs.GetPath().c_str(),GetPath().c_str());
2156
2157 // combine components, factors, and variations ... when there is a name clash will combine on that object
2158 for (auto &c : rhs.components()) {
2159 if (auto _c = components().find(c->GetName()); _c) {
2160 _c->Combine(*c);
2161 } else {
2162 Add(*c);
2163 }
2164 }
2165
2166 for (auto &f : rhs.factors()) {
2167 if (auto _f = factors().find(f->GetName()); _f) {
2168 _f->Combine(*f);
2169 } else {
2170 Multiply(*f);
2171 }
2172 }
2173
2174 for (auto &v : rhs.variations()) {
2175 if (auto _v = variations().find(v->GetName()); _v) {
2176 _v->Combine(*v);
2177 } else {
2178 Vary(*v);
2179 }
2180 }
2181
2182 // todo: Should also transfer over binnings of observables
2183
2184 return *this;
2185}
2186
2187xRooNode xRooNode::shallowCopy(const std::string &name, std::shared_ptr<xRooNode> parent)
2188{
2189 xRooNode out(name.c_str(), nullptr,
2190 parent /*? parent : fParent -- was passing fParent for getObject benefit before fProvider concept*/);
2191 // if(!parent) out.fAcquirer = true;
2192 if (!parent)
2193 out.fProvider = fParent;
2194
2195 auto o = get();
2196 if (!o) {
2197 return out;
2198 }
2199
2200 if (auto s = get<RooSimultaneous>(); s) {
2201 auto chans = bins();
2202 if (!chans.empty()) {
2203 // create a new RooSimultaneous with shallow copies of each channel
2204
2205 std::shared_ptr<RooSimultaneous> pdf = out.acquire<RooSimultaneous>(
2206 name.c_str(), o->GetTitle(), const_cast<RooAbsCategoryLValue &>(s->indexCat()));
2207
2208 for (auto &c : chans) {
2209 TString cName(c->GetName());
2210 cName = cName(cName.Index('=') + 1, cName.Length());
2211 // by passing out as the parent, will ensure out acquires everything created
2212 auto c_copy =
2213 c->shallowCopy(name + "_" + c->get()->GetName(), std::shared_ptr<xRooNode>(&out, [](xRooNode *) {}));
2214 pdf->addPdf(*dynamic_cast<RooAbsPdf *>(c_copy.get()), cName);
2215 }
2216 out.fComp = pdf;
2217 return out;
2218 }
2219 } else if (auto p = dynamic_cast<RooProdPdf *>(o); p) {
2220 // main pdf will be copied too
2221 std::shared_ptr<RooProdPdf> pdf =
2222 std::dynamic_pointer_cast<RooProdPdf>(out.acquire(std::shared_ptr<TObject>(p->Clone(/*name.c_str()*/)), false,
2223 true)); // use clone to copy all attributes etc too
2224 auto main = mainChild();
2225 if (main) {
2226 auto newMain =
2227 std::dynamic_pointer_cast<RooAbsArg>(out.acquire(std::shared_ptr<TObject>(main->Clone()), false, true));
2228 std::cout << newMain << " " << newMain->GetName() << std::endl;
2229 // pdf->replaceServer(*pdf->pdfList().find(main->GetName()), *newMain, true, true);
2230 // const_cast<RooArgList&>(pdf->pdfList()).replace(*pdf->pdfList().find(main->GetName()), *newMain);
2231 pdf->redirectServers(RooArgList(*newMain));
2232 }
2233 out.fComp = pdf;
2234 out.sterilize();
2235 return out;
2236 }
2237
2238 return out;
2239}
2240
2242{
2243 static std::unique_ptr<cout_redirect> capture;
2244 std::string captureStr;
2245 bool doCapture = false;
2246 if (!capture && gROOT->FromPopUp()) { // FromPopUp means user executed from the context menu
2247 capture = std::make_unique<cout_redirect>(captureStr);
2248 doCapture = true;
2249 }
2250
2251 TString sOpt(opt);
2252 int depth = 0;
2253 if (sOpt.Contains("depth=")) {
2254 depth = TString(sOpt(sOpt.Index("depth=") + 6, sOpt.Length())).Atoi();
2255 sOpt.ReplaceAll(TString::Format("depth=%d", depth), "");
2256 }
2257 int indent = 0;
2258 if (sOpt.Contains("indent=")) {
2259 indent = TString(sOpt(sOpt.Index("indent=") + 7, sOpt.Length())).Atoi();
2260 sOpt.ReplaceAll(TString::Format("indent=%d", indent), "");
2261 }
2262 bool _more = sOpt.Contains("m");
2263 if (_more)
2264 sOpt.Replace(sOpt.Index("m"), 1, "");
2265 if (sOpt != "")
2266 _more = true;
2267
2268 if (indent == 0) { // only print self if not indenting (will already be printed above if tree traverse)
2269 std::cout << GetPath();
2270 if (get() && get() != this) {
2271 std::cout << ": ";
2272 if (_more || (get<RooAbsArg>() && get<RooAbsArg>()->isFundamental()) || get<RooConstVar>() ||
2273 get<RooAbsData>() || get<RooProduct>() || get<RooFitResult>()) {
2274 auto _deps = coords(false).argList(); // want to revert coords after print
2275 auto _snap = std::unique_ptr<RooAbsCollection>(_deps.snapshot());
2276 coords(); // move to coords before printing (in case this matters)
2277 get()->Print(sOpt);
2278 if (auto _fr = get<RooFitResult>(); _fr && dynamic_cast<RooStringVar *>(_fr->constPars().find(".log"))) {
2279 std::cout << "Minimization Logs:" << std::endl;
2280 std::cout << dynamic_cast<RooStringVar *>(_fr->constPars().find(".log"))->getVal() << std::endl;
2281 }
2282 _deps.assignValueOnly(*_snap);
2283 // std::cout << std::endl;
2284 } else {
2285 TString _suffix = "";
2286 if (auto _type = GetNodeType(); strlen(_type)) {
2287 // decided not to show const values until figure out how to update if value changes
2288 /*if (TString(_type)=="Const") _name += TString::Format("
2289 [%s=%g]",_type,v->get<RooConstVar>()->getVal()); else*/
2290 _suffix += TString::Format(" [%s]", _type);
2291 }
2292 if (auto fv = get<RooFormulaVar>()) {
2293 TString formu = TString::Format(" [%s]", fv->expression());
2294 for (size_t i = 0; i < fv->dependents().size(); i++) {
2295 formu.ReplaceAll(TString::Format("x[%zu]", i), fv->dependents()[i].GetName());
2296 }
2297 _suffix += formu;
2298 } else if (auto gv = get<RooGenericPdf>()) {
2299 TString formu = TString::Format(" [%s]", gv->expression());
2300 for (size_t i = 0; i < gv->dependents().size(); i++) {
2301 formu.ReplaceAll(TString::Format("x[%zu]", i), gv->dependents()[i].GetName());
2302 }
2303 _suffix += formu;
2304 }
2305 std::cout << get()->ClassName() << "::" << get()->GetName() << _suffix.Data() << std::endl;
2306 }
2307
2308 } else if (!get()) {
2309 std::cout << std::endl;
2310 }
2311 }
2312 const_cast<xRooNode *>(this)->browse();
2313 std::vector<std::string> folderNames;
2314 for (auto &k : *this) {
2315 if (std::find(folderNames.begin(), folderNames.end(), k->fFolder) == folderNames.end()) {
2316 folderNames.push_back(k->fFolder);
2317 }
2318 }
2319 for (auto &f : folderNames) {
2320 int i = 0;
2321 int iindent = indent;
2322 if (!f.empty()) {
2323 for (int j = 0; j < indent; j++)
2324 std::cout << " ";
2325 std::cout << f << std::endl;
2326 iindent += 1;
2327 }
2328 for (auto &k : *this) {
2329 if (k->fFolder != f) {
2330 i++;
2331 continue;
2332 }
2333 for (int j = 0; j < iindent; j++)
2334 std::cout << " ";
2335 std::cout << i++ << ") " << k->GetName() << " : ";
2336 if (k->get()) {
2337 if (_more || (k->get<RooAbsArg>() && k->get<RooAbsArg>()->isFundamental()) || k->get<RooConstVar>() ||
2338 k->get<RooAbsData>() /*|| k->get<RooProduct>()*/) {
2339 auto _deps = k->coords(false).argList();
2340 auto _snap = std::unique_ptr<RooAbsCollection>(_deps.snapshot());
2341 k->coords(); // move to coords before printing (in case this matters)
2342 k->get()->Print(sOpt); // assumes finishes with an endl
2343 _deps.assignValueOnly(*_snap);
2344 } else {
2345 TString _suffix = "";
2346 if (auto _type = k->GetNodeType(); strlen(_type)) {
2347 // decided not to show const values until figure out how to update if value changes
2348 /*if (TString(_type)=="Const") _name += TString::Format("
2349 [%s=%g]",_type,v->get<RooConstVar>()->getVal()); else*/
2350 _suffix += TString::Format(" [%s]", _type);
2351 }
2352 if (auto fv = k->get<RooFormulaVar>()) {
2353 TString formu = TString::Format(" [%s]", fv->expression());
2354 for (size_t j = 0; j < fv->dependents().size(); j++) {
2355 formu.ReplaceAll(TString::Format("x[%zu]", j), fv->dependents()[j].GetName());
2356 }
2357 _suffix += formu;
2358 } else if (auto gv = k->get<RooGenericPdf>()) {
2359 TString formu = TString::Format(" [%s]", gv->expression());
2360 for (size_t j = 0; j < gv->dependents().size(); j++) {
2361 formu.ReplaceAll(TString::Format("x[%zu]", j), gv->dependents()[j].GetName());
2362 }
2363 _suffix += formu;
2364 }
2365 std::cout << k->get()->ClassName() << "::" << k->get()->GetName() << _suffix.Data() << std::endl;
2366 }
2367 if (depth != 0) {
2368 k->Print(sOpt + TString::Format("depth=%dindent=%d", depth - 1, iindent + 1));
2369 }
2370 } else
2371 std::cout << " NULL " << std::endl;
2372 }
2373 }
2374 if (doCapture) {
2375 capture.reset(); // no captureStr has the string to display
2376 // inject line breaks to avoid msgbox being too wide
2377 size_t lastBreak = 0;
2378 std::string captureStrWithBreaks;
2379 for (size_t i = 0; i < captureStr.size(); i++) {
2380 captureStrWithBreaks += captureStr[i];
2381 if (captureStr[i] == '\n') {
2382 lastBreak = i;
2383 }
2384 if (i - lastBreak > 150) {
2385 captureStrWithBreaks += '\n';
2386 lastBreak = i;
2387 }
2388 }
2389 const TGWindow *w =
2390 (gROOT->GetListOfBrowsers()->At(0))
2391 ? dynamic_cast<TGWindow *>(static_cast<TBrowser *>(gROOT->GetListOfBrowsers()->At(0))->GetBrowserImp())
2392 : gClient->GetRoot();
2393 new TGMsgBox(gClient->GetRoot(), w, GetName(),
2394 captureStrWithBreaks.c_str()); //,nullptr,kMBDismiss,nullptr,kVerticalFrame,kTextLeft|kTextCenterY);
2395 }
2396}
2397
2399{
2400 if (!child.get()) {
2401
2402 if (auto v = get<RooRealVar>(); v) {
2403
2404 TString constrType = child.GetName();
2405 double mean = std::numeric_limits<double>::quiet_NaN();
2406 double sigma = mean;
2407 if (constrType.BeginsWith("gaussian(")) {
2408 // extract the mean and stddev parameters
2409 // if only one given, it is the stddev
2410 if (constrType.Contains(",")) {
2411 mean = TString(constrType(9, constrType.Index(',') - 9)).Atof();
2412 sigma = TString(constrType(constrType.Index(',') + 1, constrType.Index(')') - constrType.Index(',') + 1))
2413 .Atof();
2414 } else {
2415 mean = std::numeric_limits<double>::quiet_NaN(); // will use the var current value below to set mean
2416 sigma = TString(constrType(9, constrType.Index(')') - 9)).Atof();
2417 }
2418 constrType = "normal";
2419 } else if (constrType == "normal") {
2420 mean = 0;
2421 sigma = 1;
2422 } else if (constrType == "gaussian") {
2423 // extract parameters from the variable
2424 // use current value and error on v as constraint
2425 if (!v->hasError())
2426 throw std::runtime_error("No error on parameter for gaussian constraint");
2427 sigma = v->getError();
2428 mean = v->getVal();
2429 constrType = "normal";
2430 } else if (constrType == "poisson") {
2431 if (!v->hasError())
2432 throw std::runtime_error("No error on parameter for poisson constraint");
2433 mean = 1;
2434 sigma = pow(v->getVal() / v->getError(), 2);
2435 }
2436
2437 if (constrType == "poisson") {
2438 // use current value and error on v as constraint
2439 double tau_val = sigma;
2440 auto globs = acquire<RooRealVar>(Form("globs_%s", v->GetName()), Form("globs_%s", v->GetName()),
2441 v->getVal() * tau_val, (v->getVal() - 5 * v->getError()) * tau_val,
2442 (v->getVal() + 5 * v->getError()) * tau_val);
2443 globs->setConstant();
2444 globs->setAttribute("obs");
2445 globs->setAttribute("global");
2446 globs->setStringAttribute("nominal", TString::Format("%f", tau_val));
2447 auto tau = acquireNew<RooConstVar>(TString::Format("tau_%s", v->GetName()), "", tau_val);
2448 auto constr = acquireNew<RooPoisson>(
2449 Form("pois_%s", v->GetName()), TString::Format("Poisson Constraint of %s", v->GetTitle()), *globs,
2450 *acquireNew<RooProduct>(TString::Format("mean_%s", v->GetName()),
2451 TString::Format("Poisson Constraint of %s", globs->GetTitle()),
2452 RooArgList(*v, *tau)),
2453 true /* no rounding */);
2454
2455 auto out = Constrain(xRooNode(Form("pois_%s", GetName()), constr));
2456 if (!v->hasError())
2457 v->setError(mean / sqrt(tau_val)); // if v doesnt have an uncert, will put one on it now
2458 Info("Constrain", "Added poisson constraint pdf RooPoisson::%s (tau=%g) for %s", out->GetName(), tau_val,
2459 GetName());
2460 return out;
2461 } else if (constrType == "normal") {
2462
2463 auto globs = acquire<RooRealVar>(Form("globs_%s", v->GetName()), Form("globs_%s", v->GetName()), mean,
2464 mean - 10 * sigma, mean + 10 * sigma);
2465 globs->setAttribute("obs");
2466 globs->setAttribute("global");
2467 globs->setConstant();
2468
2469 globs->setStringAttribute("nominal", TString::Format("%f", mean));
2470 auto constr = acquireNew<RooGaussian>(
2471 Form("gaus_%s", v->GetName()), TString::Format("Gaussian Constraint of %s", v->GetTitle()), *globs, *v,
2472 *acquireNew<RooConstVar>(TString::Format("sigma_%s", v->GetName()), "", sigma));
2473 auto out = Constrain(xRooNode(Form("gaus_%s", GetName()), constr));
2474 if (!v->hasError())
2475 v->setError(sigma); // if v doesnt have an uncert, will put one on it now
2476 Info("Constrain", "Added gaussian constraint pdf RooGaussian::%s (mean=%g,sigma=%g) for %s", out->GetName(),
2477 mean, sigma, GetName());
2478 return out;
2479 }
2480 }
2481 } else if (auto p = child.get<RooAbsPdf>(); p) {
2482
2483 auto _me = get<RooAbsArg>();
2484 if (!_me) {
2485 throw std::runtime_error("Cannot constrain non arg");
2486 }
2487
2488 if (!p->dependsOn(*_me)) {
2489 throw std::runtime_error("Constraint does not depend on constrainee");
2490 }
2491
2492 // find a parent that can swallow this pdf ... either a RooProdPdf or a RooWorkspace
2493 auto x = fParent;
2494 while (x && !x->get<RooProdPdf>() && !x->get<RooSimultaneous>() && !x->get<RooWorkspace>()) {
2495 x = x->fParent;
2496 }
2497 if (!x) {
2498 throw std::runtime_error("Nowhere to put constraint");
2499 }
2500
2501 if (auto s = x->get<RooSimultaneous>(); s) {
2502 // put into every channel that features parameter
2503 x->browse();
2504 for (auto &c : *x) {
2505 if (auto a = c->get<RooAbsArg>(); a->dependsOn(*_me))
2506 c->Multiply(child);
2507 }
2508 return child;
2509 } else if (x->get<RooProdPdf>()) {
2510 return x->Multiply(child);
2511 } else {
2512 return x->Add(child, "+");
2513 }
2514 }
2515
2516 throw std::runtime_error(TString::Format("Cannot constrain %s", GetName()));
2517}
2518
2520{
2521
2522 class AutoUpdater {
2523 public:
2524 AutoUpdater(xRooNode &_n) : n(_n) {}
2525 ~AutoUpdater() { n.browse(); }
2526 xRooNode &n;
2527 };
2528 AutoUpdater xxx(*this);
2529
2530 if (fBinNumber != -1) {
2531 // scaling a bin ...
2532 if (child.get<RooAbsReal>()) { // if not child then let fall through to create a child and call self again below
2533 // doing a bin-multiplication .. the parent should have a ParamHistFunc called binFactors
2534 // if it doesn't then create one
2535 auto o = std::dynamic_pointer_cast<RooAbsReal>(acquire(child.fComp));
2536
2537 // get binFactor unless parent is a ParamHistFunc already ...
2538
2539 auto binFactors = (fParent->get<ParamHistFunc>()) ? fParent : fParent->factors().find("binFactors");
2540
2541 // it can happen in a loop over bins() that another node has moved fParent inside a product
2542 // so check for fParent having a client with the ORIGNAME:<name> attribute
2543 if (!binFactors && fParent->get<RooAbsArg>()) {
2544 for (auto c : fParent->get<RooAbsArg>()->clients()) {
2545 if (c->IsA() == RooProduct::Class() &&
2546 c->getAttribute(TString::Format("ORIGNAME:%s", fParent->get()->GetName()))) {
2547 // try getting binFactors out of this
2548 binFactors = xRooNode(*c).factors().find("binFactors");
2549 break;
2550 }
2551 }
2552 }
2553
2554 if (!binFactors) {
2555 fParent
2556 ->Multiply(TString::Format("%s_binFactors",
2557 (fParent->mainChild().get())
2558 ? fParent->mainChild()->GetName()
2559 : (fParent->get() ? fParent->get()->GetName() : fParent->GetName()))
2560 .Data(),
2561 "blankshape")
2562 .SetName("binFactors"); // creates ParamHistFunc with all pars = 1 (shared const)
2563 binFactors = fParent->factors().find("binFactors");
2564 if (!binFactors) {
2565 throw std::runtime_error(
2566 TString::Format("Could not create binFactors in parent %s", fParent->GetName()));
2567 }
2568 // auto phf = binFactors->get<ParamHistFunc>();
2569
2570 // create RooProducts for all the bins ... so that added factors don't affect selves
2571 int i = 1;
2572 for (auto &b : binFactors->bins()) {
2573 auto p = acquireNew<RooProduct>(TString::Format("%s_bin%d", binFactors->get()->GetName(), i),
2574 TString::Format("binFactors of bin %d", i), RooArgList());
2575 p->setStringAttribute("alias", TString::Format("%s=%g", binFactors->GetXaxis()->GetParent()->GetName(),
2576 binFactors->GetXaxis()->GetBinCenter(i)));
2577 b->Multiply(*p);
2578 i++;
2579 }
2580 }
2581 // then scale the relevant bin ... if the relevant bin is a "1" then just drop in our factor (inside a
2582 // RooProduct though, to avoid it getting modified by subsequent multiplies)
2583 auto _bin = binFactors->bins().at(fBinNumber - 1);
2584 if (auto phf = binFactors->get<ParamHistFunc>(); phf && _bin) {
2585#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
2586 RooArgList &pSet = phf->_paramSet;
2587#else
2588 RooArgList &pSet = const_cast<RooArgList &>(phf->paramList());
2589#endif
2590 if (strcmp(_bin->GetName(), "1") == 0) {
2591 RooArgList all;
2592 for (std::size_t i = 0; i < pSet.size(); i++) {
2593 if (int(i) != fBinNumber - 1) {
2594 all.add(*pSet.at(i));
2595 } else {
2596 all.add(*o);
2597 }
2598 }
2599 pSet.removeAll();
2600 pSet.add(all);
2601 } else {
2602 _bin->fBinNumber = -1; // to avoid infinite loop
2603 return _bin->Multiply(child, opt);
2604 }
2605 // } else {else if(_bin->get<RooProduct>()) {
2606 // // multiply the element which will just add it as a factor in the rooproduct
2607 // return _bin->Multiply(child,opt);
2608 // } else {
2609 // // not a rooproduct in this bin yet ... so need to replace with a rooproduct and
2610 // multiply that
2611 // // this avoids the undesired behaviour of shared binFactors getting all impacted by
2612 // mulitplies RooArgList all; auto new_p =
2613 // acquireNew<RooProduct>(TString::Format("%s_bin%d",binFactors->get()->GetName(),fBinNumber),TString::Format("binFactors
2614 // of bin %d",fBinNumber),RooArgList(*_bin->get<RooAbsArg>()));
2615 // new_p->setStringAttribute("alias","")
2616 // for (int i = 0; i < phf->_paramSet.size(); i++) {
2617 // if (i != fBinNumber - 1) all.add(*phf->_paramSet.at(i));
2618 // else all.add(*new_p);
2619 // }
2620 // phf->_paramSet.removeAll();
2621 // phf->_paramSet.add(all);
2622 // // now multiply that bin having converted it to RooProduct
2623 // return binFactors->bins().at(fBinNumber - 1)->Multiply(child,opt);
2624 // }
2625 }
2626 return xRooNode(*o, binFactors);
2627 }
2628 } else if (!get() && fParent) {
2629 // try to 'create' object based on parentage
2630 // add child as a temporary child to help with decision making
2631 auto _ref = emplace_back(std::shared_ptr<xRooNode>(&const_cast<xRooNode &>(child), [](TObject *) {}));
2632 try {
2633 fComp = fParent->Add(*this, "+").fComp;
2634 } catch (...) {
2635 resize(size() - 1);
2636 std::rethrow_exception(std::current_exception());
2637 }
2638 resize(size() - 1); // remove the temporarily added node
2639 }
2640
2641 if (!child.get()) {
2642 TString sOpt(opt);
2643 sOpt.ToLower();
2644 if (auto o = getObject<RooAbsReal>(child.GetName())) {
2645 auto out = Multiply(xRooNode(o, child.fParent));
2646 // have to protect bin case where get() is null (could change but then must change logic above too)
2647 if (get()) {
2648 Info("Multiply", "Scaled %s by existing factor %s::%s",
2649 mainChild().get() ? mainChild().get()->GetName() : get()->GetName(), o->ClassName(), o->GetName());
2650 }
2651 return out;
2652 } else if (sOpt == "norm") {
2653 if (TString(child.GetName()).Contains("[") && ws()) {
2654 // assume factory method wanted
2655 auto arg = ws()->factory(child.GetName());
2656 if (arg) {
2657 auto out = Multiply(*arg);
2658 if (get()) {
2659 Info("Multiply", "Scaled %s by new norm factor %s",
2660 mainChild().get() ? mainChild().get()->GetName() : get()->GetName(), out->GetName());
2661 }
2662 return out;
2663 }
2664 throw std::runtime_error(TString::Format("Failed to create new normFactor %s", child.GetName()));
2665 }
2666 auto out = Multiply(RooRealVar(child.GetName(), child.GetTitle(), 1, -1e-5, 100));
2667 if (get()) {
2668 Info("Multiply", "Scaled %s by new norm factor %s",
2669 mainChild().get() ? mainChild().get()->GetName() : get()->GetName(), out->GetName());
2670 }
2671 return out;
2672 } else if (sOpt == "shape" || sOpt == "histo" || sOpt == "blankshape") {
2673 // needs axis defined
2674 if (auto ax = GetXaxis(); ax) {
2675 auto h = std::shared_ptr<TH1>(BuildHistogram(dynamic_cast<RooAbsLValue *>(ax->GetParent()), true));
2676 h->Reset();
2677 for (int i = 1; i <= h->GetNbinsX(); i++) {
2678 h->SetBinContent(i, 1);
2679 }
2680 h->SetMinimum(0);
2681 h->SetMaximum(100);
2682 h->SetName(TString::Format(";%s", child.GetName())); // ; char indicates don't "rename" this thing
2683 h->SetTitle(child.GetTitle());
2684 if (sOpt.Contains("shape"))
2685 h->SetOption(sOpt);
2686 auto out = Multiply(*h);
2687 if (get()) {
2688 Info("Multiply", "Scaled %s by new %s factor %s",
2689 mainChild().get() ? mainChild().get()->GetName() : get()->GetName(), sOpt.Data(), out->GetName());
2690 }
2691 return out;
2692 }
2693 } else if (sOpt == "overall") {
2694 auto out = Multiply(acquireNew<RooStats::HistFactory::FlexibleInterpVar>(
2695 child.GetName(), child.GetTitle(), RooArgList(), 1, std::vector<double>(), std::vector<double>()));
2696 if (get() /* can happen this is null if on a bin node with no shapeFactors*/) {
2697 Info("Multiply", "Scaled %s by new overall factor %s",
2698 mainChild().get() ? mainChild().get()->GetName() : get()->GetName(), out->GetName());
2699 }
2700 return out;
2701 } else if (sOpt == "func" && ws()) {
2702 // need to get way to get dependencies .. can't pass all as causes circular dependencies issues.
2703 if (auto arg = ws()->factory(TString("expr::") + child.GetName())) {
2704 auto out = Multiply(*arg);
2705 if (get() /* can happen this is null if on a bin node with no shapeFactors*/) {
2706 Info("Multiply", "Scaled %s by new func factor %s",
2707 mainChild().get() ? mainChild().get()->GetName() : get()->GetName(), out->GetName());
2708 }
2709 return out;
2710 }
2711 }
2712 }
2713 if (auto h = child.get<TH1>(); h && strlen(h->GetOption()) == 0 && strlen(opt) > 0) {
2714 // put the option in the hist
2715 h->SetOption(opt);
2716 }
2717 if (auto w = get<RooWorkspace>(); w) {
2718 // just acquire
2719 std::shared_ptr<TObject> out;
2720 child.convertForAcquisition(*this);
2721 if (child.get<RooAbsReal>())
2722 out = acquire(child.fComp);
2723 return out;
2724 }
2725
2726 if (strcmp(GetName(), ".coef") == 0) { // covers both .coef and .coefs
2727 // need to add this into the relevant coef ... if its not a RooProduct, replace it with one first
2728 if (auto p = fParent->fParent->get<RooAddPdf>()) {
2729 for (size_t i = 0; i < p->pdfList().size(); i++) {
2730 if (p->pdfList().at(i) == fParent->get<RooAbsArg>()) {
2731 auto coefs = p->coefList().at(i);
2732 if (!coefs->InheritsFrom("RooProduct")) {
2733 RooArgList oldCoef;
2734 if (!(strcmp(coefs->GetName(), "1") == 0 || strcmp(coefs->GetName(), "ONE") == 0))
2735 oldCoef.add(*coefs);
2736 auto newCoefs = fParent->acquireNew<RooProduct>(
2737 TString::Format("coefs_%s", fParent->GetName()),
2738 TString::Format("coefficients for %s", fParent->GetName()), oldCoef);
2739 RooArgList oldCoefs;
2740 for (size_t j = 0; j < p->coefList().size(); j++) {
2741 if (i == j) {
2742 oldCoefs.add(*newCoefs);
2743 } else {
2744 oldCoefs.add(*p->coefList().at(j));
2745 }
2746 }
2747 const_cast<RooArgList &>(p->coefList()).removeAll();
2748 const_cast<RooArgList &>(p->coefList()).add(oldCoefs);
2749 coefs = newCoefs.get();
2750 }
2751 return xRooNode(*coefs, fParent).Multiply(child);
2752 }
2753 }
2754 }
2755 throw std::runtime_error("this coefs case is not supported");
2756 }
2757
2758 if (auto p = get<RooProduct>(); p) {
2759 std::shared_ptr<TObject> out;
2760 auto cc = child.fComp;
2761 bool isConverted = (child.convertForAcquisition(*this) != cc);
2762 if (child.get<RooAbsReal>())
2763 out = acquire(child.fComp);
2764
2765 // child may be a histfunc or a rooproduct of a histfunc and a paramhist if has stat errors
2766 if (auto _f = std::dynamic_pointer_cast<RooHistFunc>(
2767 (child.get<RooProduct>()) ? child.factors()[child.GetName()]->fComp : out);
2768 _f && _f->getAttribute("autodensity")) {
2769 // should we flag this as a density? yes if there's no other term marked as the density
2770 bool hasDensity = false;
2771 for (auto &f : factors()) {
2772 if (f->get<RooAbsArg>()->getAttribute("density")) {
2773 hasDensity = true;
2774 break;
2775 }
2776 }
2777 _f->setAttribute("density", !hasDensity && fParent && fParent->get<RooRealSumPdf>());
2778 if (_f->getAttribute("density")) {
2779
2780 // need to divide by bin widths first
2781 for (int i = 0; i < _f->dataHist().numEntries(); i++) {
2782 auto bin_pars = _f->dataHist().get(i);
2783 _f->dataHist().set(*bin_pars, _f->dataHist().weight() / _f->dataHist().binVolume(*bin_pars));
2784 }
2785 _f->setValueDirty();
2786
2787 // promote the axis vars to observables
2788 for (auto &x : xRooNode("tmp", _f).vars()) {
2789 x->get<RooAbsArg>()->setAttribute("obs");
2790 }
2791 }
2792 _f->setAttribute("autodensity", false);
2793 }
2794
2795 if (isConverted && child.get<RooHistFunc>()) {
2796 Info("Multiply", "Created %s factor %s in %s",
2797 child.get<RooAbsArg>()->getAttribute("density") ? "densityhisto" : "histo", child->GetName(),
2798 p->GetName());
2799 } else if (isConverted && child.get<ParamHistFunc>()) {
2800 Info("Multiply", "Created shape factor %s in %s", child->GetName(), p->GetName());
2801 }
2802
2803 if (auto _f = std::dynamic_pointer_cast<RooAbsReal>(out); _f) {
2804#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
2805 p->_compRSet.add(*_f);
2806#else
2807 const_cast<RooArgList &>(p->realComponents()).add(*_f);
2808#endif
2809 p->setValueDirty();
2810
2811 browse();
2812 xRooNode _out(_f, *this);
2813 for (auto &_par : _out.pars()) {
2814 if (auto s = _par->get<RooAbsArg>()->getStringAttribute("boundConstraint"); s) {
2815 bool found = false;
2816 for (auto &_constr : _par->constraints()) {
2817 if (strcmp(s, _constr->get()->GetName()) == 0) {
2818 // constraint is already included
2819 found = true;
2820 break;
2821 }
2822 }
2823 if (!found) {
2824 Info("Multiply", "Pulling in %s boundConstraint: %s", _par->GetName(), s);
2825 auto _pdf = getObject<RooAbsPdf>(s);
2826 if (!_pdf) {
2827 throw std::runtime_error("Couldn't find boundConstraint");
2828 }
2829 _par->Constrain(_pdf);
2830 }
2831 }
2832 }
2833 sterilize();
2834 return _out;
2835 }
2836 } else if (auto p2 = get<RooProdPdf>(); p2) {
2837
2838 std::shared_ptr<TObject> out;
2839 child.convertForAcquisition(*this);
2840 if (child.get<RooAbsPdf>()) {
2841 out = acquire(child.fComp);
2842 } else if (child.get<RooAbsReal>() && mainChild().get<RooRealSumPdf>()) {
2843 // cannot multiply a RooProdPdf by a non pdf
2844 throw std::runtime_error(TString::Format("Cannot multiply %s by non-pdf %s", GetName(), child.GetName()));
2845 // return mainChild().Add(child); - nov 2022 - used to do this but now replaced with exception above
2846 } else if (!child.get() || child.get<RooAbsReal>()) {
2847 // need to create or hide inside a sumpdf or rooadpdf
2848 std::shared_ptr<RooAbsPdf> _pdf;
2849 if (!child.get() && strcmp(child.GetName(), "components") == 0) {
2850 auto _sumpdf = acquireNew<RooAddPdf>(Form("%s_%s", p2->GetName(), child.GetName()),
2851 (strlen(child.GetTitle()) && strcmp(child.GetTitle(), child.GetName()))
2852 ? child.GetTitle()
2853 : p2->GetTitle(),
2854 RooArgList(), RooArgList());
2855 _pdf = _sumpdf;
2856 } else {
2857 auto _sumpdf = acquireNew<RooRealSumPdf>(
2858 Form("%s_%s", p2->GetName(), child.GetName()),
2859 (strlen(child.GetTitle()) && strcmp(child.GetTitle(), child.GetName())) ? child.GetTitle()
2860 : p2->GetTitle(),
2861 RooArgList(), RooArgList(), true);
2862 _sumpdf->setFloor(true);
2863 _pdf = _sumpdf;
2864 }
2865 _pdf->setStringAttribute("alias", child.GetName());
2866 // transfer axis attributes if present (TODO: should GetXaxis look beyond the immediate parent?)
2867 _pdf->setStringAttribute("xvar", p2->getStringAttribute("xvar"));
2868 _pdf->setStringAttribute("binning", p2->getStringAttribute("binning"));
2869 out = _pdf;
2870 Info("Multiply", "Created %s::%s in channel %s", _pdf->ClassName(), _pdf->GetName(), p2->GetName());
2871 if (child.get<RooAbsReal>())
2872 xRooNode(*out, *this).Add(child);
2873 }
2874
2875 if (auto _pdf = std::dynamic_pointer_cast<RooAbsPdf>(out); _pdf) {
2876#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
2877 const_cast<RooArgList &>(p2->pdfList()).add(*_pdf);
2878#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 26, 00)
2879 p2->_pdfNSetList.emplace_back(std::make_unique<RooArgSet>("nset"));
2880#else
2881 p->_pdfNSetList.Add(new RooArgSet("nset"));
2882#endif
2883 if (!p2->canBeExtended() && _pdf->canBeExtended()) {
2884 p2->_extendedIndex = p2->_pdfList.size() - 1;
2885 }
2886#else
2887 p2->addPdfs(RooArgSet(*_pdf));
2888#endif
2889 sterilize();
2890 browse();
2891 return xRooNode(_pdf, *this);
2892 }
2893 } else if (auto p3 = get<RooRealSumPdf>(); p3) {
2894 // multiplying all current and future components
2895 std::shared_ptr<TObject> out;
2896 child.convertForAcquisition(*this);
2897 if (child.get<RooAbsReal>()) {
2898 out = acquire(child.fComp);
2899 for (auto &c : components()) {
2900 c->Multiply(out);
2901 }
2902 TString s = p3->getStringAttribute("global_factors");
2903 if (s != "")
2904 s += ";";
2905 s += out->GetName();
2906 p3->setStringAttribute("global_factors", s);
2907 Info(
2908 "Multiply",
2909 "Flagged %s as a global factor in channel %s (is applied to all current and future samples in the channel)",
2910 out->GetName(), p3->GetName());
2911 return xRooNode(out, *this);
2912 }
2913
2914 } else if (auto p4 = get<RooAbsPdf>(); p4 && !(fParent && fParent->get<RooRealSumPdf>())) {
2915 // multiply the coefs (if this isn't part of a RooAddPdf or RooRealSumPdf then we will eventually throw exception
2916 return coefs().Multiply(child);
2917 } else if (auto p5 = get<RooAbsReal>(); p5 && (!get<RooAbsPdf>() || (fParent && fParent->get<RooRealSumPdf>()))) {
2918 // replace this obj with a RooProduct to allow for multiplication
2919
2920 // get the list of clients BEFORE creating the new interpolation ... seems list of clients is inaccurate after
2921 std::set<RooAbsArg *> cl;
2922 for (auto &arg : p5->clients()) {
2923 cl.insert(arg);
2924 }
2925
2926 // if multiple clients, see if only one client is in parentage route
2927 // if so, then assume thats the only client we should replace in
2928 if (cl.size() > 1) {
2929 if (cl.count(fParent->get<RooAbsArg>()) > 0) {
2930 cl.clear();
2931 cl.insert(fParent->get<RooAbsArg>());
2932 } else {
2933 Warning("Multiply", "Scaling %s that has multiple clients", p5->GetName());
2934 }
2935 }
2936
2937 auto new_p = acquireNew<RooProduct>(TString::Format("prod_%s", p5->GetName()), p5->GetTitle(), RooArgList(*p5));
2938 // copy attributes over
2939 for (auto &a : p5->attributes())
2940 new_p->setAttribute(a.c_str());
2941 for (auto &a : p5->stringAttributes())
2942 new_p->setStringAttribute(a.first.c_str(), a.second.c_str());
2943 if (!new_p->getStringAttribute("alias"))
2944 new_p->setStringAttribute("alias", p5->GetName());
2945 auto old_p = p5;
2946 new_p->setAttribute(Form("ORIGNAME:%s", old_p->GetName())); // used in redirectServers to say what this replaces
2947 for (auto arg : cl) {
2948 arg->redirectServers(RooArgSet(*new_p), false, true);
2949 }
2950
2951 fComp = new_p;
2952 return Multiply(child);
2953 }
2954
2955 // before giving up here, assume user wanted a norm factor type if child is just a name
2956 if (!child.get() && strlen(opt) == 0)
2957 return Multiply(child, "norm");
2958
2959 throw std::runtime_error(
2960 TString::Format("Cannot multiply %s by %s%s", GetPath().c_str(), child.GetName(),
2961 (!child.get() && strlen(opt) == 0) ? " (forgot to specify factor type?)" : ""));
2962}
2963
2965{
2966
2967 auto p5 = get<RooAbsArg>();
2968 if (!p5) {
2969 throw std::runtime_error("Only replacement of RooAbsArg is supported");
2970 }
2971 node.convertForAcquisition(*this, "func");
2972
2973 auto new_p = node.get<RooAbsArg>();
2974 if (!new_p) {
2975 throw std::runtime_error(TString::Format("Cannot replace with %s", node.GetName()));
2976 }
2977 auto out = acquire(node.fComp);
2978 new_p = std::dynamic_pointer_cast<RooAbsArg>(out).get();
2979
2980 std::set<RooAbsArg *> cl;
2981 for (auto &arg : p5->clients()) {
2982 if (arg == new_p)
2983 continue; // do not replace in self ... although redirectServers will prevent that anyway
2984 cl.insert(arg);
2985 }
2986
2987 // if multiple clients, see if only one client is in parentage route
2988 // if so, then assume thats the only client we should replace in
2989 if (cl.size() > 1) {
2990 if (fParent && fParent->get<RooAbsArg>() && cl.count(fParent->get<RooAbsArg>()) > 0) {
2991 cl.clear();
2992 cl.insert(fParent->get<RooAbsArg>());
2993 } else {
2994 std::stringstream clientList;
2995 for (auto c : cl)
2996 clientList << c->GetName() << ",";
2997 Warning("Replace", "Replacing %s in all clients: %s", p5->GetName(), clientList.str().c_str());
2998 }
2999 }
3000
3001 new_p->setAttribute(Form("ORIGNAME:%s", p5->GetName())); // used in redirectServers to say what this replaces
3002 for (auto arg : cl) {
3003 // if RooFormulaVar need to ensure the internal formula has been "constructed" otherwise will try to construct
3004 // it from the original expression that may have old parameter in it.
3005 if (auto p = dynamic_cast<RooFormulaVar *>(arg))
3006 p->ok(); // triggers creation of RooFormula
3007 arg->redirectServers(RooArgSet(*new_p), false, true);
3008 }
3009 return node;
3010}
3011
3013{
3014
3015 class AutoUpdater {
3016 public:
3017 AutoUpdater(xRooNode &_n) : n(_n) {}
3018 ~AutoUpdater() { n.browse(); }
3019 xRooNode &n;
3020 };
3021 AutoUpdater xxx(*this);
3022
3023 if (!get() && fParent) {
3024 // try to 'create' object based on parentage
3025 // add child as a temporary child to help with decision making
3026 auto _ref = emplace_back(std::shared_ptr<xRooNode>(&const_cast<xRooNode &>(child), [](TObject *) {}));
3027 try {
3028 fComp = fParent->Add(*this, "+").fComp;
3029 } catch (...) {
3030 resize(size() - 1);
3031 std::rethrow_exception(std::current_exception());
3032 }
3033 resize(size() - 1); // remove the temporarily added node
3034 }
3035
3036 if (auto p = mainChild(); p) {
3037 // variations applied to the main child if has one
3038 return p.Vary(child);
3039 }
3040
3041 if (auto s = get<RooSimultaneous>(); s && s->indexCat().IsA() == RooCategory::Class()) {
3042 // name is used as cat label
3043 std::string label = child.GetName();
3044 if (auto pos = label.find('='); pos != std::string::npos)
3045 label = label.substr(pos + 1);
3046 if (!s->indexCat().hasLabel(label)) {
3047 static_cast<RooCategory &>(const_cast<RooAbsCategoryLValue &>(s->indexCat())).defineType(label.c_str());
3048 }
3049 std::shared_ptr<TObject> out;
3050 child.convertForAcquisition(*this);
3051 if (child.get<RooAbsPdf>()) {
3052 out = acquire(child.fComp); // may create a channel from a histogram
3053 } else if (!child.fComp) {
3054 out = acquireNew<RooProdPdf>(TString::Format("%s_%s", s->GetName(), label.c_str()),
3055 (strlen(child.GetTitle())) ? child.GetTitle() : label.c_str(), RooArgList());
3056 Info("Vary", "Created channel RooProdPdf::%s in model %s", out->GetName(), s->GetName());
3057 }
3058
3059 if (auto _pdf = std::dynamic_pointer_cast<RooAbsPdf>(out); _pdf) {
3060 s->addPdf(*_pdf, label.c_str());
3061 sterilize();
3062 // clear children for reload and update shared axis
3063 clear();
3064 fXAxis.reset();
3065 browse();
3066 return xRooNode(TString::Format("%s=%s", s->indexCat().GetName(), label.data()), _pdf, *this);
3067 }
3068
3069 } else if (auto p = get<RooStats::HistFactory::FlexibleInterpVar>(); p) {
3070
3071 // child needs to be a constvar ...
3072 child.convertForAcquisition(*this);
3073 auto _c = child.get<RooConstVar>();
3074 if (!_c && child.get()) {
3075 throw std::runtime_error("Only pure consts can be set as variations of a flexible interpvar");
3076 }
3077#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
3078 double value = (_c ? _c->getVal() : p->_nominal);
3079 double nomVal = p->_nominal;
3080#else
3081 double value = (_c ? _c->getVal() : p->nominal());
3082 double nomVal = p->nominal();
3083#endif
3084
3085 TString cName(child.GetName());
3086 if (cName == "nominal") {
3087 p->setNominal(value);
3088 return *(this->variations().at(cName.Data()));
3089 }
3090 if (cName.CountChar('=') != 1) {
3091 throw std::runtime_error("unsupported variation form");
3092 }
3093 std::string parName = cName(0, cName.Index('='));
3094 double parVal = TString(cName(cName.Index('=') + 1, cName.Length())).Atof();
3095 if (parVal != 1 && parVal != -1) {
3096 throw std::runtime_error("unsupported variation magnitude");
3097 }
3098 bool high = parVal > 0;
3099
3100 if (parName.empty()) {
3101 p->setNominal(value);
3102 } else {
3103 auto v = fParent->getObject<RooRealVar>(parName);
3104 if (!v)
3105 v = fParent->acquire<RooRealVar>(parName.c_str(), parName.c_str(), -5, 5);
3106 if (!v->hasError())
3107 v->setError(1);
3108
3109 if (!p->findServer(*v)) {
3110#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
3111 p->_paramList.add(*v);
3112 p->_low.push_back(0);
3113 p->_high.push_back(0);
3114 p->_interpCode.push_back(4);
3115#else
3116 const_cast<RooListProxy &>(p->variables()).add(*v);
3117 const_cast<std::vector<double> &>(p->low()).push_back(0);
3118 const_cast<std::vector<double> &>(p->high()).push_back(0);
3119 const_cast<std::vector<int> &>(p->interpolationCodes()).push_back(4);
3120#endif
3121 v->setAttribute(Form("SYMMETRIC%s_%s", high ? "+" : "-", GetName())); // flag for symmetrized
3122 }
3123
3124 if (high) {
3125 p->setHigh(*v, value);
3126 if (v->getAttribute(Form("SYMMETRIC+_%s", GetName()))) {
3127 p->setLow(*v, 2 * nomVal - value);
3128 }
3129 v->setAttribute(Form("SYMMETRIC-_%s", GetName()), false);
3130 } else {
3131 p->setLow(*v, value);
3132 if (v->getAttribute(Form("SYMMETRIC-_%s", GetName()))) {
3133 p->setHigh(*v, 2 * nomVal - value);
3134 }
3135 v->setAttribute(Form("SYMMETRIC+_%s", GetName()), false);
3136 }
3137
3138 /*if (!unconstrained && fParent->pars()[v->GetName()].constraints().empty()) {
3139 fParent->pars()[v->GetName()].constraints().add("normal");
3140 }*/
3141 }
3142 return *(this->variations().at(cName.Data()));
3143 } else if (auto p2 = get<PiecewiseInterpolation>(); p2) {
3144 TString cName(child.GetName());
3145 if (cName.CountChar('=') != 1) {
3146 throw std::runtime_error("unsupported variation form");
3147 }
3148 TString parName = cName(0, cName.Index('='));
3149 double parVal = TString(cName(cName.Index('=') + 1, cName.Length())).Atof();
3150 if (parVal != 1 && parVal != -1) {
3151 throw std::runtime_error("unsupported variation magnitude");
3152 }
3153#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
3154 RooHistFunc *f = dynamic_cast<RooHistFunc *>(p2->_nominal.absArg());
3155 if (!f) {
3156 throw std::runtime_error(
3157 TString::Format("Interpolating %s instead of RooHistFunc", p2->_nominal.absArg()->ClassName()));
3158 }
3159#else
3160 RooHistFunc *f = dynamic_cast<RooHistFunc *>(const_cast<RooAbsReal *>(p2->nominalHist()));
3161 if (!f) {
3162 throw std::runtime_error(
3163 TString::Format("Interpolating %s instead of RooHistFunc", p2->nominalHist()->ClassName()));
3164 }
3165#endif
3166 RooHistFunc *nomf = f;
3167 RooHistFunc *otherf = nullptr;
3168 size_t i = 0;
3169 for (auto par : p2->paramList()) {
3170 if (parName == par->GetName()) {
3171 f = dynamic_cast<RooHistFunc *>((parVal > 0 ? p2->highList() : p2->lowList()).at(i));
3172 otherf = dynamic_cast<RooHistFunc *>((parVal > 0 ? p2->lowList() : p2->highList()).at(i));
3173 break;
3174 }
3175 i++;
3176 }
3177 if (i == p2->paramList().size() && !child.get<RooAbsReal>()) {
3178
3179 // need to add the parameter
3180 auto v = acquire<RooRealVar>(parName, parName, -5, 5);
3181 if (!v->hasError())
3182 v->setError(1);
3183
3184 std::shared_ptr<RooHistFunc> up(
3185 static_cast<RooHistFunc *>(f->Clone(Form("%s_%s_up", f->GetName(), parName.Data()))));
3186 std::shared_ptr<RooHistFunc> down(
3187 static_cast<RooHistFunc *>(f->Clone(Form("%s_%s_down", f->GetName(), parName.Data()))));
3188 // RooHistFunc doesn't clone it's data hist ... do it ourself (will be cloned again if imported into a ws)
3189#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
3190 std::unique_ptr<RooDataHist> h1(
3191 static_cast<RooDataHist *>(f->dataHist().Clone(Form("hist_%s", up->GetName()))));
3192 std::unique_ptr<RooDataHist> h2(
3193 static_cast<RooDataHist *>(f->dataHist().Clone(Form("hist_%s", down->GetName()))));
3194 up->_dataHist = dynamic_cast<RooDataHist *>(f->dataHist().Clone(Form("hist_%s", up->GetName())));
3195 down->_dataHist = dynamic_cast<RooDataHist *>(f->dataHist().Clone(Form("hist_%s", down->GetName())));
3196#else
3197 up->cloneAndOwnDataHist(TString::Format("hist_%s", up->GetName()));
3198 down->cloneAndOwnDataHist(TString::Format("hist_%s", down->GetName()));
3199#endif
3200 auto ups = std::dynamic_pointer_cast<RooHistFunc>(acquire(up, false, true));
3201 auto downs = std::dynamic_pointer_cast<RooHistFunc>(acquire(down, false, true));
3202#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
3203 p2->_highSet.add(*ups.get());
3204 p2->_lowSet.add(*downs.get());
3205 p2->_interpCode.push_back(4);
3206 p2->_paramSet.add(*v);
3207#else
3208 const_cast<RooArgList &>(p2->highList()).add(*ups);
3209 const_cast<RooArgList &>(p2->lowList()).add(*downs);
3210 const_cast<std::vector<int> &>(p2->interpolationCodes()).push_back(4);
3211 const_cast<RooArgList &>(p2->paramList()).add(*v);
3212#endif
3213 p2->setValueDirty();
3214 f = ((parVal > 0) ? ups : downs).get();
3215 otherf = ((parVal > 0) ? downs : ups).get();
3216 // start off with everything being symmetric
3217 f->setStringAttribute("symmetrizes", otherf->GetName());
3218 f->setStringAttribute("symmetrize_nominal", nomf->GetName());
3219 otherf->setStringAttribute("symmetrized_by", f->GetName());
3220
3221 // constrain par if required
3222 /*if (!unconstrained && fParent->pars()[v->GetName()].constraints().empty()) {
3223 fParent->pars()[v->GetName()].constraints().add("normal");
3224 }*/
3225 }
3226
3227 // child.convertForAcquisition(*this);
3228 if (f) {
3229 if (child.get())
3230 xRooNode("tmp", *f, *this) = *child.get();
3231 f->setValueDirty();
3232 xRooNode out(*f, *this);
3233 out.sterilize();
3234 return out;
3235 }
3236
3237 } else if (auto p3 = get<RooConstVar>(); p3) {
3238
3239 // never vary the universal consts ... its too dangerous
3240 if (p3->getAttribute("RooRealConstant_Factory_Object")) {
3241 throw std::runtime_error("Cannot vary pure constants");
3242 }
3243
3244 // inject a FlexibleInterpVar ...
3245
3246 // get the list of clients BEFORE creating the new interpolation ... seems list of clients is inaccurate after
3247 std::set<RooAbsArg *> cl;
3248 for (auto &arg : p3->clients()) {
3249 cl.insert(arg);
3250 }
3251 // if multiple clients, see if only one client is in parentage route
3252 // if so, then assume thats the only client we should replace in
3253 if (cl.size() > 1) {
3254 if (cl.count(fParent->get<RooAbsArg>()) > 0) {
3255 cl.clear();
3256 cl.insert(fParent->get<RooAbsArg>());
3257 } else {
3258 Warning("Vary", "Varying %s that has multiple clients", p3->GetName());
3259 }
3260 }
3261 p3->setStringAttribute("origName", p3->GetName());
3262 TString n = p3->GetName();
3263 p3->SetName(Form("%s_nominal", p3->GetName())); // if problems should perhaps not rename here
3264
3265 auto new_p = acquireNew<RooStats::HistFactory::FlexibleInterpVar>(n, p3->GetTitle(), RooArgList(), p3->getVal(),
3266 std::vector<double>(), std::vector<double>());
3267
3268 // copy attributes over
3269 for (auto &a : p3->attributes())
3270 new_p->setAttribute(a.c_str());
3271 for (auto &a : p3->stringAttributes())
3272 new_p->setStringAttribute(a.first.c_str(), a.second.c_str());
3273 // if (!new_p->getStringAttribute("alias")) new_p->setStringAttribute("alias",p->GetName());
3274 auto old_p = p3;
3275 new_p->setAttribute(Form("ORIGNAME:%s", old_p->GetName())); // used in redirectServers to say what this replaces
3276 for (auto arg : cl) {
3277 arg->redirectServers(RooArgSet(*new_p), false, true);
3278 }
3279
3280 fComp = new_p;
3281 return Vary(child);
3282
3283 } else if (auto p4 = get<RooAbsReal>(); p4) {
3284 // inject an interpolation node
3285
3286 // get the list of clients BEFORE creating the new interpolation ... seems list of clients is inaccurate after
3287 std::set<RooAbsArg *> cl;
3288 for (auto &arg : p4->clients()) {
3289 cl.insert(arg);
3290 }
3291 // if multiple clients, see if only one client is in parentage route
3292 // if so, then assume thats the only client we should replace in
3293 if (cl.size() > 1) {
3294 if (cl.count(fParent->get<RooAbsArg>()) > 0) {
3295 cl.clear();
3296 cl.insert(fParent->get<RooAbsArg>());
3297 } else {
3298 Warning("Vary", "Varying %s that has multiple clients", p4->GetName());
3299 }
3300 }
3301 p4->setStringAttribute("origName", p4->GetName());
3302 TString n = p4->GetName();
3303 p4->SetName(Form("%s_nominal", p4->GetName())); // if problems should perhaps not rename here
3304
3305 auto new_p = acquireNew<PiecewiseInterpolation>(n, p4->GetTitle(), *p4, RooArgList(), RooArgList(), RooArgList());
3306
3307 // copy attributes over
3308 for (auto &a : p4->attributes())
3309 new_p->setAttribute(a.c_str());
3310 for (auto &a : p4->stringAttributes())
3311 new_p->setStringAttribute(a.first.c_str(), a.second.c_str());
3312 // if (!new_p->getStringAttribute("alias")) new_p->setStringAttribute("alias",p->GetName());
3313 auto old_p = p4;
3314 new_p->setAttribute(Form("ORIGNAME:%s", old_p->GetName())); // used in redirectServers to say what this replaces
3315 for (auto arg : cl) {
3316 arg->redirectServers(RooArgSet(*new_p), false, true);
3317 }
3318
3319 fComp = new_p;
3320 return Vary(child);
3321 }
3322
3323 Print();
3324 throw std::runtime_error(TString::Format("Cannot vary %s with %s", GetName(), child.GetName()));
3325}
3326
3328{
3330}
3331
3332bool xRooNode::SetContent(double value, const char *par, double val)
3333{
3334 return SetContents(RooConstVar(GetName(), GetTitle(), value), par, val);
3335}
3336
3339 {
3340 if (x && b)
3341 x->setBinning(*b);
3342 if (b)
3343 delete b;
3344 }
3345 RooRealVar *x = nullptr;
3346 RooAbsBinning *b = nullptr;
3347};
3348
3350{
3351
3352 if (!get()) {
3353 fComp = std::shared_ptr<TObject>(const_cast<TObject *>(&o), [](TObject *) {});
3354 if (fParent && !fParent->find(GetName())) {
3355 // either a temporary or a placeholder so need to try genuinely adding
3356 fComp = fParent->Add(*this, "+").fComp;
3357 if (auto a = get<RooAbsArg>(); a && strcmp(a->GetName(), GetName()) && !a->getStringAttribute("alias")) {
3358 a->setStringAttribute("alias", GetName());
3359 }
3360 if (!fComp)
3361 throw std::runtime_error("Cannot determine type");
3362 return *this;
3363 }
3364 }
3365
3366 if (auto h = dynamic_cast<const TH1 *>(&o); h) {
3367 /*auto f = get<RooHistFunc>();
3368 if (!f) {
3369 // if it's a RooProduct locate child with the same name
3370 if (get<RooProduct>()) {
3371 f = factors()[GetName()]->get<RooHistFunc>();
3372 }
3373
3374
3375
3376 }*/
3377 bool _isData = get<RooAbsData>();
3378 BinningRestorer _b;
3379 if (_isData) {
3380 // need to ensure x-axis matches this h
3381 auto ax = GetXaxis();
3382 if (!ax)
3383 throw std::runtime_error("no xaxis");
3384 auto _v = dynamic_cast<RooRealVar *>(ax->GetParent());
3385 if (_v) {
3386 _b.x = _v;
3387 _b.b = dynamic_cast<RooAbsBinning *>(_v->getBinningPtr(nullptr)->Clone());
3388 if (h->GetXaxis()->IsVariableBinSize()) {
3389 _v->setBinning(RooBinning(h->GetNbinsX(), h->GetXaxis()->GetXbins()->GetArray()));
3390 } else {
3391 _v->setBinning(RooUniformBinning(h->GetXaxis()->GetXmin(), h->GetXaxis()->GetXmax(), h->GetNbinsX()));
3392 }
3393 }
3394 }
3395
3396 if (true) {
3397 for (int bin = 1; bin <= h->GetNbinsX(); bin++) {
3398 SetBinContent(bin, h->GetBinContent(bin));
3399 /*double value = h->GetBinContent(bin);
3400 auto bin_pars = f->dataHist().get(bin - 1);
3401 if (f->getAttribute("density")) {
3402 value /= f->dataHist().binVolume(*bin_pars);
3403 }
3404 f->dataHist().set(*bin_pars, value);*/
3405 if (!_isData && h->GetSumw2N() && !SetBinError(bin, h->GetBinError(bin)))
3406 throw std::runtime_error("Failed setting stat error");
3407 }
3408 return *this;
3409 }
3410 } else if (auto _c = dynamic_cast<const RooConstVar *>(&o); _c) {
3411
3412 if (auto a = get<RooAbsArg>();
3413 (a && a->isFundamental()) || get<RooConstVar>() || get<RooStats::HistFactory::FlexibleInterpVar>()) {
3414 SetBinContent(1, _c->getVal());
3415 return *this;
3416 } else if (get<RooAbsData>()) { // try to do assignment to a dataset (usually setting a bin content)
3417 SetBinContent(0, _c->getVal());
3418 return *this;
3419 }
3420 }
3421
3422 throw std::runtime_error("Assignment failed");
3423
3424 /*
3425
3426 if (fParent && !fParent->mk()) {
3427 throw std::runtime_error("mk failure");
3428 }
3429
3430 if (fComp) return *this;
3431
3432 if (o.InheritsFrom("RooAbsArg")) {
3433 fComp = acquire(std::shared_ptr<TObject>(const_cast<TObject*>(&o),[](TObject* o){}));
3434 std::dynamic_pointer_cast<RooAbsArg>(fComp)->setStringAttribute("alias",GetName());
3435 }
3436
3437 if (fComp && fParent) {
3438 fParent->incorporate(fComp);
3439 }
3440
3441
3442 return *this;
3443 */
3444}
3445
3446void xRooNode::_fit_(const char *constParValues)
3447{
3448 try {
3449 auto _pars = pars();
3450 // std::unique_ptr<RooAbsCollection> snap(_pars.argList().snapshot());
3451 TStringToken pattern(constParValues, ",");
3452 while (pattern.NextToken()) {
3453 auto idx = pattern.Index('=');
3454 TString pat = (idx == -1) ? TString(pattern) : TString(pattern(0, idx));
3455 double val =
3456 (idx == -1) ? std::numeric_limits<double>::quiet_NaN() : TString(pattern(idx + 1, pattern.Length())).Atof();
3457 for (auto p : _pars.argList()) {
3458 if (TString(p->GetName()).Contains(TRegexp(pat, true))) {
3459 p->setAttribute("Constant", true);
3460 if (!std::isnan(val)) {
3461 dynamic_cast<RooAbsRealLValue *>(p)->setVal(val);
3462 }
3463 }
3464 }
3465 }
3466 // use the first selected dataset
3467 auto _dsets = datasets();
3468 TString dsetName = "";
3469 for (auto &d : _dsets) {
3470 if (d->get()->TestBit(1 << 20)) {
3471 dsetName = d->get()->GetName();
3472 break;
3473 }
3474 }
3475 auto _nll = nll(dsetName.Data());
3476 _nll.fitConfigOptions()->SetValue("LogSize", 65536);
3477 _nll.fitConfig()->MinimizerOptions().SetPrintLevel(0);
3478 auto fr = _nll.minimize();
3479 //_pars.argList() = *snap; // restore values - irrelevant as SetFitResult will restore values
3480 if (!fr.get())
3481 throw std::runtime_error("Fit Failed");
3482 SetFitResult(fr.get());
3483 TString statusCodes;
3484 for (unsigned int i = 0; i < fr->numStatusHistory(); i++) {
3485 statusCodes += TString::Format("\n%s = %d", fr->statusLabelHistory(i), fr->statusCodeHistory(i));
3486 }
3487 const TGWindow *w =
3488 (gROOT->GetListOfBrowsers()->At(0))
3489 ? dynamic_cast<TGWindow *>(static_cast<TBrowser *>(gROOT->GetListOfBrowsers()->At(0))->GetBrowserImp())
3490 : gClient->GetRoot();
3491 if (fr->status() != 0) {
3492 new TGMsgBox(gClient->GetRoot(), w, "Fit Finished with Bad Status Code",
3493 TString::Format("%s\nData = %s\nFit Status Code = %d\nCov Quality = %d\n-------------%s",
3494 fr->GetName(), dsetName.Data(), fr->status(), fr->covQual(), statusCodes.Data()),
3496 } else if (fr->covQual() != 3 && _nll.fitConfig()->ParabErrors()) {
3497 new TGMsgBox(gClient->GetRoot(), w, "Fit Finished with Bad Covariance Quality",
3498 TString::Format("%s\nData = %s\nFit Status Code = %d\nCov Quality = %d\n-------------%s",
3499 fr->GetName(), dsetName.Data(), fr->status(), fr->covQual(), statusCodes.Data()),
3501 } else {
3502 new TGMsgBox(gClient->GetRoot(), w, "Fit Finished Successfully",
3503 TString::Format("%s\nData = %s\nFit Status Code = %d\nCov Quality = %d\n-------------%s",
3504 fr->GetName(), dsetName.Data(), fr->status(), fr->covQual(), statusCodes.Data()));
3505 }
3506 } catch (const std::exception &e) {
3507 new TGMsgBox(
3508 gClient->GetRoot(),
3509 (gROOT->GetListOfBrowsers()->At(0))
3510 ? dynamic_cast<TGWindow *>(static_cast<TBrowser *>(gROOT->GetListOfBrowsers()->At(0))->GetBrowserImp())
3511 : gClient->GetRoot(),
3512 "Exception", e.what(), kMBIconExclamation, kMBOk); // deletes self on dismiss?
3513 }
3514}
3515
3516void xRooNode::_generate_(const char *datasetName, bool expected)
3517{
3518 try {
3519 datasets().Add(datasetName, expected ? "asimov" : "toy");
3520 } catch (const std::exception &e) {
3521 new TGMsgBox(
3522 gClient->GetRoot(),
3523 (gROOT->GetListOfBrowsers()->At(0))
3524 ? dynamic_cast<TGWindow *>(static_cast<TBrowser *>(gROOT->GetListOfBrowsers()->At(0))->GetBrowserImp())
3525 : gClient->GetRoot(),
3526 "Exception", e.what(),
3527 kMBIconExclamation); // deletes self on dismiss?
3528 }
3529}
3530
3531void xRooNode::_scan_(const char *what, double nToys, const char *xvar, int nBinsX, double lowX,
3532 double highX /*, const char*, int, double, double*/, const char *constParValues)
3533{
3534 try {
3535 TString sXvar(xvar);
3536 TString sWhat(what);
3537
3538 // use the first selected dataset
3539 auto _dsets = datasets();
3540 TString dsetName = "";
3541 for (auto &d : _dsets) {
3542 if (d->get()->TestBit(1 << 20)) {
3543 dsetName = d->get()->GetName();
3544 break;
3545 }
3546 }
3547 auto _pars = pars();
3548 std::unique_ptr<RooAbsCollection> snap(_pars.argList().snapshot());
3549 TStringToken pattern(constParValues, ",");
3550 while (pattern.NextToken()) {
3551 auto idx = pattern.Index('=');
3552 TString pat = (idx == -1) ? TString(pattern) : TString(pattern(0, idx));
3553 double val =
3554 (idx == -1) ? std::numeric_limits<double>::quiet_NaN() : TString(pattern(idx + 1, pattern.Length())).Atof();
3555 for (auto par : _pars.argList()) {
3556 if (TString(par->GetName()).Contains(TRegexp(pat, true))) {
3557 par->setAttribute("Constant", true);
3558 if (!std::isnan(val)) {
3559 dynamic_cast<RooAbsRealLValue *>(par)->setVal(val);
3560 }
3561 }
3562 }
3563 }
3564 auto hs = nll(dsetName.Data()).hypoSpace(sXvar);
3565 if (nToys) {
3566 sWhat += " toys";
3567 if (nToys > 0) {
3568 sWhat += TString::Format("=%g", nToys);
3569 }
3570 }
3571 hs.SetTitle(sWhat + " scan" + ((dsetName != "") ? TString::Format(" [data=%s]", dsetName.Data()) : ""));
3572 int scanStatus = hs.scan(sWhat + " visualize", nBinsX, lowX, highX);
3573 if (scanStatus != 0) {
3574 new TGMsgBox(
3575 gClient->GetRoot(),
3576 (gROOT->GetListOfBrowsers()->At(0))
3577 ? dynamic_cast<TGWindow *>(static_cast<TBrowser *>(gROOT->GetListOfBrowsers()->At(0))->GetBrowserImp())
3578 : gClient->GetRoot(),
3579 "Scan Finished with Bad Status Code",
3580 TString::Format("%s\nData = %s\nScan Status Code = %d", hs.GetName(), dsetName.Data(), scanStatus),
3582 }
3583 hs.SetName(TUUID().AsString());
3584 if (ws()) {
3585 if (auto res = hs.result())
3586 ws()->import(*res);
3587 }
3588
3589 _pars.argList() = *snap; // restore pars
3590
3591 } catch (const std::exception &e) {
3592 new TGMsgBox(
3593 gClient->GetRoot(),
3594 (gROOT->GetListOfBrowsers()->At(0))
3595 ? dynamic_cast<TGWindow *>(static_cast<TBrowser *>(gROOT->GetListOfBrowsers()->At(0))->GetBrowserImp())
3596 : gClient->GetRoot(),
3597 "Exception", e.what(), kMBIconExclamation);
3598 }
3599}
3600
3601void xRooNode::_SetBinContent_(int bin, double value, const char *par, double parVal)
3602{
3603 try {
3604 SetBinContent(bin, value, strlen(par) > 0 ? par : nullptr, parVal);
3605 } catch (const std::exception &e) {
3606 new TGMsgBox(gClient->GetRoot(), gClient->GetRoot(), "Exception", e.what(),
3607 kMBIconExclamation); // deletes self on dismiss?
3608 }
3609}
3610
3612{
3613 try {
3614 if (!SetContent(value))
3615 throw std::runtime_error("Failed to SetContent");
3616 } catch (const std::exception &e) {
3617 new TGMsgBox(gClient->GetRoot(), gClient->GetRoot(), "Exception", e.what(),
3618 kMBIconExclamation); // deletes self on dismiss?
3619 }
3620}
3621
3622bool xRooNode::SetBinContent(int bin, double value, const char *par, double parVal)
3623{
3624
3625 // create if needed
3626 if (!get()) {
3627 if (fParent && !find(GetName())) {
3628 // if have a binning we create a histogram to match it
3629 if (auto ax = GetXaxis(); ax) {
3630 std::shared_ptr<TH1D> h;
3631 auto _b = dynamic_cast<Axis2 *>(ax)->binning();
3632 auto t = TH1::AddDirectoryStatus();
3633 TH1::AddDirectory(false);
3634 if (_b->isUniform()) {
3635 h.reset(new TH1D(GetName(), GetTitle(), _b->numBins(), _b->lowBound(), _b->highBound()));
3636 } else {
3637 h.reset(new TH1D(GetName(), GetTitle(), _b->numBins(), _b->array()));
3638 }
3639 h->SetDirectory(nullptr);
3641 h->GetXaxis()->SetName(TString::Format("%s;%s", ax->GetParent()->GetName(), ax->GetName()));
3642 fComp = h;
3643 }
3644 fComp = fParent->Add(*this, "sample").fComp;
3645 }
3646 }
3647
3648 // if it's a RooProduct locate child with the same name
3649 if (get<RooProduct>()) {
3650 return factors()[GetName()]->SetBinContent(bin, value, par, parVal);
3651 }
3652
3653 if (get<RooAbsData>()) {
3654 if (auto _data = get<RooDataSet>(); _data) {
3655 auto _ax = (bin) ? GetXaxis() : nullptr;
3656 if (!_ax && bin) {
3657 throw std::runtime_error("Cannot determine binning to fill data");
3658 }
3659 if (_ax && _ax->GetNbins() < bin) {
3660 throw std::out_of_range(TString::Format("%s range %s only has %d bins", _ax->GetParent()->GetName(),
3661 _ax->GetName(), _ax->GetNbins()));
3662 }
3663 RooArgSet obs;
3664
3665 TString cut = "";
3666
3667 for (auto _c : coords()) { // coords() moves vars to their respective coordinates too
3668 if (auto _cat = _c->get<RooAbsCategoryLValue>(); _cat) {
3669 if (cut != "")
3670 cut += " && ";
3671 cut += TString::Format("%s==%d", _cat->GetName(), _cat->getCurrentIndex());
3672 obs.add(*_cat); // note: if we ever changed coords to return clones, would need to keep coords alive
3673 } else if (auto _rv = _c->get<RooAbsRealLValue>(); _rv) {
3674 // todo: check coordRange is a single range rather than multirange
3675 if (cut != "")
3676 cut += " && ";
3677 cut +=
3678 TString::Format("%s>=%f&&%s<%f", _rv->GetName(), _rv->getMin(_rv->getStringAttribute("coordRange")),
3679 _rv->GetName(), _rv->getMax(_rv->getStringAttribute("coordRange")));
3680 obs.add(*_rv); // note: if we ever changed coords to return clones, would need to keep coords alive
3681 } else {
3682 throw std::runtime_error("SetBinContent of data: Unsupported coordinate type");
3683 }
3684 }
3685
3686 RooFormulaVar cutFormula("cut1", cut, obs); // doing this to avoid complaints about unused vars
3687 RooFormulaVar icutFormula("icut1", TString::Format("!(%s)", cut.Data()), obs);
3688
3689 TString cut2;
3690 if (_ax) {
3691 cut2 = TString::Format("%s >= %f && %s < %f", _ax->GetParent()->GetName(), _ax->GetBinLowEdge(bin),
3692 _ax->GetParent()->GetName(), _ax->GetBinUpEdge(bin));
3693 obs.add(*dynamic_cast<RooAbsArg *>(_ax->GetParent()));
3694 } else {
3695 cut2 = "1==1";
3696 }
3697 RooFormulaVar cutFormula2("cut2", cut + " && " + cut2, obs);
3698 RooFormulaVar icutFormula2("icut2", TString::Format("!(%s && %s)", cut.Data(), cut2.Data()), obs);
3699
3700 // // go up through parents looking for slice obs
3701 // auto _p = fParent;
3702 // while(_p) {
3703 // TString pName(_p->GetName());
3704 // if (auto pos = pName.Index('='); pos != -1) {
3705 // if(auto _obs = _p->getObject<RooAbsLValue>(pName(0,pos)); _obs) {
3706 // if(auto _cat = dynamic_cast<RooAbsCategoryLValue*>(_obs.get()); _cat) {
3707 // _cat->setLabel(pName(pos+1,pName.Length()));
3708 // cut += TString::Format("%s%s==%d", (cut=="")?"":" && ",_cat->GetName(),
3709 // _cat->getCurrentIndex());
3710 // } else if(auto _var = dynamic_cast<RooAbsRealLValue*>(_obs.get()); _var) {
3711 // _var->setVal(TString(pName(pos+1,pName.Length())).Atof());
3712 // // TODO: Cut for this!!
3713 // }
3714 // obs.add(*dynamic_cast<RooAbsArg*>(_obs.get()));
3715 // } else {
3716 // throw std::runtime_error("Unknown observable, could not find");
3717 // }
3718 // }
3719 // _p = _p->fParent;
3720 // }
3721
3722 // add observables to dataset if necessary
3723 RooArgSet l(obs);
3724 l.remove(*_data->get(), true, true);
3725 if (!l.empty()) {
3726 // addColumns method is buggy: https://github.com/root-project/root/issues/8787
3727 // incredibly though, addColumn works??
3728 for (auto &x : l) {
3729 _data->addColumn(*x);
3730 }
3731 // instead create a copy dataset and merge it into current
3732 // cant use merge because it drops weightVar
3733 /*RooDataSet tmp("tmp","tmp",l);
3734 for(int i=0;i<_data->numEntries();i++) tmp.add(l);
3735 _data->merge(&tmp);*/
3736 // delete _data->addColumns(l);
3737 }
3738 // before adding, ensure range is good to cover
3739 for (auto &o : obs) {
3740 if (auto v = dynamic_cast<RooRealVar *>(o); v) {
3741 if (auto dv = dynamic_cast<RooRealVar *>(_data->get()->find(v->GetName())); dv) {
3742 if (v->getMin() < dv->getMin())
3743 dv->setMin(v->getMin());
3744 if (v->getMax() > dv->getMax())
3745 dv->setMax(v->getMax());
3746 }
3747 } else if (auto c = dynamic_cast<RooCategory *>(o); c) {
3748 if (auto dc = dynamic_cast<RooCategory *>(_data->get()->find(c->GetName())); dc) {
3749 if (!dc->hasLabel(c->getCurrentLabel())) {
3750 dc->defineType(c->getCurrentLabel(), c->getCurrentIndex());
3751 }
3752 }
3753 }
3754 }
3755
3756 // using SetBinContent means dataset must take on a binned form at these coordinates
3757 // if number of entries doesnt match number of bins then will 'bin' the data
3758 if (bin) {
3759 if (auto _nentries = std::unique_ptr<RooAbsData>(_data->reduce(cutFormula))->numEntries();
3760 _nentries != _ax->GetNbins()) {
3761 auto _contents = GetBinContents(1, _ax->GetNbins());
3762
3763 if (_nentries > 0) {
3764 Info("SetBinContent", "Binning %s in channel: %s", GetName(), cut.Data());
3765 auto _reduced = std::unique_ptr<RooAbsData>(_data->reduce(icutFormula));
3766 _data->reset();
3767 for (int j = 0; j < _reduced->numEntries(); j++) {
3768 auto _obs = _reduced->get(j);
3769 _data->add(*_obs, _reduced->weight());
3770 }
3771 }
3772 for (int i = 1; i <= _ax->GetNbins(); i++) {
3773 // can skip over the bin we will be setting to save a reduce step below
3774 if (i == bin)
3775 continue;
3776 dynamic_cast<RooAbsLValue *>(_ax->GetParent())->setBin(i - 1, _ax->GetName());
3777 _data->add(obs, _contents.at(i - 1));
3778 }
3779 }
3780 }
3781 // remove existing entries
3782 if (std::unique_ptr<RooAbsData>(_data->reduce(cutFormula2))->numEntries() > 0) {
3783 auto _reduced = std::unique_ptr<RooAbsData>(_data->reduce(icutFormula2));
3784 _data->reset();
3785 for (int j = 0; j < _reduced->numEntries(); j++) {
3786 auto _obs = _reduced->get(j);
3787 _data->add(*_obs, _reduced->weight());
3788 }
3789 }
3790 if (_ax)
3791 dynamic_cast<RooAbsLValue *>(_ax->GetParent())->setBin(bin - 1, _ax->GetName());
3792 _data->add(obs, value);
3793 if (auto bb = getBrowsable(".sourceds"))
3794 return bb->SetBinContent(bin, value, par, parVal); // apply to source ds if we have one
3795 return true;
3796
3797 } else if (get<RooDataHist>()) {
3798 throw std::runtime_error("RooDataHist not supported yet");
3799 }
3800 }
3801
3802 if (auto _varies = variations(); !_varies.empty() || (par && strlen(par))) {
3803 if (!par || strlen(par) == 0) {
3804 return _varies["nominal"]->SetBinContent(bin, value, par, parVal);
3805 } else if (auto it = _varies.find(Form("%s=%g", par, parVal)); it) {
3806 return it->SetBinContent(bin, value);
3807 } else {
3808 // need to create the variation : note - if no variations existed up to now this will inject a new node
3809 // so we should redirect ourself to the new node
3810 // TODO: Do we need to redirect parents?
3811 TString s = Form("%s=%g", par, parVal);
3812 return Vary(s.Data()).SetBinContent(bin, value);
3813 }
3814 }
3815
3816 auto o = get();
3817 if (auto p = dynamic_cast<RooRealVar *>(o); p) {
3818 if (!par || strlen(par) == 0) {
3819 if (p->getMax() < value)
3820 p->setMax(value);
3821 if (p->getMin() > value)
3822 p->setMin(value);
3823 p->setVal(value);
3824 sterilize();
3825 return true;
3826 }
3827
3828 } else if (auto c = dynamic_cast<RooConstVar *>(o); c) {
3829
3830 // if parent is a FlexibleInterpVar, change the value in that .
3831 if (strcmp(c->GetName(), Form("%g", c->getVal())) == 0) {
3832 c->SetNameTitle(Form("%g", value), Form("%g", value));
3833 }
3834#if ROOT_VERSION_CODE < ROOT_VERSION(6, 24, 00)
3835 c->_value = value; // in future ROOT versions there is a changeVal method!
3836#else
3837 c->changeVal(value);
3838#endif
3839
3841 fParent->Vary(*this);
3842 }
3843
3844 sterilize();
3845 return true;
3846 } else if (auto f = dynamic_cast<RooHistFunc *>(o); f) {
3847 auto bin_pars = f->dataHist().get(bin - 1);
3848 if (f->getAttribute("density")) {
3849 value /= f->dataHist().binVolume(*bin_pars);
3850 }
3851 f->dataHist().set(*bin_pars, value);
3852 f->setValueDirty();
3853
3854 if (auto otherfName = f->getStringAttribute("symmetrized_by"); otherfName) {
3855 // broken symmetry, so update flags ...
3856 f->setStringAttribute("symmetrized_by", nullptr);
3857 if (auto x = getObject<RooAbsArg>(otherfName); x) {
3858 x->setStringAttribute("symmetrizes", nullptr);
3859 x->setStringAttribute("symmetrize_nominal", nullptr);
3860 }
3861 } else if (auto otherfName2 = f->getStringAttribute("symmetrizes"); otherfName2) {
3862 auto nomf = getObject<RooHistFunc>(f->getStringAttribute("symmetrize_nominal"));
3863 auto otherf = getObject<RooHistFunc>(otherfName2);
3864 if (nomf && otherf) {
3865 otherf->dataHist().set(*bin_pars, 2 * nomf->dataHist().weight(bin - 1) - value);
3866 otherf->setValueDirty();
3867 }
3868 }
3869 sterilize();
3870 return true;
3871 } else if (auto f2 = dynamic_cast<RooStats::HistFactory::FlexibleInterpVar *>(o); f2) {
3872 // changing nominal value
3873 f2->setNominal(value);
3874 }
3875 throw std::runtime_error(TString::Format("unable to set bin content of %s", GetPath().c_str()));
3876}
3877
3878bool xRooNode::SetBinData(int bin, double value, const char *dataName)
3879{
3880 return datasets()[dataName]->SetBinContent(bin, value);
3881}
3882
3883bool xRooNode::SetData(const TObject &obj, const char *dataName)
3884{
3885 return datasets()[dataName]->SetContents(obj);
3886}
3887
3888bool xRooNode::SetBinError(int bin, double value)
3889{
3890
3891 // if it's a RooProduct locate child with the same name
3892 if (get<RooProduct>()) {
3893 return factors()[GetName()]->SetBinError(bin, value);
3894 }
3895
3896 if (auto _varies = variations(); !_varies.empty()) {
3897 return _varies["nominal"]->SetBinError(bin, value);
3898 }
3899
3900 auto o = get();
3901
3902 if (auto f = dynamic_cast<RooHistFunc *>(o); f) {
3903
3904 // if (f->getAttribute("density")) { value /= f->dataHist().binVolume(*bin_pars); } - commented out because DON'T
3905 // convert .. sumw and sumw2 attributes will be stored not as densities
3906
3907 // NOTE: Can only do this because factors() makes parents of its children it's own parent (it isn't the parent)
3908 // If ever make factors etc part of the parentage then this would need tweaking to get to the true parent
3909 // find first parent that is a RooProduct, that is where the statFactor would live
3910 // stop as soon as we reach pdf object
3911 auto _prodParent = fParent;
3912 while (_prodParent && !_prodParent->get<RooProduct>() && !_prodParent->get<RooAbsPdf>()) {
3913 if (_prodParent->get<PiecewiseInterpolation>() && strcmp(GetName(), "nominal")) {
3914 _prodParent.reset();
3915 break; // only the 'nominal' variation can look for a statFactor outside the variation container
3916 }
3917 _prodParent = _prodParent->fParent;
3918 }
3919 auto _f_stat =
3920 (_prodParent && !_prodParent->get<RooAbsPdf>()) ? _prodParent->factors().find("statFactor") : nullptr;
3921 auto f_stat = (_f_stat) ? _f_stat->get<ParamHistFunc>() : nullptr;
3922 if (_f_stat && _f_stat->get() && !f_stat) {
3923 throw std::runtime_error("stat factor must be a paramhistfunc");
3924 }
3925
3926 // stat uncertainty lives in the "statFactor" factor, each sample has its own one,
3927 // but they can share parameters
3928 if (!f_stat) {
3929 if (value == 0)
3930 return true;
3931 TString parNames;
3932 for (auto &p : xRooNode("tmp", *f, std::shared_ptr<xRooNode>(nullptr)).vars()) {
3933 if (parNames != "")
3934 parNames += ",";
3935 parNames += p->get()->GetName();
3936 }
3937 auto h = std::unique_ptr<TH1>(f->dataHist().createHistogram(parNames
3938#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 27, 00)
3939 ,
3941#endif
3942 ));
3943 h->Reset();
3944 h->SetName("statFactor");
3945 h->SetTitle(TString::Format("StatFactor of %s", f->GetTitle()));
3946 h->SetOption("blankshape");
3947
3948 // multiply parent if is nominal
3949 auto toMultiply = this;
3950 if (strcmp(GetName(), "nominal") == 0 && fParent && fParent->get<PiecewiseInterpolation>())
3951 toMultiply = fParent.get();
3952
3953 f_stat = dynamic_cast<ParamHistFunc *>(toMultiply->Multiply(*h).get());
3954 if (!f_stat) {
3955 throw std::runtime_error("Failed creating stat shapeFactor");
3956 }
3957 }
3958
3959 auto phf = f_stat;
3960
3961 TString prefix = f->getStringAttribute("statPrefix");
3962 if (value && prefix == "") {
3963 // find the first parent that can hold components (RooAddPdf, RooRealSumPdf, RooAddition, RooWorkspace) ... use
3964 // that name for the stat factor
3965 auto _p = fParent;
3966 while (_p && !(_p->get()->InheritsFrom("RooRealSumPdf") || _p->get()->InheritsFrom("RooAddPdf") ||
3967 _p->get()->InheritsFrom("RooWorkspace") || _p->get()->InheritsFrom("RooAddition"))) {
3968 _p = _p->fParent;
3969 }
3970 prefix = TString::Format("stat_%s", (_p && _p->get<RooAbsReal>()) ? _p->get()->GetName() : f->GetName());
3971 }
3972 auto newVar = (value == 0) ? getObject<RooRealVar>("1")
3973 : acquire<RooRealVar>(Form("%s_bin%d", prefix.Data(), bin),
3974 Form("%s_bin%d", prefix.Data(), bin), 1);
3975#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
3976 RooArgList &pSet = phf->_paramSet;
3977#else
3978 RooArgList &pSet = const_cast<RooArgList &>(phf->paramList());
3979#endif
3980 auto var = dynamic_cast<RooRealVar *>(&pSet[bin - 1]);
3981
3982 if (newVar.get() != var) {
3983 // need to swap out var for newVar
3984 // replace ith element in list with new func, or inject into RooProduct
3985 RooArgList all;
3986 for (std::size_t i = 0; i < pSet.size(); i++) {
3987 if (int(i) != bin - 1) {
3988 all.add(*pSet.at(i));
3989 } else {
3990 all.add(*newVar);
3991 }
3992 }
3993 pSet.removeAll();
3994 pSet.add(all);
3995 }
3996
3997 xRooNode v((value == 0) ? *var : *newVar, *this);
3998 auto rrv = dynamic_cast<RooRealVar *>(v.get());
3999 if (strcmp(rrv->GetName(), "1") != 0) {
4000 TString origName = (f->getStringAttribute("origName")) ? f->getStringAttribute("origName") : GetName();
4001 rrv->setStringAttribute(Form("sumw2_%s", origName.Data()), TString::Format("%f", pow(value, 2)));
4002 auto bin_pars = f->dataHist().get(bin - 1);
4003 auto _binContent = f->dataHist().weight();
4004 if (f->getAttribute("density")) {
4005 _binContent *= f->dataHist().binVolume(*bin_pars);
4006 }
4007 rrv->setStringAttribute(Form("sumw_%s", origName.Data()), TString::Format("%f", _binContent));
4008 double sumw2 = 0;
4009 double sumw = 0;
4010 for (auto &[s, sv] : rrv->stringAttributes()) {
4011 if (s.find("sumw_") == 0) {
4012 sumw += TString(sv).Atof();
4013 } else if (s.find("sumw2_") == 0) {
4014 sumw2 += TString(sv).Atof();
4015 }
4016 }
4017 if (sumw2 && sumw2 != std::numeric_limits<double>::infinity()) {
4018 double tau = pow(sumw, 2) / sumw2;
4019 rrv->setError((tau < 1e-15) ? 1e15 : (/*rrv->getVal()*/ 1. / sqrt(tau))); // not sure why was rrv->getVal()?
4020 rrv->setConstant(false);
4021 // parameter must be constrained
4022 auto _constr = v.constraints();
4023 // std::cout << " setting constraint " << v.GetName() << " nomin=" << tau << std::endl;
4024 if (_constr.empty()) {
4025 rrv->setStringAttribute("boundConstraint", _constr.Add("poisson").get()->GetName());
4026 } else {
4027 auto _glob = _constr.at(0)->obs().at(0)->get<RooRealVar>();
4028 // TODO: Update any globs snapshots that are designed to match the nominal
4029 _glob->setStringAttribute("nominal", TString::Format("%f", tau));
4030 double _min = tau * (1. - 5. * sqrt(1. / tau));
4031 double _max = tau * (1. + 5. * sqrt(1. / tau));
4032 _glob->setRange(_min, _max);
4033 _glob->setVal(tau);
4034 _constr.at(0)->pp().at(0)->SetBinContent(0, tau);
4035 rrv->setStringAttribute("boundConstraint", _constr.at(0)->get()->GetName());
4036 }
4037 rrv->setRange(std::max((1. - 5. * sqrt(1. / tau)), 1e-15), 1. + 5. * sqrt(1. / tau));
4038 } else {
4039 // remove constraint
4040 if (auto _constr = v.constraints(); !_constr.empty()) {
4041 v.constraints().Remove(*_constr.at(0));
4042 }
4043 // set const if sumw2 is 0 (i.e. no error)
4044 rrv->setVal(1);
4045 rrv->setError(0);
4046 rrv->setConstant(sumw2 == 0);
4047 }
4048 }
4049
4050 return true;
4051 }
4052
4053 throw std::runtime_error(TString::Format("%s SetBinError failed", GetName()));
4054}
4055
4056std::shared_ptr<xRooNode> xRooNode::at(const std::string &name, bool browseResult) const
4057{
4058 auto res = find(name, browseResult);
4059 if (res == nullptr)
4060 throw std::out_of_range(name + " does not exist");
4061 return res;
4062}
4063
4065{
4066 if (auto _w = get<RooWorkspace>(); _w)
4067 return _w;
4068 if (auto a = get<RooAbsArg>(); a && GETWS(a)) {
4069 return GETWS(a);
4070 }
4071 if (fParent)
4072 return fParent->ws();
4073 return nullptr;
4074}
4075
4077{
4078
4079 xRooNode out(".constraints", nullptr, *this);
4080
4081 std::function<RooAbsPdf *(const xRooNode &n, RooAbsArg &par, std::set<RooAbsPdf *> ignore)> getConstraint;
4082 getConstraint = [&](const xRooNode &n, RooAbsArg &par, std::set<RooAbsPdf *> ignore) {
4083 if (auto _pdf = n.get<RooAbsPdf>()) {
4084 if (ignore.count(_pdf))
4085 return (RooAbsPdf *)nullptr;
4086 ignore.insert(_pdf);
4087 }
4088 auto o = n.get<RooProdPdf>();
4089 if (!o) {
4090 if (n.get<RooSimultaneous>()) {
4091 // check all channels for a constraint if is simultaneous
4092 for (auto &c : n.bins()) {
4093 if (auto oo = getConstraint(*c, par, ignore); oo) {
4094 return oo;
4095 }
4096 }
4097 return (RooAbsPdf *)nullptr;
4098 } else if (n.get<RooAbsPdf>() && n.fParent && n.fParent->get<RooWorkspace>()) {
4099 // reached top-level pdf, which wasn't a simultaneous, so stop here
4100 return (RooAbsPdf *)nullptr;
4101 } else if (auto _ws = n.get<RooWorkspace>(); _ws) {
4102 // reached a workspace, check for any pdf depending on parameter that isnt the ignore
4103 for (auto p : _ws->allPdfs()) {
4104 if (ignore.count(static_cast<RooAbsPdf *>(p)))
4105 continue;
4106 if (p->dependsOn(par)) {
4107 out.emplace_back(std::make_shared<xRooNode>(par.GetName(), *p, *this));
4108 }
4109 }
4110 }
4111 if (!n.fParent)
4112 return (RooAbsPdf *)nullptr;
4113 return getConstraint(*n.fParent, par, ignore);
4114 }
4115 for (auto p : o->pdfList()) {
4116 if (ignore.count(static_cast<RooAbsPdf *>(p)))
4117 continue;
4118 if (p->dependsOn(par)) {
4119 out.emplace_back(std::make_shared<xRooNode>(par.GetName(), *p, *this));
4120 }
4121 }
4122 return (RooAbsPdf *)nullptr;
4123 };
4124
4125 for (auto &p : vars()) {
4126 auto v = dynamic_cast<RooAbsReal *>(p->get());
4127 if (!v)
4128 continue;
4129 if (v->getAttribute("Constant") && v != get<RooAbsReal>())
4130 continue; // skip constants unless we are getting the constraints of a parameter itself
4131 if (v->getAttribute("obs"))
4132 continue; // skip observables ... constraints constrain pars not obs
4133 getConstraint(*this, *v, {get<RooAbsPdf>()});
4134 /*if (auto c = ; c) {
4135 out.emplace_back(std::make_shared<Node2>(p->GetName(), *c, *this));
4136 }*/
4137 }
4138
4139 // finish by removing any constraint that contains another constraint for the same par
4140 // and consolidate common pars
4141 auto it = out.std::vector<std::shared_ptr<xRooNode>>::begin();
4142 while (it != out.std::vector<std::shared_ptr<xRooNode>>::end()) {
4143 bool removeIt = false;
4144 for (auto &c : out) {
4145 if (c.get() == it->get())
4146 continue;
4147 if ((*it)->get<RooAbsArg>()->dependsOn(*c->get<RooAbsArg>())) {
4148 removeIt = true;
4149 std::set<std::string> parNames;
4150 std::string _cName = c->GetName();
4151 do {
4152 parNames.insert(_cName.substr(0, _cName.find(';')));
4153 _cName = _cName.substr(_cName.find(';') + 1);
4154 } while (_cName.find(';') != std::string::npos);
4155 parNames.insert(_cName);
4156 _cName = it->get()->GetName();
4157 do {
4158 parNames.insert(_cName.substr(0, _cName.find(';')));
4159 _cName = _cName.substr(_cName.find(';') + 1);
4160 } while (_cName.find(';') != std::string::npos);
4161 parNames.insert(_cName);
4162 _cName = "";
4163 for (auto &x : parNames) {
4164 if (!_cName.empty())
4165 _cName += ";";
4166 _cName += x;
4167 }
4168 c->TNamed::SetName(_cName.c_str());
4169 break;
4170 }
4171 }
4172 if (removeIt) {
4173 it = out.erase(it);
4174 } else {
4175 ++it;
4176 }
4177 }
4178
4179 // if getting constraints of a fundamental then use the constraint names instead of the par name (because would be
4180 // all same otherwise)
4181 if (get<RooAbsArg>() && get<RooAbsArg>()->isFundamental()) {
4182 for (auto &o : out) {
4183 o->TNamed::SetName(o->get()->GetName());
4184 }
4185 }
4186
4187 return out;
4188}
4189
4190std::shared_ptr<TObject> xRooNode::convertForAcquisition(xRooNode &acquirer, const char *opt) const
4191{
4192
4193 TString sOpt(opt);
4194 sOpt.ToLower();
4195 TString sName(GetName());
4196 if (sOpt == "func")
4197 sName = TString("factory:") + sName;
4198
4199 // if arg is a histogram, will acquire it as a RooHistFunc unless no conversion
4200 // todo: could flag not to convert
4201 if (auto h = get<TH1>(); h) {
4202 TString sOpt2(h->GetOption());
4203 std::map<std::string, std::string> stringAttrs;
4204 while (sOpt2.Contains("=")) {
4205 auto pos = sOpt2.Index("=");
4206 auto start = sOpt2.Index(";") + 1;
4207 if (start > pos)
4208 start = 0;
4209 auto end = sOpt2.Index(";", pos);
4210 if (end == -1)
4211 end = sOpt2.Length();
4212 stringAttrs[sOpt2(start, pos - start)] = sOpt2(pos + 1, end - pos - 1);
4213 sOpt2 = TString(sOpt2(0, start)) + TString(sOpt2(end + 1, sOpt2.Length()));
4214 }
4215 TString newObjName = GetName();
4216 TString origName = GetName();
4217 if (origName.BeginsWith(';'))
4218 origName = origName(1, origName.Length());
4219 if (newObjName.BeginsWith(';')) {
4220 newObjName =
4221 newObjName(1, newObjName.Length()); // special case if starts with ';' then don't create a fancy name
4222 } else if (acquirer.get() && !acquirer.get<RooWorkspace>()) {
4223 newObjName = TString::Format(
4224 "%s_%s", (acquirer.mainChild().get()) ? acquirer.mainChild()->GetName() : acquirer->GetName(),
4225 newObjName.Data());
4226 }
4227 // can convert to a RooHistFunc, or RooParamHist if option contains 'shape'
4228 TString varName = h->GetXaxis()->GetName();
4229 std::string binningName = newObjName.Data();
4230 if (auto pos = varName.Index(';'); pos != -1) {
4231 binningName = varName(pos + 1, varName.Length());
4232 varName = varName(0, pos);
4233 }
4234
4235 if (varName == "xaxis" &&
4236 !acquirer.get<RooSimultaneous>()) { // default case, try to take axis var and binning from the acquirer
4237 if (auto ax = acquirer.GetXaxis(); ax) {
4238 varName = ax->GetParent()->GetName();
4239 // TODO: check the binning is consistent before using - at least will check nBins below
4240 binningName = ax->GetName();
4241 } else if (acquirer.obs().size() == 1)
4242 varName = acquirer.obs().at(0)->get()->GetName(); // TODO what if no obs but Xaxis var is defined?
4243 }
4244 auto x = acquirer.acquire<RooRealVar>(varName, h->GetXaxis()->GetTitle(), h->GetXaxis()->GetXmin(),
4245 h->GetXaxis()->GetXmax());
4246 if (x->getMin() > h->GetXaxis()->GetXmin())
4247 x->setMin(h->GetXaxis()->GetXmin());
4248 if (x->getMax() < h->GetXaxis()->GetXmax())
4249 x->setMax(h->GetXaxis()->GetXmax());
4250 if (!x->hasBinning(binningName.c_str())) {
4251 if (h->GetXaxis()->IsVariableBinSize()) {
4252 x->setBinning(RooBinning(h->GetNbinsX(), h->GetXaxis()->GetXbins()->GetArray()), binningName.c_str());
4253 } else {
4254 x->setBinning(
4255 RooUniformBinning(h->GetXaxis()->GetXmin(), h->GetXaxis()->GetXmax(), h->GetXaxis()->GetNbins()),
4256 binningName.c_str());
4257 }
4258 x->getBinning(binningName.c_str()).SetTitle(h->GetXaxis()->GetTitle());
4259 if (x->getBinningNames().size() == 2) {
4260 // this was the first binning, so copy it over to be the default binning too
4261 x->setBinning(x->getBinning(binningName.c_str()));
4262 }
4263 } else {
4264 // TODO check binning is compatible with histogram
4265 if (x->getBinning(binningName.c_str()).numBins() != h->GetNbinsX()) {
4266 throw std::runtime_error(
4267 TString::Format("binning mismatch for binning %s of %s", binningName.c_str(), x->GetName()));
4268 }
4269 }
4270
4271 std::shared_ptr<RooAbsArg> _f;
4272
4273 // if acquirer is a RooSimultaneous, will use histogram to define a channel
4274 if (acquirer.get<RooSimultaneous>()) {
4275 _f = acquirer.acquireNew<RooProdPdf>(newObjName, (strlen(h->GetTitle())) ? h->GetTitle() : h->GetName(),
4276 RooArgList());
4277 for (auto &[k, v] : stringAttrs) {
4278 _f->setStringAttribute(k.c_str(), v.c_str());
4279 }
4280 x->setAttribute("obs", true);
4281 } else if (sOpt2.Contains("shape")) {
4282 RooArgList list;
4283 for (int i = 0; i < x->getBinning(binningName.c_str()).numBins(); i++) {
4284 std::shared_ptr<RooAbsArg> arg;
4285 if (sOpt2.Contains("blankshape")) {
4286 arg = acquirer.acquire2<RooAbsArg, RooRealVar>("1", "1", 1);
4287 } else {
4288 if (!h) {
4289 arg = acquirer.acquireNew<RooRealVar>(TString::Format("%s_bin%d", newObjName.Data(), i + 1), "", 1);
4290 }
4291 if (h->GetMinimumStored() != -1111 || h->GetMaximumStored() != -1111) {
4292 arg = acquirer.acquireNew<RooRealVar>(TString::Format("%s_bin%d", newObjName.Data(), i + 1), "",
4293 h->GetBinContent(i + 1), h->GetMinimumStored(),
4294 h->GetMaximumStored());
4295 } else {
4296 arg = acquirer.acquireNew<RooRealVar>(TString::Format("%s_bin%d", newObjName.Data(), i + 1), "",
4297 h->GetBinContent(i + 1));
4298 }
4299 }
4300 list.add(*arg);
4301 }
4302 // paramhistfunc requires the binnings to be loaded as default at construction time
4303 // so load binning temporarily
4304 auto tmp = dynamic_cast<RooAbsBinning *>(x->getBinningPtr(nullptr)->Clone());
4305 x->setBinning(x->getBinning(binningName.c_str()));
4306 _f = acquirer.acquireNew<ParamHistFunc>(newObjName, h->GetTitle(), *x, list);
4307#if ROOT_VERSION_CODE < ROOT_VERSION(6, 27, 00)
4308 dynamic_cast<ParamHistFunc *>(_f.get())->_paramSet.setName("paramSet"); // so can see when print
4309#else
4310 const_cast<RooArgList &>(dynamic_cast<ParamHistFunc *>(_f.get())->paramList())
4311 .setName("paramSet"); // so can see when print
4312#endif
4313 x->setBinning(*tmp); // restore binning
4314 delete tmp;
4315 for (auto &[k, v] : stringAttrs) {
4316 _f->setStringAttribute(k.c_str(), v.c_str());
4317 }
4318 } else {
4319 auto dh = acquirer.acquireNew<RooDataHist>(Form("hist_%s", newObjName.Data()), h->GetTitle(), *x,
4320 binningName.c_str() /* binning name*/);
4321 if (!dh) {
4322 throw std::runtime_error("Couldn't make data hist");
4323 }
4324 auto f = acquirer.acquireNew<RooHistFunc>(newObjName, h->GetTitle(), *x, *dh,
4325 0 /*interpolation order between bins*/);
4326 f->forceNumInt();
4327 f->setAttribute("autodensity"); // where it gets inserted will determine if this is a density or not
4328 _f = f;
4329
4330 for (auto &[k, v] : stringAttrs) {
4331 _f->setStringAttribute(k.c_str(), v.c_str());
4332 }
4333
4334 // need to do these settings here because used in the assignment step
4335 _f->setStringAttribute("xvar", x->GetName());
4336 _f->setStringAttribute("binning", binningName.c_str());
4337 if (strcmp(_f->GetName(), origName.Data()) && !_f->getStringAttribute("alias"))
4338 _f->setStringAttribute("alias", origName);
4339
4340 // copy values over using the assignment operator - may convert to a RooProduct if there are stat uncerts
4341 xRooNode tmp(h->GetName(), _f, acquirer);
4342 tmp = *h;
4343 _f = std::dynamic_pointer_cast<RooAbsArg>(tmp.fComp); // in case got upgrade to a RooProduct
4344 }
4345
4346 _f->setStringAttribute("xvar", x->GetName());
4347 _f->setStringAttribute("binning", binningName.c_str());
4348 // style(h); // will transfer styling to object if necessary - not doing because this method used with plane hists
4349 // frequently
4350 if (strcmp(_f->GetName(), origName.Data()) && !_f->getStringAttribute("alias"))
4351 _f->setStringAttribute("alias", origName);
4352
4353 fComp = _f;
4354 return _f;
4355 } else if (!get() && sName.BeginsWith("factory:") && acquirer.ws()) {
4356 TString s(sName);
4357 s = TString(s(8, s.Length()));
4358 fComp.reset(acquirer.ws()->factory(s), [](TObject *) {});
4359 return fComp;
4360 }