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