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