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