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 <stack>
43#include <stdexcept>
44
45/** \class RooJSONFactoryWSTool
46\ingroup roofit_dev_docs_hs3
47
48When using \ref Roofitmain, statistical models can be conveniently handled and
49stored as a RooWorkspace. However, for the sake of interoperability
50with other statistical frameworks, and also ease of manipulation, it
51may be useful to store statistical models in text form.
52
53The RooJSONFactoryWSTool is a helper class to achieve exactly this,
54exporting to and importing from JSON.
55
56In order to import a workspace from a JSON file, you can do
57
58~~~ {.py}
59ws = ROOT.RooWorkspace("ws")
60tool = ROOT.RooJSONFactoryWSTool(ws)
61tool.importJSON("myjson.json")
62~~~
63
64Similarly, in order to export a workspace to a JSON file, you can do
65
66~~~ {.py}
67tool = ROOT.RooJSONFactoryWSTool(ws)
68tool.exportJSON("myjson.json")
69~~~
70
71Analogously, in C++, you can do
72
73~~~ {.cxx}
74#include "RooFitHS3/RooJSONFactoryWSTool.h"
75// ...
76RooWorkspace ws("ws");
77RooJSONFactoryWSTool tool(ws);
78tool.importJSON("myjson.json");
79~~~
80
81and
82
83~~~ {.cxx}
84#include "RooFitHS3/RooJSONFactoryWSTool.h"
85// ...
86RooJSONFactoryWSTool tool(ws);
87tool.exportJSON("myjson.json");
88~~~
89
90For more details, consult the tutorial <a href="rf515__hfJSON_8py.html">rf515_hfJSON</a>.
91
92The RooJSONFactoryWSTool only knows about a limited set of classes for
93import and export. If import or export of a class you're interested in
94fails, you might need to add your own importer or exporter. Please
95consult the relevant section in the \ref roofit_dev_docs to learn how to do that (\ref roofit_dev_docs_hs3).
96
97You can always get a list of all the available importers and exporters by calling the following functions:
98~~~ {.py}
99ROOT.RooFit.JSONIO.printImporters()
100ROOT.RooFit.JSONIO.printExporters()
101ROOT.RooFit.JSONIO.printFactoryExpressions()
102ROOT.RooFit.JSONIO.printExportKeys()
103~~~
104
105Alternatively, you can generate a LaTeX version of the available importers and exporters by calling
106~~~ {.py}
107tool = ROOT.RooJSONFactoryWSTool(ws)
108tool.writedoc("hs3.tex")
109~~~
110*/
111
112constexpr auto hs3VersionTag = "0.2";
113
116
117namespace {
118
119std::vector<std::string> valsToStringVec(JSONNode const &node)
120{
121 std::vector<std::string> out;
122 out.reserve(node.num_children());
123 for (JSONNode const &elem : node.children()) {
124 out.push_back(elem.val());
125 }
126 return out;
127}
128
129/**
130 * @brief Check if the number of components in CombinedData matches the number of categories in the RooSimultaneous PDF.
131 *
132 * This function checks whether the number of components in the provided CombinedData 'data' matches the number of
133 * categories in the provided RooSimultaneous PDF 'pdf'.
134 *
135 * @param data The reference to the CombinedData to be checked.
136 * @param pdf The pointer to the RooSimultaneous PDF for comparison.
137 * @return bool Returns true if the number of components in 'data' matches the number of categories in 'pdf'; otherwise,
138 * returns false.
139 */
140bool matches(const RooJSONFactoryWSTool::CombinedData &data, const RooSimultaneous *pdf)
141{
142 return data.components.size() == pdf->indexCat().size();
143}
144
145/**
146 * @brief Check if a string represents a valid number.
147 *
148 * This function checks whether the provided string 'str' represents a valid number.
149 * The function returns true if the entire string can be parsed as a number (integer or floating-point); otherwise, it
150 * returns false.
151 *
152 * @param str The string to be checked.
153 * @return bool Returns true if the string 'str' represents a valid number; otherwise, returns false.
154 */
155bool isNumber(const std::string &str)
156{
157 bool seen_digit = false;
158 bool seen_dot = false;
159 bool seen_e = false;
160 bool after_e = false;
161 bool sign_allowed = true;
162
163 for (size_t i = 0; i < str.size(); ++i) {
164 char c = str[i];
165
166 if (std::isdigit(c)) {
167 seen_digit = true;
168 sign_allowed = false;
169 } else if ((c == '+' || c == '-') && sign_allowed) {
170 // Sign allowed at the beginning or right after 'e'/'E'
171 sign_allowed = false;
172 } else if (c == '.' && !seen_dot && !after_e) {
173 seen_dot = true;
174 sign_allowed = false;
175 } else if ((c == 'e' || c == 'E') && seen_digit && !seen_e) {
176 seen_e = true;
177 after_e = true;
178 sign_allowed = true; // allow sign immediately after 'e'
179 seen_digit = false; // reset: we now expect digits after e
180 } else {
181 return false;
182 }
183 }
184
185 return seen_digit;
186}
187
188/**
189 * @brief Configure a RooRealVar based on information from a JSONNode.
190 *
191 * This function configures the provided RooRealVar 'v' based on the information provided in the JSONNode 'p'.
192 * The JSONNode 'p' contains information about various properties of the RooRealVar, such as its value, error, number of
193 * bins, etc. The function reads these properties from the JSONNode and sets the corresponding properties of the
194 * RooRealVar accordingly.
195 *
196 * @param domains The reference to the RooFit::JSONIO::Detail::Domains containing domain information for variables (not
197 * used in this function).
198 * @param p The JSONNode containing information about the properties of the RooRealVar 'v'.
199 * @param v The reference to the RooRealVar to be configured.
200 * @return void
201 */
203{
204 if (!p.has_child("name")) {
205 RooJSONFactoryWSTool::error("cannot instantiate variable without \"name\"!");
206 }
207 if (auto n = p.find("value"))
208 v.setVal(n->val_double());
209 domains.writeVariable(v);
210 if (auto n = p.find("nbins"))
211 v.setBins(n->val_int());
212 if (auto n = p.find("relErr"))
213 v.setError(v.getVal() * n->val_double());
214 if (auto n = p.find("err"))
215 v.setError(n->val_double());
216 if (auto n = p.find("const")) {
217 v.setConstant(n->val_bool());
218 } else {
219 v.setConstant(false);
220 }
221}
222
224{
225 auto paramPointsNode = rootNode.find("parameter_points");
226 if (!paramPointsNode)
227 return nullptr;
228 auto out = RooJSONFactoryWSTool::findNamedChild(*paramPointsNode, "default_values");
229 if (out == nullptr)
230 return nullptr;
231 return &((*out)["parameters"]);
232}
233
234std::string genPrefix(const JSONNode &p, bool trailing_underscore)
235{
236 std::string prefix;
237 if (!p.is_map())
238 return prefix;
239 if (auto node = p.find("namespaces")) {
240 for (const auto &ns : node->children()) {
241 if (!prefix.empty())
242 prefix += "_";
243 prefix += ns.val();
244 }
245 }
246 if (trailing_underscore && !prefix.empty())
247 prefix += "_";
248 return prefix;
249}
250
251// helpers for serializing / deserializing binned datasets
252void genIndicesHelper(std::vector<std::vector<int>> &combinations, std::vector<int> &curr_comb,
253 const std::vector<int> &vars_numbins, size_t curridx)
254{
255 if (curridx == vars_numbins.size()) {
256 // we have filled a combination. Copy it.
257 combinations.emplace_back(curr_comb);
258 } else {
259 for (int i = 0; i < vars_numbins[curridx]; ++i) {
260 curr_comb[curridx] = i;
262 }
263 }
264}
265
266/**
267 * @brief Import attributes from a JSONNode into a RooAbsArg.
268 *
269 * This function imports attributes, represented by the provided JSONNode 'node', into the provided RooAbsArg 'arg'.
270 * The attributes are read from the JSONNode and applied to the RooAbsArg.
271 *
272 * @param arg The pointer to the RooAbsArg to which the attributes will be imported.
273 * @param node The JSONNode containing information about the attributes to be imported.
274 * @return void
275 */
276void importAttributes(RooAbsArg *arg, JSONNode const &node)
277{
278 if (auto seq = node.find("dict")) {
279 for (const auto &attr : seq->children()) {
280 arg->setStringAttribute(attr.key().c_str(), attr.val().c_str());
281 }
282 }
283 if (auto seq = node.find("tags")) {
284 for (const auto &attr : seq->children()) {
285 arg->setAttribute(attr.val().c_str());
286 }
287 }
288}
289
290void addIfPresent(RooArgSet &out, RooArgSet const *args)
291{
292 if (args) {
293 out.add(*args, true);
294 }
295}
296
299{
300 for (TObject *obj : workspace.allGenericObjects()) {
301 auto const *mc = dynamic_cast<RooFit::ModelConfig const *>(obj);
302 if (!mc) {
303 continue;
304 }
305
306 addIfPresent(candidates, mc->GetParametersOfInterest());
307 addIfPresent(candidates, mc->GetNuisanceParameters());
308
309 addIfPresent(excluded, mc->GetObservables());
310 addIfPresent(excluded, mc->GetGlobalObservables());
311 addIfPresent(excluded, mc->GetConditionalObservables());
312 }
313}
314
315void collectParameterStepWidthCandidatesFromPdfs(std::vector<RooAbsPdf *> const &pdfs,
316 std::vector<RooAbsData *> const &data, RooArgSet &candidates,
318{
319 for (RooAbsPdf const *pdf : pdfs) {
320 RooArgSet observables;
321 for (RooAbsData const *dataset : data) {
322 std::unique_ptr<RooArgSet> pdfObs{pdf->getObservables(*dataset->get())};
323 observables.add(*pdfObs, true);
324 }
325
326 if (observables.empty()) {
327 continue;
328 }
329
330 RooArgSet params;
331 pdf->getParameters(&observables, params);
332 candidates.add(params, true);
333 excluded.add(observables, true);
334 }
335}
336
337void exportParameterStepWidths(RooWorkspace const &workspace, std::vector<RooAbsPdf *> const &pdfs,
338 std::vector<RooAbsData *> const &data, JSONNode &rootnode)
339{
342
345
346 candidates.sort();
347
349 for (RooAbsArg *arg : candidates) {
350 if (excluded.find(*arg)) {
351 continue;
352 }
353
354 auto *var = dynamic_cast<RooRealVar *>(arg);
355 if (!var || !var->hasError()) {
356 continue;
357 }
358
360 parameterStepWidthsNode = &rootnode["misc"]["minimization"]["parameter_stepwidths"].set_seq();
361 }
362
364 stepWidthNode["step_width"] << var->getError();
365 }
366}
367
368void importParameterStepWidths(RooWorkspace &workspace, JSONNode const &rootnode)
369{
370 auto const *parameterStepWidthsNode = rootnode.find("misc", "minimization", "parameter_stepwidths");
372 return;
373 }
374 if (!parameterStepWidthsNode->is_seq()) {
375 RooJSONFactoryWSTool::warning("RooFitHS3: misc.minimization.parameter_stepwidths is not a sequence, skipping.");
376 return;
377 }
378
379 for (JSONNode const &stepWidthNode : parameterStepWidthsNode->children()) {
380 if (!stepWidthNode.is_map() || !stepWidthNode.has_child("name") || !stepWidthNode.has_child("step_width")) {
381 RooJSONFactoryWSTool::warning("RooFitHS3: skipping malformed parameter_stepwidths entry.");
382 continue;
383 }
384
385 const std::string name = RooJSONFactoryWSTool::name(stepWidthNode);
386 RooAbsArg *arg = workspace.arg(name);
387 auto *var = dynamic_cast<RooRealVar *>(arg);
388 if (!var) {
390 "RooFitHS3: skipping parameter_stepwidths entry for unknown or non-real variable '" + name + "'.");
391 continue;
392 }
393
394 var->setError(stepWidthNode.find("step_width")->val_double());
395 }
396}
397
398// RooWSFactoryTool expression handling
399std::string generate(const RooFit::JSONIO::ImportExpression &ex, const JSONNode &p, RooJSONFactoryWSTool *tool)
400{
401 std::stringstream expression;
402 std::string classname(ex.tclass->GetName());
403 size_t colon = classname.find_last_of(':');
404 expression << (colon < classname.size() ? classname.substr(colon + 1) : classname);
405 bool first = true;
406 const auto &name = RooJSONFactoryWSTool::name(p);
407 for (auto k : ex.arguments) {
408 expression << (first ? "::" + name + "(" : ",");
409 first = false;
410 if (k == "true" || k == "false") {
411 expression << (k == "true" ? "1" : "0");
412 } else if (!p.has_child(k)) {
413 std::stringstream errMsg;
414 errMsg << "node '" << name << "' is missing key '" << k << "'";
416 } else if (p[k].is_seq()) {
417 bool firstInner = true;
418 expression << "{";
419 for (RooAbsArg *arg : tool->requestArgList<RooAbsReal>(p, k)) {
420 expression << (firstInner ? "" : ",") << arg->GetName();
421 firstInner = false;
422 }
423 expression << "}";
424 } else {
425 tool->requestArg<RooAbsReal>(p, p[k].key());
426 expression << p[k].val();
427 }
428 }
429 expression << ")";
430 return expression.str();
431}
432
433/**
434 * @brief Generate bin indices for a set of RooRealVars.
435 *
436 * This function generates all possible combinations of bin indices for the provided RooArgSet 'vars' containing
437 * RooRealVars. Each bin index represents a possible bin selection for the corresponding RooRealVar. The bin indices are
438 * stored in a vector of vectors, where each inner vector represents a combination of bin indices for all RooRealVars.
439 *
440 * @param vars The RooArgSet containing the RooRealVars for which bin indices will be generated.
441 * @return std::vector<std::vector<int>> A vector of vectors containing all possible combinations of bin indices.
442 */
443std::vector<std::vector<int>> generateBinIndices(const RooArgSet &vars)
444{
445 std::vector<std::vector<int>> combinations;
446 std::vector<int> vars_numbins;
447 vars_numbins.reserve(vars.size());
448 for (const auto *absv : static_range_cast<RooRealVar *>(vars)) {
449 vars_numbins.push_back(absv->getBins());
450 }
451 std::vector<int> curr_comb(vars.size());
453 return combinations;
454}
455
456template <typename... Keys_t>
457JSONNode const *findRooFitInternal(JSONNode const &node, Keys_t const &...keys)
458{
459 return node.find("misc", "ROOT_internal", keys...);
460}
461
462/**
463 * @brief Check if a RooAbsArg is a literal constant variable.
464 *
465 * This function checks whether the provided RooAbsArg 'arg' is a literal constant variable.
466 * A literal constant variable is a RooConstVar with a numeric value as a name.
467 *
468 * @param arg The reference to the RooAbsArg to be checked.
469 * @return bool Returns true if 'arg' is a literal constant variable; otherwise, returns false.
470 */
471bool isLiteralConstVar(RooAbsArg const &arg)
472{
473 bool isRooConstVar = dynamic_cast<RooConstVar const *>(&arg);
474 return isRooConstVar && isNumber(arg.GetName());
475}
476
477/**
478 * @brief Export attributes of a RooAbsArg to a JSONNode.
479 *
480 * This function exports the attributes of the provided RooAbsArg 'arg' to the JSONNode 'rootnode'.
481 *
482 * @param arg The pointer to the RooAbsArg from which attributes will be exported.
483 * @param rootnode The JSONNode to which the attributes will be exported.
484 * @return void
485 */
486void exportAttributes(const RooAbsArg *arg, JSONNode &rootnode)
487{
488 // If this RooConst is a literal number, we don't need to export the attributes.
489 if (isLiteralConstVar(*arg)) {
490 return;
491 }
492
493 JSONNode *node = nullptr;
494
495 auto initializeNode = [&]() {
496 if (node)
497 return;
498
499 node = &RooJSONFactoryWSTool::getRooFitInternal(rootnode, "attributes").set_map()[arg->GetName()].set_map();
500 };
501
502 if (dynamic_cast<RooConstVar const *>(arg)) {
503 return;
504 }
505
506 // export all string attributes of an object
507 if (!arg->stringAttributes().empty()) {
508 for (const auto &it : arg->stringAttributes()) {
509 // Skip some RooFit internals
510 if (it.first == "factory_tag" || it.first == "PROD_TERM_TYPE")
511 continue;
513 (*node)["dict"].set_map()[it.first] << it.second;
514 }
515 }
516 if (!arg->attributes().empty()) {
517 for (auto const &attr : arg->attributes()) {
518 // Skip some RooFit internals
519 if (attr == "SnapShot_ExtRefClone" || attr == "RooRealConstant_Factory_Object")
520 continue;
522 (*node)["tags"].set_seq().append_child() << attr;
523 }
524 }
525}
526
527/**
528 * @brief Create several observables in the workspace.
529 *
530 * This function obtains a list of observables from the provided
531 * RooWorkspace 'ws' based on their names given in the 'axes" field of
532 * the JSONNode 'node'. The observables are added to the RooArgSet
533 * 'out'.
534 *
535 * @param ws The RooWorkspace in which the observables will be created.
536 * @param node The JSONNode containing information about the observables to be created.
537 * @param out The RooAbsCollection to which the created observables will be added.
538 * @return void
539 */
540void getObservables(RooWorkspace const &ws, const JSONNode &node, RooAbsCollection &out)
541{
542 for (const auto &p : node["axes"].children()) {
543 std::string name(RooJSONFactoryWSTool::name(p));
544 if (ws.var(name)) {
545 out.add(*ws.var(name));
546 } else {
547 std::stringstream errMsg;
548 errMsg << "The observable \"" << name << "\" could not be found in the workspace!";
550 }
551 }
552}
553
554/**
555 * @brief Import data from the JSONNode into the workspace.
556 *
557 * This function imports data, represented by the provided JSONNode 'p', into the workspace represented by the provided
558 * RooWorkspace. The data information is read from the JSONNode and added to the workspace.
559 *
560 * @param p The JSONNode representing the data to be imported.
561 * @param workspace The RooWorkspace to which the data will be imported.
562 * @return std::unique_ptr<RooAbsData> A unique pointer to the RooAbsData object representing the imported data.
563 * The caller is responsible for managing the memory of the returned object.
564 */
565std::unique_ptr<RooAbsData> loadData(const JSONNode &p, RooWorkspace &workspace)
566{
567 std::string name(RooJSONFactoryWSTool::name(p));
568
570
571 std::string const &type = p["type"].val();
572 if (type == "binned") {
573 // binned
575 } else if (type == "unbinned") {
576 // unbinned
577 RooArgList varlist;
578 getObservables(workspace, p, varlist);
579 RooArgSet vars(varlist);
580 auto data = std::make_unique<RooDataSet>(name, name, vars, RooFit::WeightVar());
581 auto &coords = p["entries"];
582 if (!coords.is_seq()) {
583 RooJSONFactoryWSTool::error("key 'entries' is not a list!");
584 }
585 std::vector<double> weightVals;
586 if (p.has_child("weights")) {
587 auto &weights = p["weights"];
588 if (coords.num_children() != weights.num_children()) {
589 RooJSONFactoryWSTool::error("inconsistent number of entries and weights!");
590 }
591 for (auto const &weight : weights.children()) {
592 weightVals.push_back(weight.val_double());
593 }
594 }
595 std::size_t i = 0;
596 for (auto const &point : coords.children()) {
597 if (!point.is_seq()) {
598 std::stringstream errMsg;
599 errMsg << "coordinate point '" << i << "' is not a list!";
601 }
602 if (point.num_children() != varlist.size()) {
603 RooJSONFactoryWSTool::error("inconsistent number of entries and observables!");
604 }
605 std::size_t j = 0;
606 for (auto const &pointj : point.children()) {
607 auto *v = static_cast<RooRealVar *>(varlist.at(j));
608 v->setVal(pointj.val_double());
609 ++j;
610 }
611 if (weightVals.size() > 0) {
612 data->add(vars, weightVals[i]);
613 } else {
614 data->add(vars, 1.);
615 }
616 ++i;
617 }
618 return data;
619 }
620
621 std::stringstream ss;
622 ss << "RooJSONFactoryWSTool() failed to create dataset " << name << std::endl;
624 return nullptr;
625}
626
627/**
628 * @brief Import an analysis from the JSONNode into the workspace.
629 *
630 * This function imports an analysis, represented by the provided JSONNodes 'analysisNode' and 'likelihoodsNode',
631 * into the workspace represented by the provided RooWorkspace. The analysis information is read from the JSONNodes
632 * and added to the workspace as one or more RooFit::ModelConfig objects.
633 *
634 * @param rootnode The root JSONNode representing the entire JSON file.
635 * @param analysisNode The JSONNode representing the analysis to be imported.
636 * @param likelihoodsNode The JSONNode containing information about likelihoods associated with the analysis.
637 * @param domainsNode The JSONNode containing information about domains associated with the analysis.
638 * @param workspace The RooWorkspace to which the analysis will be imported.
639 * @param datasets A vector of unique pointers to RooAbsData objects representing the data associated with the analysis.
640 * @return void
641 */
642void importAnalysis(const JSONNode &rootnode, const JSONNode &analysisNode, const JSONNode &likelihoodsNode,
643 const JSONNode &domainsNode, RooWorkspace &workspace,
644 const std::vector<std::unique_ptr<RooAbsData>> &datasets)
645{
646 // if this is a toplevel pdf, also create a modelConfig for it
648 JSONNode const *mcAuxNode = findRooFitInternal(rootnode, "ModelConfigs", analysisName);
649
650 JSONNode const *mcNameNode = mcAuxNode ? mcAuxNode->find("mcName") : nullptr;
651 std::string mcname = mcNameNode ? mcNameNode->val() : analysisName;
652 if (workspace.obj(mcname))
653 return;
654
655 workspace.import(RooFit::ModelConfig{mcname.c_str(), mcname.c_str()});
656 auto *mc = static_cast<RooFit::ModelConfig *>(workspace.obj(mcname));
657 mc->SetWS(workspace);
658
660 if (!nllNode) {
661 throw std::runtime_error("likelihood node not found!");
662 }
663 if (!nllNode->has_child("distributions")) {
664 throw std::runtime_error("likelihood node has no distributions attached!");
665 }
666 if (!nllNode->has_child("data")) {
667 throw std::runtime_error("likelihood node has no data attached!");
668 }
669 std::vector<std::string> nllDistNames = valsToStringVec((*nllNode)["distributions"]);
671 for (auto &nameNode : (*nllNode)["aux_distributions"].children()) {
672 if (RooAbsArg *extConstraint = workspace.arg(nameNode.val())) {
674 }
675 }
676 RooArgSet observables;
677 for (auto &nameNode : (*nllNode)["data"].children()) {
678 bool found = false;
679 for (const auto &d : datasets) {
680 if (d->GetName() == nameNode.val()) {
681 found = true;
682 observables.add(*d->get(), true);
683 }
684 }
685 if (nameNode.val() != "0" && !found)
686 throw std::runtime_error("dataset '" + nameNode.val() + "' cannot be found!");
687 }
688
689 JSONNode const *pdfNameNode = mcAuxNode ? mcAuxNode->find("pdfName") : nullptr;
690 std::string const pdfName = pdfNameNode ? pdfNameNode->val() : "simPdf";
691
692 RooAbsPdf *pdf = static_cast<RooSimultaneous *>(workspace.pdf(pdfName));
693
694 if (!pdf) {
695 // if there is no simultaneous pdf, we can check whether there is only one pdf in the list
696 if (nllDistNames.size() == 1) {
697 // if so, we can use that one to populate the ModelConfig
698 pdf = workspace.pdf(nllDistNames[0]);
699 } else {
700 // otherwise, we have no choice but to build a simPdf by hand
701 std::string simPdfName = analysisName + "_simPdf";
702 std::string indexCatName = analysisName + "_categoryIndex";
703 RooCategory indexCat{indexCatName.c_str(), indexCatName.c_str()};
704 std::map<std::string, RooAbsPdf *> pdfMap;
705 for (std::size_t i = 0; i < nllDistNames.size(); ++i) {
706 indexCat.defineType(nllDistNames[i], i);
707 pdfMap[nllDistNames[i]] = workspace.pdf(nllDistNames[i]);
708 }
709 RooSimultaneous simPdf{simPdfName.c_str(), simPdfName.c_str(), pdfMap, indexCat};
711 pdf = static_cast<RooSimultaneous *>(workspace.pdf(simPdfName));
712 }
713 }
714
715 mc->SetPdf(*pdf);
716
717 if (!extConstraints.empty())
718 mc->SetExternalConstraints(extConstraints);
719
720 auto readArgSet = [&](std::string const &name) {
721 RooArgSet out;
722 for (auto const &child : analysisNode[name].children()) {
723 out.add(*workspace.arg(child.val()));
724 }
725 return out;
726 };
727
728 mc->SetParametersOfInterest(readArgSet("parameters_of_interest"));
729 mc->SetObservables(observables);
730 RooArgSet pars;
731 pdf->getParameters(&observables, pars);
732
733 // Figure out the set parameters that appear in the main measurement:
734 // getAllConstraints() has the side effect to remove all parameters from
735 // "mainPars" that are not part of any pdf over observables.
736 RooArgSet mainPars{pars};
737 pdf->getAllConstraints(observables, mainPars, /*stripDisconnected*/ true);
738
740 for (auto &domain : analysisNode["domains"].children()) {
742 if (!thisDomain || !thisDomain->has_child("axes"))
743 continue;
744 for (auto &var : (*thisDomain)["axes"].children()) {
745 auto *wsvar = workspace.var(RooJSONFactoryWSTool::name(var));
746 if (wsvar)
747 domainPars.add(*wsvar);
748 }
749 }
750
752 RooArgSet globs;
753 for (const auto &p : pars) {
754 if (mc->GetParametersOfInterest()->find(*p))
755 continue;
756 if (p->isConstant() && !mainPars.find(*p) && domainPars.find(*p)) {
757 globs.add(*p);
758 } else if (domainPars.find(*p)) {
759 nps.add(*p);
760 }
761 }
762
763 mc->SetGlobalObservables(globs);
764 mc->SetNuisanceParameters(nps);
765
766 if (mcAuxNode) {
767 if (auto found = mcAuxNode->find("combined_data_name")) {
768 pdf->setStringAttribute("combined_data_name", found->val().c_str());
769 }
770 }
771
772 if (analysisNode.has_child("init") && workspace.getSnapshot(analysisNode["init"].val().c_str())) {
773 mc->SetSnapshot(*workspace.getSnapshot(analysisNode["init"].val().c_str()));
774 }
775}
776
777void combinePdfs(const JSONNode &rootnode, RooWorkspace &ws)
778{
779 auto *combinedPdfInfoNode = findRooFitInternal(rootnode, "combined_distributions");
780
781 // If there is no info on combining pdfs
782 if (combinedPdfInfoNode == nullptr) {
783 return;
784 }
785
786 for (auto &info : combinedPdfInfoNode->children()) {
787
788 // parse the information
789 std::string combinedName = info.key();
790 std::string indexCatName = info["index_cat"].val();
791 std::vector<std::string> labels = valsToStringVec(info["labels"]);
792 std::vector<int> indices;
793 std::vector<std::string> pdfNames = valsToStringVec(info["distributions"]);
794 for (auto &n : info["indices"].children()) {
795 indices.push_back(n.val_int());
796 }
797
798 RooCategory indexCat{indexCatName.c_str(), indexCatName.c_str()};
799 std::map<std::string, RooAbsPdf *> pdfMap;
800
801 for (std::size_t iChannel = 0; iChannel < labels.size(); ++iChannel) {
802 indexCat.defineType(labels[iChannel], indices[iChannel]);
803 pdfMap[labels[iChannel]] = ws.pdf(pdfNames[iChannel]);
804 }
805
806 RooSimultaneous simPdf{combinedName.c_str(), combinedName.c_str(), pdfMap, indexCat};
808 }
809}
810
811void combineDatasets(const JSONNode &rootnode, std::vector<std::unique_ptr<RooAbsData>> &datasets)
812{
813 auto *combinedDataInfoNode = findRooFitInternal(rootnode, "combined_datasets");
814
815 // If there is no info on combining datasets
816 if (combinedDataInfoNode == nullptr) {
817 return;
818 }
819
820 for (auto &info : combinedDataInfoNode->children()) {
821
822 // parse the information
823 std::string combinedName = info.key();
824 std::string indexCatName = info["index_cat"].val();
825 std::vector<std::string> labels = valsToStringVec(info["labels"]);
826 std::vector<int> indices;
827 for (auto &n : info["indices"].children()) {
828 indices.push_back(n.val_int());
829 }
830 if (indices.size() != labels.size()) {
831 RooJSONFactoryWSTool::error("mismatch in number of indices and labels!");
832 }
833
834 // Create the combined dataset for RooFit
835 std::map<std::string, std::unique_ptr<RooAbsData>> dsMap;
836 RooCategory indexCat{indexCatName.c_str(), indexCatName.c_str()};
837 RooArgSet allVars{indexCat};
838 for (std::size_t iChannel = 0; iChannel < labels.size(); ++iChannel) {
839 auto componentName = combinedName + "_" + labels[iChannel];
840 // We move the found channel data out of the "datasets" vector, such that
841 // the data components don't get imported anymore.
842 std::unique_ptr<RooAbsData> &component = *std::find_if(
843 datasets.begin(), datasets.end(), [&](auto &d) { return d && d->GetName() == componentName; });
844 if (!component)
845 RooJSONFactoryWSTool::error("unable to obtain component matching component name '" + componentName + "'");
846 allVars.add(*component->get(), true);
847 dsMap.insert({labels[iChannel], std::move(component)});
848 indexCat.defineType(labels[iChannel], indices[iChannel]);
849 }
850
851 auto combined = std::make_unique<RooDataSet>(combinedName, combinedName, allVars, RooFit::Import(dsMap),
852 RooFit::Index(indexCat));
853 datasets.emplace_back(std::move(combined));
854 }
855}
856
857template <class T>
858void sortByName(T &coll)
859{
860 std::sort(coll.begin(), coll.end(), [](auto &l, auto &r) { return strcmp(l->GetName(), r->GetName()) < 0; });
861}
862
863} // namespace
864
866
868
870{
871 const size_t old_children = node.num_children();
872 node.set_seq();
873 size_t n = 0;
874 for (RooAbsArg const *arg : coll) {
875 if (n >= nMax)
876 break;
877 if (isLiteralConstVar(*arg)) {
878 node.append_child() << static_cast<RooConstVar const *>(arg)->getVal();
879 } else {
880 node.append_child() << arg->GetName();
881 }
882 ++n;
883 }
884 if (node.num_children() != old_children + coll.size()) {
885 error("unable to stream collection " + std::string(coll.GetName()) + " to " + node.key());
886 }
887}
888
890{
892 return node.set_map()[name].set_map();
893 }
894 JSONNode &child = node.set_seq().append_child().set_map();
895 child["name"] << name;
896 return child;
897}
898
899JSONNode const *RooJSONFactoryWSTool::findNamedChild(JSONNode const &node, std::string const &name)
900{
902 if (!node.is_map())
903 return nullptr;
904 return node.find(name);
905 }
906 if (!node.is_seq())
907 return nullptr;
908 for (JSONNode const &child : node.children()) {
909 if (child["name"].val() == name)
910 return &child;
911 }
912
913 return nullptr;
914}
915
916/**
917 * @brief Check if a string is a valid name.
918 *
919 * A valid name should start with a letter or an underscore, followed by letters, digits, or underscores.
920 * Only characters from the ASCII character set are allowed.
921 *
922 * @param str The string to be checked.
923 * @return bool Returns true if the string is a valid name; otherwise, returns false.
924 */
925bool RooJSONFactoryWSTool::isValidName(const std::string &str)
926{
927 // Check if the string is empty or starts with a non-letter/non-underscore character
928 if (str.empty() || !(std::isalpha(str[0]) || str[0] == '_')) {
929 return false;
930 }
931
932 // Check the remaining characters in the string
933 for (char c : str) {
934 // Allow letters, digits, and underscore
935 if (!(std::isalnum(c) || c == '_')) {
936 return false;
937 }
938 }
939
940 // If all characters are valid, the string is a valid name
941 return true;
942}
943
945{
947 std::stringstream ss;
948 ss << "RooJSONFactoryWSTool() name '" << name << "' is not valid!" << std::endl
949 << "Sanitize names by setting RooJSONFactoryWSTool::config().allowSanitizeNames = true." << std::endl;
950 if (RooJSONFactoryWSTool::config().allowExportInvalidNames && !forceError) {
952 return false;
953 } else {
955 }
956 }
957 return true;
958}
959
961{
962 return useListsInsteadOfDicts ? n["name"].val() : n.key();
963}
964
966{
967 return appendNamedChild(rootNode["parameter_points"], "default_values")["parameters"];
968}
969
970template <>
971RooRealVar *RooJSONFactoryWSTool::requestImpl<RooRealVar>(const std::string &objname)
972{
974 return retval;
975 if (const auto *vars = getVariablesNode(*_rootnodeInput)) {
976 if (const auto &node = vars->find(objname)) {
977 this->importVariable(*node);
979 return retval;
980 }
981 }
982 return nullptr;
983}
984
985template <>
986RooAbsPdf *RooJSONFactoryWSTool::requestImpl<RooAbsPdf>(const std::string &objname)
987{
989 return retval;
990 auto it = _distributionsByName.find(objname);
991 if (it != _distributionsByName.end()) {
992 this->importFunction(*it->second, true);
994 return retval;
995 }
996 return nullptr;
997}
998
999template <>
1000RooAbsReal *RooJSONFactoryWSTool::requestImpl<RooAbsReal>(const std::string &objname)
1001{
1003 return retval;
1004 if (isNumber(objname))
1007 return pdf;
1009 return var;
1011 return retval;
1012 auto it = _functionsByName.find(objname);
1013 if (it != _functionsByName.end()) {
1014 this->importFunction(*it->second, true);
1016 return retval;
1017 }
1018 return nullptr;
1019}
1020
1021/**
1022 * @brief Export a variable from the workspace to a JSONNode.
1023 *
1024 * This function exports a variable, represented by the provided RooAbsArg pointer 'v', from the workspace to a
1025 * JSONNode. The variable's information is added to the JSONNode as key-value pairs.
1026 *
1027 * @param v The pointer to the RooAbsArg representing the variable to be exported.
1028 * @param node The JSONNode to which the variable will be exported.
1029 * @return void
1030 */
1032{
1033 auto *cv = dynamic_cast<const RooConstVar *>(v);
1034 auto *rrv = dynamic_cast<const RooRealVar *>(v);
1035 if (!cv && !rrv)
1036 return;
1037
1038 // for RooConstVar, if name and value are the same, we don't need to do anything
1039 if (cv && strcmp(cv->GetName(), TString::Format("%g", cv->getVal()).Data()) == 0) {
1040 return;
1041 }
1042
1043 JSONNode &var = appendNamedChild(node, v->GetName());
1044
1045 if (cv) {
1046 var["value"] << cv->getVal();
1047 var["const"] << true;
1048 } else if (rrv) {
1049 var["value"] << rrv->getVal();
1050 if (rrv->isConstant() && storeConstant) {
1051 var["const"] << rrv->isConstant();
1052 } else if (storeBins) {
1053 var["min"] << rrv->getMin();
1054 var["max"] << rrv->getMax();
1055 }
1056 if (rrv->getBins() != 100 && storeBins) {
1057 var["nbins"] << rrv->getBins();
1058 }
1059 _domains->readVariable(*rrv);
1060 }
1061}
1062
1063/**
1064 * @brief Export variables from the workspace to a JSONNode.
1065 *
1066 * This function exports variables, represented by the provided RooArgSet, from the workspace to a JSONNode.
1067 * The variables' information is added to the JSONNode as key-value pairs.
1068 *
1069 * @param allElems The RooArgSet representing the variables to be exported.
1070 * @param n The JSONNode to which the variables will be exported.
1071 * @return void
1072 */
1074{
1075 // export a list of RooRealVar objects
1076 n.set_seq();
1077 for (RooAbsArg *arg : allElems) {
1079 }
1080}
1081
1083 const std::string &formula)
1084{
1085 std::string newname = std::string(original->GetName()) + suffix;
1087 trafo_node["type"] << "generic_function";
1088 trafo_node["expression"] << TString::Format(formula.c_str(), original->GetName()).Data();
1089 this->setAttribute(newname, "roofit_skip"); // this function should not be imported back in
1090 return newname;
1091}
1092
1093/**
1094 * @brief Export an object from the workspace to a JSONNode.
1095 *
1096 * This function exports an object, represented by the provided RooAbsArg, from the workspace to a JSONNode.
1097 * The object's information is added to the JSONNode as key-value pairs.
1098 *
1099 * @param func The RooAbsArg representing the object to be exported.
1100 * @param exportedObjectNames A set of strings containing names of previously exported objects to avoid duplicates.
1101 * This set is updated with the name of the newly exported object.
1102 * @return void
1103 */
1105{
1106 // const std::string name = sanitizeName(func.GetName());
1107 std::string name = func.GetName();
1108
1109 // if this element was already exported, skip
1111 return;
1112
1113 exportedObjectNames.insert(name);
1114
1115 if (auto simPdf = dynamic_cast<RooSimultaneous const *>(&func)) {
1116 // RooSimultaneous is not used in the HS3 standard, we only export the
1117 // dependents and some ROOT internal information.
1119
1120 std::vector<std::string> channelNames;
1121 for (auto const &item : simPdf->indexCat()) {
1122 channelNames.push_back(item.first);
1123 }
1124
1125 auto &infoNode = getRooFitInternal(*_rootnodeOutput, "combined_distributions").set_map();
1126 auto &child = infoNode[simPdf->GetName()].set_map();
1127 child["index_cat"] << simPdf->indexCat().GetName();
1128 exportCategory(simPdf->indexCat(), child);
1129 child["distributions"].set_seq();
1130 for (auto const &item : simPdf->indexCat()) {
1131 child["distributions"].append_child() << simPdf->getPdf(item.first.c_str())->GetName();
1132 }
1133
1134 return;
1135 } else if (dynamic_cast<RooAbsCategory const *>(&func)) {
1136 // categories are created by the respective RooSimultaneous, so we're skipping the export here
1137 return;
1138 } else if (dynamic_cast<RooRealVar const *>(&func) || dynamic_cast<RooConstVar const *>(&func)) {
1139 exportVariable(&func, *_varsNode, true, false);
1140 return;
1141 }
1142
1143 auto &collectionNode = (*_rootnodeOutput)[dynamic_cast<RooAbsPdf const *>(&func) ? "distributions" : "functions"];
1144
1145 auto const &exporters = RooFit::JSONIO::exporters();
1146 auto const &exportKeys = RooFit::JSONIO::exportKeys();
1147
1148 TClass *cl = func.IsA();
1149
1151
1152 auto it = exporters.find(cl);
1153 if (it != exporters.end()) { // check if we have a specific exporter available
1154 for (auto &exp : it->second) {
1155 _serversToExport.clear();
1156 _serversToDelete.clear();
1157 if (!exp->exportObject(this, &func, elem)) {
1158 // The exporter might have messed with the content of the node
1159 // before failing. That's why we clear it and only reset the name.
1160 elem.clear();
1161 elem.set_map();
1163 elem["name"] << name;
1164 }
1165 continue;
1166 }
1167 if (exp->autoExportDependants()) {
1169 } else {
1171 }
1172 for (auto &s : _serversToDelete) {
1173 delete s;
1174 }
1175 return;
1176 }
1177 }
1178
1179 // generic export using the factory expressions
1180 const auto &dict = exportKeys.find(cl);
1181 if (dict == exportKeys.end()) {
1182 std::cerr << "unable to export class '" << cl->GetName() << "' - no export keys available!\n"
1183 << "there are several possible reasons for this:\n"
1184 << " 1. " << cl->GetName() << " is a custom class that you or some package you are using added.\n"
1185 << " 2. " << cl->GetName()
1186 << " is a ROOT class that nobody ever bothered to write a serialization definition for.\n"
1187 << " 3. something is wrong with your setup, e.g. you might have called "
1188 "RooFit::JSONIO::clearExportKeys() and/or never successfully read a file defining these "
1189 "keys with RooFit::JSONIO::loadExportKeys(filename)\n"
1190 << "either way, please make sure that:\n"
1191 << " 3: you are reading a file with export keys - call RooFit::JSONIO::printExportKeys() to "
1192 "see what is available\n"
1193 << " 2 & 1: you might need to write a serialization definition yourself. check "
1194 "https://root.cern/doc/master/group__roofit__dev__docs__hs3.html to "
1195 "see how to do this!\n";
1196 return;
1197 }
1198
1199 elem["type"] << dict->second.type;
1200
1201 size_t nprox = func.numProxies();
1202
1203 for (size_t i = 0; i < nprox; ++i) {
1204 RooAbsProxy *p = func.getProxy(i);
1205 if (!p)
1206 continue;
1207
1208 // some proxies start with a "!". This is a magic symbol that we don't want to stream
1209 std::string pname(p->name());
1210 if (pname[0] == '!')
1211 pname.erase(0, 1);
1212
1213 auto k = dict->second.proxies.find(pname);
1214 if (k == dict->second.proxies.end()) {
1215 std::cerr << "failed to find key matching proxy '" << pname << "' for type '" << dict->second.type
1216 << "', encountered in '" << func.GetName() << "', skipping" << std::endl;
1217 return;
1218 }
1219
1220 // empty string is interpreted as an instruction to ignore this value
1221 if (k->second.empty())
1222 continue;
1223
1224 if (auto l = dynamic_cast<RooAbsCollection *>(p)) {
1225 fillSeq(elem[k->second], *l);
1226 }
1227 if (auto r = dynamic_cast<RooArgProxy *>(p)) {
1228 if (isLiteralConstVar(*r->absArg())) {
1229 elem[k->second] << static_cast<RooConstVar *>(r->absArg())->getVal();
1230 } else {
1231 elem[k->second] << r->absArg()->GetName();
1232 }
1233 }
1234 }
1235
1236 // export all the servers of a given RooAbsArg
1237 for (RooAbsArg *s : func.servers()) {
1238 if (!s) {
1239 std::cerr << "unable to locate server of " << func.GetName() << std::endl;
1240 continue;
1241 }
1243 }
1244}
1245
1246/**
1247 * @brief Import a function from the JSONNode into the workspace.
1248 *
1249 * This function imports a function from the given JSONNode into the workspace.
1250 * The function's information is read from the JSONNode and added to the workspace.
1251 *
1252 * @param p The JSONNode representing the function to be imported.
1253 * @param importAllDependants A boolean flag indicating whether to import all dependants (servers) of the function.
1254 * @return void
1255 */
1257{
1258 std::string name(RooJSONFactoryWSTool::name(p));
1259
1260 // If this node if marked to be skipped by RooFit, exit
1261 if (hasAttribute(name, "roofit_skip")) {
1262 return;
1263 }
1264
1265 auto const &importers = RooFit::JSONIO::importers();
1267
1268 // some preparations: what type of function are we dealing with here?
1270
1271 // if the RooAbsArg already exists, we don't need to do anything
1272 if (_workspace.arg(name)) {
1273 return;
1274 }
1275 // if the key we found is not a map, it's an error
1276 if (!p.is_map()) {
1277 std::stringstream ss;
1278 ss << "RooJSONFactoryWSTool() function node " + name + " is not a map!";
1280 return;
1281 }
1282 std::string prefix = genPrefix(p, true);
1283 if (!prefix.empty())
1284 name = prefix + name;
1285 if (!p.has_child("type")) {
1286 std::stringstream ss;
1287 ss << "RooJSONFactoryWSTool() no type given for function '" << name << "', skipping." << std::endl;
1289 return;
1290 }
1291
1292 std::string functype(p["type"].val());
1293
1294 // import all dependents if importing a workspace, not for creating new objects
1295 if (!importAllDependants) {
1296 this->importDependants(p);
1297 }
1298
1299 // check for specific implementations
1300 auto it = importers.find(functype);
1301 bool ok = false;
1302 if (it != importers.end()) {
1303 for (auto &imp : it->second) {
1304 try {
1305 ok = imp->importArg(this, p);
1306 } catch (const std::exception &e) {
1307 std::stringstream ss;
1308 const auto *ptr = imp.get();
1309 ss << "RooJSONFactoryWSTool() failed. The importer " << typeid(*ptr).name()
1310 << " emitted and error: " << e.what() << std::endl;
1312 }
1313 if (ok)
1314 break;
1315 }
1316 }
1317 if (!ok) { // generic import using the factory expressions
1318 auto expr = factoryExpressions.find(functype);
1319 if (expr != factoryExpressions.end()) {
1320 std::string expression = ::generate(expr->second, p, this);
1321 if (!_workspace.factory(expression)) {
1322 std::stringstream ss;
1323 ss << "RooJSONFactoryWSTool() failed to create " << expr->second.tclass->GetName() << " '" << name
1324 << "', skipping. expression was\n"
1325 << expression << std::endl;
1327 }
1328 } else {
1329 std::stringstream ss;
1330 ss << "RooJSONFactoryWSTool() no handling for type '" << functype << "' implemented, skipping."
1331 << "\n"
1332 << "there are several possible reasons for this:\n"
1333 << " 1. " << functype << " is a custom type that is not available in RooFit.\n"
1334 << " 2. " << functype
1335 << " is a ROOT class that nobody ever bothered to write a deserialization definition for.\n"
1336 << " 3. something is wrong with your setup, e.g. you might have called "
1337 "RooFit::JSONIO::clearFactoryExpressions() and/or never successfully read a file defining "
1338 "these expressions with RooFit::JSONIO::loadFactoryExpressions(filename)\n"
1339 << "either way, please make sure that:\n"
1340 << " 3: you are reading a file with factory expressions - call "
1341 "RooFit::JSONIO::printFactoryExpressions() "
1342 "to see what is available\n"
1343 << " 2 & 1: you might need to write a deserialization definition yourself. check "
1344 "https://root.cern/doc/master/group__roofit__dev__docs__hs3.html to see "
1345 "how to do this!"
1346 << std::endl;
1348 return;
1349 }
1350 }
1352 if (!func) {
1353 std::stringstream err;
1354 err << "something went wrong importing function '" << name << "'.";
1355 RooJSONFactoryWSTool::error(err.str());
1356 }
1357}
1358
1359/**
1360 * @brief Import a function from a JSON string into the workspace.
1361 *
1362 * This function imports a function from the provided JSON string into the workspace.
1363 * The function's information is read from the JSON string and added to the workspace.
1364 *
1365 * @param jsonString The JSON string containing the function information.
1366 * @param importAllDependants A boolean flag indicating whether to import all dependants (servers) of the function.
1367 * @return void
1368 */
1370{
1371 this->importFunction((JSONTree::create(jsonString))->rootnode(), importAllDependants);
1372}
1373
1374/**
1375 * @brief Export the name and binning of a RooRealVar to a JSONNode.
1376 *
1377 * @param obsNode The JSONNode to which the axis information will be exported.
1378 * @param var The RooRealVar representing the axis to be exported.
1379 * @return void
1380 */
1382{
1383 std::string name = var.GetName();
1385 obsNode["name"] << name;
1386
1387 auto const &binning = var.getBinning();
1388 if (binning.isUniform()) {
1389 obsNode["min"] << var.getMin();
1390 obsNode["max"] << var.getMax();
1391 obsNode["nbins"] << var.getBins();
1392 } else {
1393 auto &edges = obsNode["edges"].set_seq();
1394 edges.append_child() << binning.binLow(0);
1395 for (int i = 0; i < binning.numBins(); ++i) {
1396 edges.append_child() << binning.binHigh(i);
1397 }
1398 }
1399}
1400
1401/**
1402 * @brief Export histogram data to a JSONNode.
1403 *
1404 * This function exports histogram data, represented by the provided variables and contents, to a JSONNode.
1405 * The histogram's axes information and bin contents are added as key-value pairs to the JSONNode.
1406 *
1407 * @param vars The RooArgSet representing the variables associated with the histogram.
1408 * @param n The number of bins in the histogram.
1409 * @param contents A pointer to the array containing the bin contents of the histogram.
1410 * @param output The JSONNode to which the histogram data will be exported.
1411 * @return void
1412 */
1413void RooJSONFactoryWSTool::exportHisto(RooArgSet const &vars, std::size_t n, double const *contents, JSONNode &output)
1414{
1415 auto &observablesNode = output["axes"].set_seq();
1416 // axes have to be ordered to get consistent bin indices
1417 for (auto *var : static_range_cast<RooRealVar *>(vars)) {
1418 exportAxis(observablesNode.append_child().set_map(), *var);
1419 }
1420
1421 return exportArray(n, contents, output["contents"]);
1422}
1423
1424/**
1425 * @brief Export an array of doubles to a JSONNode.
1426 *
1427 * This function exports an array of doubles, represented by the provided size and contents,
1428 * to a JSONNode. The array elements are added to the JSONNode as a sequence of values.
1429 *
1430 * @param n The size of the array.
1431 * @param contents A pointer to the array containing the double values.
1432 * @param output The JSONNode to which the array will be exported.
1433 * @return void
1434 */
1435void RooJSONFactoryWSTool::exportArray(std::size_t n, double const *contents, JSONNode &output)
1436{
1437 output.set_seq();
1438 for (std::size_t i = 0; i < n; ++i) {
1439 double w = contents[i];
1440 // To make sure there are no unnecessary floating points in the JSON
1441 if (int(w) == w) {
1442 output.append_child() << int(w);
1443 } else {
1444 output.append_child() << w;
1445 }
1446 }
1447}
1448
1449/**
1450 * @brief Export a RooAbsCategory object to a JSONNode.
1451 *
1452 * This function exports a RooAbsCategory object, represented by the provided categories and indices,
1453 * to a JSONNode. The category labels and corresponding indices are added to the JSONNode as key-value pairs.
1454 *
1455 * @param cat The RooAbsCategory object to be exported.
1456 * @param node The JSONNode to which the category data will be exported.
1457 * @return void
1458 */
1459namespace {
1460
1461// Turn an arbitrary string into a valid variable name, but refuse to change the
1462// first character (which would silently rename the object).
1463std::string makeValidNameOrError(std::string const &in)
1464{
1465 if (!std::isalpha(in[0])) {
1466 RooJSONFactoryWSTool::error("refusing to change first character of string '" + in + "' to make a valid name!");
1467 }
1468 std::string out = RooFit::Detail::makeValidVarName(in);
1469 if (out != in) {
1470 oocoutW(nullptr, IO) << "RooFitHS3: changed '" << in << "' to '" << out << "' to become a valid name";
1471 }
1472 return out;
1473}
1474
1475} // namespace
1476
1478{
1479 auto &labels = node["labels"].set_seq();
1480 auto &indices = node["indices"].set_seq();
1481
1482 for (auto const &item : cat) {
1483 labels.append_child() << makeValidNameOrError(item.first);
1484 indices.append_child() << item.second;
1485 }
1486}
1487
1488/**
1489 * @brief Export combined data from the workspace to a custom struct.
1490 *
1491 * This function exports combined data from the workspace, represented by the provided RooAbsData object,
1492 * to a CombinedData struct. The struct contains information such as variables, categories,
1493 * and bin contents of the combined data.
1494 *
1495 * @param data The RooAbsData object representing the combined data to be exported.
1496 * @return CombinedData A custom struct containing the exported combined data.
1497 */
1499{
1500 // find category observables
1501 RooAbsCategory *cat = nullptr;
1502 for (RooAbsArg *obs : *data.get()) {
1503 if (dynamic_cast<RooAbsCategory *>(obs)) {
1504 if (cat) {
1505 RooJSONFactoryWSTool::error("dataset '" + std::string(data.GetName()) +
1506 " has several category observables!");
1507 }
1508 cat = static_cast<RooAbsCategory *>(obs);
1509 }
1510 }
1511
1512 // prepare return value
1514
1515 if (!cat)
1516 return datamap;
1517 // this is a combined dataset
1518
1519 datamap.name = data.GetName();
1520
1521 // Write information necessary to reconstruct the combined dataset upon import
1522 auto &child = getRooFitInternal(*_rootnodeOutput, "combined_datasets").set_map()[data.GetName()].set_map();
1523 child["index_cat"] << cat->GetName();
1524 exportCategory(*cat, child);
1525
1526 // Find a RooSimultaneous model that would fit to this dataset
1527 RooSimultaneous const *simPdf = nullptr;
1528 auto *combinedPdfInfoNode = findRooFitInternal(*_rootnodeOutput, "combined_distributions");
1529 if (combinedPdfInfoNode) {
1530 for (auto &info : combinedPdfInfoNode->children()) {
1531 if (info["index_cat"].val() == cat->GetName()) {
1532 simPdf = static_cast<RooSimultaneous const *>(_workspace.pdf(info.key()));
1533 }
1534 }
1535 }
1536
1537 // If there is an associated simultaneous pdf for the index category, we
1538 // use the RooAbsData::split() overload that takes the RooSimultaneous.
1539 // Like this, the observables that are not relevant for a given channel
1540 // are automatically split from the component datasets.
1541 std::vector<std::unique_ptr<RooAbsData>> dataList{simPdf ? data.split(*simPdf, true) : data.split(*cat, true)};
1542
1543 for (std::unique_ptr<RooAbsData> const &absData : dataList) {
1544 std::string catName(absData->GetName());
1545 std::string dataName = makeValidNameOrError(catName);
1546 absData->SetName((std::string(data.GetName()) + "_" + dataName).c_str());
1547 datamap.components[catName] = absData->GetName();
1548 this->exportData(*absData);
1549 }
1550 return datamap;
1551}
1552
1553/**
1554 * @brief Export data from the workspace to a JSONNode.
1555 *
1556 * This function exports data represented by the provided RooAbsData object,
1557 * to a JSONNode. The data's information is added as key-value pairs to the JSONNode.
1558 *
1559 * @param data The RooAbsData object representing the data to be exported.
1560 * @return void
1561 */
1563{
1564 // find category observables
1565
1566 RooAbsCategory *cat = nullptr;
1567 for (RooAbsArg *obs : *data.get()) {
1568 if (dynamic_cast<RooAbsCategory *>(obs)) {
1569 if (cat) {
1570 RooJSONFactoryWSTool::error("dataset '" + std::string(data.GetName()) +
1571 " has several category observables!");
1572 }
1573 cat = static_cast<RooAbsCategory *>(obs);
1574 }
1575 }
1576
1577 if (cat)
1578 return;
1579
1580 JSONNode &output = appendNamedChild((*_rootnodeOutput)["data"], data.GetName());
1581
1582 // This works around a problem in RooStats/HistFactory that was only fixed
1583 // in ROOT 6.30: until then, the weight variable of the observed dataset,
1584 // called "weightVar", was added to the observables. Therefore, it also got
1585 // added to the Asimov dataset. But the Asimov has its own weight variable,
1586 // called "binWeightAsimov", making "weightVar" an actual observable in the
1587 // Asimov data. But this is only by accident and should be removed.
1588 RooArgSet variables = *data.get();
1589 if (auto weightVar = variables.find("weightVar")) {
1590 variables.remove(*weightVar);
1591 }
1592
1593 // this is a regular binned dataset
1594 if (auto dh = dynamic_cast<RooDataHist const *>(&data)) {
1595 output["type"] << "binned";
1596 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1597 _domains->readVariable(*var);
1598 }
1599 return exportHisto(variables, dh->numEntries(), dh->weightArray(), output);
1600 }
1601
1602 // Check if this actually represents a binned dataset, and then import it
1603 // like a RooDataHist. This happens frequently when people create combined
1604 // RooDataSets from binned data to fit HistFactory models. In this case, it
1605 // doesn't make sense to export them like an unbinned dataset, because the
1606 // coordinates are redundant information with the binning. We only do this
1607 // for 1D data for now.
1608 if (data.isWeighted() && variables.size() == 1) {
1609 bool isBinnedData = false;
1610 auto &x = static_cast<RooRealVar const &>(*variables[0]);
1611 std::vector<double> contents;
1612 int i = 0;
1613 for (; i < data.numEntries(); ++i) {
1614 data.get(i);
1615 if (x.getBin() != i)
1616 break;
1617 contents.push_back(data.weight());
1618 }
1619 if (i == x.getBins())
1620 isBinnedData = true;
1621 if (isBinnedData) {
1622 output["type"] << "binned";
1623 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1624 _domains->readVariable(*var);
1625 }
1626 return exportHisto(variables, data.numEntries(), contents.data(), output);
1627 }
1628 }
1629
1630 // this really is an unbinned dataset
1631 output["type"] << "unbinned";
1632 auto &observablesNode = output["axes"].set_seq();
1633 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1634 _domains->readVariable(*var);
1635 exportAxis(observablesNode.append_child().set_map(), *var);
1636 }
1637 auto &coords = output["entries"].set_seq();
1638 std::vector<double> weightVals;
1639 bool hasNonUnityWeights = false;
1640 for (int i = 0; i < data.numEntries(); ++i) {
1641 data.get(i);
1642 coords.append_child().fill_seq(variables, [](auto x) { return static_cast<RooRealVar *>(x)->getVal(); });
1643 std::string datasetName = data.GetName();
1644 if (data.isWeighted()) {
1645 weightVals.push_back(data.weight());
1646 if (data.weight() != 1.)
1647 hasNonUnityWeights = true;
1648 }
1649 }
1650 if (data.isWeighted() && hasNonUnityWeights) {
1651 output["weights"].fill_seq(weightVals);
1652 }
1653}
1654
1655/**
1656 * @brief Read axes from the JSONNode and create a RooArgSet representing them.
1657 *
1658 * This function reads axes information from the given JSONNode and
1659 * creates a RooArgSet with variables representing these axes.
1660 *
1661 * @param topNode The JSONNode containing the axes information to be read.
1662 * @return RooArgSet A RooArgSet containing the variables created from the JSONNode.
1663 */
1665{
1666 RooArgSet vars;
1667
1668 for (JSONNode const &node : topNode["axes"].children()) {
1669 if (node.has_child("edges")) {
1670 std::vector<double> edges;
1671 for (auto const &bound : node["edges"].children()) {
1672 edges.push_back(bound.val_double());
1673 }
1674 auto obs = std::make_unique<RooRealVar>(node["name"].val().c_str(), node["name"].val().c_str(), edges[0],
1675 edges[edges.size() - 1]);
1676 RooBinning bins(obs->getMin(), obs->getMax());
1677 for (auto b : edges) {
1678 bins.addBoundary(b);
1679 }
1680 obs->setBinning(bins);
1681 vars.addOwned(std::move(obs));
1682 } else {
1683 auto obs = std::make_unique<RooRealVar>(node["name"].val().c_str(), node["name"].val().c_str(),
1684 node["min"].val_double(), node["max"].val_double());
1685 obs->setBins(node["nbins"].val_int());
1686 vars.addOwned(std::move(obs));
1687 }
1688 }
1689
1690 return vars;
1691}
1692
1693/**
1694 * @brief Read binned data from the JSONNode and create a RooDataHist object.
1695 *
1696 * This function reads binned data from the given JSONNode and creates a RooDataHist object.
1697 * The binned data is associated with the specified name and variables (RooArgSet) in the workspace.
1698 *
1699 * @param n The JSONNode representing the binned data to be read.
1700 * @param name The name to be associated with the created RooDataHist object.
1701 * @param vars The RooArgSet representing the variables associated with the binned data.
1702 * @return std::unique_ptr<RooDataHist> A unique pointer to the created RooDataHist object.
1703 */
1704std::unique_ptr<RooDataHist>
1705RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std::string &name, RooArgSet const &vars)
1706{
1707 if (!n.has_child("contents"))
1708 RooJSONFactoryWSTool::error("no contents given");
1709
1710 JSONNode const &contents = n["contents"];
1711
1712 if (!contents.is_seq())
1713 RooJSONFactoryWSTool::error("contents are not in list form");
1714
1715 JSONNode const *errors = nullptr;
1716 if (n.has_child("errors")) {
1717 errors = &n["errors"];
1718 if (!errors->is_seq())
1719 RooJSONFactoryWSTool::error("errors are not in list form");
1720 }
1721
1722 auto bins = generateBinIndices(vars);
1723 if (contents.num_children() != bins.size()) {
1724 std::stringstream errMsg;
1725 errMsg << "inconsistent bin numbers: contents=" << contents.num_children() << ", bins=" << bins.size();
1727 }
1728 auto dh = std::make_unique<RooDataHist>(name, name, vars);
1729 std::vector<double> contentVals;
1730 contentVals.reserve(contents.num_children());
1731 for (auto const &cont : contents.children()) {
1732 contentVals.push_back(cont.val_double());
1733 }
1734 std::vector<double> errorVals;
1735 if (errors) {
1736 errorVals.reserve(errors->num_children());
1737 for (auto const &err : errors->children()) {
1738 errorVals.push_back(err.val_double());
1739 }
1740 }
1741 for (size_t ibin = 0; ibin < bins.size(); ++ibin) {
1742 const double err = errors ? errorVals[ibin] : -1;
1743 dh->set(ibin, contentVals[ibin], err);
1744 }
1745 return dh;
1746}
1747
1748/**
1749 * @brief Import a variable from the JSONNode into the workspace.
1750 *
1751 * This function imports a variable from the given JSONNode into the workspace.
1752 * The variable's information is read from the JSONNode and added to the workspace.
1753 *
1754 * @param p The JSONNode representing the variable to be imported.
1755 * @return void
1756 */
1758{
1759 // import a RooRealVar object
1760 std::string name(RooJSONFactoryWSTool::name(p));
1762
1763 if (_workspace.arg(name))
1764 return;
1765 if (!p.is_map()) {
1766 std::stringstream ss;
1767 ss << "RooJSONFactoryWSTool() node '" << name << "' is not a map, skipping.";
1768 oocoutE(nullptr, InputArguments) << ss.str() << std::endl;
1769 return;
1770 }
1771 if (config().importNoDomainParametersAsRooConstVars && !_domains->hasVariable(name.c_str())) {
1772 if (!p.has_child("value")) {
1773 RooJSONFactoryWSTool::error("cannot instantiate RooConstVar '" + name + "' without \"value\"!");
1774 }
1775 wsEmplace<RooConstVar>(name, p["value"].val_double());
1776 return;
1777 }
1779}
1780
1781/**
1782 * @brief Import all dependants (servers) of a node into the workspace.
1783 *
1784 * This function imports all the dependants (servers) of the given JSONNode into the workspace.
1785 * The dependants' information is read from the JSONNode and added to the workspace.
1786 *
1787 * @param n The JSONNode representing the node whose dependants are to be imported.
1788 * @return void
1789 */
1791{
1792 // import all the dependants of an object
1793 if (JSONNode const *varsNode = getVariablesNode(n)) {
1794 for (const auto &p : varsNode->children()) {
1796 }
1797 }
1798 if (auto seq = n.find("functions")) {
1799 for (const auto &p : seq->children()) {
1800 this->importFunction(p, true);
1801 }
1802 }
1803 if (auto seq = n.find("distributions")) {
1804 for (const auto &p : seq->children()) {
1805 this->importFunction(p, true);
1806 }
1807 }
1808}
1809
1811 const std::vector<CombinedData> &combDataSets,
1812 const std::vector<RooAbsData *> &singleDataSets)
1813{
1814 auto pdf = mc.GetPdf();
1815 auto simpdf = dynamic_cast<RooSimultaneous const *>(pdf);
1816 if (simpdf) {
1817 for (std::size_t i = 0; i < std::max(combDataSets.size(), std::size_t(1)); ++i) {
1818 const bool hasdata = i < combDataSets.size();
1819 if (hasdata && !matches(combDataSets.at(i), simpdf))
1820 continue;
1821
1822 std::string analysisName(simpdf->GetName());
1823 if (hasdata)
1824 analysisName += "_" + combDataSets[i].name;
1825
1826 exportSingleModelConfig(rootnode, mc, analysisName, hasdata ? &combDataSets[i].components : nullptr);
1827 }
1828 } else {
1829 RooArgSet observables(*mc.GetObservables());
1830 int founddata = 0;
1831 for (auto *data : singleDataSets) {
1832 if (observables.equals(*(data->get()))) {
1833 std::map<std::string, std::string> mapping;
1834 mapping[pdf->GetName()] = data->GetName();
1835 exportSingleModelConfig(rootnode, mc, std::string(pdf->GetName()) + "_" + data->GetName(), &mapping);
1836 ++founddata;
1837 }
1838 }
1839 if (founddata == 0) {
1840 exportSingleModelConfig(rootnode, mc, pdf->GetName(), nullptr);
1841 }
1842 }
1843}
1844
1846 std::string const &analysisName,
1847 std::map<std::string, std::string> const *dataComponents)
1848{
1849 auto pdf = mc.GetPdf();
1850
1851 JSONNode &analysisNode = appendNamedChild(rootnode["analyses"], analysisName);
1852
1853 auto &domains = analysisNode["domains"].set_seq();
1854
1855 analysisNode["likelihood"] << analysisName;
1856
1857 auto &nllNode = appendNamedChild(rootnode["likelihoods"], analysisName);
1858 nllNode["distributions"].set_seq();
1859 nllNode["data"].set_seq();
1860
1861 if (dataComponents) {
1862 auto simPdf = dynamic_cast<RooSimultaneous const *>(pdf);
1863 if (simPdf) {
1864 for (auto const &item : simPdf->indexCat()) {
1865 const auto &dataComp = dataComponents->find(item.first);
1866 nllNode["distributions"].append_child() << simPdf->getPdf(item.first)->GetName();
1867 nllNode["data"].append_child() << dataComp->second;
1868 }
1869 } else {
1870 for (auto it : *dataComponents) {
1871 nllNode["distributions"].append_child() << it.first;
1872 nllNode["data"].append_child() << it.second;
1873 }
1874 }
1875 } else {
1876 nllNode["distributions"].append_child() << pdf->GetName();
1877 nllNode["data"].append_child() << 0;
1878 }
1879
1880 if (mc.GetExternalConstraints()) {
1881 auto &extConstrNode = nllNode["aux_distributions"];
1882 extConstrNode.set_seq();
1883 for (const auto &constr : *mc.GetExternalConstraints()) {
1884 extConstrNode.append_child() << constr->GetName();
1885 }
1886 }
1887
1888 auto writeList = [&](const char *name, RooArgSet const *args) {
1889 if (!args || !args->size())
1890 return;
1891
1892 std::vector<std::string> names;
1893 names.reserve(args->size());
1894 for (RooAbsArg const *arg : *args)
1895 names.push_back(arg->GetName());
1896 std::sort(names.begin(), names.end());
1897 analysisNode[name].fill_seq(names);
1898 };
1899
1900 writeList("parameters_of_interest", mc.GetParametersOfInterest());
1901
1902 auto &domainsNode = rootnode["domains"];
1903
1904 auto writeProductDomain = [&](const char *suffix, RooArgSet const *args) {
1905 if (!args || args->empty())
1906 return;
1907 const std::string domainName = analysisName + suffix;
1908 domains.append_child() << domainName;
1910 for (auto *var : static_range_cast<const RooRealVar *>(*args)) {
1911 domain.readVariable(*var);
1912 }
1914 };
1915
1916 writeProductDomain("_nuisance_parameters", mc.GetNuisanceParameters());
1917 writeProductDomain("_global_observables", mc.GetGlobalObservables());
1918 writeProductDomain("_parameters_of_interest", mc.GetParametersOfInterest());
1919
1920 auto &modelConfigAux = getRooFitInternal(rootnode, "ModelConfigs", analysisName);
1921 modelConfigAux.set_map();
1922 modelConfigAux["pdfName"] << pdf->GetName();
1923 modelConfigAux["mcName"] << mc.GetName();
1924}
1925
1926/**
1927 * @brief Export all objects in the workspace to a JSONNode.
1928 *
1929 * This function exports all the objects in the workspace to the provided JSONNode.
1930 * The objects' information is added as key-value pairs to the JSONNode.
1931 *
1932 * @param n The JSONNode to which the objects will be exported.
1933 * @return void
1934 */
1936{
1937 _domains = std::make_unique<RooFit::JSONIO::Detail::Domains>();
1939 _rootnodeOutput = &n;
1940
1941 // export all toplevel pdfs
1942 std::vector<RooAbsPdf *> allpdfs;
1943 for (auto &arg : _workspace.allPdfs()) {
1944 if (!arg->hasClients()) {
1945 if (auto *pdf = dynamic_cast<RooAbsPdf *>(arg)) {
1946 allpdfs.push_back(pdf);
1947 }
1948 }
1949 }
1951 std::set<std::string> exportedObjectNames;
1953
1954 // export all toplevel functions
1955 std::vector<RooAbsReal *> allfuncs;
1956 for (auto &arg : _workspace.allFunctions()) {
1957 if (!arg->hasClients()) {
1958 if (auto *func = dynamic_cast<RooAbsReal *>(arg)) {
1959 allfuncs.push_back(func);
1960 }
1961 }
1962 }
1965
1966 // export attributes of all objects
1967 for (RooAbsArg *arg : _workspace.components()) {
1968 exportAttributes(arg, n);
1969 }
1970
1971 // collect all datasets
1972 std::vector<RooAbsData *> alldata;
1973 for (auto &d : _workspace.allData()) {
1974 alldata.push_back(d);
1975 }
1977 // first, take care of combined datasets
1978 std::vector<RooAbsData *> singleData;
1979 std::vector<RooJSONFactoryWSTool::CombinedData> combData;
1980 for (auto &d : alldata) {
1981 auto data = this->exportCombinedData(*d);
1982 if (!data.components.empty())
1983 combData.push_back(data);
1984 else
1985 singleData.push_back(d);
1986 }
1987 // next, take care datasets
1988 for (auto &d : alldata) {
1989 this->exportData(*d);
1990 }
1991
1992 // export all ModelConfig objects and attached Pdfs
1993 for (TObject *obj : _workspace.allGenericObjects()) {
1994 if (auto mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
1996 }
1997 }
1998
2000
2003 // We only want to add the variables that actually got exported and skip
2004 // the ones that the pdfs encoded implicitly (like in the case of
2005 // HistFactory).
2006 for (RooAbsArg *arg : *snsh) {
2007 bool do_export = false;
2008 for (const auto &pdf : allpdfs) {
2009 if (pdf->dependsOn(*arg)) {
2010 do_export = true;
2011 }
2012 }
2013 if (do_export) {
2014 RooJSONFactoryWSTool::testValidName(arg->GetName(), true);
2015 snapshotSorted.add(*arg);
2016 }
2017 }
2018 snapshotSorted.sort();
2019 std::string name(snsh->GetName());
2020 if (name != "default_values") {
2021 this->exportVariables(snapshotSorted, appendNamedChild(n["parameter_points"], name)["parameters"], true,
2022 false);
2023 }
2024 }
2025 _varsNode = nullptr;
2026 _domains->writeJSON(n["domains"]);
2027 _domains.reset();
2028 _rootnodeOutput = nullptr;
2029}
2030
2031/**
2032 * @brief Import the workspace from a JSON string.
2033 *
2034 * @param s The JSON string containing the workspace data.
2035 * @return bool Returns true on successful import, false otherwise.
2036 */
2038{
2039 std::stringstream ss(s);
2040 return importJSON(ss);
2041}
2042
2043/**
2044 * @brief Import the workspace from a YML string.
2045 *
2046 * @param s The YML string containing the workspace data.
2047 * @return bool Returns true on successful import, false otherwise.
2048 */
2050{
2051 std::stringstream ss(s);
2052 return importYML(ss);
2053}
2054
2055/**
2056 * @brief Export the workspace to a JSON string.
2057 *
2058 * @return std::string The JSON string representing the exported workspace.
2059 */
2061{
2062 std::stringstream ss;
2063 exportJSON(ss);
2064 return ss.str();
2065}
2066
2067/**
2068 * @brief Export the workspace to a YML string.
2069 *
2070 * @return std::string The YML string representing the exported workspace.
2071 */
2073{
2074 std::stringstream ss;
2075 exportYML(ss);
2076 return ss.str();
2077}
2078
2079/**
2080 * @brief Create a new JSON tree with version information.
2081 *
2082 * @return std::unique_ptr<JSONTree> A unique pointer to the created JSON tree.
2083 */
2085{
2086 std::unique_ptr<JSONTree> tree = JSONTree::create();
2087 JSONNode &n = tree->rootnode();
2088 n.set_map();
2089 auto &metadata = n["metadata"].set_map();
2090
2091 // add the mandatory hs3 version number
2092 metadata["hs3_version"] << hs3VersionTag;
2093
2094 // Add information about the ROOT version that was used to generate this file
2095 auto &rootInfo = appendNamedChild(metadata["packages"], "ROOT");
2096 std::string versionName = gROOT->GetVersion();
2097 // We want to consistently use dots such that the version name can be easily
2098 // digested automatically.
2099 std::replace(versionName.begin(), versionName.end(), '/', '.');
2100 rootInfo["version"] << versionName;
2101
2102 return tree;
2103}
2104
2105/**
2106 * @brief Export the workspace to JSON format and write to the output stream.
2107 *
2108 * @param os The output stream to write the JSON data to.
2109 * @return bool Returns true on successful export, false otherwise.
2110 */
2112{
2113 std::unique_ptr<JSONTree> tree = createNewJSONTree();
2114 JSONNode &n = tree->rootnode();
2115 this->exportAllObjects(n);
2116 n.writeJSON(os);
2117 return true;
2118}
2119
2120/**
2121 * @brief Export the workspace to JSON format and write to the specified file.
2122 *
2123 * @param filename The name of the JSON file to create and write the data to.
2124 * @return bool Returns true on successful export, false otherwise.
2125 */
2127{
2128 std::ofstream out(filename.c_str());
2129 if (!out.is_open())
2130 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() invalid output file '" + filename + "'.");
2131 return this->exportJSON(out);
2132}
2133
2134/**
2135 * @brief Export the workspace to YML format and write to the output stream.
2136 *
2137 * @param os The output stream to write the YML data to.
2138 * @return bool Returns true on successful export, false otherwise.
2139 */
2141{
2142 std::unique_ptr<JSONTree> tree = createNewJSONTree();
2143 JSONNode &n = tree->rootnode();
2144 this->exportAllObjects(n);
2145 n.writeYML(os);
2146 return true;
2147}
2148
2149/**
2150 * @brief Export the workspace to YML format and write to the specified file.
2151 *
2152 * @param filename The name of the YML file to create and write the data to.
2153 * @return bool Returns true on successful export, false otherwise.
2154 */
2156{
2157 std::ofstream out(filename.c_str());
2158 if (!out.is_open())
2159 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() invalid output file '" + filename + "'.");
2160 return this->exportYML(out);
2161}
2162
2163bool RooJSONFactoryWSTool::hasAttribute(const std::string &obj, const std::string &attrib)
2164{
2165 if (!_attributesNode)
2166 return false;
2167 if (auto attrNode = _attributesNode->find(obj)) {
2168 if (auto seq = attrNode->find("tags")) {
2169 for (auto &a : seq->children()) {
2170 if (a.val() == attrib)
2171 return true;
2172 }
2173 }
2174 }
2175 return false;
2176}
2177void RooJSONFactoryWSTool::setAttribute(const std::string &obj, const std::string &attrib)
2178{
2179 auto node = &RooJSONFactoryWSTool::getRooFitInternal(*_rootnodeOutput, "attributes").set_map()[obj].set_map();
2180 auto &tags = (*node)["tags"];
2181 tags.set_seq();
2182 tags.append_child() << attrib;
2183}
2184
2185std::string RooJSONFactoryWSTool::getStringAttribute(const std::string &obj, const std::string &attrib)
2186{
2187 if (!_attributesNode)
2188 return "";
2189 if (auto attrNode = _attributesNode->find(obj)) {
2190 if (auto dict = attrNode->find("dict")) {
2191 if (auto *a = dict->find(attrib)) {
2192 return a->val();
2193 }
2194 }
2195 }
2196 return "";
2197}
2198void RooJSONFactoryWSTool::setStringAttribute(const std::string &obj, const std::string &attrib,
2199 const std::string &value)
2200{
2201 auto node = &RooJSONFactoryWSTool::getRooFitInternal(*_rootnodeOutput, "attributes").set_map()[obj].set_map();
2202 auto &dict = (*node)["dict"];
2203 dict.set_map();
2204 dict[attrib] << value;
2205}
2206
2207/**
2208 * @brief Imports all nodes of the JSON data and adds them to the workspace.
2209 *
2210 * @param n The JSONNode representing the root node of the JSON data.
2211 * @return void
2212 */
2214{
2215 // Per HS3 standard, the hs3_version in the metadata is required. So we
2216 // error out if it is missing. TODO: now we are only checking if the
2217 // hs3_version tag exists, but in the future when the HS3 specification
2218 // versions are actually frozen, we should also check if the hs3_version is
2219 // one that RooFit can actually read.
2220 auto metadata = n.find("metadata");
2221 if (!metadata || !metadata->find("hs3_version")) {
2222 std::stringstream ss;
2223 ss << "The HS3 version is missing in the JSON!\n"
2224 << "Please include the HS3 version in the metadata field, e.g.:\n"
2225 << " \"metadata\" :\n"
2226 << " {\n"
2227 << " \"hs3_version\" : \"" << hs3VersionTag << "\"\n"
2228 << " }";
2229 error(ss.str());
2230 }
2231
2232 _rootnodeInput = &n;
2233
2235
2236 _domains = std::make_unique<RooFit::JSONIO::Detail::Domains>();
2237 if (auto domains = n.find("domains")) {
2238 _domains->readJSON(*domains);
2239 }
2240 _domains->populate(_workspace);
2241
2242 // Build name-keyed indices over the "functions" and "distributions"
2243 // sequences. Without these, every cross-reference resolved during import
2244 // (e.g. dependencies of a PiecewiseInterpolation, or factory-expression
2245 // arguments) triggers a linear scan over all sibling nodes via
2246 // findNamedChild(), which becomes O(N^2) on workspaces with thousands of
2247 // entries. Populating the maps up-front turns each lookup into O(1).
2248 _functionsByName.clear();
2249 _distributionsByName.clear();
2250 if (auto seq = n.find("functions")) {
2251 if (seq->is_seq()) {
2252 _functionsByName.reserve(seq->num_children());
2253 for (const auto &p : seq->children()) {
2255 }
2256 }
2257 }
2258 if (auto seq = n.find("distributions")) {
2259 if (seq->is_seq()) {
2260 _distributionsByName.reserve(seq->num_children());
2261 for (const auto &p : seq->children()) {
2263 }
2264 }
2265 }
2266
2267 this->importDependants(n);
2268
2269 if (auto paramPointsNode = n.find("parameter_points")) {
2270 for (const auto &snsh : paramPointsNode->children()) {
2271 std::string name = RooJSONFactoryWSTool::name(snsh);
2273
2274 RooArgSet vars;
2275 for (const auto &var : snsh["parameters"].children()) {
2278 vars.add(*rrv);
2279 }
2280 }
2282 }
2283 }
2284
2286
2287 // Import attributes
2288 if (_attributesNode) {
2289 for (const auto &elem : _attributesNode->children()) {
2290 if (RooAbsArg *arg = _workspace.arg(elem.key()))
2291 importAttributes(arg, elem);
2292 }
2293 }
2294
2295 _attributesNode = nullptr;
2296
2297 // We delay the import of the data to after combineDatasets(), because it
2298 // might be that some datasets are merged to combined datasets there. In
2299 // that case, we will remove the components from the "datasets" vector so they
2300 // don't get imported.
2301 std::vector<std::unique_ptr<RooAbsData>> datasets;
2302 if (auto dataNode = n.find("data")) {
2303 for (const auto &p : dataNode->children()) {
2304 datasets.push_back(loadData(p, _workspace));
2305 }
2306 }
2307
2308 // Now, read in analyses and likelihoods if there are any
2309
2310 if (auto analysesNode = n.find("analyses")) {
2311 for (JSONNode const &analysisNode : analysesNode->children()) {
2312 importAnalysis(*_rootnodeInput, analysisNode, n["likelihoods"], n["domains"], _workspace, datasets);
2313 }
2314 }
2315
2316 combineDatasets(*_rootnodeInput, datasets);
2317
2318 for (auto const &d : datasets) {
2319 if (d) {
2321 for (auto const &obs : *d->get()) {
2322 if (auto *rrv = dynamic_cast<RooRealVar *>(obs)) {
2323 _workspace.var(rrv->GetName())->setBinning(rrv->getBinning());
2324 }
2325 }
2326 }
2327 }
2328
2329 _rootnodeInput = nullptr;
2330 _domains.reset();
2331 _functionsByName.clear();
2332 _distributionsByName.clear();
2333}
2334
2335/**
2336 * @brief Imports a JSON file from the given input stream to the workspace.
2337 *
2338 * @param is The input stream containing the JSON data.
2339 * @return bool Returns true on successful import, false otherwise.
2340 */
2342{
2343 // import a JSON file to the workspace
2344 std::unique_ptr<JSONTree> tree = JSONTree::create(is);
2345 JSONNode const &rootnode = tree->rootnode();
2346 this->importAllNodes(rootnode);
2347 if (this->workspace()->getSnapshot("default_values")) {
2348 this->workspace()->loadSnapshot("default_values");
2349 }
2350 importParameterStepWidths(*this->workspace(), rootnode);
2351 return true;
2352}
2353
2354/**
2355 * @brief Imports a JSON file from the given filename to the workspace.
2356 *
2357 * @param filename The name of the JSON file to import.
2358 * @return bool Returns true on successful import, false otherwise.
2359 */
2361{
2362 // import a JSON file to the workspace
2363 std::ifstream infile(filename.c_str());
2364 if (!infile.is_open())
2365 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() invalid input file '" + filename + "'.");
2366 return this->importJSON(infile);
2367}
2368
2369/**
2370 * @brief Imports a YML file from the given input stream to the workspace.
2371 *
2372 * @param is The input stream containing the YML data.
2373 * @return bool Returns true on successful import, false otherwise.
2374 */
2376{
2377 // import a YML file to the workspace
2378 std::unique_ptr<JSONTree> tree = JSONTree::create(is);
2379 JSONNode const &rootnode = tree->rootnode();
2380 this->importAllNodes(rootnode);
2381 importParameterStepWidths(*this->workspace(), rootnode);
2382 return true;
2383}
2384
2385/**
2386 * @brief Imports a YML file from the given filename to the workspace.
2387 *
2388 * @param filename The name of the YML file to import.
2389 * @return bool Returns true on successful import, false otherwise.
2390 */
2392{
2393 // import a YML file to the workspace
2394 std::ifstream infile(filename.c_str());
2395 if (!infile.is_open())
2396 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() invalid input file '" + filename + "'.");
2397 return this->importYML(infile);
2398}
2399
2400void RooJSONFactoryWSTool::importJSONElement(const std::string &name, const std::string &jsonString)
2401{
2402 // Create the JSON Tree from the string
2403 std::unique_ptr<RooFit::Detail::JSONTree> tree = RooFit::Detail::JSONTree::create(jsonString);
2404 JSONNode &n = tree->rootnode();
2405
2406 // If the objects containts a parameter of interest, import it as a modelConfig
2407 if (n.find("poi")) {
2408
2409 RooStats::ModelConfig modelConfig{"ModelConfig"};
2410 std::string poi = n.find("poi")->val();
2411 std::string pdname = n.find("pdfName")->val();
2412 modelConfig.SetWS(_workspace);
2413 modelConfig.SetPdf(pdname.c_str());
2414 modelConfig.SetParametersOfInterest(_workspace.argSet(poi));
2416
2417 return;
2418 }
2419
2420 n["name"] << name;
2421
2422 bool isVariable = true;
2423 bool isData = false;
2424 // Check for the type of object, if it doesn't contain a type, it must be a variable
2425 if (n.find("type")) {
2426 isVariable = false;
2427 std::string elementType = n.find("type")->val();
2428 if (elementType == "binned" || elementType == "unbinned") {
2429 isData = true;
2430 }
2431 }
2432
2433 // Import the object to the workspace
2434 if (isVariable) {
2435 this->importVariableElement(n);
2436 } else if (isData) {
2437 auto absData = loadData(n, _workspace);
2439 } else {
2440 this->importFunction(n, false);
2441 }
2442}
2443
2445{
2446 std::unique_ptr<RooFit::Detail::JSONTree> tree = varJSONString(elementNode);
2447 JSONNode &n = tree->rootnode();
2448 _domains = std::make_unique<RooFit::JSONIO::Detail::Domains>();
2449 if (auto domains = n.find("domains"))
2450 _domains->readJSON(*domains);
2451
2452 _rootnodeInput = &n;
2454
2456 const auto &p = varsNode->child(0);
2458
2459 auto paramPointsNode = n.find("parameter_points");
2460 const auto &snsh = paramPointsNode->child(0);
2461 std::string name = RooJSONFactoryWSTool::name(snsh);
2462 RooArgSet vars;
2463 const auto &var = snsh["parameters"].child(0);
2466 vars.add(*rrv);
2467 }
2468
2469 // Import attributes
2470 if (_attributesNode) {
2471 for (const auto &elem : _attributesNode->children()) {
2472 if (RooAbsArg *arg = _workspace.arg(elem.key()))
2473 importAttributes(arg, elem);
2474 }
2475 }
2476
2477 _attributesNode = nullptr;
2478 _rootnodeInput = nullptr;
2479 _domains.reset();
2480}
2481
2482/**
2483 * @brief Writes a warning message to the RooFit message service.
2484 *
2485 * @param str The warning message to be logged.
2486 * @return std::ostream& A reference to the output stream.
2487 */
2488std::ostream &RooJSONFactoryWSTool::warning(std::string const &str)
2489{
2490 return RooMsgService::instance().log(nullptr, RooFit::MsgLevel::ERROR, RooFit::IO) << str << std::endl;
2491}
2492
2493/**
2494 * @brief Writes an error message to the RooFit message service and throws a runtime_error.
2495 *
2496 * @param s The error message to be logged and thrown.
2497 * @return void
2498 */
2500{
2501 RooMsgService::instance().log(nullptr, RooFit::MsgLevel::ERROR, RooFit::IO) << s << std::endl;
2502 throw std::runtime_error(s);
2503}
2504
2505/**
2506 * @brief Cleans up names to the HS3 standard
2507 *
2508 * @param str The string to be sanitized.
2509 * @return std::string
2510 */
2511std::string RooJSONFactoryWSTool::sanitizeName(const std::string str)
2512{
2513 std::string result;
2514 if (RooJSONFactoryWSTool::config().allowSanitizeNames) {
2515 for (char c : str) {
2516 switch (c) {
2517 case '[':
2518 case '|':
2519 case ',':
2520 case '(': result += '_'; break;
2521 case ']':
2522 case ')':
2523 // skip these characters entirely
2524 break;
2525 case '.': result += "_dot_"; break;
2526 case '@': result += "at"; break;
2527 case '-': result += "minus"; break;
2528 case '/': result += "_div_"; break;
2529
2530 default: result += c; break;
2531 }
2532 }
2533 return result;
2534 }
2535 return str;
2536}
2537
2539{
2540 // Variables
2541
2543 if (onlyModelConfig) {
2544 for (auto *obj : ws.allGenericObjects()) {
2545 if (auto *mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
2546 tmpWS.import(*mc->GetPdf(), RooFit::RecycleConflictNodes(true));
2547 }
2548 }
2549
2550 } else {
2551
2552 for (auto *pdf : ws.allPdfs()) {
2553 if (!pdf->hasClients()) {
2554 tmpWS.import(*pdf, RooFit::RecycleConflictNodes(true));
2555 }
2556 }
2557
2558 for (auto *func : ws.allFunctions()) {
2559 if (!func->hasClients()) {
2560 tmpWS.import(*func, RooFit::RecycleConflictNodes(true));
2561 }
2562 }
2563 }
2564
2565 for (auto *data : ws.allData()) {
2566 tmpWS.import(*data);
2567 }
2568
2569 for (auto *obj : ws.allGenericObjects()) {
2570 tmpWS.import(*obj);
2571 }
2572
2573 for (auto *obj : ws.allResolutionModels()) {
2574 tmpWS.import(*obj);
2575 }
2576
2577 for (auto *snsh : ws.getSnapshots()) {
2578 auto *snshSet = dynamic_cast<RooArgSet *>(snsh);
2579 if (snshSet) {
2580 tmpWS.saveSnapshot(snshSet->GetName(), *snshSet, true);
2581 }
2582 }
2583
2584 return tmpWS;
2585}
2586
2592
2593// Sanitize all names in the workspace to be HS3 compliant
2595{
2596 // Variables
2597
2598 RooWorkspace tmpWS = cleanWS(ws, false);
2599
2600 auto sanitizeIfNeeded = [](auto const &list) {
2601 for (auto *obj : list) {
2602 if (!isValidName(obj->GetName())) {
2603 obj->SetName(sanitizeName(obj->GetName()).c_str());
2604 }
2605 }
2606 };
2607 sanitizeIfNeeded(tmpWS.allVars());
2608 sanitizeIfNeeded(tmpWS.allFunctions());
2609 sanitizeIfNeeded(tmpWS.allPdfs());
2610 sanitizeIfNeeded(tmpWS.allResolutionModels());
2611 // Datasets
2612 for (auto *data : tmpWS.allData()) {
2613 // Sanitize dataset name
2614 if (!isValidName(data->GetName())) {
2615 data->SetName(sanitizeName(data->GetName()).c_str());
2616 }
2617 for (auto *obj : *data->get()) {
2618 obj->SetName(sanitizeName(obj->GetName()).c_str());
2619 }
2620 }
2621 for (auto *data : tmpWS.allEmbeddedData()) {
2622 // Sanitize dataset name
2623 data->SetName(sanitizeName(data->GetName()).c_str());
2624 for (auto *obj : *data->get()) {
2625 obj->SetName(sanitizeName(obj->GetName()).c_str());
2626 }
2627 }
2628 for (auto *snshObj : tmpWS.getSnapshots()) {
2629 // Snapshots are stored as TObject*, but really they are RooArgSet*
2630 auto *snsh = dynamic_cast<RooArgSet *>(snshObj);
2631 if (!snsh) {
2632 std::cerr << "Warning: found snapshot that is not a RooArgSet, skipping\n";
2633 continue;
2634 }
2635
2636 // Sanitize snapshot name
2637 if (!isValidName(snsh->GetName())) {
2638 snsh->setName(sanitizeName(snsh->GetName()).c_str());
2639 }
2640
2641 // Sanitize the variables inside the snapshot
2642 for (auto *arg : *snsh) {
2643 if (!isValidName(arg->GetName())) {
2644 arg->SetName(sanitizeName(arg->GetName()).c_str());
2645 }
2646 }
2647 }
2648
2649 // Generic objects (ModelConfigs, attributes, etc.)
2650 for (auto *obj : tmpWS.allGenericObjects()) {
2651 if (!isValidName(obj->GetName())) {
2652 if (auto *named = dynamic_cast<TNamed *>(obj)) {
2653 named->SetName(sanitizeName(named->GetName()).c_str());
2654 } else {
2655 std::cerr << "Warning: object " << obj->GetName() << " is not TNamed, cannot rename.\n";
2656 }
2657 }
2658
2659 if (auto *mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
2660 // Sanitize ModelConfig name
2661 if (!isValidName(mc->GetName())) {
2662 mc->SetName(sanitizeName(mc->GetName()).c_str());
2663 }
2664
2665 // Sanitize the sets inside ModelConfig
2666 for (auto *obs : mc->GetObservables()->get()) {
2667 if (obs) {
2668 obs->SetName(sanitizeName(obs->GetName()).c_str());
2669 }
2670 }
2671 for (auto *poi : mc->GetParametersOfInterest()->get()) {
2672 if (poi) {
2673 poi->SetName(sanitizeName(poi->GetName()).c_str());
2674 }
2675 }
2676 for (auto *nuis : mc->GetNuisanceParameters()->get()) {
2677 if (nuis) {
2678 nuis->SetName(sanitizeName(nuis->GetName()).c_str());
2679 }
2680 }
2681 for (auto *glob : mc->GetGlobalObservables()->get()) {
2682 if (glob) {
2683 glob->SetName(sanitizeName(glob->GetName()).c_str());
2684 }
2685 }
2686 }
2687 }
2688 std::string wsName = std::string{ws.GetName()} + "_sanitized";
2689 RooWorkspace newWS = cleanWS(tmpWS, false);
2690 newWS.SetName(wsName.c_str());
2691
2692 return newWS;
2693}
std::unique_ptr< RooFit::Detail::JSONTree > varJSONString(const JSONNode &treeRoot)
#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
virtual Int_t getBins(const char *name=nullptr) const
Get number of bins of currently defined range.
virtual double getMax(const char *name=nullptr) const
Get maximum of currently defined range.
virtual double getMin(const char *name=nullptr) const
Get minimum of currently defined range.
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)
bool importYML(std::string const &filename)
Imports a YML file from the given filename to the workspace.
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)
RooJSONFactoryWSTool(RooWorkspace &ws)
void exportData(RooAbsData const &data)
Export data from the workspace to a JSONNode.
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)
Export variables from the workspace to a JSONNode.
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)
std::string exportYMLtoString()
Export the workspace to a YML string.
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.
bool importYMLfromString(const std::string &s)
Import the workspace from a YML string.
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)
Export combined data from the workspace to a custom struct.
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)
Import all dependants (servers) of a node into the workspace.
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)
Import a variable from the JSONNode into the workspace.
bool exportYML(std::string const &fileName)
Export the workspace to YML format and write to the specified file.
void exportVariable(const RooAbsArg *v, RooFit::Detail::JSONNode &n, bool storeConstant, bool storeBins)
Export a variable from the workspace to a JSONNode.
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)
Export an object from the workspace to a JSONNode.
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)
Imports all nodes of the JSON data and adds them to the workspace.
static std::string name(const RooFit::Detail::JSONNode &n)
void exportAllObjects(RooFit::Detail::JSONNode &n)
Export all objects in the workspace to a JSONNode.
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:60
ExportMap & exporters()
Definition JSONIO.cxx:82
ImportExpressionMap & importExpressions()
Definition JSONIO.cxx:109
ExportKeysMap & exportKeys()
Definition JSONIO.cxx:116
TLine l
Definition textangle.C:4
static void output()