Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooJSONFactoryWSTool.cxx
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * Carsten D. Burgard, DESY/ATLAS, Dec 2021
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#include <RooFitHS3/JSONIO.h>
15
16#include <RooConstVar.h>
17#include <RooRealVar.h>
18#include <RooBinning.h>
19#include <RooAbsCategory.h>
20#include <RooRealProxy.h>
21#include <RooListProxy.h>
22#include <RooAbsProxy.h>
23#include <RooCategory.h>
24#include <RooDataSet.h>
25#include <RooDataHist.h>
26#include <RooSimultaneous.h>
27#include <RooFormulaVar.h>
28#include <RooFit/ModelConfig.h>
29#include <RooFitImplHelpers.h>
30#include <RooAbsCollection.h>
31
32#include "JSONIOUtils.h"
33#include "Domains.h"
34
35#include "RooFitImplHelpers.h"
36
37#include <TROOT.h>
38
39#include <algorithm>
40#include <fstream>
41#include <iostream>
42#include <sstream>
43#include <stack>
44#include <stdexcept>
45
46/** \class RooJSONFactoryWSTool
47\ingroup roofit_dev_docs_hs3
48
49When using \ref Roofitmain, statistical models can be conveniently handled and
50stored as a RooWorkspace. However, for the sake of interoperability
51with other statistical frameworks, and also ease of manipulation, it
52may be useful to store statistical models in text form.
53
54The RooJSONFactoryWSTool is a helper class to achieve exactly this,
55exporting to and importing from JSON.
56
57In order to import a workspace from a JSON file, you can do
58
59~~~ {.py}
60ws = ROOT.RooWorkspace("ws")
61tool = ROOT.RooJSONFactoryWSTool(ws)
62tool.importJSON("myjson.json")
63~~~
64
65Similarly, in order to export a workspace to a JSON file, you can do
66
67~~~ {.py}
68tool = ROOT.RooJSONFactoryWSTool(ws)
69tool.exportJSON("myjson.json")
70~~~
71
72Analogously, in C++, you can do
73
74~~~ {.cxx}
75#include "RooFitHS3/RooJSONFactoryWSTool.h"
76// ...
77RooWorkspace ws("ws");
78RooJSONFactoryWSTool tool(ws);
79tool.importJSON("myjson.json");
80~~~
81
82and
83
84~~~ {.cxx}
85#include "RooFitHS3/RooJSONFactoryWSTool.h"
86// ...
87RooJSONFactoryWSTool tool(ws);
88tool.exportJSON("myjson.json");
89~~~
90
91For more details, consult the tutorial <a href="rf515__hfJSON_8py.html">rf515_hfJSON</a>.
92
93The RooJSONFactoryWSTool only knows about a limited set of classes for
94import and export. If import or export of a class you're interested in
95fails, you might need to add your own importer or exporter. Please
96consult the relevant section in the \ref roofit_dev_docs to learn how to do that (\ref roofit_dev_docs_hs3).
97
98You can always get a list of all the available importers and exporters by calling the following functions:
99~~~ {.py}
100ROOT.RooFit.JSONIO.printImporters()
101ROOT.RooFit.JSONIO.printExporters()
102ROOT.RooFit.JSONIO.printFactoryExpressions()
103ROOT.RooFit.JSONIO.printExportKeys()
104~~~
105
106Alternatively, you can generate a LaTeX version of the available importers and exporters by calling
107~~~ {.py}
108tool = ROOT.RooJSONFactoryWSTool(ws)
109tool.writedoc("hs3.tex")
110~~~
111*/
112
113constexpr auto hs3VersionTag = "0.2";
114
117
118namespace {
119
120std::vector<std::string> valsToStringVec(JSONNode const &node)
121{
122 std::vector<std::string> out;
123 out.reserve(node.num_children());
124 for (JSONNode const &elem : node.children()) {
125 out.push_back(elem.val());
126 }
127 return out;
128}
129
130// True if the number of components in `data` matches the number of categories in `pdf`.
131bool matches(const RooJSONFactoryWSTool::CombinedData &data, const RooSimultaneous *pdf)
132{
133 return data.components.size() == pdf->indexCat().size();
134}
135
136// True if the entire string parses as a number (integer or floating-point).
137bool isNumber(const std::string &str)
138{
139 // Parse with the same mechanism as toDouble() and require that the whole string is consumed, so that isNumber(s) is
140 // true exactly when toDouble(s) can turn the entire string into a value. (std::from_chars for floating-point types
141 // is not portably available on all platforms ROOT supports, so we rely on the stream extraction instead.)
142 std::istringstream stream(str);
143 double value = 0.0;
144 return (stream >> value) && stream.eof();
145}
146
147// Configure `v` (value, error, binning, constness) from the JSON node `p`.
149{
150 if (!p.has_child("name")) {
151 RooJSONFactoryWSTool::error("cannot instantiate variable without \"name\"!");
152 }
153 if (auto n = p.find("value"))
154 v.setVal(n->val_double());
155 domains.writeVariable(v);
156 if (auto n = p.find("nbins"))
157 v.setBins(n->val_int());
158 if (auto n = p.find("relErr"))
159 v.setError(v.getVal() * n->val_double());
160 if (auto n = p.find("err"))
161 v.setError(n->val_double());
162 if (auto n = p.find("const")) {
163 v.setConstant(n->val_bool());
164 } else {
165 v.setConstant(false);
166 }
167}
168
170{
171 auto paramPointsNode = rootNode.find("parameter_points");
172 if (!paramPointsNode)
173 return nullptr;
174 auto out = RooJSONFactoryWSTool::findNamedChild(*paramPointsNode, "default_values");
175 if (out == nullptr)
176 return nullptr;
177 return &((*out)["parameters"]);
178}
179
180std::string genPrefix(const JSONNode &p, bool trailing_underscore)
181{
182 std::string prefix;
183 if (!p.is_map())
184 return prefix;
185 if (auto node = p.find("namespaces")) {
186 for (const auto &ns : node->children()) {
187 if (!prefix.empty())
188 prefix += "_";
189 prefix += ns.val();
190 }
191 }
192 if (trailing_underscore && !prefix.empty())
193 prefix += "_";
194 return prefix;
195}
196
197// helpers for serializing / deserializing binned datasets
198void genIndicesHelper(std::vector<std::vector<int>> &combinations, std::vector<int> &curr_comb,
199 const std::vector<int> &vars_numbins, size_t curridx)
200{
201 if (curridx == vars_numbins.size()) {
202 // we have filled a combination. Copy it.
203 combinations.emplace_back(curr_comb);
204 } else {
205 for (int i = 0; i < vars_numbins[curridx]; ++i) {
206 curr_comb[curridx] = i;
208 }
209 }
210}
211
212// Import string attributes ("dict") and boolean tags ("tags") from `node` onto `arg`.
213void importAttributes(RooAbsArg *arg, JSONNode const &node)
214{
215 if (auto seq = node.find("dict")) {
216 for (const auto &attr : seq->children()) {
217 arg->setStringAttribute(attr.key().c_str(), attr.val().c_str());
218 }
219 }
220 if (auto seq = node.find("tags")) {
221 for (const auto &attr : seq->children()) {
222 arg->setAttribute(attr.val().c_str());
223 }
224 }
225}
226
227void addIfPresent(RooArgSet &out, RooArgSet const *args)
228{
229 if (args) {
230 out.add(*args, true);
231 }
232}
233
236{
237 for (TObject *obj : workspace.allGenericObjects()) {
238 auto const *mc = dynamic_cast<RooFit::ModelConfig const *>(obj);
239 if (!mc) {
240 continue;
241 }
242
243 addIfPresent(candidates, mc->GetParametersOfInterest());
244 addIfPresent(candidates, mc->GetNuisanceParameters());
245
246 addIfPresent(excluded, mc->GetObservables());
247 addIfPresent(excluded, mc->GetGlobalObservables());
248 addIfPresent(excluded, mc->GetConditionalObservables());
249 }
250}
251
252void collectParameterStepWidthCandidatesFromPdfs(std::vector<RooAbsPdf *> const &pdfs,
253 std::vector<RooAbsData *> const &data, RooArgSet &candidates,
255{
256 for (RooAbsPdf const *pdf : pdfs) {
257 RooArgSet observables;
258 for (RooAbsData const *dataset : data) {
259 std::unique_ptr<RooArgSet> pdfObs{pdf->getObservables(*dataset->get())};
260 observables.add(*pdfObs, true);
261 }
262
263 if (observables.empty()) {
264 continue;
265 }
266
267 RooArgSet params;
268 pdf->getParameters(&observables, params);
269 candidates.add(params, true);
270 excluded.add(observables, true);
271 }
272}
273
274void exportParameterStepWidths(RooWorkspace const &workspace, std::vector<RooAbsPdf *> const &pdfs,
275 std::vector<RooAbsData *> const &data, JSONNode &rootnode)
276{
279
282
283 candidates.sort();
284
286 for (RooAbsArg *arg : candidates) {
287 if (excluded.find(*arg)) {
288 continue;
289 }
290
291 auto *var = dynamic_cast<RooRealVar *>(arg);
292 if (!var || !var->hasError()) {
293 continue;
294 }
295
297 parameterStepWidthsNode = &rootnode["misc"]["minimization"]["parameter_stepwidths"].set_seq();
298 }
299
301 stepWidthNode["step_width"] << var->getError();
302 }
303}
304
305void importParameterStepWidths(RooWorkspace &workspace, JSONNode const &rootnode)
306{
307 auto const *parameterStepWidthsNode = rootnode.find("misc", "minimization", "parameter_stepwidths");
309 return;
310 }
311 if (!parameterStepWidthsNode->is_seq()) {
312 RooJSONFactoryWSTool::warning("RooFitHS3: misc.minimization.parameter_stepwidths is not a sequence, skipping.");
313 return;
314 }
315
316 for (JSONNode const &stepWidthNode : parameterStepWidthsNode->children()) {
317 if (!stepWidthNode.is_map() || !stepWidthNode.has_child("name") || !stepWidthNode.has_child("step_width")) {
318 RooJSONFactoryWSTool::warning("RooFitHS3: skipping malformed parameter_stepwidths entry.");
319 continue;
320 }
321
322 const std::string name = RooJSONFactoryWSTool::name(stepWidthNode);
323 RooAbsArg *arg = workspace.arg(name);
324 auto *var = dynamic_cast<RooRealVar *>(arg);
325 if (!var) {
327 "RooFitHS3: skipping parameter_stepwidths entry for unknown or non-real variable '" + name + "'.");
328 continue;
329 }
330
331 var->setError(stepWidthNode.find("step_width")->val_double());
332 }
333}
334
335// RooWSFactoryTool expression handling
336std::string generate(const RooFit::JSONIO::ImportExpression &ex, const JSONNode &p, RooJSONFactoryWSTool *tool)
337{
338 std::stringstream expression;
339 std::string classname(ex.tclass->GetName());
340 size_t colon = classname.find_last_of(':');
341 expression << (colon < classname.size() ? classname.substr(colon + 1) : classname);
342 bool first = true;
343 const auto &name = RooJSONFactoryWSTool::name(p);
344 for (auto k : ex.arguments) {
345 expression << (first ? "::" + name + "(" : ",");
346 first = false;
347 if (k == "true" || k == "false") {
348 expression << (k == "true" ? "1" : "0");
349 } else if (!p.has_child(k)) {
350 std::stringstream errMsg;
351 errMsg << "node '" << name << "' is missing key '" << k << "'";
353 } else if (p[k].is_seq()) {
354 bool firstInner = true;
355 expression << "{";
356 for (RooAbsArg *arg : tool->requestArgList<RooAbsReal>(p, k)) {
357 expression << (firstInner ? "" : ",") << arg->GetName();
358 firstInner = false;
359 }
360 expression << "}";
361 } else {
362 tool->requestArg<RooAbsReal>(p, p[k].key());
363 expression << p[k].val();
364 }
365 }
366 expression << ")";
367 return expression.str();
368}
369
370// Generate all combinations of bin indices for the RooRealVars in `vars`.
371std::vector<std::vector<int>> generateBinIndices(const RooArgSet &vars)
372{
373 std::vector<std::vector<int>> combinations;
374 std::vector<int> vars_numbins;
375 vars_numbins.reserve(vars.size());
376 for (const auto *absv : static_range_cast<RooRealVar *>(vars)) {
377 vars_numbins.push_back(absv->getBins());
378 }
379 std::vector<int> curr_comb(vars.size());
381 return combinations;
382}
383
384template <typename... Keys_t>
385JSONNode const *findRooFitInternal(JSONNode const &node, Keys_t const &...keys)
386{
387 return node.find("misc", "ROOT_internal", keys...);
388}
389
390// True if `arg` is a RooConstVar whose name is a plain number (i.e. a literal constant).
391bool isLiteralConstVar(RooAbsArg const &arg)
392{
393 bool isRooConstVar = dynamic_cast<RooConstVar const *>(&arg);
394 return isRooConstVar && isNumber(arg.GetName());
395}
396
397// Export the string attributes and tags of `arg` into the ROOT-internal attributes node.
398void exportAttributes(const RooAbsArg *arg, JSONNode &rootnode)
399{
400 // If this RooConst is a literal number, we don't need to export the attributes.
401 if (isLiteralConstVar(*arg)) {
402 return;
403 }
404
405 JSONNode *node = nullptr;
406
407 auto initializeNode = [&]() {
408 if (node)
409 return;
410
411 node = &RooJSONFactoryWSTool::getRooFitInternal(rootnode, "attributes").set_map()[arg->GetName()].set_map();
412 };
413
414 if (dynamic_cast<RooConstVar const *>(arg)) {
415 return;
416 }
417
418 // export all string attributes of an object
419 if (!arg->stringAttributes().empty()) {
420 for (const auto &it : arg->stringAttributes()) {
421 // Skip some RooFit internals
422 if (it.first == "factory_tag" || it.first == "PROD_TERM_TYPE")
423 continue;
425 (*node)["dict"].set_map()[it.first] << it.second;
426 }
427 }
428 if (!arg->attributes().empty()) {
429 for (auto const &attr : arg->attributes()) {
430 // Skip some RooFit internals
431 if (attr == "SnapShot_ExtRefClone" || attr == "RooRealConstant_Factory_Object")
432 continue;
434 (*node)["tags"].set_seq().append_child() << attr;
435 }
436 }
437}
438
439// Collect the observables named in the "axes" field of `node` from the workspace into `out`.
440void getObservables(RooWorkspace const &ws, const JSONNode &node, RooAbsCollection &out)
441{
442 for (const auto &p : node["axes"].children()) {
443 std::string name(RooJSONFactoryWSTool::name(p));
444 if (ws.var(name)) {
445 out.add(*ws.var(name));
446 } else {
447 std::stringstream errMsg;
448 errMsg << "The observable \"" << name << "\" could not be found in the workspace!";
450 }
451 }
452}
453
454// Create a RooAbsData (binned or unbinned) from the JSON node `p`.
455std::unique_ptr<RooAbsData> loadData(const JSONNode &p, RooWorkspace &workspace)
456{
457 std::string name(RooJSONFactoryWSTool::name(p));
458
460
461 std::string const &type = p["type"].val();
462 if (type == "binned") {
463 // binned
465 } else if (type == "unbinned") {
466 // unbinned
467 RooArgList varlist;
468 getObservables(workspace, p, varlist);
469 RooArgSet vars(varlist);
470 auto data = std::make_unique<RooDataSet>(name, name, vars, RooFit::WeightVar());
471 auto &coords = p["entries"];
472 if (!coords.is_seq()) {
473 RooJSONFactoryWSTool::error("key 'entries' is not a list!");
474 }
475 std::vector<double> weightVals;
476 if (p.has_child("weights")) {
477 auto &weights = p["weights"];
478 if (coords.num_children() != weights.num_children()) {
479 RooJSONFactoryWSTool::error("inconsistent number of entries and weights!");
480 }
481 for (auto const &weight : weights.children()) {
482 weightVals.push_back(weight.val_double());
483 }
484 }
485 std::size_t i = 0;
486 for (auto const &point : coords.children()) {
487 if (!point.is_seq()) {
488 std::stringstream errMsg;
489 errMsg << "coordinate point '" << i << "' is not a list!";
491 }
492 if (point.num_children() != varlist.size()) {
493 RooJSONFactoryWSTool::error("inconsistent number of entries and observables!");
494 }
495 std::size_t j = 0;
496 for (auto const &pointj : point.children()) {
497 auto *v = static_cast<RooRealVar *>(varlist.at(j));
498 v->setVal(pointj.val_double());
499 ++j;
500 }
501 if (weightVals.size() > 0) {
502 data->add(vars, weightVals[i]);
503 } else {
504 data->add(vars, 1.);
505 }
506 ++i;
507 }
508 return data;
509 }
510
511 std::stringstream ss;
512 ss << "RooJSONFactoryWSTool() failed to create dataset " << name << std::endl;
514 return nullptr;
515}
516
517// Import an analysis (likelihood + domains) as one or more ModelConfig objects into the workspace.
518void importAnalysis(const JSONNode &rootnode, const JSONNode &analysisNode, const JSONNode &likelihoodsNode,
519 const JSONNode &domainsNode, RooWorkspace &workspace,
520 const std::vector<std::unique_ptr<RooAbsData>> &datasets)
521{
522 // if this is a toplevel pdf, also create a modelConfig for it
524 JSONNode const *mcAuxNode = findRooFitInternal(rootnode, "ModelConfigs", analysisName);
525
526 JSONNode const *mcNameNode = mcAuxNode ? mcAuxNode->find("mcName") : nullptr;
527 std::string mcname = mcNameNode ? mcNameNode->val() : analysisName;
528 if (workspace.obj(mcname))
529 return;
530
531 workspace.import(RooFit::ModelConfig{mcname.c_str(), mcname.c_str()});
532 auto *mc = static_cast<RooFit::ModelConfig *>(workspace.obj(mcname));
533 mc->SetWS(workspace);
534
536 if (!nllNode) {
537 throw std::runtime_error("likelihood node not found!");
538 }
539 if (!nllNode->has_child("distributions")) {
540 throw std::runtime_error("likelihood node has no distributions attached!");
541 }
542 if (!nllNode->has_child("data")) {
543 throw std::runtime_error("likelihood node has no data attached!");
544 }
545 std::vector<std::string> nllDistNames = valsToStringVec((*nllNode)["distributions"]);
547 for (auto &nameNode : (*nllNode)["aux_distributions"].children()) {
548 if (RooAbsArg *extConstraint = workspace.arg(nameNode.val())) {
550 }
551 }
552 RooArgSet observables;
553 for (auto &nameNode : (*nllNode)["data"].children()) {
554 bool found = false;
555 for (const auto &d : datasets) {
556 if (d->GetName() == nameNode.val()) {
557 found = true;
558 observables.add(*d->get(), true);
559 }
560 }
561 if (nameNode.val() != "0" && !found)
562 throw std::runtime_error("dataset '" + nameNode.val() + "' cannot be found!");
563 }
564
565 JSONNode const *pdfNameNode = mcAuxNode ? mcAuxNode->find("pdfName") : nullptr;
566 std::string const pdfName = pdfNameNode ? pdfNameNode->val() : "simPdf";
567
568 RooAbsPdf *pdf = workspace.pdf(pdfName);
569
570 if (!pdf) {
571 // if there is no simultaneous pdf, we can check whether there is only one pdf in the list
572 if (nllDistNames.size() == 1) {
573 // if so, we can use that one to populate the ModelConfig
574 pdf = workspace.pdf(nllDistNames[0]);
575 } else {
576 // otherwise, we have no choice but to build a simPdf by hand
577 std::string simPdfName = analysisName + "_simPdf";
578 std::string indexCatName = analysisName + "_categoryIndex";
579 RooCategory indexCat{indexCatName.c_str(), indexCatName.c_str()};
580 std::map<std::string, RooAbsPdf *> pdfMap;
581 for (std::size_t i = 0; i < nllDistNames.size(); ++i) {
582 indexCat.defineType(nllDistNames[i], i);
583 pdfMap[nllDistNames[i]] = workspace.pdf(nllDistNames[i]);
584 }
585 RooSimultaneous simPdf{simPdfName.c_str(), simPdfName.c_str(), pdfMap, indexCat};
587 pdf = workspace.pdf(simPdfName);
588 }
589 }
590
591 mc->SetPdf(*pdf);
592
593 if (!extConstraints.empty())
594 mc->SetExternalConstraints(extConstraints);
595
596 auto readArgSet = [&](std::string const &name) {
597 RooArgSet out;
598 for (auto const &child : analysisNode[name].children()) {
599 out.add(*workspace.arg(child.val()));
600 }
601 return out;
602 };
603
604 mc->SetParametersOfInterest(readArgSet("parameters_of_interest"));
605 mc->SetObservables(observables);
606 RooArgSet pars;
607 pdf->getParameters(&observables, pars);
608
609 // Figure out the set parameters that appear in the main measurement:
610 // getAllConstraints() has the side effect to remove all parameters from
611 // "mainPars" that are not part of any pdf over observables.
612 RooArgSet mainPars{pars};
613 pdf->getAllConstraints(observables, mainPars, /*stripDisconnected*/ true);
614
616 for (auto &domain : analysisNode["domains"].children()) {
618 if (!thisDomain || !thisDomain->has_child("axes"))
619 continue;
620 for (auto &var : (*thisDomain)["axes"].children()) {
621 auto *wsvar = workspace.var(RooJSONFactoryWSTool::name(var));
622 if (wsvar)
623 domainPars.add(*wsvar);
624 }
625 }
626
628 RooArgSet globs;
629 for (const auto &p : pars) {
630 if (mc->GetParametersOfInterest()->find(*p))
631 continue;
632 if (p->isConstant() && !mainPars.find(*p) && domainPars.find(*p)) {
633 globs.add(*p);
634 } else if (domainPars.find(*p)) {
635 nps.add(*p);
636 }
637 }
638
639 mc->SetGlobalObservables(globs);
640 mc->SetNuisanceParameters(nps);
641
642 if (mcAuxNode) {
643 if (auto found = mcAuxNode->find("combined_data_name")) {
644 pdf->setStringAttribute("combined_data_name", found->val().c_str());
645 }
646 }
647
648 if (analysisNode.has_child("init") && workspace.getSnapshot(analysisNode["init"].val().c_str())) {
649 mc->SetSnapshot(*workspace.getSnapshot(analysisNode["init"].val().c_str()));
650 }
651}
652
653void combinePdfs(const JSONNode &rootnode, RooWorkspace &ws)
654{
655 auto *combinedPdfInfoNode = findRooFitInternal(rootnode, "combined_distributions");
656
657 // If there is no info on combining pdfs
658 if (combinedPdfInfoNode == nullptr) {
659 return;
660 }
661
662 for (auto &info : combinedPdfInfoNode->children()) {
663
664 // parse the information
665 std::string combinedName = info.key();
666 std::string indexCatName = info["index_cat"].val();
667 std::vector<std::string> labels = valsToStringVec(info["labels"]);
668 std::vector<int> indices;
669 std::vector<std::string> pdfNames = valsToStringVec(info["distributions"]);
670 for (auto &n : info["indices"].children()) {
671 indices.push_back(n.val_int());
672 }
673
674 RooCategory indexCat{indexCatName.c_str(), indexCatName.c_str()};
675 std::map<std::string, RooAbsPdf *> pdfMap;
676
677 for (std::size_t iChannel = 0; iChannel < labels.size(); ++iChannel) {
678 indexCat.defineType(labels[iChannel], indices[iChannel]);
679 pdfMap[labels[iChannel]] = ws.pdf(pdfNames[iChannel]);
680 }
681
682 RooSimultaneous simPdf{combinedName.c_str(), combinedName.c_str(), pdfMap, indexCat};
684 }
685}
686
687void combineDatasets(const JSONNode &rootnode, std::vector<std::unique_ptr<RooAbsData>> &datasets)
688{
689 auto *combinedDataInfoNode = findRooFitInternal(rootnode, "combined_datasets");
690
691 // If there is no info on combining datasets
692 if (combinedDataInfoNode == nullptr) {
693 return;
694 }
695
696 for (auto &info : combinedDataInfoNode->children()) {
697
698 // parse the information
699 std::string combinedName = info.key();
700 std::string indexCatName = info["index_cat"].val();
701 std::vector<std::string> labels = valsToStringVec(info["labels"]);
702 std::vector<int> indices;
703 for (auto &n : info["indices"].children()) {
704 indices.push_back(n.val_int());
705 }
706 if (indices.size() != labels.size()) {
707 RooJSONFactoryWSTool::error("mismatch in number of indices and labels!");
708 }
709
710 // Create the combined dataset for RooFit
711 std::map<std::string, std::unique_ptr<RooAbsData>> dsMap;
712 RooCategory indexCat{indexCatName.c_str(), indexCatName.c_str()};
713 RooArgSet allVars{indexCat};
714 for (std::size_t iChannel = 0; iChannel < labels.size(); ++iChannel) {
715 auto componentName = combinedName + "_" + labels[iChannel];
716 // We move the found channel data out of the "datasets" vector, such that
717 // the data components don't get imported anymore.
718 std::unique_ptr<RooAbsData> &component = *std::find_if(
719 datasets.begin(), datasets.end(), [&](auto &d) { return d && d->GetName() == componentName; });
720 if (!component)
721 RooJSONFactoryWSTool::error("unable to obtain component matching component name '" + componentName + "'");
722 allVars.add(*component->get(), true);
723 dsMap.insert({labels[iChannel], std::move(component)});
724 indexCat.defineType(labels[iChannel], indices[iChannel]);
725 }
726
727 auto combined = std::make_unique<RooDataSet>(combinedName, combinedName, allVars, RooFit::Import(dsMap),
728 RooFit::Index(indexCat));
729 datasets.emplace_back(std::move(combined));
730 }
731}
732
733template <class T>
734void sortByName(T &coll)
735{
736 std::sort(coll.begin(), coll.end(), [](auto &l, auto &r) { return strcmp(l->GetName(), r->GetName()) < 0; });
737}
738
739} // namespace
740
742
744
746{
747 const size_t old_children = node.num_children();
748 node.set_seq();
749 size_t n = 0;
750 for (RooAbsArg const *arg : coll) {
751 if (n >= nMax)
752 break;
753 if (isLiteralConstVar(*arg)) {
754 node.append_child() << static_cast<RooConstVar const *>(arg)->getVal();
755 } else {
756 node.append_child() << arg->GetName();
757 }
758 ++n;
759 }
760 if (node.num_children() != old_children + coll.size()) {
761 error("unable to stream collection " + std::string(coll.GetName()) + " to " + node.key());
762 }
763}
764
766{
768 return node.set_map()[name].set_map();
769 }
770 JSONNode &child = node.set_seq().append_child().set_map();
771 child["name"] << name;
772 return child;
773}
774
775JSONNode const *RooJSONFactoryWSTool::findNamedChild(JSONNode const &node, std::string const &name)
776{
778 if (!node.is_map())
779 return nullptr;
780 return node.find(name);
781 }
782 if (!node.is_seq())
783 return nullptr;
784 for (JSONNode const &child : node.children()) {
785 if (child["name"].val() == name)
786 return &child;
787 }
788
789 return nullptr;
790}
791
792/**
793 * @brief Check if a string is a valid name.
794 *
795 * A valid name should start with a letter or an underscore, followed by letters, digits, or underscores.
796 * Only characters from the ASCII character set are allowed.
797 *
798 * @param str The string to be checked.
799 * @return bool Returns true if the string is a valid name; otherwise, returns false.
800 */
801bool RooJSONFactoryWSTool::isValidName(const std::string &str)
802{
803 // Check if the string is empty or starts with a non-letter/non-underscore character
804 if (str.empty() || !(std::isalpha(str[0]) || str[0] == '_')) {
805 return false;
806 }
807
808 // Check the remaining characters in the string
809 for (char c : str) {
810 // Allow letters, digits, and underscore
811 if (!(std::isalnum(c) || c == '_')) {
812 return false;
813 }
814 }
815
816 // If all characters are valid, the string is a valid name
817 return true;
818}
819
821{
823 std::stringstream ss;
824 ss << "RooJSONFactoryWSTool() name '" << name << "' is not valid!" << std::endl
825 << "Sanitize names by setting RooJSONFactoryWSTool::config().allowSanitizeNames = true." << std::endl;
826 if (RooJSONFactoryWSTool::config().allowExportInvalidNames && !forceError) {
828 return false;
829 } else {
831 }
832 }
833 return true;
834}
835
837{
838 return useListsInsteadOfDicts ? n["name"].val() : n.key();
839}
840
842{
843 return appendNamedChild(rootNode["parameter_points"], "default_values")["parameters"];
844}
845
846template <>
847RooRealVar *RooJSONFactoryWSTool::requestImpl<RooRealVar>(const std::string &objname)
848{
850 return retval;
851 if (const auto *vars = getVariablesNode(*_rootnodeInput)) {
852 if (const auto &node = vars->find(objname)) {
853 this->importVariable(*node);
855 return retval;
856 }
857 }
858 return nullptr;
859}
860
861template <>
862RooAbsPdf *RooJSONFactoryWSTool::requestImpl<RooAbsPdf>(const std::string &objname)
863{
865 return retval;
866 auto it = _distributionsByName.find(objname);
867 if (it != _distributionsByName.end()) {
868 this->importFunction(*it->second, true);
870 return retval;
871 }
872 return nullptr;
873}
874
875template <>
876RooAbsReal *RooJSONFactoryWSTool::requestImpl<RooAbsReal>(const std::string &objname)
877{
879 return retval;
880 if (isNumber(objname))
883 return pdf;
885 return var;
887 return retval;
888 auto it = _functionsByName.find(objname);
889 if (it != _functionsByName.end()) {
890 this->importFunction(*it->second, true);
892 return retval;
893 }
894 return nullptr;
895}
896
897// Export a single variable (RooRealVar or RooConstVar) `v` as a named child of `node`.
899{
900 auto *cv = dynamic_cast<const RooConstVar *>(v);
901 auto *rrv = dynamic_cast<const RooRealVar *>(v);
902 if (!cv && !rrv)
903 return;
904
905 // for RooConstVar, if name and value are the same, we don't need to do anything
906 if (cv && strcmp(cv->GetName(), TString::Format("%g", cv->getVal()).Data()) == 0) {
907 return;
908 }
909
910 JSONNode &var = appendNamedChild(node, v->GetName());
911
912 if (cv) {
913 var["value"] << cv->getVal();
914 var["const"] << true;
915 } else if (rrv) {
916 var["value"] << rrv->getVal();
917 if (storeConstant && (rrv->isConstant() || rrv->getMin() >= rrv->getMax())) {
918 var["const"] << true;
919 } else if (storeBins) {
920 var["min"] << rrv->getMin();
921 var["max"] << rrv->getMax();
922 }
923 if (rrv->getBins() != 100 && storeBins) {
924 var["nbins"] << rrv->getBins();
925 }
926 _domains->readVariable(*rrv);
927 }
928}
929
930// Export all variables in `allElems` as a sequence under `n`.
932{
933 // export a list of RooRealVar objects
934 n.set_seq();
935 for (RooAbsArg *arg : allElems) {
937 }
938}
939
941 const std::string &formula)
942{
943 std::string newname = std::string(original->GetName()) + suffix;
945 trafo_node["type"] << "generic";
946 trafo_node["expression"] << TString::Format(formula.c_str(), original->GetName()).Data();
947 this->setAttribute(newname, "roofit_skip"); // this function should not be imported back in
948 return newname;
949}
950
951// Export a single object `func` (pdf, function, variable or category) to the output JSON, recording its name in
952// `exportedObjectNames` to avoid exporting it twice.
953void RooJSONFactoryWSTool::exportObject(RooAbsArg const &func, std::set<std::string> &exportedObjectNames)
954{
955 std::string name = func.GetName();
956
957 // if this element was already exported, skip
959 return;
960
962
963 if (auto simPdf = dynamic_cast<RooSimultaneous const *>(&func)) {
964 // RooSimultaneous is not used in the HS3 standard, we only export the
965 // dependents and some ROOT internal information.
967
968 std::vector<std::string> channelNames;
969 for (auto const &item : simPdf->indexCat()) {
970 channelNames.push_back(item.first);
971 }
972
973 auto &infoNode = getRooFitInternal(*_rootnodeOutput, "combined_distributions").set_map();
974 auto &child = infoNode[simPdf->GetName()].set_map();
975 child["index_cat"] << simPdf->indexCat().GetName();
976 exportCategory(simPdf->indexCat(), child);
977 child["distributions"].set_seq();
978 for (auto const &item : simPdf->indexCat()) {
979 child["distributions"].append_child() << simPdf->getPdf(item.first.c_str())->GetName();
980 }
981
982 return;
983 } else if (dynamic_cast<RooAbsCategory const *>(&func)) {
984 // categories are created by the respective RooSimultaneous, so we're skipping the export here
985 return;
986 } else if (dynamic_cast<RooRealVar const *>(&func) || dynamic_cast<RooConstVar const *>(&func)) {
987 exportVariable(&func, *_varsNode, true, false);
988 return;
989 }
990
991 auto &collectionNode = (*_rootnodeOutput)[dynamic_cast<RooAbsPdf const *>(&func) ? "distributions" : "functions"];
992
993 auto const &exporters = RooFit::JSONIO::exporters();
994 auto const &exportKeys = RooFit::JSONIO::exportKeys();
995
996 TClass *cl = func.IsA();
997
999
1000 auto it = exporters.find(cl);
1001 if (it != exporters.end()) { // check if we have a specific exporter available
1002 for (auto &exp : it->second) {
1003 _serversToExport.clear();
1004 _serversToDelete.clear();
1005 if (!exp->exportObject(this, &func, elem)) {
1006 // The exporter might have messed with the content of the node
1007 // before failing. That's why we clear it and only reset the name.
1008 elem.clear();
1009 elem.set_map();
1011 elem["name"] << name;
1012 }
1013 continue;
1014 }
1015 if (exp->autoExportDependants()) {
1017 } else {
1019 }
1020 for (auto &s : _serversToDelete) {
1021 delete s;
1022 }
1023 return;
1024 }
1025 }
1026
1027 // generic export using the factory expressions
1028 const auto &dict = exportKeys.find(cl);
1029 if (dict == exportKeys.end()) {
1030 std::cerr << "unable to export class '" << cl->GetName() << "' - no export keys available!\n"
1031 << "there are several possible reasons for this:\n"
1032 << " 1. " << cl->GetName() << " is a custom class that you or some package you are using added.\n"
1033 << " 2. " << cl->GetName()
1034 << " is a ROOT class that nobody ever bothered to write a serialization definition for.\n"
1035 << " 3. something is wrong with your setup, e.g. you might have called "
1036 "RooFit::JSONIO::clearExportKeys() and/or never successfully read a file defining these "
1037 "keys with RooFit::JSONIO::loadExportKeys(filename)\n"
1038 << "either way, please make sure that:\n"
1039 << " 3: you are reading a file with export keys - call RooFit::JSONIO::printExportKeys() to "
1040 "see what is available\n"
1041 << " 2 & 1: you might need to write a serialization definition yourself. check "
1042 "https://root.cern/doc/master/group__roofit__dev__docs__hs3.html to "
1043 "see how to do this!\n";
1044 return;
1045 }
1046
1047 elem["type"] << dict->second.type;
1048
1049 size_t nprox = func.numProxies();
1050
1051 for (size_t i = 0; i < nprox; ++i) {
1052 RooAbsProxy *p = func.getProxy(i);
1053 if (!p)
1054 continue;
1055
1056 // some proxies start with a "!". This is a magic symbol that we don't want to stream
1057 std::string pname(p->name());
1058 if (pname[0] == '!')
1059 pname.erase(0, 1);
1060
1061 auto k = dict->second.proxies.find(pname);
1062 if (k == dict->second.proxies.end()) {
1063 std::cerr << "failed to find key matching proxy '" << pname << "' for type '" << dict->second.type
1064 << "', encountered in '" << func.GetName() << "', skipping" << std::endl;
1065 return;
1066 }
1067
1068 // empty string is interpreted as an instruction to ignore this value
1069 if (k->second.empty())
1070 continue;
1071
1072 if (auto l = dynamic_cast<RooAbsCollection *>(p)) {
1073 fillSeq(elem[k->second], *l);
1074 }
1075 if (auto r = dynamic_cast<RooArgProxy *>(p)) {
1076 if (isLiteralConstVar(*r->absArg())) {
1077 elem[k->second] << static_cast<RooConstVar *>(r->absArg())->getVal();
1078 } else {
1079 elem[k->second] << r->absArg()->GetName();
1080 }
1081 }
1082 }
1083
1084 // export all the servers of a given RooAbsArg
1085 for (RooAbsArg *s : func.servers()) {
1086 if (!s) {
1087 std::cerr << "unable to locate server of " << func.GetName() << std::endl;
1088 continue;
1089 }
1091 }
1092}
1093
1094/**
1095 * @brief Import a function from the JSONNode into the workspace.
1096 *
1097 * This function imports a function from the given JSONNode into the workspace.
1098 * The function's information is read from the JSONNode and added to the workspace.
1099 *
1100 * @param p The JSONNode representing the function to be imported.
1101 * @param importAllDependants A boolean flag indicating whether to import all dependants (servers) of the function.
1102 * @return void
1103 */
1105{
1106 std::string name(RooJSONFactoryWSTool::name(p));
1107
1108 // If this node if marked to be skipped by RooFit, exit
1109 if (hasAttribute(name, "roofit_skip")) {
1110 return;
1111 }
1112
1113 auto const &importers = RooFit::JSONIO::importers();
1115
1116 // some preparations: what type of function are we dealing with here?
1118
1119 // if the RooAbsArg already exists, we don't need to do anything
1120 if (_workspace.arg(name)) {
1121 return;
1122 }
1123 // if the key we found is not a map, it's an error
1124 if (!p.is_map()) {
1125 std::stringstream ss;
1126 ss << "RooJSONFactoryWSTool() function node " + name + " is not a map!";
1128 return;
1129 }
1130 std::string prefix = genPrefix(p, true);
1131 if (!prefix.empty())
1132 name = prefix + name;
1133 if (!p.has_child("type")) {
1134 std::stringstream ss;
1135 ss << "RooJSONFactoryWSTool() no type given for function '" << name << "', skipping." << std::endl;
1137 return;
1138 }
1139
1140 std::string functype(p["type"].val());
1141
1142 // import all dependents if importing a workspace, not for creating new objects
1143 if (!importAllDependants) {
1144 this->importDependants(p);
1145 }
1146
1147 // check for specific implementations
1148 auto it = importers.find(functype);
1149 bool ok = false;
1150 if (it != importers.end()) {
1151 for (auto &imp : it->second) {
1152 try {
1153 ok = imp->importArg(this, p);
1154 } catch (const std::exception &e) {
1155 std::stringstream ss;
1156 const auto *ptr = imp.get();
1157 ss << "RooJSONFactoryWSTool() failed. The importer " << typeid(*ptr).name()
1158 << " emitted and error: " << e.what() << std::endl;
1160 }
1161 if (ok)
1162 break;
1163 }
1164 }
1165 if (!ok) { // generic import using the factory expressions
1166 auto expr = factoryExpressions.find(functype);
1167 if (expr != factoryExpressions.end()) {
1168 std::string expression = ::generate(expr->second, p, this);
1169 if (!_workspace.factory(expression)) {
1170 std::stringstream ss;
1171 ss << "RooJSONFactoryWSTool() failed to create " << expr->second.tclass->GetName() << " '" << name
1172 << "', skipping. expression was\n"
1173 << expression << std::endl;
1175 }
1176 } else {
1177 std::stringstream ss;
1178 ss << "RooJSONFactoryWSTool() no handling for type '" << functype << "' implemented, skipping."
1179 << "\n"
1180 << "there are several possible reasons for this:\n"
1181 << " 1. " << functype << " is a custom type that is not available in RooFit.\n"
1182 << " 2. " << functype
1183 << " is a ROOT class that nobody ever bothered to write a deserialization definition for.\n"
1184 << " 3. something is wrong with your setup, e.g. you might have called "
1185 "RooFit::JSONIO::clearFactoryExpressions() and/or never successfully read a file defining "
1186 "these expressions with RooFit::JSONIO::loadFactoryExpressions(filename)\n"
1187 << "either way, please make sure that:\n"
1188 << " 3: you are reading a file with factory expressions - call "
1189 "RooFit::JSONIO::printFactoryExpressions() "
1190 "to see what is available\n"
1191 << " 2 & 1: you might need to write a deserialization definition yourself. check "
1192 "https://root.cern/doc/master/group__roofit__dev__docs__hs3.html to see "
1193 "how to do this!"
1194 << std::endl;
1196 return;
1197 }
1198 }
1200 if (!func) {
1201 std::stringstream err;
1202 err << "something went wrong importing function '" << name << "'.";
1203 RooJSONFactoryWSTool::error(err.str());
1204 }
1205}
1206
1207/**
1208 * @brief Import a function from a JSON string into the workspace.
1209 *
1210 * This function imports a function from the provided JSON string into the workspace.
1211 * The function's information is read from the JSON string and added to the workspace.
1212 *
1213 * @param jsonString The JSON string containing the function information.
1214 * @param importAllDependants A boolean flag indicating whether to import all dependants (servers) of the function.
1215 * @return void
1216 */
1218{
1219 this->importFunction((JSONTree::create(jsonString))->rootnode(), importAllDependants);
1220}
1221
1222/**
1223 * @brief Export the name and binning of a RooRealVar to a JSONNode.
1224 *
1225 * @param obsNode The JSONNode to which the axis information will be exported.
1226 * @param var The RooRealVar representing the axis to be exported.
1227 * @return void
1228 */
1230{
1231 std::string name = var.GetName();
1233 obsNode["name"] << name;
1234
1236}
1237
1238/**
1239 * @brief Export histogram data to a JSONNode.
1240 *
1241 * This function exports histogram data, represented by the provided variables and contents, to a JSONNode.
1242 * The histogram's axes information and bin contents are added as key-value pairs to the JSONNode.
1243 *
1244 * @param vars The RooArgSet representing the variables associated with the histogram.
1245 * @param n The number of bins in the histogram.
1246 * @param contents A pointer to the array containing the bin contents of the histogram.
1247 * @param output The JSONNode to which the histogram data will be exported.
1248 * @return void
1249 */
1250void RooJSONFactoryWSTool::exportHisto(RooArgSet const &vars, std::size_t n, double const *contents, JSONNode &output)
1251{
1252 auto &observablesNode = output["axes"].set_seq();
1253 // axes have to be ordered to get consistent bin indices
1254 for (auto *var : static_range_cast<RooRealVar *>(vars)) {
1255 exportAxis(observablesNode.append_child().set_map(), *var);
1256 }
1257
1258 return exportArray(n, contents, output["contents"]);
1259}
1260
1261/**
1262 * @brief Export an array of doubles to a JSONNode.
1263 *
1264 * This function exports an array of doubles, represented by the provided size and contents,
1265 * to a JSONNode. The array elements are added to the JSONNode as a sequence of values.
1266 *
1267 * @param n The size of the array.
1268 * @param contents A pointer to the array containing the double values.
1269 * @param output The JSONNode to which the array will be exported.
1270 * @return void
1271 */
1272void RooJSONFactoryWSTool::exportArray(std::size_t n, double const *contents, JSONNode &output)
1273{
1274 output.set_seq();
1275 for (std::size_t i = 0; i < n; ++i) {
1276 double w = contents[i];
1277 // To make sure there are no unnecessary floating points in the JSON
1278 if (int(w) == w) {
1279 output.append_child() << int(w);
1280 } else {
1281 output.append_child() << w;
1282 }
1283 }
1284}
1285
1286namespace {
1287
1288// Turn an arbitrary string into a valid variable name, but refuse to change the
1289// first character (which would silently rename the object).
1290std::string makeValidNameOrError(std::string const &in)
1291{
1292 if (!std::isalpha(in[0])) {
1293 RooJSONFactoryWSTool::error("refusing to change first character of string '" + in + "' to make a valid name!");
1294 }
1295 std::string out = RooFit::Detail::makeValidVarName(in);
1296 if (out != in) {
1297 oocoutW(nullptr, IO) << "RooFitHS3: changed '" << in << "' to '" << out << "' to become a valid name";
1298 }
1299 return out;
1300}
1301
1302} // namespace
1303
1304/**
1305 * @brief Export a RooAbsCategory object to a JSONNode.
1306 *
1307 * This function exports a RooAbsCategory object, represented by the provided categories and indices,
1308 * to a JSONNode. The category labels and corresponding indices are added to the JSONNode as key-value pairs.
1309 *
1310 * @param cat The RooAbsCategory object to be exported.
1311 * @param node The JSONNode to which the category data will be exported.
1312 * @return void
1313 */
1315{
1316 auto &labels = node["labels"].set_seq();
1317 auto &indices = node["indices"].set_seq();
1318
1319 for (auto const &item : cat) {
1320 labels.append_child() << makeValidNameOrError(item.first);
1321 indices.append_child() << item.second;
1322 }
1323}
1324
1325// Split `data` by its index category into per-channel datasets and export each, returning the resulting
1326// component-name map.
1328{
1329 // find category observables
1330 RooAbsCategory *cat = nullptr;
1331 for (RooAbsArg *obs : *data.get()) {
1332 if (dynamic_cast<RooAbsCategory *>(obs)) {
1333 if (cat) {
1334 RooJSONFactoryWSTool::error("dataset '" + std::string(data.GetName()) +
1335 " has several category observables!");
1336 }
1337 cat = static_cast<RooAbsCategory *>(obs);
1338 }
1339 }
1340
1341 // prepare return value
1343
1344 if (!cat)
1345 return datamap;
1346 // this is a combined dataset
1347
1348 datamap.name = data.GetName();
1349
1350 // Write information necessary to reconstruct the combined dataset upon import
1351 auto &child = getRooFitInternal(*_rootnodeOutput, "combined_datasets").set_map()[data.GetName()].set_map();
1352 child["index_cat"] << cat->GetName();
1353 exportCategory(*cat, child);
1354
1355 // Find a RooSimultaneous model that would fit to this dataset
1356 RooSimultaneous const *simPdf = nullptr;
1357 auto *combinedPdfInfoNode = findRooFitInternal(*_rootnodeOutput, "combined_distributions");
1358 if (combinedPdfInfoNode) {
1359 for (auto &info : combinedPdfInfoNode->children()) {
1360 if (info["index_cat"].val() == cat->GetName()) {
1361 simPdf = static_cast<RooSimultaneous const *>(_workspace.pdf(info.key()));
1362 }
1363 }
1364 }
1365
1366 // If there is an associated simultaneous pdf for the index category, we
1367 // use the RooAbsData::split() overload that takes the RooSimultaneous.
1368 // Like this, the observables that are not relevant for a given channel
1369 // are automatically split from the component datasets.
1370 std::vector<std::unique_ptr<RooAbsData>> dataList{simPdf ? data.split(*simPdf, true) : data.split(*cat, true)};
1371
1372 for (std::unique_ptr<RooAbsData> const &absData : dataList) {
1373 std::string catName(absData->GetName());
1374 std::string dataName = makeValidNameOrError(catName);
1375 absData->SetName((std::string(data.GetName()) + "_" + dataName).c_str());
1376 datamap.components[catName] = absData->GetName();
1377 this->exportData(*absData);
1378 }
1379 return datamap;
1380}
1381
1382// Export a single dataset `data` (binned or unbinned) to the output JSON.
1384{
1385 // find category observables
1386
1387 RooAbsCategory *cat = nullptr;
1388 for (RooAbsArg *obs : *data.get()) {
1389 if (dynamic_cast<RooAbsCategory *>(obs)) {
1390 if (cat) {
1391 RooJSONFactoryWSTool::error("dataset '" + std::string(data.GetName()) +
1392 " has several category observables!");
1393 }
1394 cat = static_cast<RooAbsCategory *>(obs);
1395 }
1396 }
1397
1398 if (cat)
1399 return;
1400
1401 JSONNode &output = appendNamedChild((*_rootnodeOutput)["data"], data.GetName());
1402
1403 // This works around a problem in RooStats/HistFactory that was only fixed
1404 // in ROOT 6.30: until then, the weight variable of the observed dataset,
1405 // called "weightVar", was added to the observables. Therefore, it also got
1406 // added to the Asimov dataset. But the Asimov has its own weight variable,
1407 // called "binWeightAsimov", making "weightVar" an actual observable in the
1408 // Asimov data. But this is only by accident and should be removed.
1409 RooArgSet variables = *data.get();
1410 if (auto weightVar = variables.find("weightVar")) {
1411 variables.remove(*weightVar);
1412 }
1413
1414 // this is a regular binned dataset
1415 if (auto dh = dynamic_cast<RooDataHist const *>(&data)) {
1416 output["type"] << "binned";
1417 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1418 _domains->readVariable(*var);
1419 }
1420 return exportHisto(variables, dh->numEntries(), dh->weightArray(), output);
1421 }
1422
1423 // Check if this actually represents a binned dataset, and then import it
1424 // like a RooDataHist. This happens frequently when people create combined
1425 // RooDataSets from binned data to fit HistFactory models. In this case, it
1426 // doesn't make sense to export them like an unbinned dataset, because the
1427 // coordinates are redundant information with the binning. We only do this
1428 // for 1D data for now.
1429 if (data.isWeighted() && variables.size() == 1) {
1430 bool isBinnedData = false;
1431 auto &x = static_cast<RooRealVar const &>(*variables[0]);
1432 std::vector<double> contents;
1433 int i = 0;
1434 for (; i < data.numEntries(); ++i) {
1435 data.get(i);
1436 if (x.getBin() != i)
1437 break;
1438 contents.push_back(data.weight());
1439 }
1440 if (i == x.getBins())
1441 isBinnedData = true;
1442 if (isBinnedData) {
1443 output["type"] << "binned";
1444 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1445 _domains->readVariable(*var);
1446 }
1447 return exportHisto(variables, data.numEntries(), contents.data(), output);
1448 }
1449 }
1450
1451 // this really is an unbinned dataset
1452 output["type"] << "unbinned";
1453 auto &observablesNode = output["axes"].set_seq();
1454 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1455 _domains->readVariable(*var);
1456 exportAxis(observablesNode.append_child().set_map(), *var);
1457 }
1458 auto &coords = output["entries"].set_seq();
1459 std::vector<double> weightVals;
1460 bool hasNonUnityWeights = false;
1461 for (int i = 0; i < data.numEntries(); ++i) {
1462 data.get(i);
1463 coords.append_child().fill_seq(variables, [](auto x) { return static_cast<RooRealVar *>(x)->getVal(); });
1464 if (data.isWeighted()) {
1465 weightVals.push_back(data.weight());
1466 if (data.weight() != 1.)
1467 hasNonUnityWeights = true;
1468 }
1469 }
1470 if (data.isWeighted() && hasNonUnityWeights) {
1471 output["weights"].fill_seq(weightVals);
1472 }
1473}
1474
1475/**
1476 * @brief Read axes from the JSONNode and create a RooArgSet representing them.
1477 *
1478 * This function reads axes information from the given JSONNode and
1479 * creates a RooArgSet with variables representing these axes.
1480 *
1481 * @param topNode The JSONNode containing the axes information to be read.
1482 * @return RooArgSet A RooArgSet containing the variables created from the JSONNode.
1483 */
1485{
1486 RooArgSet vars;
1487
1488 for (JSONNode const &node : topNode["axes"].children()) {
1489 if (node.has_child("edges")) {
1490 std::vector<double> edges;
1491 for (auto const &bound : node["edges"].children()) {
1492 edges.push_back(bound.val_double());
1493 }
1494 auto obs = std::make_unique<RooRealVar>(node["name"].val().c_str(), node["name"].val().c_str(), edges[0],
1495 edges[edges.size() - 1]);
1496 RooBinning bins(obs->getMin(), obs->getMax());
1497 for (auto b : edges) {
1498 bins.addBoundary(b);
1499 }
1500 obs->setBinning(bins);
1501 vars.addOwned(std::move(obs));
1502 } else {
1503 auto obs = std::make_unique<RooRealVar>(node["name"].val().c_str(), node["name"].val().c_str(),
1504 node["min"].val_double(), node["max"].val_double());
1505 obs->setBins(node["nbins"].val_int());
1506 vars.addOwned(std::move(obs));
1507 }
1508 }
1509
1510 return vars;
1511}
1512
1513/**
1514 * @brief Read binned data from the JSONNode and create a RooDataHist object.
1515 *
1516 * This function reads binned data from the given JSONNode and creates a RooDataHist object.
1517 * The binned data is associated with the specified name and variables (RooArgSet) in the workspace.
1518 *
1519 * @param n The JSONNode representing the binned data to be read.
1520 * @param name The name to be associated with the created RooDataHist object.
1521 * @param vars The RooArgSet representing the variables associated with the binned data.
1522 * @return std::unique_ptr<RooDataHist> A unique pointer to the created RooDataHist object.
1523 */
1524std::unique_ptr<RooDataHist>
1525RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std::string &name, RooArgSet const &vars)
1526{
1527 if (!n.has_child("contents"))
1528 RooJSONFactoryWSTool::error("no contents given");
1529
1530 JSONNode const &contents = n["contents"];
1531
1532 if (!contents.is_seq())
1533 RooJSONFactoryWSTool::error("contents are not in list form");
1534
1535 JSONNode const *errors = nullptr;
1536 if (n.has_child("errors")) {
1537 errors = &n["errors"];
1538 if (!errors->is_seq())
1539 RooJSONFactoryWSTool::error("errors are not in list form");
1540 }
1541
1542 auto bins = generateBinIndices(vars);
1543 if (contents.num_children() != bins.size()) {
1544 std::stringstream errMsg;
1545 errMsg << "inconsistent bin numbers: contents=" << contents.num_children() << ", bins=" << bins.size();
1547 }
1548 auto dh = std::make_unique<RooDataHist>(name, name, vars);
1549 std::vector<double> contentVals;
1550 contentVals.reserve(contents.num_children());
1551 for (auto const &cont : contents.children()) {
1552 contentVals.push_back(cont.val_double());
1553 }
1554 std::vector<double> errorVals;
1555 if (errors) {
1556 errorVals.reserve(errors->num_children());
1557 for (auto const &err : errors->children()) {
1558 errorVals.push_back(err.val_double());
1559 }
1560 }
1561 for (size_t ibin = 0; ibin < bins.size(); ++ibin) {
1562 const double err = errors ? errorVals[ibin] : -1;
1563 dh->set(ibin, contentVals[ibin], err);
1564 }
1565 return dh;
1566}
1567
1568// Import a single variable (RooRealVar or RooConstVar) from the JSON node `p` into the workspace.
1570{
1571 // import a RooRealVar object
1572 std::string name(RooJSONFactoryWSTool::name(p));
1574
1575 if (_workspace.arg(name))
1576 return;
1577 if (!p.is_map()) {
1578 std::stringstream ss;
1579 ss << "RooJSONFactoryWSTool() node '" << name << "' is not a map, skipping.";
1580 oocoutE(nullptr, InputArguments) << ss.str() << std::endl;
1581 return;
1582 }
1583 if (config().importNoDomainParametersAsRooConstVars && !_domains->hasVariable(name.c_str())) {
1584 if (!p.has_child("value")) {
1585 RooJSONFactoryWSTool::error("cannot instantiate RooConstVar '" + name + "' without \"value\"!");
1586 }
1587 wsEmplace<RooConstVar>(name, p["value"].val_double());
1588 return;
1589 }
1591}
1592
1593// Import all dependants (variables, functions and distributions) of node `n` into the workspace.
1595{
1596 // import all the dependants of an object
1597 if (JSONNode const *varsNode = getVariablesNode(n)) {
1598 for (const auto &p : varsNode->children()) {
1600 }
1601 }
1602 if (auto seq = n.find("functions")) {
1603 for (const auto &p : seq->children()) {
1604 this->importFunction(p, true);
1605 }
1606 }
1607 if (auto seq = n.find("distributions")) {
1608 for (const auto &p : seq->children()) {
1609 this->importFunction(p, true);
1610 }
1611 }
1612}
1613
1615 const std::vector<CombinedData> &combDataSets,
1616 const std::vector<RooAbsData *> &singleDataSets)
1617{
1618 auto pdf = mc.GetPdf();
1619 auto simpdf = dynamic_cast<RooSimultaneous const *>(pdf);
1620 if (simpdf) {
1621 for (std::size_t i = 0; i < std::max(combDataSets.size(), std::size_t(1)); ++i) {
1622 const bool hasdata = i < combDataSets.size();
1623 if (hasdata && !matches(combDataSets.at(i), simpdf))
1624 continue;
1625
1626 std::string analysisName(simpdf->GetName());
1627 if (hasdata)
1628 analysisName += "_" + combDataSets[i].name;
1629
1630 exportSingleModelConfig(rootnode, mc, analysisName, hasdata ? &combDataSets[i].components : nullptr);
1631 }
1632 } else {
1633 RooArgSet observables(*mc.GetObservables());
1634 int founddata = 0;
1635 for (auto *data : singleDataSets) {
1636 if (observables.equals(*(data->get()))) {
1637 std::map<std::string, std::string> mapping;
1638 mapping[pdf->GetName()] = data->GetName();
1639 exportSingleModelConfig(rootnode, mc, std::string(pdf->GetName()) + "_" + data->GetName(), &mapping);
1640 ++founddata;
1641 }
1642 }
1643 if (founddata == 0) {
1644 exportSingleModelConfig(rootnode, mc, pdf->GetName(), nullptr);
1645 }
1646 }
1647}
1648
1650 std::string const &analysisName,
1651 std::map<std::string, std::string> const *dataComponents)
1652{
1653 auto pdf = mc.GetPdf();
1654
1655 JSONNode &analysisNode = appendNamedChild(rootnode["analyses"], analysisName);
1656
1657 auto &domains = analysisNode["domains"].set_seq();
1658
1659 analysisNode["likelihood"] << analysisName;
1660
1661 auto &nllNode = appendNamedChild(rootnode["likelihoods"], analysisName);
1662 nllNode["distributions"].set_seq();
1663 nllNode["data"].set_seq();
1664
1665 if (dataComponents) {
1666 auto simPdf = dynamic_cast<RooSimultaneous const *>(pdf);
1667 if (simPdf) {
1668 for (auto const &item : simPdf->indexCat()) {
1669 const auto &dataComp = dataComponents->find(item.first);
1670 nllNode["distributions"].append_child() << simPdf->getPdf(item.first)->GetName();
1671 nllNode["data"].append_child() << dataComp->second;
1672 }
1673 } else {
1674 for (auto it : *dataComponents) {
1675 nllNode["distributions"].append_child() << it.first;
1676 nllNode["data"].append_child() << it.second;
1677 }
1678 }
1679 } else {
1680 nllNode["distributions"].append_child() << pdf->GetName();
1681 nllNode["data"].append_child() << 0;
1682 }
1683
1684 if (mc.GetExternalConstraints()) {
1685 auto &extConstrNode = nllNode["aux_distributions"];
1686 extConstrNode.set_seq();
1687 for (const auto &constr : *mc.GetExternalConstraints()) {
1688 extConstrNode.append_child() << constr->GetName();
1689 }
1690 }
1691
1692 auto writeList = [&](const char *name, RooArgSet const *args) {
1693 if (!args || !args->size())
1694 return;
1695
1696 std::vector<std::string> names;
1697 names.reserve(args->size());
1698 for (RooAbsArg const *arg : *args)
1699 names.push_back(arg->GetName());
1700 std::sort(names.begin(), names.end());
1701 analysisNode[name].fill_seq(names);
1702 };
1703
1704 writeList("parameters_of_interest", mc.GetParametersOfInterest());
1705
1706 auto &domainsNode = rootnode["domains"];
1707
1708 auto writeProductDomain = [&](const char *suffix, RooArgSet const *args) {
1709 if (!args || args->empty())
1710 return;
1711 const std::string domainName = analysisName + suffix;
1712 domains.append_child() << domainName;
1714 for (auto *var : static_range_cast<const RooRealVar *>(*args)) {
1715 domain.readVariable(*var);
1716 }
1718 };
1719
1720 writeProductDomain("_nuisance_parameters", mc.GetNuisanceParameters());
1721 writeProductDomain("_global_observables", mc.GetGlobalObservables());
1722 writeProductDomain("_parameters_of_interest", mc.GetParametersOfInterest());
1723
1724 auto &modelConfigAux = getRooFitInternal(rootnode, "ModelConfigs", analysisName);
1725 modelConfigAux.set_map();
1726 modelConfigAux["pdfName"] << pdf->GetName();
1727 modelConfigAux["mcName"] << mc.GetName();
1728}
1729
1730// Export all top-level pdfs, functions, datasets and ModelConfigs of the workspace into `n`.
1732{
1733 _domains = std::make_unique<RooFit::JSONIO::Detail::Domains>();
1735 _rootnodeOutput = &n;
1736
1737 // export all toplevel pdfs
1738 std::vector<RooAbsPdf *> allpdfs;
1739 for (auto &arg : _workspace.allPdfs()) {
1740 if (!arg->hasClients()) {
1741 if (auto *pdf = dynamic_cast<RooAbsPdf *>(arg)) {
1742 allpdfs.push_back(pdf);
1743 }
1744 }
1745 }
1747 std::set<std::string> exportedObjectNames;
1749
1750 // export all toplevel functions
1751 std::vector<RooAbsReal *> allfuncs;
1752 for (auto &arg : _workspace.allFunctions()) {
1753 if (!arg->hasClients()) {
1754 if (auto *func = dynamic_cast<RooAbsReal *>(arg)) {
1755 allfuncs.push_back(func);
1756 }
1757 }
1758 }
1761
1762 // export attributes of all objects
1763 for (RooAbsArg *arg : _workspace.components()) {
1764 exportAttributes(arg, n);
1765 }
1766
1767 // collect all datasets
1768 std::vector<RooAbsData *> alldata;
1769 for (auto &d : _workspace.allData()) {
1770 alldata.push_back(d);
1771 }
1773 // first, take care of combined datasets
1774 std::vector<RooAbsData *> singleData;
1775 std::vector<RooJSONFactoryWSTool::CombinedData> combData;
1776 for (auto &d : alldata) {
1777 auto data = this->exportCombinedData(*d);
1778 if (!data.components.empty())
1779 combData.push_back(data);
1780 else
1781 singleData.push_back(d);
1782 }
1783 // next, take care datasets
1784 for (auto &d : alldata) {
1785 this->exportData(*d);
1786 }
1787
1788 // export all ModelConfig objects and attached Pdfs
1789 for (TObject *obj : _workspace.allGenericObjects()) {
1790 if (auto mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
1792 }
1793 }
1794
1796
1799 // We only want to add the variables that actually got exported and skip
1800 // the ones that the pdfs encoded implicitly (like in the case of
1801 // HistFactory).
1802 for (RooAbsArg *arg : *snsh) {
1803 bool do_export = false;
1804 for (const auto &pdf : allpdfs) {
1805 if (pdf->dependsOn(*arg)) {
1806 do_export = true;
1807 }
1808 }
1809 if (do_export) {
1810 RooJSONFactoryWSTool::testValidName(arg->GetName(), true);
1811 snapshotSorted.add(*arg);
1812 }
1813 }
1814 snapshotSorted.sort();
1815 std::string name(snsh->GetName());
1816 if (name != "default_values") {
1817 this->exportVariables(snapshotSorted, appendNamedChild(n["parameter_points"], name)["parameters"], true,
1818 false);
1819 }
1820 }
1821 _varsNode = nullptr;
1822 _domains->writeJSON(n["domains"]);
1823 _domains.reset();
1824 _rootnodeOutput = nullptr;
1825}
1826
1827/**
1828 * @brief Import the workspace from a JSON string.
1829 *
1830 * @param s The JSON string containing the workspace data.
1831 * @return bool Returns true on successful import, false otherwise.
1832 */
1834{
1835 std::stringstream ss(s);
1836 return importJSON(ss);
1837}
1838
1839/**
1840 * @brief Export the workspace to a JSON string.
1841 *
1842 * @return std::string The JSON string representing the exported workspace.
1843 */
1845{
1846 std::stringstream ss;
1847 exportJSON(ss);
1848 return ss.str();
1849}
1850
1851/**
1852 * @brief Create a new JSON tree with version information.
1853 *
1854 * @return std::unique_ptr<JSONTree> A unique pointer to the created JSON tree.
1855 */
1857{
1858 std::unique_ptr<JSONTree> tree = JSONTree::create();
1859 JSONNode &n = tree->rootnode();
1860 n.set_map();
1861 auto &metadata = n["metadata"].set_map();
1862
1863 // add the mandatory hs3 version number
1864 metadata["hs3_version"] << hs3VersionTag;
1865
1866 // Add information about the ROOT version that was used to generate this file
1867 auto &rootInfo = appendNamedChild(metadata["packages"], "ROOT");
1868 std::string versionName = gROOT->GetVersion();
1869 // We want to consistently use dots such that the version name can be easily
1870 // digested automatically.
1871 std::replace(versionName.begin(), versionName.end(), '/', '.');
1872 rootInfo["version"] << versionName;
1873
1874 return tree;
1875}
1876
1877/**
1878 * @brief Export the workspace to JSON format and write to the output stream.
1879 *
1880 * @param os The output stream to write the JSON data to.
1881 * @return bool Returns true on successful export, false otherwise.
1882 */
1884{
1885 std::unique_ptr<JSONTree> tree = createNewJSONTree();
1886 JSONNode &n = tree->rootnode();
1887 this->exportAllObjects(n);
1888 n.writeJSON(os);
1889 return true;
1890}
1891
1892/**
1893 * @brief Export the workspace to JSON format and write to the specified file.
1894 *
1895 * @param filename The name of the JSON file to create and write the data to.
1896 * @return bool Returns true on successful export, false otherwise.
1897 */
1899{
1900 std::ofstream out(filename.c_str());
1901 if (!out.is_open())
1902 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() invalid output file '" + filename + "'.");
1903 return this->exportJSON(out);
1904}
1905
1906bool RooJSONFactoryWSTool::hasAttribute(const std::string &obj, const std::string &attrib)
1907{
1908 if (!_attributesNode)
1909 return false;
1910 if (auto attrNode = _attributesNode->find(obj)) {
1911 if (auto seq = attrNode->find("tags")) {
1912 for (auto &a : seq->children()) {
1913 if (a.val() == attrib)
1914 return true;
1915 }
1916 }
1917 }
1918 return false;
1919}
1920void RooJSONFactoryWSTool::setAttribute(const std::string &obj, const std::string &attrib)
1921{
1922 auto node = &RooJSONFactoryWSTool::getRooFitInternal(*_rootnodeOutput, "attributes").set_map()[obj].set_map();
1923 auto &tags = (*node)["tags"];
1924 tags.set_seq();
1925 tags.append_child() << attrib;
1926}
1927
1928std::string RooJSONFactoryWSTool::getStringAttribute(const std::string &obj, const std::string &attrib)
1929{
1930 if (!_attributesNode)
1931 return "";
1932 if (auto attrNode = _attributesNode->find(obj)) {
1933 if (auto dict = attrNode->find("dict")) {
1934 if (auto *a = dict->find(attrib)) {
1935 return a->val();
1936 }
1937 }
1938 }
1939 return "";
1940}
1941void RooJSONFactoryWSTool::setStringAttribute(const std::string &obj, const std::string &attrib,
1942 const std::string &value)
1943{
1944 auto node = &RooJSONFactoryWSTool::getRooFitInternal(*_rootnodeOutput, "attributes").set_map()[obj].set_map();
1945 auto &dict = (*node)["dict"];
1946 dict.set_map();
1947 dict[attrib] << value;
1948}
1949
1950// Import all nodes of the JSON document rooted at `n` into the workspace.
1952{
1953 // Per HS3 standard, the hs3_version in the metadata is required. So we
1954 // error out if it is missing. TODO: now we are only checking if the
1955 // hs3_version tag exists, but in the future when the HS3 specification
1956 // versions are actually frozen, we should also check if the hs3_version is
1957 // one that RooFit can actually read.
1958 auto metadata = n.find("metadata");
1959 if (!metadata || !metadata->find("hs3_version")) {
1960 std::stringstream ss;
1961 ss << "The HS3 version is missing in the JSON!\n"
1962 << "Please include the HS3 version in the metadata field, e.g.:\n"
1963 << " \"metadata\" :\n"
1964 << " {\n"
1965 << " \"hs3_version\" : \"" << hs3VersionTag << "\"\n"
1966 << " }";
1967 error(ss.str());
1968 }
1969
1970 _rootnodeInput = &n;
1971
1973
1974 _domains = std::make_unique<RooFit::JSONIO::Detail::Domains>();
1975 if (auto domains = n.find("domains")) {
1976 _domains->readJSON(*domains);
1977 }
1978 _domains->populate(_workspace);
1979
1980 // Build name-keyed indices over the "functions" and "distributions"
1981 // sequences. Without these, every cross-reference resolved during import
1982 // (e.g. dependencies of a PiecewiseInterpolation, or factory-expression
1983 // arguments) triggers a linear scan over all sibling nodes via
1984 // findNamedChild(), which becomes O(N^2) on workspaces with thousands of
1985 // entries. Populating the maps up-front turns each lookup into O(1).
1986 _functionsByName.clear();
1987 _distributionsByName.clear();
1988 if (auto seq = n.find("functions")) {
1989 if (seq->is_seq()) {
1990 _functionsByName.reserve(seq->num_children());
1991 for (const auto &p : seq->children()) {
1993 }
1994 }
1995 }
1996 if (auto seq = n.find("distributions")) {
1997 if (seq->is_seq()) {
1998 _distributionsByName.reserve(seq->num_children());
1999 for (const auto &p : seq->children()) {
2001 }
2002 }
2003 }
2004
2005 this->importDependants(n);
2006
2007 if (auto paramPointsNode = n.find("parameter_points")) {
2008 for (const auto &snsh : paramPointsNode->children()) {
2009 std::string name = RooJSONFactoryWSTool::name(snsh);
2011
2012 RooArgSet vars;
2013 for (const auto &var : snsh["parameters"].children()) {
2016 vars.add(*rrv);
2017 }
2018 }
2020 }
2021 }
2022
2024
2025 // Import attributes
2026 if (_attributesNode) {
2027 for (const auto &elem : _attributesNode->children()) {
2028 if (RooAbsArg *arg = _workspace.arg(elem.key()))
2029 importAttributes(arg, elem);
2030 }
2031 }
2032
2033 _attributesNode = nullptr;
2034
2035 // We delay the import of the data to after combineDatasets(), because it
2036 // might be that some datasets are merged to combined datasets there. In
2037 // that case, we will remove the components from the "datasets" vector so they
2038 // don't get imported.
2039 std::vector<std::unique_ptr<RooAbsData>> datasets;
2040 if (auto dataNode = n.find("data")) {
2041 for (const auto &p : dataNode->children()) {
2042 datasets.push_back(loadData(p, _workspace));
2043 }
2044 }
2045
2046 // Now, read in analyses and likelihoods if there are any
2047
2048 if (auto analysesNode = n.find("analyses")) {
2049 for (JSONNode const &analysisNode : analysesNode->children()) {
2050 importAnalysis(*_rootnodeInput, analysisNode, n["likelihoods"], n["domains"], _workspace, datasets);
2051 }
2052 }
2053
2054 combineDatasets(*_rootnodeInput, datasets);
2055
2056 for (auto const &d : datasets) {
2057 if (d) {
2059 for (auto const &obs : *d->get()) {
2060 if (auto *rrv = dynamic_cast<RooRealVar *>(obs)) {
2061 _workspace.var(rrv->GetName())->setBinning(rrv->getBinning());
2062 }
2063 }
2064 }
2065 }
2066
2067 _rootnodeInput = nullptr;
2068 _domains.reset();
2069 _functionsByName.clear();
2070 _distributionsByName.clear();
2071}
2072
2073/**
2074 * @brief Imports a JSON file from the given input stream to the workspace.
2075 *
2076 * @param is The input stream containing the JSON data.
2077 * @return bool Returns true on successful import, false otherwise.
2078 */
2080{
2081 // import a JSON file to the workspace
2082 std::unique_ptr<JSONTree> tree = JSONTree::create(is);
2083 JSONNode const &rootnode = tree->rootnode();
2084 this->importAllNodes(rootnode);
2085 if (this->workspace()->getSnapshot("default_values")) {
2086 this->workspace()->loadSnapshot("default_values");
2087 }
2088 importParameterStepWidths(*this->workspace(), rootnode);
2089 return true;
2090}
2091
2092/**
2093 * @brief Imports a JSON file from the given filename to the workspace.
2094 *
2095 * @param filename The name of the JSON file to import.
2096 * @return bool Returns true on successful import, false otherwise.
2097 */
2099{
2100 // import a JSON file to the workspace
2101 std::ifstream infile(filename.c_str());
2102 if (!infile.is_open())
2103 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() invalid input file '" + filename + "'.");
2104 return this->importJSON(infile);
2105}
2106
2107void RooJSONFactoryWSTool::importJSONElement(const std::string &name, const std::string &jsonString)
2108{
2109 // Create the JSON Tree from the string
2110 std::unique_ptr<RooFit::Detail::JSONTree> tree = RooFit::Detail::JSONTree::create(jsonString);
2111 JSONNode &n = tree->rootnode();
2112
2113 // If the objects containts a parameter of interest, import it as a modelConfig
2114 if (n.find("poi")) {
2115
2116 RooStats::ModelConfig modelConfig{"ModelConfig"};
2117 std::string poi = n.find("poi")->val();
2118 std::string pdname = n.find("pdfName")->val();
2119 modelConfig.SetWS(_workspace);
2120 modelConfig.SetPdf(pdname.c_str());
2121 modelConfig.SetParametersOfInterest(_workspace.argSet(poi));
2123
2124 return;
2125 }
2126
2127 n["name"] << name;
2128
2129 bool isVariable = true;
2130 bool isData = false;
2131 // Check for the type of object, if it doesn't contain a type, it must be a variable
2132 if (n.find("type")) {
2133 isVariable = false;
2134 std::string elementType = n.find("type")->val();
2135 if (elementType == "binned" || elementType == "unbinned") {
2136 isData = true;
2137 }
2138 }
2139
2140 // Import the object to the workspace
2141 if (isVariable) {
2142 this->importVariableElement(n);
2143 } else if (isData) {
2144 auto absData = loadData(n, _workspace);
2146 } else {
2147 this->importFunction(n, false);
2148 }
2149}
2150
2152{
2153 std::unique_ptr<RooFit::Detail::JSONTree> tree = varJSONString(elementNode);
2154 JSONNode &n = tree->rootnode();
2155 _domains = std::make_unique<RooFit::JSONIO::Detail::Domains>();
2156 if (auto domains = n.find("domains"))
2157 _domains->readJSON(*domains);
2158
2159 _rootnodeInput = &n;
2161
2163 const auto &p = varsNode->child(0);
2165
2166 auto paramPointsNode = n.find("parameter_points");
2167 const auto &snsh = paramPointsNode->child(0);
2168 std::string name = RooJSONFactoryWSTool::name(snsh);
2169 RooArgSet vars;
2170 const auto &var = snsh["parameters"].child(0);
2173 vars.add(*rrv);
2174 }
2175
2176 // Import attributes
2177 if (_attributesNode) {
2178 for (const auto &elem : _attributesNode->children()) {
2179 if (RooAbsArg *arg = _workspace.arg(elem.key()))
2180 importAttributes(arg, elem);
2181 }
2182 }
2183
2184 _attributesNode = nullptr;
2185 _rootnodeInput = nullptr;
2186 _domains.reset();
2187}
2188
2189/**
2190 * @brief Writes a warning message to the RooFit message service.
2191 *
2192 * @param str The warning message to be logged.
2193 * @return std::ostream& A reference to the output stream.
2194 */
2195std::ostream &RooJSONFactoryWSTool::warning(std::string const &str)
2196{
2197 return RooMsgService::instance().log(nullptr, RooFit::MsgLevel::WARNING, RooFit::IO) << str << std::endl;
2198}
2199
2200/**
2201 * @brief Writes an error message to the RooFit message service and throws a runtime_error.
2202 *
2203 * @param s The error message to be logged and thrown.
2204 * @return void
2205 */
2207{
2208 RooMsgService::instance().log(nullptr, RooFit::MsgLevel::ERROR, RooFit::IO) << s << std::endl;
2209 throw std::runtime_error(s);
2210}
2211
2212/**
2213 * @brief Cleans up names to the HS3 standard
2214 *
2215 * @param str The string to be sanitized.
2216 * @return std::string
2217 */
2218std::string RooJSONFactoryWSTool::sanitizeName(const std::string str)
2219{
2220 std::string result;
2221 if (RooJSONFactoryWSTool::config().allowSanitizeNames) {
2222 for (char c : str) {
2223 switch (c) {
2224 case '[':
2225 case '|':
2226 case ',':
2227 case '(': result += '_'; break;
2228 case ']':
2229 case ')':
2230 // skip these characters entirely
2231 break;
2232 case '.': result += "_dot_"; break;
2233 case '@': result += "at"; break;
2234 case '-': result += "minus"; break;
2235 case '/': result += "_div_"; break;
2236
2237 default: result += c; break;
2238 }
2239 }
2240 return result;
2241 }
2242 return str;
2243}
2244
2246{
2247 // Variables
2248
2250 if (onlyModelConfig) {
2251 for (auto *obj : ws.allGenericObjects()) {
2252 if (auto *mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
2253 tmpWS.import(*mc->GetPdf(), RooFit::RecycleConflictNodes(true));
2254 }
2255 }
2256
2257 } else {
2258
2259 for (auto *pdf : ws.allPdfs()) {
2260 if (!pdf->hasClients()) {
2261 tmpWS.import(*pdf, RooFit::RecycleConflictNodes(true));
2262 }
2263 }
2264
2265 for (auto *func : ws.allFunctions()) {
2266 if (!func->hasClients()) {
2267 tmpWS.import(*func, RooFit::RecycleConflictNodes(true));
2268 }
2269 }
2270 }
2271
2272 for (auto *data : ws.allData()) {
2273 tmpWS.import(*data);
2274 }
2275
2276 for (auto *obj : ws.allGenericObjects()) {
2277 tmpWS.import(*obj);
2278 }
2279
2280 for (auto *obj : ws.allResolutionModels()) {
2281 tmpWS.import(*obj);
2282 }
2283
2284 for (auto *snsh : ws.getSnapshots()) {
2285 auto *snshSet = dynamic_cast<RooArgSet *>(snsh);
2286 if (snshSet) {
2287 tmpWS.saveSnapshot(snshSet->GetName(), *snshSet, true);
2288 }
2289 }
2290
2291 return tmpWS;
2292}
2293
2299
2300// Sanitize all names in the workspace to be HS3 compliant
2302{
2303 // Variables
2304
2305 RooWorkspace tmpWS = cleanWS(ws, false);
2306
2307 auto sanitizeIfNeeded = [](auto const &list) {
2308 for (auto *obj : list) {
2309 if (!isValidName(obj->GetName())) {
2310 obj->SetName(sanitizeName(obj->GetName()).c_str());
2311 }
2312 }
2313 };
2314 sanitizeIfNeeded(tmpWS.allVars());
2315 sanitizeIfNeeded(tmpWS.allFunctions());
2316 sanitizeIfNeeded(tmpWS.allPdfs());
2317 sanitizeIfNeeded(tmpWS.allResolutionModels());
2318 // Datasets
2319 for (auto *data : tmpWS.allData()) {
2320 // Sanitize dataset name
2321 if (!isValidName(data->GetName())) {
2322 data->SetName(sanitizeName(data->GetName()).c_str());
2323 }
2324 for (auto *obj : *data->get()) {
2325 obj->SetName(sanitizeName(obj->GetName()).c_str());
2326 }
2327 }
2328 for (auto *data : tmpWS.allEmbeddedData()) {
2329 // Sanitize dataset name
2330 data->SetName(sanitizeName(data->GetName()).c_str());
2331 for (auto *obj : *data->get()) {
2332 obj->SetName(sanitizeName(obj->GetName()).c_str());
2333 }
2334 }
2335 for (auto *snshObj : tmpWS.getSnapshots()) {
2336 // Snapshots are stored as TObject*, but really they are RooArgSet*
2337 auto *snsh = dynamic_cast<RooArgSet *>(snshObj);
2338 if (!snsh) {
2339 std::cerr << "Warning: found snapshot that is not a RooArgSet, skipping\n";
2340 continue;
2341 }
2342
2343 // Sanitize snapshot name
2344 if (!isValidName(snsh->GetName())) {
2345 snsh->setName(sanitizeName(snsh->GetName()).c_str());
2346 }
2347
2348 // Sanitize the variables inside the snapshot
2349 for (auto *arg : *snsh) {
2350 if (!isValidName(arg->GetName())) {
2351 arg->SetName(sanitizeName(arg->GetName()).c_str());
2352 }
2353 }
2354 }
2355
2356 // Generic objects (ModelConfigs, attributes, etc.)
2357 for (auto *obj : tmpWS.allGenericObjects()) {
2358 if (!isValidName(obj->GetName())) {
2359 if (auto *named = dynamic_cast<TNamed *>(obj)) {
2360 named->SetName(sanitizeName(named->GetName()).c_str());
2361 } else {
2362 std::cerr << "Warning: object " << obj->GetName() << " is not TNamed, cannot rename.\n";
2363 }
2364 }
2365
2366 if (auto *mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
2367 // Sanitize ModelConfig name
2368 if (!isValidName(mc->GetName())) {
2369 mc->SetName(sanitizeName(mc->GetName()).c_str());
2370 }
2371
2372 // Sanitize the sets inside ModelConfig
2373 for (auto *obs : mc->GetObservables()->get()) {
2374 if (obs) {
2375 obs->SetName(sanitizeName(obs->GetName()).c_str());
2376 }
2377 }
2378 for (auto *poi : mc->GetParametersOfInterest()->get()) {
2379 if (poi) {
2380 poi->SetName(sanitizeName(poi->GetName()).c_str());
2381 }
2382 }
2383 for (auto *nuis : mc->GetNuisanceParameters()->get()) {
2384 if (nuis) {
2385 nuis->SetName(sanitizeName(nuis->GetName()).c_str());
2386 }
2387 }
2388 for (auto *glob : mc->GetGlobalObservables()->get()) {
2389 if (glob) {
2390 glob->SetName(sanitizeName(glob->GetName()).c_str());
2391 }
2392 }
2393 }
2394 }
2395 std::string wsName = std::string{ws.GetName()} + "_sanitized";
2396 RooWorkspace newWS = cleanWS(tmpWS, false);
2397 newWS.SetName(wsName.c_str());
2398
2399 return newWS;
2400}
std::unique_ptr< RooFit::Detail::JSONTree > varJSONString(const JSONNode &treeRoot)
void writeAxisBinning(JSONNode &node, const RooAbsBinning &binning)
#define d(i)
Definition RSha256.hxx:102
#define b(i)
Definition RSha256.hxx:100
#define c(i)
Definition RSha256.hxx:101
#define a(i)
Definition RSha256.hxx:99
#define e(i)
Definition RSha256.hxx:103
double toDouble(const char *s)
constexpr auto hs3VersionTag
#define oocoutW(o, a)
#define oocoutE(o, a)
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char filename
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t r
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t child
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t attr
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t type
char name[80]
Definition TGX11.cxx:145
#define gROOT
Definition TROOT.h:426
const_iterator begin() const
const_iterator end() const
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:76
TClass * IsA() const override
Definition RooAbsArg.h:678
void setStringAttribute(const Text_t *key, const Text_t *value)
Associate string 'value' to this object under key 'key'.
RooFit::OwningPtr< RooArgSet > getParameters(const RooAbsData *data, bool stripDisconnected=true) const
Create a list of leaf nodes in the arg tree starting with ourself as top node that don't match any of...
RooFit::OwningPtr< RooArgSet > getObservables(const RooArgSet &set, bool valueOnly=true) const
Given a set of possible observables, return the observables that this PDF depends on.
const std::set< std::string > & attributes() const
Definition RooAbsArg.h:258
const RefCountList_t & servers() const
List of all servers of this object.
Definition RooAbsArg.h:145
const std::map< std::string, std::string > & stringAttributes() const
Definition RooAbsArg.h:267
Int_t numProxies() const
Return the number of registered proxies.
void setAttribute(const Text_t *name, bool value=true)
Set (default) or clear a named boolean attribute of this object.
RooAbsProxy * getProxy(Int_t index) const
Return the nth proxy from the proxy list.
A space to attach TBranches.
Abstract container object that can hold multiple RooAbsArg objects.
bool equals(const RooAbsCollection &otherColl) const
Check if this and other collection have identically-named contents.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
Storage_t::size_type size() const
virtual bool addOwned(RooAbsArg &var, bool silent=false)
Add an argument and transfer the ownership to the collection.
Abstract base class for binned and unbinned datasets.
Definition RooAbsData.h:56
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
std::unique_ptr< RooArgSet > getAllConstraints(const RooArgSet &observables, RooArgSet &constrainedParams, bool stripDisconnected=true) const
This helper function finds and collects all constraints terms of all component p.d....
Abstract interface for proxy classes.
Definition RooAbsProxy.h:37
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
RooArgList is a container object that can hold multiple RooAbsArg objects.
Definition RooArgList.h:22
RooAbsArg * at(Int_t idx) const
Return object at given index, or nullptr if index is out of range.
Definition RooArgList.h:110
Abstract interface for RooAbsArg proxy classes.
Definition RooArgProxy.h:24
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:24
Implements a RooAbsBinning in terms of an array of boundary values, posing no constraints on the choi...
Definition RooBinning.h:27
bool addBoundary(double boundary)
Add bin boundary at given value.
Object to represent discrete states.
Definition RooCategory.h:28
Represents a constant real-valued object.
Definition RooConstVar.h:23
Container class to hold N-dimensional binned data.
Definition RooDataHist.h:40
virtual JSONNode & set_map()=0
virtual JSONNode & append_child()=0
virtual children_view children()
virtual size_t num_children() const =0
virtual JSONNode & set_seq()=0
virtual bool is_seq() const =0
virtual bool is_map() const =0
virtual std::string key() const =0
JSONNode const * find(std::string const &key) const
static std::unique_ptr< JSONTree > create()
void writeJSON(RooFit::Detail::JSONNode &) const
Definition Domains.cxx:248
When using RooFit, statistical models can be conveniently handled and stored as a RooWorkspace.
static constexpr bool useListsInsteadOfDicts
std::string getStringAttribute(const std::string &obj, const std::string &attrib)
static void fillSeq(RooFit::Detail::JSONNode &node, RooAbsCollection const &coll, size_t nMax=-1)
void exportObjects(T const &args, std::set< std::string > &exportedObjectNames)
void exportCategory(RooAbsCategory const &cat, RooFit::Detail::JSONNode &node)
Export a RooAbsCategory object to a JSONNode.
RooJSONFactoryWSTool(RooWorkspace &ws)
void exportData(RooAbsData const &data)
void exportModelConfig(RooFit::Detail::JSONNode &rootnode, RooStats::ModelConfig const &mc, const std::vector< RooJSONFactoryWSTool::CombinedData > &combined, const std::vector< RooAbsData * > &single)
bool hasAttribute(const std::string &obj, const std::string &attrib)
bool importJSON(std::string const &filename)
Imports a JSON file from the given filename to the workspace.
void exportVariables(const RooArgSet &allElems, RooFit::Detail::JSONNode &n, bool storeConstant, bool storeBins)
static std::unique_ptr< RooDataHist > readBinnedData(const RooFit::Detail::JSONNode &n, const std::string &namecomp, RooArgSet const &vars)
Read binned data from the JSONNode and create a RooDataHist object.
static RooFit::Detail::JSONNode & appendNamedChild(RooFit::Detail::JSONNode &node, std::string const &name)
static RooFit::Detail::JSONNode & getRooFitInternal(RooFit::Detail::JSONNode &node, Keys_t const &...keys)
static void exportArray(std::size_t n, double const *contents, RooFit::Detail::JSONNode &output)
Export an array of doubles to a JSONNode.
static bool testValidName(const std::string &str, bool forcError)
RooFit::Detail::JSONNode * _rootnodeOutput
static void exportHisto(RooArgSet const &vars, std::size_t n, double const *contents, RooFit::Detail::JSONNode &output)
Export histogram data to a JSONNode.
std::vector< RooAbsArg const * > _serversToDelete
std::unordered_map< std::string, RooFit::Detail::JSONNode const * > _functionsByName
void exportSingleModelConfig(RooFit::Detail::JSONNode &rootnode, RooStats::ModelConfig const &mc, std::string const &analysisName, std::map< std::string, std::string > const *dataComponents)
static std::unique_ptr< RooFit::Detail::JSONTree > createNewJSONTree()
Create a new JSON tree with version information.
const RooFit::Detail::JSONNode * _rootnodeInput
RooJSONFactoryWSTool::CombinedData exportCombinedData(RooAbsData const &data)
std::string exportJSONtoString()
Export the workspace to a JSON string.
static RooWorkspace cleanWS(const RooWorkspace &ws, bool onlyModelConfig=false)
std::string exportTransformed(const RooAbsReal *original, const std::string &suffix, const std::string &formula)
const RooFit::Detail::JSONNode * _attributesNode
static bool isValidName(const std::string &str)
Check if a string is a valid name.
void importDependants(const RooFit::Detail::JSONNode &n)
static void exportAxis(RooFit::Detail::JSONNode &obsNode, RooRealVar const &var)
Export the name and binning of a RooRealVar to a JSONNode.
void importJSONElement(const std::string &name, const std::string &jsonString)
static RooWorkspace sanitizeWS(const RooWorkspace &ws)
static void error(const char *s)
Writes an error message to the RooFit message service and throws a runtime_error.
void setAttribute(const std::string &obj, const std::string &attrib)
void importVariable(const RooFit::Detail::JSONNode &p)
void exportVariable(const RooAbsArg *v, RooFit::Detail::JSONNode &n, bool storeConstant, bool storeBins)
void importFunction(const RooFit::Detail::JSONNode &p, bool importAllDependants)
Import a function from the JSONNode into the workspace.
bool importJSONfromString(const std::string &s)
Import the workspace from a JSON string.
RooFit::Detail::JSONNode * _varsNode
void exportObject(RooAbsArg const &func, std::set< std::string > &exportedObjectNames)
static RooFit::Detail::JSONNode & makeVariablesNode(RooFit::Detail::JSONNode &rootNode)
static std::string sanitizeName(const std::string str)
Cleans up names to the HS3 standard.
void importAllNodes(const RooFit::Detail::JSONNode &n)
static std::string name(const RooFit::Detail::JSONNode &n)
void exportAllObjects(RooFit::Detail::JSONNode &n)
bool exportJSON(std::string const &fileName)
Export the workspace to JSON format and write to the specified file.
static RooFit::Detail::JSONNode const * findNamedChild(RooFit::Detail::JSONNode const &node, std::string const &name)
std::unordered_map< std::string, RooFit::Detail::JSONNode const * > _distributionsByName
void setStringAttribute(const std::string &obj, const std::string &attrib, const std::string &value)
std::vector< RooAbsArg const * > _serversToExport
std::unique_ptr< RooFit::JSONIO::Detail::Domains > _domains
static std::ostream & warning(const std::string &s)
Writes a warning message to the RooFit message service.
static RooArgSet readAxes(const RooFit::Detail::JSONNode &node)
Read axes from the JSONNode and create a RooArgSet representing them.
void importVariableElement(const RooFit::Detail::JSONNode &n)
static RooMsgService & instance()
Return reference to singleton instance.
Variable that can be changed from the outside.
Definition RooRealVar.h:37
void setVal(double value) override
Set value of variable to 'value'.
const RooAbsBinning & getBinning(const char *name=nullptr, bool verbose=true, bool createOnTheFly=false, bool shared=true) const override
Return binning definition with name.
Facilitates simultaneous fitting of multiple PDFs to subsets of a given dataset.
const RooAbsCategoryLValue & indexCat() const
< A class that holds configuration information for a model using a workspace as a store
Definition ModelConfig.h:34
Persistable container for RooFit projects.
TObject * obj(RooStringView name) const
Return any type of object (RooAbsArg, RooAbsData or generic object) with given name)
const RooArgSet * getSnapshot(const char *name) const
Return the RooArgSet containing a snapshot of variables contained in the workspace.
RooAbsPdf * pdf(RooStringView name) const
Retrieve p.d.f (RooAbsPdf) with given name. A null pointer is returned if not found.
RooArgSet argSet(RooStringView nameList) const
Return set of RooAbsArgs matching to given list of names.
RooArgSet allResolutionModels() const
Return set with all resolution model objects.
bool saveSnapshot(RooStringView, const char *paramNames)
Save snapshot of values and attributes (including "Constant") of given parameters.
RooArgSet allPdfs() const
Return set with all probability density function objects.
std::list< RooAbsData * > allData() const
Return list of all dataset in the workspace.
RooLinkedList const & getSnapshots() const
std::list< TObject * > allGenericObjects() const
Return list of all generic objects in the workspace.
RooAbsReal * function(RooStringView name) const
Retrieve function (RooAbsReal) with given name. Note that all RooAbsPdfs are also RooAbsReals....
RooAbsArg * arg(RooStringView name) const
Return RooAbsArg with given name. A null pointer is returned if none is found.
const RooArgSet & components() const
RooArgSet allFunctions() const
Return set with all function objects.
RooFactoryWSTool & factory()
Return instance to factory tool.
RooRealVar * var(RooStringView name) const
Retrieve real-valued variable (RooRealVar) with given name. A null pointer is returned if not found.
bool loadSnapshot(const char *name)
Load the values and attributes of the parameters in the snapshot saved with the given name.
bool import(const RooAbsArg &arg, const RooCmdArg &arg1={}, const RooCmdArg &arg2={}, const RooCmdArg &arg3={}, const RooCmdArg &arg4={}, const RooCmdArg &arg5={}, const RooCmdArg &arg6={}, const RooCmdArg &arg7={}, const RooCmdArg &arg8={}, const RooCmdArg &arg9={})
Import a RooAbsArg object, e.g.
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
The TNamed class is the base class for all named ROOT classes.
Definition TNamed.h:29
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
Mother of all ROOT objects.
Definition TObject.h:42
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
Definition TString.cxx:2385
RooCmdArg RecycleConflictNodes(bool flag=true)
RooConstVar & RooConst(double val)
RooCmdArg Silence(bool flag=true)
RooCmdArg Index(RooCategory &icat)
RooCmdArg WeightVar(const char *name="weight", bool reinterpretAsWeight=false)
RooCmdArg Import(const char *state, TH1 &histo)
Double_t x[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
Double_t ex[n]
Definition legend1.C:17
std::string makeValidVarName(std::string const &in)
ImportMap & importers()
Definition JSONIO.cxx:59
ExportMap & exporters()
Definition JSONIO.cxx:81
ImportExpressionMap & importExpressions()
Definition JSONIO.cxx:108
ExportKeysMap & exportKeys()
Definition JSONIO.cxx:115
TLine l
Definition textangle.C:4
static void output()