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