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