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