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 <RooNumber.h>
18#include <RooRealVar.h>
19#include <RooBinning.h>
20#include <RooAbsCategory.h>
21#include <RooArgProxy.h>
22#include <RooAbsProxy.h>
23#include <RooAbsLValue.h>
24#include <RooCategory.h>
25#include <RooDataSet.h>
26#include <RooDataHist.h>
27#include <RooSimultaneous.h>
28#include <RooFit/ModelConfig.h>
29#include <RooFitImplHelpers.h>
30#include <RooAbsCollection.h>
31
32#include "JSONIOUtils.h"
33#include "HS3Builder.h"
34#include "HS3Schema.h"
35#include "JSONReadRecorder.h"
36
37#include <TROOT.h>
38
39#include <algorithm>
40#include <fstream>
41#include <iostream>
42#include <numeric>
43#include <set>
44#include <sstream>
45#include <stdexcept>
46
47/** \class RooJSONFactoryWSTool
48\ingroup roofit_dev_docs_hs3
49
50When using \ref Roofitmain, statistical models can be conveniently handled and
51stored as a RooWorkspace. However, for the sake of interoperability
52with other statistical frameworks, and also ease of manipulation, it
53may be useful to store statistical models in text form.
54
55The RooJSONFactoryWSTool is a helper class to achieve exactly this,
56exporting to and importing from JSON.
57
58In order to import a workspace from a JSON file, you can do
59
60~~~ {.py}
61ws = ROOT.RooWorkspace("ws")
62tool = ROOT.RooJSONFactoryWSTool(ws)
63tool.importJSON("myjson.json")
64~~~
65
66Similarly, in order to export a workspace to a JSON file, you can do
67
68~~~ {.py}
69tool = ROOT.RooJSONFactoryWSTool(ws)
70tool.exportJSON("myjson.json")
71~~~
72
73Analogously, in C++, you can do
74
75~~~ {.cxx}
76#include "RooFitHS3/RooJSONFactoryWSTool.h"
77// ...
78RooWorkspace ws("ws");
79RooJSONFactoryWSTool tool(ws);
80tool.importJSON("myjson.json");
81~~~
82
83and
84
85~~~ {.cxx}
86#include "RooFitHS3/RooJSONFactoryWSTool.h"
87// ...
88RooJSONFactoryWSTool tool(ws);
89tool.exportJSON("myjson.json");
90~~~
91
92For more details, consult the tutorial <a href="rf515__hfJSON_8py.html">rf515_hfJSON</a>.
93
94The RooJSONFactoryWSTool only knows about a limited set of classes for
95import and export. If import or export of a class you're interested in
96fails, you might need to add your own importer or exporter. Please
97consult the relevant section in the \ref roofit_dev_docs to learn how to do that (\ref roofit_dev_docs_hs3).
98
99You can always get a list of all the available importers and exporters by calling the following functions:
100~~~ {.py}
101ROOT.RooFit.JSONIO.printImporters()
102ROOT.RooFit.JSONIO.printExporters()
103ROOT.RooFit.JSONIO.printFactoryExpressions()
104ROOT.RooFit.JSONIO.printExportKeys()
105~~~
106
107Alternatively, you can generate a LaTeX version of the available importers and exporters by calling
108~~~ {.py}
109tool = ROOT.RooJSONFactoryWSTool(ws)
110tool.writedoc("hs3.tex")
111~~~
112*/
113
114constexpr auto hs3VersionTag = "0.3";
115
119
120namespace HS3 = RooFit::HS3;
121
122/// What the tool needs to know about the document that it is importing.
124 explicit ToolImportState(HS3Document const &doc) : document{doc}
125 {
126 // Without these indices, every cross-reference resolved during import (e.g. dependencies of a
127 // PiecewiseInterpolation, or factory-expression arguments) would need a linear scan over all sibling objects,
128 // which becomes O(N^2) on workspaces with thousands of entries.
129 for (auto const &function : doc.functions) {
130 functions.emplace(function.name, &function);
131 }
132 for (auto const &distribution : doc.distributions) {
133 distributions.emplace(distribution.name, &distribution);
134 }
135 for (auto const &point : doc.parameterPoints) {
136 if (point.name == "default_values") {
137 defaultValues = &point;
138 for (auto const &p : point.parameters) {
139 defaultValuesByName.emplace(p.name, &p);
140 }
141 break;
142 }
143 }
144 }
145
147 std::unordered_map<std::string, HS3::Function const *> functions;
148 std::unordered_map<std::string, HS3::Distribution const *> distributions;
150 std::unordered_map<std::string, HS3::ParameterValue const *> defaultValuesByName;
152};
153
154/// The document that the tool is exporting.
157 JSONNode *root = nullptr;
158 /// The ranges and binnings of the exported variables, by domain and variable name.
159 std::map<std::string, std::map<std::string, HS3::DomainAxis>> domains;
160 /// Set by the exporter of an object that is interpreted by the document.
161 std::optional<HistFactoryDist> typedObject;
162 std::vector<DistributionRef> channelConstraints;
163};
164
166 std::vector<DistributionRef> channelConstraints)
167{
168 tool._export->typedObject = std::move(dist);
169 tool._export->channelConstraints = std::move(channelConstraints);
170}
171
172namespace {
173
174HS3::Binning toBinning(RooAbsBinning const &binning)
175{
176 if (binning.isUniform()) {
177 return HS3::RegularBinning{binning.numBins()};
178 }
179 HS3::EdgesBinning edges;
180 edges.edges.push_back(binning.binLow(0));
181 for (int i = 0; i < binning.numBins(); ++i) {
182 edges.edges.push_back(binning.binHigh(i));
183 }
184 return edges;
185}
186
187// The axis of a domain for a range and binning. Infinite bounds are omitted, and only a bounded range has a binning.
188HS3::DomainAxis toDomainAxis(std::string const &name, RooAbsBinning const &binning)
189{
190 HS3::DomainAxis axis;
191 axis.name = name;
192 if (!RooNumber::isInfinite(binning.lowBound())) {
193 axis.min = binning.lowBound();
194 }
195 if (!RooNumber::isInfinite(binning.highBound())) {
196 axis.max = binning.highBound();
197 }
198 if (axis.min && axis.max && binning.numBins() > 0) {
199 axis.binning = toBinning(binning);
200 }
201 return axis;
202}
203
204// Record the range and binning of a variable in the "default_domain", and its named ranges in the domains of their
205// names.
206void recordDomains(std::map<std::string, std::map<std::string, HS3::DomainAxis>> &domains, RooRealVar const &var)
207{
208 domains[HS3::defaultDomainName][var.GetName()] = toDomainAxis(var.GetName(), var.getBinning());
209 for (auto const &name : var.getBinningNames()) {
210 if (!name.empty()) {
211 domains[name][var.GetName()] = toDomainAxis(var.GetName(), var.getBinning(name.c_str()));
212 }
213 }
214}
215
216void appendDomain(std::vector<HS3::Domain> &domains, std::string const &name,
217 std::map<std::string, HS3::DomainAxis> axes)
218{
219 HS3::Domain &domain = domains.emplace_back();
220 domain.name = name;
221 for (auto &item : axes) {
222 domain.axes.emplace_back(std::move(item.second));
223 }
224}
225
226// Set the binning of a variable, or the binning of one of its named ranges.
227void applyBinning(RooRealVar &var, HS3::Binning const &binning, const char *name = nullptr)
228{
229 if (auto const *edges = std::get_if<HS3::EdgesBinning>(&binning)) {
230 if (edges->edges.empty()) {
231 return;
232 }
233 RooBinning bins(edges->edges.front(), edges->edges.back());
234 for (double edge : edges->edges) {
235 bins.addBoundary(edge);
236 }
237 var.setBinning(bins, name);
238 } else if (int nbins = std::get<HS3::RegularBinning>(binning).nbins) {
239 var.setBins(nbins, name);
240 }
241}
242
243void applyRange(RooRealVar &var, HS3::DomainAxis const &axis)
244{
245 if (axis.min) {
246 if (RooNumber::isInfinite(*axis.min)) {
247 var.removeMin();
248 } else {
249 var.setMin(*axis.min);
250 }
251 }
252 if (axis.max) {
253 if (RooNumber::isInfinite(*axis.max)) {
254 var.removeMax();
255 } else {
256 var.setMax(*axis.max);
257 }
258 }
259 if (axis.binning) {
260 applyBinning(var, *axis.binning);
261 }
262}
263
264// Create the variables of the domains that don't exist yet, and the domains other than the "default_domain" as named
265// ranges.
266void createVariables(RooWorkspace &ws, std::vector<HS3::Domain> const &domains, HS3::DomainRanges const &ranges)
267{
268 auto forEachAxis = [&](auto &&fn) {
269 for (HS3::Domain const &domain : domains) {
270 for (auto const &axis : domain.axes) {
271 if (auto const *continuous = std::get_if<HS3::DomainAxis>(&axis)) {
272 fn(domain.name, *continuous);
273 }
274 }
275 }
276 };
277 const double inf = RooNumber::infinity();
278 // Parameters with a single value are RooConstVars, whichever domain gives the value.
279 forEachAxis([&](std::string const &, HS3::DomainAxis const &axis) {
280 if (axis.fixedValue && !ws.arg(axis.name)) {
281 ws.import(RooConstVar{axis.name.c_str(), axis.name.c_str(), *axis.fixedValue}, RooFit::Silence());
282 }
283 });
284 forEachAxis([&](std::string const &, HS3::DomainAxis const &axis) {
285 if (!axis.fixedValue && ranges.find(axis.name) == &axis && !ws.arg(axis.name)) {
286 RooRealVar var{axis.name.c_str(), axis.name.c_str(), axis.min.value_or(-inf), axis.max.value_or(inf)};
287 if (axis.binning) {
288 applyBinning(var, *axis.binning);
289 }
290 ws.import(var, RooFit::Silence());
291 }
292 });
293 // A categorical axis is a RooCategory with one state per category, in the order of the document and with the
294 // position as index, so that the bins of a RooDataHist follow the order of "categories". An existing RooCategory of
295 // that name must have the same labels. That all domains agree on the categories is checked by the validation.
296 for (HS3::Domain const &domain : domains) {
297 for (auto const &axisVariant : domain.axes) {
298 auto const *axis = std::get_if<HS3::CategoricalAxis>(&axisVariant);
299 if (!axis) {
300 continue;
301 }
302 if (RooAbsArg *existing = ws.arg(axis->name)) {
303 auto *cat = dynamic_cast<RooCategory *>(existing);
304 if (!cat) {
305 RooJSONFactoryWSTool::error("the axis '" + axis->name + "' is categorical, but '" + axis->name +
306 "' already exists and is not a category");
307 }
308 std::set<std::string> existingLabels;
309 for (auto const &state : *cat) {
310 existingLabels.insert(state.first);
311 }
312 if (existingLabels != std::set<std::string>(axis->categories.begin(), axis->categories.end())) {
313 RooJSONFactoryWSTool::error("the categories of the axis '" + axis->name +
314 "' differ from the states of the existing category '" + axis->name + "'");
315 }
316 continue;
317 }
318 RooCategory cat{axis->name.c_str(), axis->name.c_str()};
319 for (std::size_t i = 0; i < axis->categories.size(); ++i) {
320 cat.defineType(axis->categories[i], static_cast<int>(i));
321 }
322 ws.import(cat, RooFit::Silence());
323 }
324 }
325 forEachAxis([&](std::string const &domain, HS3::DomainAxis const &axis) {
326 RooRealVar *var = ws.var(axis.name);
327 if (var && domain != HS3::defaultDomainName) {
328 var->setRange(domain.c_str(), axis.min.value_or(-inf), axis.max.value_or(inf));
329 if (axis.binning) {
330 applyBinning(*var, *axis.binning, domain.c_str());
331 }
332 }
333 });
334}
335
336} // namespace
337
338namespace {
339
340// Keeps track of the objects that are being imported. Objects that depend on each other would otherwise recurse until
341// the stack overflows.
342class ImportGuard {
343public:
344 ImportGuard(std::vector<std::string> &stack, std::string const &name) : _stack{stack}
345 {
346 if (std::find(_stack.begin(), _stack.end(), name) != _stack.end()) {
347 std::string chain;
348 for (std::string const &ancestor : _stack) {
349 chain += ancestor + " -> ";
350 }
351 RooJSONFactoryWSTool::error("circular dependency in the document: " + chain + name);
352 }
353 _stack.push_back(name);
354 }
355 ~ImportGuard() { _stack.pop_back(); }
356
357private:
358 std::vector<std::string> &_stack;
359};
360
361} // namespace
362
363void RooFit::HS3::report(Diagnostics const &diagnostics)
364{
365 std::string errors;
366 for (Diagnostic const &diagnostic : diagnostics) {
367 const std::string text =
368 diagnostic.path.empty() ? diagnostic.message : diagnostic.path + ": " + diagnostic.message;
369 if (diagnostic.severity == Diagnostic::Severity::Error) {
370 errors += (errors.empty() ? "" : "\n") + text;
371 } else {
372 RooJSONFactoryWSTool::warning("RooFitHS3: " + text);
373 }
374 }
375 if (!errors.empty()) {
377 }
378}
379
380namespace {
381
382// True if the number of components in `data` matches the number of categories in `pdf`.
383bool matches(const RooJSONFactoryWSTool::CombinedData &data, const RooSimultaneous *pdf)
384{
385 return data.components.size() == pdf->indexCat().size();
386}
387
388// True if the entire string parses as a number (integer or floating-point).
389bool isNumber(const std::string &str)
390{
391 // Parse with the same mechanism as toDouble() and require that the whole string is consumed, so that isNumber(s) is
392 // true exactly when toDouble(s) can turn the entire string into a value. (std::from_chars for floating-point types
393 // is not portably available on all platforms ROOT supports, so we rely on the stream extraction instead.)
394 std::istringstream stream(str);
395 double value = 0.0;
396 return (stream >> value) && stream.eof();
397}
398
399// Configure `v` (value, error, binning, constness) from the parameter `p`.
400void configureVariable(HS3::DomainRanges const &ranges, HS3::ParameterValue const &p, RooRealVar &v)
401{
402 if (p.value) {
403 if (auto const *value = std::get_if<double>(&*p.value)) {
404 v.setVal(*value);
405 }
406 }
407 if (HS3::DomainAxis const *range = ranges.find(v.GetName())) {
408 applyRange(v, *range);
409 }
410 if (p.nbins)
411 v.setBins(*p.nbins);
412 if (p.relError)
413 v.setError(v.getVal() * *p.relError);
414 if (p.error)
415 v.setError(*p.error);
416 v.setConstant(p.isConst);
417}
418
419std::string genPrefix(const JSONNode &p, bool trailing_underscore)
420{
421 std::string prefix;
422 if (!p.is_map())
423 return prefix;
424 if (auto node = p.find("namespaces")) {
425 for (const auto &ns : node->children()) {
426 if (!prefix.empty())
427 prefix += "_";
428 prefix += ns.val();
429 }
430 }
431 if (trailing_underscore && !prefix.empty())
432 prefix += "_";
433 return prefix;
434}
435
436// helpers for serializing / deserializing binned datasets
437void genIndicesHelper(std::vector<std::vector<int>> &combinations, std::vector<int> &curr_comb,
438 const std::vector<int> &vars_numbins, size_t curridx)
439{
440 if (curridx == vars_numbins.size()) {
441 // we have filled a combination. Copy it.
442 combinations.emplace_back(curr_comb);
443 } else {
444 for (int i = 0; i < vars_numbins[curridx]; ++i) {
445 curr_comb[curridx] = i;
446 ::genIndicesHelper(combinations, curr_comb, vars_numbins, curridx + 1);
447 }
448 }
449}
450
451// Import string attributes ("dict") and boolean tags ("tags") onto `arg`.
452void importAttributes(RooAbsArg *arg, HS3::Attributes const &attributes)
453{
454 for (auto const &attr : attributes.dict) {
455 arg->setStringAttribute(attr.first.c_str(), attr.second.c_str());
456 }
457 for (auto const &tag : attributes.tags) {
458 arg->setAttribute(tag.c_str());
459 }
460}
461
462void addIfPresent(RooArgSet &out, RooArgSet const *args)
463{
464 if (args) {
465 out.add(*args, true);
466 }
467}
468
469void collectParameterStepWidthCandidatesFromModelConfigs(RooWorkspace const &workspace, RooArgSet &candidates,
470 RooArgSet &excluded)
471{
472 for (TObject *obj : workspace.allGenericObjects()) {
473 auto const *mc = dynamic_cast<RooFit::ModelConfig const *>(obj);
474 if (!mc) {
475 continue;
476 }
477
478 addIfPresent(candidates, mc->GetParametersOfInterest());
479 addIfPresent(candidates, mc->GetNuisanceParameters());
480
481 addIfPresent(excluded, mc->GetObservables());
482 addIfPresent(excluded, mc->GetGlobalObservables());
483 addIfPresent(excluded, mc->GetConditionalObservables());
484 }
485}
486
487void collectParameterStepWidthCandidatesFromPdfs(std::vector<RooAbsPdf *> const &pdfs,
488 std::vector<RooAbsData *> const &data, RooArgSet &candidates,
489 RooArgSet &excluded)
490{
491 for (RooAbsPdf const *pdf : pdfs) {
492 RooArgSet observables;
493 for (RooAbsData const *dataset : data) {
494 std::unique_ptr<RooArgSet> pdfObs{pdf->getObservables(*dataset->get())};
495 observables.add(*pdfObs, true);
496 }
497
498 if (observables.empty()) {
499 continue;
500 }
501
502 RooArgSet params;
503 pdf->getParameters(&observables, params);
504 candidates.add(params, true);
505 excluded.add(observables, true);
506 }
507}
508
509void exportParameterStepWidths(RooWorkspace const &workspace, std::vector<RooAbsPdf *> const &pdfs,
510 std::vector<RooAbsData *> const &data, std::vector<HS3::StepWidth> &out)
511{
512 RooArgSet candidates;
513 RooArgSet excluded;
514
515 collectParameterStepWidthCandidatesFromModelConfigs(workspace, candidates, excluded);
516 collectParameterStepWidthCandidatesFromPdfs(pdfs, data, candidates, excluded);
517
518 candidates.sort();
519
520 for (RooAbsArg *arg : candidates) {
521 if (excluded.find(*arg)) {
522 continue;
523 }
524
525 auto *var = dynamic_cast<RooRealVar *>(arg);
526 if (!var || !var->hasError()) {
527 continue;
528 }
529
530 out.push_back({var->GetName(), var->getError()});
531 }
532}
533
534void importParameterStepWidths(RooWorkspace &workspace, HS3Document const &doc)
535{
536 for (HS3::StepWidth const &stepWidth : doc.rooFit.stepWidths) {
537 auto *var = dynamic_cast<RooRealVar *>(workspace.arg(stepWidth.name));
538 if (!var) {
539 RooJSONFactoryWSTool::warning("RooFitHS3: skipping parameter_stepwidths entry for unknown or non-real "
540 "variable '" +
541 stepWidth.name + "'.");
542 continue;
543 }
544 var->setError(stepWidth.width);
545 }
546}
547
548// RooWSFactoryTool expression handling
549std::string generate(const RooFit::JSONIO::ImportExpression &ex, const JSONNode &p, RooJSONFactoryWSTool *tool)
550{
551 std::stringstream expression;
552 std::string classname(ex.tclass->GetName());
553 size_t colon = classname.find_last_of(':');
554 expression << (colon < classname.size() ? classname.substr(colon + 1) : classname);
555 bool first = true;
556 const auto &name = RooJSONFactoryWSTool::name(p);
557 for (auto k : ex.arguments) {
558 expression << (first ? "::" + name + "(" : ",");
559 first = false;
560 if (k == "true" || k == "false") {
561 expression << (k == "true" ? "1" : "0");
562 } else if (!p.has_child(k)) {
563 RooJSONFactoryWSTool::error("node '" + name + "' is missing key '" + k + "'");
564 } else if (p[k].is_seq()) {
565 bool firstInner = true;
566 expression << "{";
567 for (RooAbsArg *arg : tool->requestArgList<RooAbsReal>(p, k)) {
568 expression << (firstInner ? "" : ",") << arg->GetName();
569 firstInner = false;
570 }
571 expression << "}";
572 } else {
573 tool->requestArg<RooAbsReal>(p, p[k].key());
574 expression << p[k].val();
575 }
576 }
577 expression << ")";
578 return expression.str();
579}
580
581// Generate all combinations of bin indices for the RooRealVars in `vars`.
582std::vector<std::vector<int>> generateBinIndices(const RooArgSet &vars)
583{
584 std::vector<std::vector<int>> combinations;
585 std::vector<int> vars_numbins;
586 vars_numbins.reserve(vars.size());
587 for (const RooAbsArg *arg : vars) {
588 // Continuous variables and categories (one bin per state)
589 vars_numbins.push_back(dynamic_cast<RooAbsLValue const &>(*arg).numBins());
590 }
591 std::vector<int> curr_comb(vars.size());
592 ::genIndicesHelper(combinations, curr_comb, vars_numbins, 0);
593 return combinations;
594}
595
596template <typename... Keys_t>
597JSONNode const *findRooFitInternal(JSONNode const &node, Keys_t const &...keys)
598{
599 return node.find("misc", "ROOT_internal", keys...);
600}
601
602// True if `arg` is a RooConstVar whose name is a plain number (i.e. a literal constant).
603bool isLiteralConstVar(RooAbsArg const &arg)
604{
605 bool isRooConstVar = dynamic_cast<RooConstVar const *>(&arg);
606 return isRooConstVar && isNumber(arg.GetName());
607}
608
609// Export the string attributes and tags of `arg` into the ROOT-internal attributes.
610void exportAttributes(const RooAbsArg *arg, std::map<std::string, HS3::Attributes> &attributes)
611{
612 // If this RooConst is a literal number, we don't need to export the attributes.
613 if (isLiteralConstVar(*arg) || dynamic_cast<RooConstVar const *>(arg)) {
614 return;
615 }
616
617 HS3::Attributes *out = nullptr;
618 auto initialize = [&]() {
619 if (!out) {
620 out = &attributes[arg->GetName()];
621 }
622 };
623
624 // export all string attributes of an object
625 for (const auto &it : arg->stringAttributes()) {
626 // Skip some RooFit internals
627 if (it.first == "factory_tag" || it.first == "PROD_TERM_TYPE")
628 continue;
629 initialize();
630 out->dict[it.first] = it.second;
631 }
632 for (auto const &attr : arg->attributes()) {
633 // Skip some RooFit internals
634 if (attr == "SnapShot_ExtRefClone" || attr == "RooRealConstant_Factory_Object")
635 continue;
636 initialize();
637 out->tags.push_back(attr);
638 }
639}
640
641} // namespace
642
644{
646 RooAbsBinning const &binning = var.getBinning();
647 HS3::BinnedAxis axis;
648 axis.name = var.GetName();
649 axis.min = binning.lowBound();
650 axis.max = binning.highBound();
651 axis.binning = toBinning(binning);
652 return axis;
653}
654
656{
657 if (isLiteralConstVar(arg)) {
658 return static_cast<RooConstVar const &>(arg).getVal();
659 }
660 return std::string{arg.GetName()};
661}
662
663namespace {
664
665std::unique_ptr<RooRealVar> createObservable(HS3::BinnedAxis const &axis)
666{
667 auto obs = std::make_unique<RooRealVar>(axis.name.c_str(), axis.name.c_str(), axis.min, axis.max);
668 applyBinning(*obs, axis.binning);
669 return obs;
670}
671
672// The labels of a category, as a set.
673std::set<std::string> labelsOf(RooCategory const &cat)
674{
675 std::set<std::string> labels;
676 for (auto const &state : cat) {
677 labels.insert(state.first);
678 }
679 return labels;
680}
681
682// Create the variables for the axes of binned data. A categorical axis is one bin per category. HS3 identifies
683// categories by name, so the data may list them in another order than the domain that declares the category.
684// RooDataHist looks up the bin of a category by the order of the states of the category it is given, so the category
685// of the data takes over the states and their order from the category of the workspace. `binOrder` is filled with the
686// position in the document of each bin in RooFit's order (last axis fastest), or left empty if the orders agree.
687RooArgSet createBinnedObservables(std::vector<std::variant<HS3::BinnedAxis, HS3::CategoricalAxis>> const &axes,
688 RooWorkspace const &workspace, std::string const &dataName,
689 std::vector<std::size_t> &binOrder)
690{
691 RooArgSet vars;
692 std::vector<std::vector<std::size_t>> axisPositions;
693 bool reordered = false;
694 for (auto const &axisVariant : axes) {
695 if (auto const *axis = std::get_if<HS3::BinnedAxis>(&axisVariant)) {
696 auto obs = createObservable(*axis);
697 std::vector<std::size_t> positions(obs->numBins());
698 std::iota(positions.begin(), positions.end(), 0);
699 axisPositions.push_back(std::move(positions));
700 vars.addOwned(std::move(obs));
701 continue;
702 }
703 auto const &axis = std::get<HS3::CategoricalAxis>(axisVariant);
704 auto cat = std::make_unique<RooCategory>(axis.name.c_str(), axis.name.c_str());
705 if (RooCategory const *wsCat = workspace.cat(axis.name)) {
706 if (labelsOf(*wsCat) != std::set<std::string>(axis.categories.begin(), axis.categories.end())) {
707 RooJSONFactoryWSTool::error("the categories of the axis '" + axis.name + "' of the dataset '" + dataName +
708 "' differ from those of the category '" + axis.name + "'");
709 }
710 for (unsigned int i = 0; i < wsCat->size(); ++i) {
711 auto const &state = wsCat->getOrdinal(i);
712 cat->defineType(state.first, state.second);
713 }
714 } else {
715 for (std::size_t i = 0; i < axis.categories.size(); ++i) {
716 cat->defineType(axis.categories[i], static_cast<int>(i));
717 }
718 }
719 std::vector<std::size_t> positions(axis.categories.size());
720 for (std::size_t i = 0; i < positions.size(); ++i) {
721 auto found = std::find(axis.categories.begin(), axis.categories.end(), cat->getOrdinal(i).first);
722 positions[i] = std::distance(axis.categories.begin(), found);
723 reordered |= positions[i] != i;
724 }
725 axisPositions.push_back(std::move(positions));
726 vars.addOwned(std::move(cat));
727 }
728 binOrder.clear();
729 if (!reordered) {
730 return vars;
731 }
732 std::size_t total = 1;
733 for (auto const &positions : axisPositions) {
734 total *= positions.size();
735 }
736 binOrder.resize(total);
737 for (std::size_t k = 0; k < total; ++k) {
738 // Decompose RooFit's bin index (last axis fastest) and recompose it with the positions in the document.
739 std::size_t rest = k;
740 std::size_t docIndex = 0;
741 std::size_t stride = 1;
742 for (std::size_t j = axisPositions.size(); j-- > 0;) {
743 const std::size_t nBins = axisPositions[j].size();
744 docIndex += axisPositions[j][rest % nBins] * stride;
745 rest /= nBins;
746 stride *= nBins;
747 }
748 binOrder[k] = docIndex;
749 }
750 return vars;
751}
752
753// The values in RooFit's bin order, from the order of the document (see createBinnedObservables()).
754std::vector<double> permute(std::vector<double> const &values, std::vector<std::size_t> const &order)
755{
756 if (order.empty() || values.size() != order.size()) {
757 return values;
758 }
759 std::vector<double> out(values.size());
760 for (std::size_t k = 0; k < order.size(); ++k) {
761 out[k] = values[order[k]];
762 }
763 return out;
764}
765
766// Make the range of an observable of unbinned data consistent with the "min" and "max" of the axis. Variables that
767// didn't get their range from a domain take it from the axis. If `ranges` is null, all ranges are considered explicit.
768void applyUnbinnedAxisRange(HS3::ContinuousAxis const &axis, std::string const &dataName, RooRealVar &var,
769 HS3::DomainRanges const *ranges)
770{
771 if (!axis.min || !axis.max) {
772 return;
773 }
774 if (ranges && !ranges->contains(axis.name)) {
775 var.setRange(*axis.min, *axis.max);
776 } else if (var.getMin() != *axis.min || var.getMax() != *axis.max) {
777 std::stringstream ss;
778 ss << "RooFitHS3: the range [" << *axis.min << ", " << *axis.max << "] of the axis '" << axis.name
779 << "' of the dataset '" << dataName << "' differs from the range [" << var.getMin() << ", " << var.getMax()
780 << "] of the variable.";
782 }
783}
784
785std::unique_ptr<RooAbsData> createUnbinnedData(std::string const &name, HS3::UnbinnedData const &input,
786 RooWorkspace &workspace, HS3::DomainRanges const *ranges)
787{
788 RooArgList varlist;
789 for (auto const &axisVariant : input.axes) {
790 auto const *axis = std::get_if<HS3::ContinuousAxis>(&axisVariant);
791 if (!axis) {
792 // The category of a categorical axis is declared by a domain.
793 auto const &catAxis = std::get<HS3::CategoricalAxis>(axisVariant);
794 RooCategory *cat = workspace.cat(catAxis.name);
795 if (!cat) {
796 RooJSONFactoryWSTool::error("The categorical observable \"" + catAxis.name +
797 "\" could not be found in the workspace! Categorical observables are "
798 "declared by a categorical axis in a domain.");
799 }
800 if (labelsOf(*cat) != std::set<std::string>(catAxis.categories.begin(), catAxis.categories.end())) {
801 RooJSONFactoryWSTool::error("The categories of the axis \"" + catAxis.name +
802 "\" differ from the states of the category \"" + catAxis.name + "\"");
803 }
804 varlist.add(*cat);
805 continue;
806 }
807 RooRealVar *var = workspace.var(axis->name);
808 if (!var) {
809 RooJSONFactoryWSTool::error("The observable \"" + axis->name + "\" could not be found in the workspace!");
810 }
811 varlist.add(*var);
812 applyUnbinnedAxisRange(*axis, name, *var, ranges);
813 }
814 RooArgSet vars(varlist);
815 auto data = std::make_unique<RooDataSet>(name, name, vars, RooFit::WeightVar());
816 const std::size_t nAxes = input.axes.size();
817 for (std::size_t i = 0; i < input.numEntries(); ++i) {
818 for (std::size_t j = 0; j < nAxes; ++j) {
819 const double value = input.entries[i * nAxes + j];
820 if (auto const *catAxis = std::get_if<HS3::CategoricalAxis>(&input.axes[j])) {
821 // The entry of a categorical axis is the position of the label in its "categories".
822 static_cast<RooCategory *>(varlist.at(j))->setLabel(catAxis->categories[static_cast<std::size_t>(value)]);
823 } else {
824 static_cast<RooRealVar *>(varlist.at(j))->setVal(value);
825 }
826 }
827 data->add(vars, input.weights ? (*input.weights)[i] : 1.);
828 }
829 return data;
830}
831
832// Create a RooAbsData (binned or unbinned) from its description.
833std::unique_ptr<RooAbsData>
834loadData(HS3::Data const &input, RooWorkspace &workspace, HS3::DomainRanges const *ranges)
835{
837
838 // What RooFit can't store is reported, instead of being dropped silently.
839 if (auto const *binned = std::get_if<HS3::BinnedData>(&input.body)) {
840 if (binned->uncertainty && !binned->uncertainty->correlation.empty()) {
841 RooJSONFactoryWSTool::warning("RooFitHS3: the correlations of the uncertainties of the dataset '" +
842 input.name + "' are ignored, because RooDataHist can't store them.");
843 }
844 std::vector<std::size_t> binOrder;
845 RooArgSet vars = createBinnedObservables(binned->axes, workspace, input.name, binOrder);
846 std::vector<double> sigma;
847 if (binned->uncertainty) {
848 sigma = permute(binned->uncertainty->sigma, binOrder);
849 }
850 return HS3::createDataHist(input.name, vars, permute(binned->contents, binOrder),
851 binned->uncertainty ? &sigma : nullptr);
852 }
853 if (auto const *unbinned = std::get_if<HS3::UnbinnedData>(&input.body)) {
854 if (unbinned->hasEntriesUncertainties) {
855 RooJSONFactoryWSTool::warning("RooFitHS3: the \"entries_uncertainties\" of the dataset '" + input.name +
856 "' are ignored, because RooDataSet can't store them.");
857 }
858 return createUnbinnedData(input.name, *unbinned, workspace, ranges);
859 }
860 RooJSONFactoryWSTool::error("datasets of type \"point\" like '" + input.name + "' are not supported yet");
861}
862
863// The version of ROOT that writes the document, with dots only such that it can be read automatically.
864std::string rootVersion()
865{
866 std::string versionName = gROOT->GetVersion();
867 std::replace(versionName.begin(), versionName.end(), '/', '.');
868 return versionName;
869}
870
871// A distribution of the document can be a function in RooFit, or it can be skipped.
872void requirePdf(RooAbsArg const *arg, std::string const &what)
873{
874 if (!dynamic_cast<RooAbsPdf const *>(arg)) {
876 (arg ? std::string("a ") + arg->ClassName() + ", not a pdf" : "not imported"));
877 }
878}
879
880// Import an analysis (likelihood + domains) as one or more ModelConfig objects into the workspace.
881void importAnalysis(HS3Document const &doc, HS3::Analysis const &analysis, RooWorkspace &workspace,
882 const std::vector<std::unique_ptr<RooAbsData>> &datasets)
883{
884 // if this is a toplevel pdf, also create a modelConfig for it
885 std::string const &analysisName = analysis.name;
886 auto foundInfo = doc.rooFit.modelConfigs.find(analysisName);
887 HS3::ModelConfigInfo const *mcInfo = foundInfo != doc.rooFit.modelConfigs.end() ? &foundInfo->second : nullptr;
888
889 std::string mcname = mcInfo && mcInfo->mcName ? *mcInfo->mcName : analysisName;
890 if (workspace.obj(mcname))
891 return;
892
893 workspace.import(RooFit::ModelConfig{mcname.c_str(), mcname.c_str()});
894 auto *mc = static_cast<RooFit::ModelConfig *>(workspace.obj(mcname));
895 mc->SetWS(workspace);
896
897 auto foundLikelihood = std::find_if(doc.likelihoods.begin(), doc.likelihoods.end(),
898 [&](HS3::Likelihood const &l) { return l.name == analysis.likelihood.name; });
899 if (foundLikelihood == doc.likelihoods.end()) {
900 throw std::runtime_error("likelihood node not found!");
901 }
902 HS3::Likelihood const &likelihood = *foundLikelihood;
903
904 auto checkPdf = [&](RooAbsArg const *arg, std::string const &name, std::string const &what) {
905 requirePdf(arg, "the " + what + " '" + name + "' of analysis '" + analysisName + "'");
906 };
907
908 std::vector<std::string> nllDistNames;
909 nllDistNames.reserve(likelihood.terms.size());
910 for (auto const &term : likelihood.terms) {
911 checkPdf(workspace.arg(term.distribution.name), term.distribution.name, "distribution");
912 nllDistNames.push_back(term.distribution.name);
913 }
914 // The constraint terms of HistFactory distributions are listed as auxiliary distributions, but they are part of the
915 // distributions in RooFit.
916 std::set<std::string> channelConstraints;
917 for (auto const &item : doc.rooFit.channelConstraints) {
918 for (auto const &ref : item.second) {
919 channelConstraints.insert(ref.name);
920 }
921 }
922 RooArgSet extConstraints;
923 for (auto const &aux : likelihood.auxDistributions) {
924 RooAbsArg *extConstraint = workspace.arg(aux.name);
925 if (!extConstraint) {
926 continue;
927 }
928 checkPdf(extConstraint, aux.name, "auxiliary distribution");
929 // Only if the constraint really ended up in a distribution of the likelihood.
930 if (channelConstraints.count(aux.name) &&
931 std::any_of(nllDistNames.begin(), nllDistNames.end(), [&](std::string const &distName) {
932 RooAbsArg const *dist = workspace.arg(distName);
933 return dist && dist->dependsOn(*extConstraint);
934 })) {
935 continue;
936 }
937 extConstraints.add(*extConstraint);
938 }
939 RooArgSet observables;
940 for (auto const &term : likelihood.terms) {
941 auto const *dataRef = std::get_if<HS3::DataRef>(&term.data);
942 if (!dataRef) {
943 continue;
944 }
945 bool found = false;
946 for (const auto &d : datasets) {
947 if (d->GetName() == dataRef->name) {
948 found = true;
949 observables.add(*d->get(), true);
950 }
951 }
952 if (!found)
953 throw std::runtime_error("dataset '" + dataRef->name + "' cannot be found!");
954 }
955
956 std::string const pdfName = mcInfo && mcInfo->pdfName ? *mcInfo->pdfName : "simPdf";
957
958 RooAbsPdf *pdf = workspace.pdf(pdfName);
959
960 if (!pdf) {
961 // if there is no simultaneous pdf, we can check whether there is only one pdf in the list
962 if (nllDistNames.size() == 1) {
963 // if so, we can use that one to populate the ModelConfig
964 pdf = workspace.pdf(nllDistNames[0]);
965 } else {
966 // otherwise, we have no choice but to build a simPdf by hand
967 std::string simPdfName = analysisName + "_simPdf";
968 std::string indexCatName = analysisName + "_categoryIndex";
969 RooCategory indexCat{indexCatName.c_str(), indexCatName.c_str()};
970 std::map<std::string, RooAbsPdf *> pdfMap;
971 for (std::size_t i = 0; i < nllDistNames.size(); ++i) {
972 indexCat.defineType(nllDistNames[i], i);
973 pdfMap[nllDistNames[i]] = workspace.pdf(nllDistNames[i]);
974 }
975 RooSimultaneous simPdf{simPdfName.c_str(), simPdfName.c_str(), pdfMap, indexCat};
976 workspace.import(simPdf, RooFit::RecycleConflictNodes(true), RooFit::Silence(true));
977 pdf = workspace.pdf(simPdfName);
978 }
979 }
980
981 mc->SetPdf(*pdf);
982
983 if (!extConstraints.empty())
984 mc->SetExternalConstraints(extConstraints);
985
986 if (analysis.prior) {
987 if (RooAbsArg *prior = workspace.arg(analysis.prior->name)) {
988 checkPdf(prior, analysis.prior->name, "prior");
989 mc->SetPriorPdf(static_cast<RooAbsPdf &>(*prior));
990 }
991 }
992
993 RooArgSet parametersOfInterest;
994 for (auto const &poi : analysis.parametersOfInterest) {
995 RooAbsArg *arg = workspace.arg(poi.name);
996 if (!arg) {
997 RooJSONFactoryWSTool::error("the object '" + poi.name + "' in \"parameters_of_interest\" of analysis '" +
998 analysisName + "' does not exist");
999 }
1000 if (!dynamic_cast<RooRealVar *>(arg)) {
1001 RooJSONFactoryWSTool::error("the parameter of interest '" + poi.name + "' of analysis '" + analysisName +
1002 "' is a " + arg->ClassName() + ", not a variable" +
1003 (dynamic_cast<RooConstVar *>(arg) ? ", because no domain lists it" : ""));
1004 }
1005 parametersOfInterest.add(*arg);
1006 }
1007
1008 mc->SetParametersOfInterest(parametersOfInterest);
1009 mc->SetObservables(observables);
1010 RooArgSet pars;
1011 pdf->getParameters(&observables, pars);
1012
1013 // Figure out the set parameters that appear in the main measurement:
1014 // getAllConstraints() has the side effect to remove all parameters from
1015 // "mainPars" that are not part of any pdf over observables.
1016 RooArgSet mainPars{pars};
1017 pdf->getAllConstraints(observables, mainPars, /*stripDisconnected*/ true);
1018
1019 RooArgSet domainPars;
1020 for (auto const &domainRef : analysis.domains) {
1021 auto thisDomain = std::find_if(doc.domains.begin(), doc.domains.end(),
1022 [&](HS3::Domain const &d) { return d.name == domainRef.name; });
1023 if (thisDomain == doc.domains.end())
1024 continue;
1025 for (auto const &axis : thisDomain->axes) {
1026 auto *wsvar = workspace.var(std::visit([](auto const &a) -> std::string const & { return a.name; }, axis));
1027 if (wsvar)
1028 domainPars.add(*wsvar);
1029 }
1030 }
1031
1032 RooArgSet nps;
1033 RooArgSet globs;
1034 for (const auto &p : pars) {
1035 if (mc->GetParametersOfInterest()->find(*p))
1036 continue;
1037 if (p->isConstant() && !mainPars.find(*p) && domainPars.find(*p)) {
1038 globs.add(*p);
1039 } else if (domainPars.find(*p)) {
1040 nps.add(*p);
1041 }
1042 }
1043
1044 mc->SetGlobalObservables(globs);
1045 mc->SetNuisanceParameters(nps);
1046
1047 if (mcInfo && mcInfo->combinedDataName) {
1048 pdf->setStringAttribute("combined_data_name", mcInfo->combinedDataName->c_str());
1049 }
1050
1051 // A missing parameter point was reported by HS3::validate().
1052 RooArgSet const *init = analysis.init ? workspace.getSnapshot(analysis.init->name.c_str()) : nullptr;
1053 if (init) {
1054 const std::string &initName = analysis.init->name;
1055 // ModelConfig::SetSnapshot() saves the snapshot as "<mc name>_<set name>_snapshot". If "init" already follows
1056 // this pattern, which is the case for files written by RooFit, pick the set name such that it is preserved.
1057 std::string setName = initName;
1058 const std::string prefix = mcname + "_";
1059 const std::string suffix = "_snapshot";
1060 if (initName.size() >= prefix.size() + suffix.size() && startsWith(initName, prefix) &&
1061 endsWith(initName, suffix)) {
1062 setName = initName.substr(prefix.size(), initName.size() - prefix.size() - suffix.size());
1063 }
1064 // Take an owning copy: SetSnapshot() might replace the workspace snapshot that `init` points to.
1065 RooArgSet initSet;
1066 init->snapshot(initSet);
1067 initSet.setName(setName.c_str());
1068 mc->SetSnapshot(initSet);
1069 }
1070}
1071
1072void combinePdfs(HS3Document const &doc, RooWorkspace &ws)
1073{
1074 for (auto const &item : doc.rooFit.combinedDistributions) {
1075 std::string const &combinedName = item.first;
1076 HS3::CombinedDistribution const &info = item.second;
1077
1078 RooCategory indexCat{info.indexCat.c_str(), info.indexCat.c_str()};
1079 std::map<std::string, RooAbsPdf *> pdfMap;
1080
1081 for (std::size_t iChannel = 0; iChannel < info.labels.size(); ++iChannel) {
1082 indexCat.defineType(info.labels[iChannel], info.indices[iChannel]);
1083 std::string const &name = info.distributions[iChannel].name;
1084 requirePdf(ws.arg(name),
1085 "the distribution '" + name + "' of the combined distribution '" + combinedName + "'");
1086 pdfMap[info.labels[iChannel]] = ws.pdf(name);
1087 }
1088
1089 RooSimultaneous simPdf{combinedName.c_str(), combinedName.c_str(), pdfMap, indexCat};
1090 ws.import(simPdf, RooFit::RecycleConflictNodes(true), RooFit::Silence(true));
1091 }
1092}
1093
1094void combineDatasets(HS3Document const &doc, std::vector<std::unique_ptr<RooAbsData>> &datasets)
1095{
1096 for (auto const &item : doc.rooFit.combinedDatasets) {
1097 std::string const &combinedName = item.first;
1098 HS3::CombinedDataset const &info = item.second;
1099
1100 // Create the combined dataset for RooFit
1101 std::map<std::string, std::unique_ptr<RooAbsData>> dsMap;
1102 RooCategory indexCat{info.indexCat.c_str(), info.indexCat.c_str()};
1103 RooArgSet allVars{indexCat};
1104 for (std::size_t iChannel = 0; iChannel < info.labels.size(); ++iChannel) {
1105 auto componentName = combinedName + "_" + info.labels[iChannel];
1106 // We move the found channel data out of the "datasets" vector, such that
1107 // the data components don't get imported anymore.
1108 auto found =
1109 std::find_if(datasets.begin(), datasets.end(), [&](auto &d) { return d && d->GetName() == componentName; });
1110 if (found == datasets.end()) {
1111 RooJSONFactoryWSTool::error("unable to obtain component matching component name '" + componentName + "'");
1112 }
1113 allVars.add(*(*found)->get(), true);
1114 dsMap.insert({info.labels[iChannel], std::move(*found)});
1115 indexCat.defineType(info.labels[iChannel], info.indices[iChannel]);
1116 }
1117
1118 auto combined = std::make_unique<RooDataSet>(combinedName, combinedName, allVars, RooFit::Import(dsMap),
1119 RooFit::Index(indexCat));
1120 datasets.emplace_back(std::move(combined));
1121 }
1122}
1123
1124template <class T>
1125void sortByName(T &coll)
1126{
1127 std::sort(coll.begin(), coll.end(), [](auto &l, auto &r) { return strcmp(l->GetName(), r->GetName()) < 0; });
1128}
1129
1130/// Check whether an object is a top-level object of the workspace, i.e. not a
1131/// component of some other workspace object. Only clients that are part of the
1132/// workspace count: RooFit also registers evaluation artifacts as clients,
1133/// like the normalization integral that RooAbsPdf caches after
1134/// getVal(normSet), or the integral returned by createIntegral(). These live
1135/// outside the workspace and are not evidence that the object is a sub-node of
1136/// a bigger model (see https://github.com/root-project/root/issues/23221).
1137bool isTopLevel(RooAbsArg const &arg, RooWorkspace const &ws)
1138{
1139 for (RooAbsArg const *client : arg.clients()) {
1140 if (ws.components().containsInstance(*client)) {
1141 return false;
1142 }
1143 }
1144 return true;
1145}
1146
1147/// Find the single category observable of a dataset, if any.
1148RooAbsCategory *findCategoryObservable(RooAbsData const &data)
1149{
1150 RooAbsCategory *cat = nullptr;
1151 for (RooAbsArg *obs : *data.get()) {
1152 if (auto *c = dynamic_cast<RooAbsCategory *>(obs)) {
1153 if (cat) {
1154 RooJSONFactoryWSTool::error("dataset '" + std::string(data.GetName()) +
1155 " has several category observables!");
1156 }
1157 cat = c;
1158 }
1159 }
1160 return cat;
1161}
1162
1163} // namespace
1164
1165RooArgSet RooFit::HS3::createObservables(std::vector<BinnedAxis> const &axes)
1166{
1167 RooArgSet vars;
1168 for (BinnedAxis const &axis : axes) {
1169 vars.addOwned(createObservable(axis));
1170 }
1171 return vars;
1172}
1173
1174std::unique_ptr<RooDataHist> RooFit::HS3::createDataHist(std::string const &name, RooArgSet const &vars,
1175 std::vector<double> const &contents,
1176 std::vector<double> const *errors)
1177{
1178 if (errors && errors->size() != contents.size()) {
1179 RooJSONFactoryWSTool::error("inconsistent number of errors and contents in '" + name + "': errors=" +
1180 std::to_string(errors->size()) + ", contents=" + std::to_string(contents.size()));
1181 }
1182 auto bins = generateBinIndices(vars);
1183 if (contents.size() != bins.size()) {
1184 RooJSONFactoryWSTool::error("inconsistent bin numbers: contents=" + std::to_string(contents.size()) +
1185 ", bins=" + std::to_string(bins.size()));
1186 }
1187 auto dh = std::make_unique<RooDataHist>(name, name, vars);
1188 // RooDataHist only starts to store squared weights when it sees a positive error, and then initializes them from
1189 // the weights so far. Trigger that before setting any weight, so that errors of zero stay zero.
1190 if (errors && !errors->empty()) {
1191 dh->set(0, 0., 1.);
1192 }
1193 for (std::size_t ibin = 0; ibin < bins.size(); ++ibin) {
1194 dh->set(ibin, contents[ibin], errors ? (*errors)[ibin] : -1);
1195 }
1196 return dh;
1197}
1198
1200
1202
1203void RooJSONFactoryWSTool::fillSeq(JSONNode &node, RooAbsCollection const &coll, size_t nMax)
1204{
1205 const size_t old_children = node.num_children();
1206 node.set_seq();
1207 size_t n = 0;
1208 for (RooAbsArg const *arg : coll) {
1209 if (n >= nMax)
1210 break;
1211 if (isLiteralConstVar(*arg)) {
1212 node.append_child() << static_cast<RooConstVar const *>(arg)->getVal();
1213 } else {
1214 node.append_child() << arg->GetName();
1215 }
1216 ++n;
1217 }
1218 if (node.num_children() != old_children + coll.size()) {
1219 error("unable to stream collection " + std::string(coll.GetName()) + " to " + node.key());
1220 }
1221}
1222
1224{
1226 child["name"] << name;
1227 return child;
1228}
1229
1230JSONNode const *RooJSONFactoryWSTool::findNamedChild(JSONNode const &node, std::string const &name)
1231{
1232 if (!node.is_seq())
1233 return nullptr;
1234 for (JSONNode const &child : node.children()) {
1235 if (child["name"].val() == name)
1236 return &child;
1237 }
1238
1239 return nullptr;
1240}
1241
1242/**
1243 * @brief Check if a string is a valid name.
1244 *
1245 * A valid name should start with a letter or an underscore, followed by letters, digits, or underscores.
1246 * Only characters from the ASCII character set are allowed.
1247 *
1248 * @param str The string to be checked.
1249 * @return bool Returns true if the string is a valid name; otherwise, returns false.
1250 */
1251bool RooJSONFactoryWSTool::isValidName(const std::string &str)
1252{
1253 // Check if the string is empty or starts with a non-letter/non-underscore character
1254 if (str.empty() || !(std::isalpha(str[0]) || str[0] == '_')) {
1255 return false;
1256 }
1257
1258 // Check the remaining characters in the string
1259 for (char c : str) {
1260 // Allow letters, digits, and underscore
1261 if (!(std::isalnum(c) || c == '_')) {
1262 return false;
1263 }
1264 }
1265
1266 // If all characters are valid, the string is a valid name
1267 return true;
1268}
1269
1270bool RooJSONFactoryWSTool::testValidName(const std::string &name, bool forceError)
1271{
1273 std::stringstream ss;
1274 ss << "RooJSONFactoryWSTool() name '" << name << "' is not valid!" << std::endl
1275 << "Sanitize names by setting RooJSONFactoryWSTool::config().allowSanitizeNames = true." << std::endl;
1276 if (RooJSONFactoryWSTool::config().allowExportInvalidNames && !forceError) {
1278 return false;
1279 } else {
1281 }
1282 }
1283 return true;
1284}
1285
1287{
1288 return n["name"].val();
1289}
1290
1292{
1293 return appendNamedChild(rootNode["parameter_points"], "default_values")["parameters"];
1294}
1295
1296template <>
1297RooRealVar *RooJSONFactoryWSTool::requestImpl<RooRealVar>(const std::string &objname)
1298{
1299 if (RooRealVar *retval = _workspace.var(objname))
1300 return retval;
1301 if (_import) {
1302 auto it = _import->defaultValuesByName.find(objname);
1303 if (it != _import->defaultValuesByName.end()) {
1304 this->importVariable(*it->second);
1305 if (RooRealVar *retval = _workspace.var(objname))
1306 return retval;
1307 }
1308 }
1309 return nullptr;
1310}
1311
1312template <>
1313RooAbsPdf *RooJSONFactoryWSTool::requestImpl<RooAbsPdf>(const std::string &objname)
1314{
1315 if (RooAbsPdf *retval = _workspace.pdf(objname))
1316 return retval;
1317 if (_import) {
1318 auto it = _import->distributions.find(objname);
1319 if (it != _import->distributions.end()) {
1320 this->importDistribution(*it->second);
1321 if (RooAbsPdf *retval = _workspace.pdf(objname))
1322 return retval;
1323 }
1324 // HS3 lists some resolution models, which are pdfs in RooFit, as functions.
1325 auto function = _import->functions.find(objname);
1326 if (function != _import->functions.end()) {
1327 this->importFunction(*function->second->body.node, true);
1328 if (RooAbsPdf *retval = _workspace.pdf(objname))
1329 return retval;
1330 }
1331 }
1332 return nullptr;
1333}
1334
1335template <>
1336RooAbsReal *RooJSONFactoryWSTool::requestImpl<RooAbsReal>(const std::string &objname)
1337{
1338 if (RooAbsReal *retval = _workspace.function(objname))
1339 return retval;
1340 if (isNumber(objname))
1341 return &RooFit::RooConst(toDouble(objname));
1342 if (RooAbsPdf *pdf = requestImpl<RooAbsPdf>(objname))
1343 return pdf;
1344 if (RooRealVar *var = requestImpl<RooRealVar>(objname))
1345 return var;
1346 if (RooAbsReal *retval = _workspace.function(objname))
1347 return retval;
1348 if (_import) {
1349 auto it = _import->functions.find(objname);
1350 if (it != _import->functions.end()) {
1351 this->importFunction(*it->second->body.node, true);
1352 if (RooAbsReal *retval = _workspace.function(objname))
1353 return retval;
1354 }
1355 }
1356 return nullptr;
1357}
1358
1359// Export a single variable (RooRealVar or RooConstVar) `v` to the parameter point.
1361{
1362 auto *cv = dynamic_cast<const RooConstVar *>(v);
1363 auto *rrv = dynamic_cast<const RooRealVar *>(v);
1364 if (!cv && !rrv)
1365 return;
1366
1367 // for RooConstVar, if name and value are the same, we don't need to do anything
1368 if (cv && strcmp(cv->GetName(), TString::Format("%g", cv->getVal()).Data()) == 0) {
1369 return;
1370 }
1371
1372 // A named RooConstVar only takes its value: it is written as a domain axis with that single value, not in the
1373 // parameter points.
1374 if (cv) {
1375 HS3::DomainAxis axis;
1376 axis.name = cv->GetName();
1377 axis.fixedValue = cv->getVal();
1378 _export->domains[HS3::defaultDomainName][axis.name] = std::move(axis);
1379 return;
1380 }
1381
1383 var.name = v->GetName();
1384 var.value = rrv->getVal();
1385 var.isConst = storeConstant && (rrv->isConstant() || rrv->getMin() >= rrv->getMax());
1386 recordDomains(_export->domains, *rrv);
1387 point.parameters.push_back(std::move(var));
1388}
1389
1390// Export all variables in `allElems` to the parameter point.
1391void RooJSONFactoryWSTool::exportVariables(const RooArgSet &allElems, HS3::ParameterPoint &point, bool storeConstant)
1392{
1393 for (RooAbsArg *arg : allElems) {
1394 exportVariable(arg, point, storeConstant);
1395 }
1396}
1397
1398std::string RooJSONFactoryWSTool::exportTransformed(const RooAbsReal *original, const std::string &suffix,
1399 const std::string &formula)
1400{
1401 std::string newname = std::string(original->GetName()) + suffix;
1402 // Another object that shares the parameter might have exported the helper already.
1403 auto &functions = _export->doc.functions;
1404 if (std::find_if(functions.begin(), functions.end(), [&](auto const &f) { return f.name == newname; }) !=
1405 functions.end()) {
1406 return newname;
1407 }
1408 RooFit::Detail::JSONNode &trafo_node = appendNamedChild((*_export->root)["functions"], newname);
1409 trafo_node["type"] << "generic";
1410 trafo_node["expression"] << TString::Format(formula.c_str(), original->GetName()).Data();
1411 functions.push_back({newname, {"generic", nullptr}});
1412 this->setAttribute(newname, "roofit_skip"); // this function should not be imported back in
1413 return newname;
1414}
1415
1416namespace {
1417
1418// Turn an arbitrary string into a valid variable name, but refuse to change the
1419// first character (which would silently rename the object).
1420std::string makeValidNameOrError(std::string const &in)
1421{
1422 if (!std::isalpha(in[0])) {
1423 RooJSONFactoryWSTool::error("refusing to change first character of string '" + in + "' to make a valid name!");
1424 }
1425 std::string out = RooFit::Detail::makeValidVarName(in);
1426 if (out != in) {
1427 oocoutW(nullptr, ObjectHandling) << "RooFitHS3: changed '" << in << "' to '" << out << "' to become a valid name";
1428 }
1429 return out;
1430}
1431
1432} // namespace
1433
1434namespace {
1435
1436// Record an object that was written by an exporter in the document.
1437void recordGenericObject(HS3Document &doc, bool isPdf, std::string const &name, JSONNode const &elem)
1438{
1439 std::string type = elem.has_child("type") ? elem["type"].val() : "";
1440 if (isPdf) {
1441 doc.distributions.push_back({name, HS3::GenericObject{std::move(type), nullptr}});
1442 } else {
1443 doc.functions.push_back({name, {std::move(type), nullptr}});
1444 }
1445}
1446
1447} // namespace
1448
1449// Export a single object `func` (pdf, function, variable or category) to the output JSON, recording its name in
1450// `exportedObjectNames` to avoid exporting it twice.
1451void RooJSONFactoryWSTool::exportObject(RooAbsArg const &func, std::set<std::string> &exportedObjectNames)
1452{
1453 std::string name = func.GetName();
1454
1455 // if this element was already exported, skip
1456 if (exportedObjectNames.find(name) != exportedObjectNames.end())
1457 return;
1458
1459 exportedObjectNames.insert(name);
1460
1461 if (auto simPdf = dynamic_cast<RooSimultaneous const *>(&func)) {
1462 // RooSimultaneous is not used in the HS3 standard, we only export the
1463 // dependents and some ROOT internal information.
1464 exportObjects(func.servers(), exportedObjectNames);
1465
1466 HS3::CombinedDistribution &info = _export->doc.rooFit.combinedDistributions[simPdf->GetName()];
1467 info.indexCat = simPdf->indexCat().GetName();
1468 for (auto const &item : simPdf->indexCat()) {
1469 info.labels.push_back(makeValidNameOrError(item.first));
1470 info.indices.push_back(item.second);
1471 info.distributions.push_back({simPdf->getPdf(item.first.c_str())->GetName()});
1472 }
1473
1474 return;
1475 } else if (dynamic_cast<RooAbsCategory const *>(&func)) {
1476 // categories are created by the respective RooSimultaneous, so we're skipping the export here
1477 return;
1478 } else if (dynamic_cast<RooRealVar const *>(&func) || dynamic_cast<RooConstVar const *>(&func)) {
1479 exportVariable(&func, _export->doc.parameterPoints.front(), true);
1480 return;
1481 }
1482
1483 // HS3 lists some resolution models as functions.
1484 const bool isPdf = dynamic_cast<RooAbsPdf const *>(&func) && !func.InheritsFrom("RooGaussModel") &&
1485 !func.InheritsFrom("RooTruthModel");
1486 auto &collectionNode = (*_export->root)[isPdf ? "distributions" : "functions"];
1487
1488 auto const &exporters = RooFit::JSONIO::exporters();
1489 auto const &exportKeys = RooFit::JSONIO::exportKeys();
1490
1491 TClass *cl = func.IsA();
1492
1493 auto &elem = appendNamedChild(collectionNode, name);
1494
1495 auto it = exporters.find(cl);
1496 if (it != exporters.end()) { // check if we have a specific exporter available
1497 for (auto &exp : it->second) {
1498 _serversToExport.clear();
1499 _serversToDelete.clear();
1500 if (!exp->exportObject(this, &func, elem)) {
1501 // The exporter might have messed with the content of the node
1502 // before failing. That's why we clear it and only reset the name.
1503 elem.clear();
1504 elem.set_map();
1505 elem["name"] << name;
1506 continue;
1507 }
1508 // Objects that are interpreted by the document are written here, the others were written by the exporter.
1509 if (_export->typedObject) {
1510 HS3::writeHistFactoryDist(elem, *_export->typedObject);
1511 elem["name"] << name;
1512 _export->doc.distributions.push_back({name, std::move(*_export->typedObject)});
1513 _export->typedObject.reset();
1514 if (!_export->channelConstraints.empty()) {
1515 _export->doc.rooFit.channelConstraints[name] = std::move(_export->channelConstraints);
1516 }
1517 _export->channelConstraints.clear();
1518 } else {
1519 recordGenericObject(_export->doc, isPdf, name, elem);
1520 }
1521 if (exp->autoExportDependants()) {
1522 exportObjects(func.servers(), exportedObjectNames);
1523 } else {
1524 exportObjects(_serversToExport, exportedObjectNames);
1525 }
1526 for (auto &s : _serversToDelete) {
1527 delete s;
1528 }
1529 return;
1530 }
1531 }
1532
1533 // generic export using the factory expressions
1534 const auto &dict = exportKeys.find(cl);
1535 if (dict == exportKeys.end()) {
1536 std::cerr << "unable to export class '" << cl->GetName() << "' - no export keys available!\n"
1537 << "there are several possible reasons for this:\n"
1538 << " 1. " << cl->GetName() << " is a custom class that you or some package you are using added.\n"
1539 << " 2. " << cl->GetName()
1540 << " is a ROOT class that nobody ever bothered to write a serialization definition for.\n"
1541 << " 3. something is wrong with your setup, e.g. you might have called "
1542 "RooFit::JSONIO::clearExportKeys() and/or never successfully read a file defining these "
1543 "keys with RooFit::JSONIO::loadExportKeys(filename)\n"
1544 << "either way, please make sure that:\n"
1545 << " 3: you are reading a file with export keys - call RooFit::JSONIO::printExportKeys() to "
1546 "see what is available\n"
1547 << " 2 & 1: you might need to write a serialization definition yourself. check "
1548 "https://root.cern/doc/master/group__roofit__dev__docs__hs3.html to "
1549 "see how to do this!\n";
1550 return;
1551 }
1552
1553 elem["type"] << dict->second.type;
1554 recordGenericObject(_export->doc, isPdf, name, elem);
1555
1556 size_t nprox = func.numProxies();
1557
1558 for (size_t i = 0; i < nprox; ++i) {
1559 RooAbsProxy *p = func.getProxy(i);
1560 if (!p)
1561 continue;
1562
1563 // some proxies start with a "!". This is a magic symbol that we don't want to stream
1564 std::string pname(p->name());
1565 if (pname[0] == '!')
1566 pname.erase(0, 1);
1567
1568 auto k = dict->second.proxies.find(pname);
1569 if (k == dict->second.proxies.end()) {
1570 std::cerr << "failed to find key matching proxy '" << pname << "' for type '" << dict->second.type
1571 << "', encountered in '" << func.GetName() << "', skipping" << std::endl;
1572 return;
1573 }
1574
1575 // empty string is interpreted as an instruction to ignore this value
1576 if (k->second.empty())
1577 continue;
1578
1579 if (auto l = dynamic_cast<RooAbsCollection *>(p)) {
1580 fillSeq(elem[k->second], *l);
1581 }
1582 if (auto r = dynamic_cast<RooArgProxy *>(p)) {
1583 if (isLiteralConstVar(*r->absArg())) {
1584 elem[k->second] << static_cast<RooConstVar *>(r->absArg())->getVal();
1585 } else {
1586 elem[k->second] << r->absArg()->GetName();
1587 }
1588 }
1589 }
1590
1591 // export all the servers of a given RooAbsArg
1592 for (RooAbsArg *s : func.servers()) {
1593 if (!s) {
1594 std::cerr << "unable to locate server of " << func.GetName() << std::endl;
1595 continue;
1596 }
1597 this->exportObject(*s, exportedObjectNames);
1598 }
1599}
1600
1601/**
1602 * @brief Import a function from the JSONNode into the workspace.
1603 *
1604 * This function imports a function from the given JSONNode into the workspace.
1605 * The function's information is read from the JSONNode and added to the workspace.
1606 *
1607 * @param p The JSONNode representing the function to be imported.
1608 * @param importAllDependants A boolean flag indicating whether to import all dependants (servers) of the function.
1609 * @return void
1610 */
1611void RooJSONFactoryWSTool::importFunction(const JSONNode &p, bool importAllDependants)
1612{
1613 // To report the components that no importer understands.
1614 const RooFit::Detail::JSONReadRecorder recorder{p};
1615 std::string name(RooJSONFactoryWSTool::name(p));
1616
1617 // If this node if marked to be skipped by RooFit, exit
1618 if (hasAttribute(name, "roofit_skip")) {
1619 return;
1620 }
1621
1622 auto const &importers = RooFit::JSONIO::importers();
1623 auto const &factoryExpressions = RooFit::JSONIO::importExpressions();
1624
1625 // some preparations: what type of function are we dealing with here?
1627
1628 // if the RooAbsArg already exists, we don't need to do anything
1629 if (_workspace.arg(name)) {
1630 return;
1631 }
1632 ImportGuard importGuard{_importStack, name};
1633 // if the key we found is not a map, it's an error
1634 if (!p.is_map()) {
1635 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() function node " + name + " is not a map!");
1636 }
1637 std::string prefix = genPrefix(p, true);
1638 if (!prefix.empty())
1639 name = prefix + name;
1640 if (!p.has_child("type")) {
1641 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() no type given for function '" + name + "', skipping.");
1642 }
1643
1644 std::string functype(p["type"].val());
1645
1646 // import all dependents if importing a workspace, not for creating new objects
1647 if (!importAllDependants) {
1648 this->importDependants(p);
1649 }
1650
1651 // check for specific implementations
1652 auto it = importers.find(functype);
1653 bool ok = false;
1654 if (it != importers.end()) {
1655 for (auto &imp : it->second) {
1656 try {
1657 // The built-in importer doesn't see the constraint terms that the document adds to the channel.
1659 auto const &channelConstraints = _import->document.rooFit.channelConstraints;
1660 auto found = channelConstraints.find(name);
1661 HS3::importHistFactoryNode(*this, p, found != channelConstraints.end() ? &found->second : nullptr);
1662 ok = true;
1663 } else {
1664 ok = imp->importArg(this, p);
1665 }
1666 } catch (const std::exception &e) {
1667 std::stringstream ss;
1668 const auto *ptr = imp.get();
1669 ss << "RooJSONFactoryWSTool() failed. The importer " << typeid(*ptr).name()
1670 << " emitted and error: " << e.what() << std::endl;
1672 }
1673 if (ok)
1674 break;
1675 }
1676 }
1677 if (!ok) { // generic import using the factory expressions
1678 auto expr = factoryExpressions.find(functype);
1679 if (expr != factoryExpressions.end()) {
1680 std::string expression = ::generate(expr->second, p, this);
1681 if (!_workspace.factory(expression)) {
1682 std::stringstream ss;
1683 ss << "RooJSONFactoryWSTool() failed to create " << expr->second.tclass->GetName() << " '" << name
1684 << "', skipping. expression was\n"
1685 << expression << std::endl;
1687 }
1688 } else {
1689 std::stringstream ss;
1690 ss << "RooJSONFactoryWSTool() no handling for type '" << functype << "' implemented, skipping."
1691 << "\n"
1692 << "there are several possible reasons for this:\n"
1693 << " 1. " << functype << " is a custom type that is not available in RooFit.\n"
1694 << " 2. " << functype
1695 << " is a ROOT class that nobody ever bothered to write a deserialization definition for.\n"
1696 << " 3. something is wrong with your setup, e.g. you might have called "
1697 "RooFit::JSONIO::clearFactoryExpressions() and/or never successfully read a file defining "
1698 "these expressions with RooFit::JSONIO::loadFactoryExpressions(filename)\n"
1699 << "either way, please make sure that:\n"
1700 << " 3: you are reading a file with factory expressions - call "
1701 "RooFit::JSONIO::printFactoryExpressions() "
1702 "to see what is available\n"
1703 << " 2 & 1: you might need to write a deserialization definition yourself. check "
1704 "https://root.cern/doc/master/group__roofit__dev__docs__hs3.html to see "
1705 "how to do this!"
1706 << std::endl;
1708 return;
1709 }
1710 }
1712 if (!func) {
1713 RooJSONFactoryWSTool::error("something went wrong importing function '" + name + "'.");
1714 }
1715 for (std::string const &component : recorder.unreadComponents()) {
1716 warning("RooFitHS3: " + name + ": unknown component \"" + component + "\" is ignored");
1717 }
1718}
1719
1720// Import a distribution of the document, which is interpreted by the document or by the registered importers.
1722{
1723 if (auto const *generic = std::get_if<HS3::GenericObject>(&distribution.body)) {
1724 this->importFunction(*generic->node, true);
1725 return;
1726 }
1727 std::string const &name = distribution.name;
1728 if (hasAttribute(name, "roofit_skip")) {
1729 return;
1730 }
1732 if (_workspace.arg(name)) {
1733 return;
1734 }
1735 ImportGuard importGuard{_importStack, name};
1736 auto const &channelConstraints = _import->document.rooFit.channelConstraints;
1737 auto found = channelConstraints.find(name);
1738 HS3::buildHistFactoryDist(*this, name, std::get<HS3::HistFactoryDist>(distribution.body),
1739 found != channelConstraints.end() ? &found->second : nullptr);
1740}
1741
1742/**
1743 * @brief Import a function from a JSON string into the workspace.
1744 *
1745 * This function imports a function from the provided JSON string into the workspace.
1746 * The function's information is read from the JSON string and added to the workspace.
1747 *
1748 * @param jsonString The JSON string containing the function information.
1749 * @param importAllDependants A boolean flag indicating whether to import all dependants (servers) of the function.
1750 * @return void
1751 */
1752void RooJSONFactoryWSTool::importFunction(const std::string &jsonString, bool importAllDependants)
1753{
1754 this->importFunction((JSONTree::create(jsonString))->rootnode(), importAllDependants);
1755}
1756
1757/**
1758 * @brief Export the name and binning of a RooRealVar to a JSONNode.
1759 *
1760 * @param obsNode The JSONNode to which the axis information will be exported.
1761 * @param var The RooRealVar representing the axis to be exported.
1762 * @return void
1763 */
1765{
1766 std::string name = var.GetName();
1768 obsNode["name"] << name;
1769
1770 writeAxisBinning(obsNode, var.getBinning());
1771}
1772
1773/**
1774 * @brief Export histogram data to a JSONNode.
1775 *
1776 * This function exports histogram data, represented by the provided variables and contents, to a JSONNode.
1777 * The histogram's axes information and bin contents are added as key-value pairs to the JSONNode.
1778 *
1779 * @param vars The RooArgSet representing the variables associated with the histogram.
1780 * @param n The number of bins in the histogram.
1781 * @param contents A pointer to the array containing the bin contents of the histogram.
1782 * @param output The JSONNode to which the histogram data will be exported.
1783 * @return void
1784 */
1785void RooJSONFactoryWSTool::exportHisto(RooArgSet const &vars, std::size_t n, double const *contents, JSONNode &output)
1786{
1787 auto &observablesNode = output["axes"].set_seq();
1788 // axes have to be ordered to get consistent bin indices
1789 for (auto *var : static_range_cast<RooRealVar *>(vars)) {
1790 exportAxis(observablesNode.append_child().set_map(), *var);
1791 }
1792
1793 return exportArray(n, contents, output["contents"]);
1794}
1795
1796/**
1797 * @brief Export an array of doubles to a JSONNode.
1798 *
1799 * This function exports an array of doubles, represented by the provided size and contents,
1800 * to a JSONNode. The array elements are added to the JSONNode as a sequence of values.
1801 *
1802 * @param n The size of the array.
1803 * @param contents A pointer to the array containing the double values.
1804 * @param output The JSONNode to which the array will be exported.
1805 * @return void
1806 */
1807void RooJSONFactoryWSTool::exportArray(std::size_t n, double const *contents, JSONNode &output)
1808{
1809 output.set_seq();
1810 for (std::size_t i = 0; i < n; ++i) {
1811 double w = contents[i];
1812 // To make sure there are no unnecessary floating points in the JSON
1813 if (int(w) == w) {
1814 output.append_child() << int(w);
1815 } else {
1816 output.append_child() << w;
1817 }
1818 }
1819}
1820
1821/**
1822 * @brief Export a RooAbsCategory object to a JSONNode.
1823 *
1824 * This function exports a RooAbsCategory object, represented by the provided categories and indices,
1825 * to a JSONNode. The category labels and corresponding indices are added to the JSONNode as key-value pairs.
1826 *
1827 * @param cat The RooAbsCategory object to be exported.
1828 * @param node The JSONNode to which the category data will be exported.
1829 * @return void
1830 */
1832{
1833 auto &labels = node["labels"].set_seq();
1834 auto &indices = node["indices"].set_seq();
1835
1836 for (auto const &item : cat) {
1837 labels.append_child() << makeValidNameOrError(item.first);
1838 indices.append_child() << item.second;
1839 }
1840}
1841
1842// Split `data` by its index category into per-channel datasets and export each, returning the resulting
1843// component-name map.
1845{
1846 RooAbsCategory *cat = findCategoryObservable(data);
1847
1848 // prepare return value
1850
1851 if (!cat)
1852 return datamap;
1853 // this is a combined dataset
1854
1855 datamap.name = data.GetName();
1856
1857 // Write information necessary to reconstruct the combined dataset upon import
1858 HS3::CombinedDataset &info = _export->doc.rooFit.combinedDatasets[data.GetName()];
1859 info.indexCat = cat->GetName();
1860 for (auto const &item : *cat) {
1861 info.labels.push_back(makeValidNameOrError(item.first));
1862 info.indices.push_back(item.second);
1863 }
1864
1865 // Find a RooSimultaneous model that would fit to this dataset
1866 RooSimultaneous const *simPdf = nullptr;
1867 for (auto const &item : _export->doc.rooFit.combinedDistributions) {
1868 if (item.second.indexCat == cat->GetName()) {
1869 simPdf = static_cast<RooSimultaneous const *>(_workspace.pdf(item.first));
1870 }
1871 }
1872
1873 // If there is an associated simultaneous pdf for the index category, we
1874 // use the RooAbsData::split() overload that takes the RooSimultaneous.
1875 // Like this, the observables that are not relevant for a given channel
1876 // are automatically split from the component datasets.
1877 std::vector<std::unique_ptr<RooAbsData>> dataList{simPdf ? data.split(*simPdf, true) : data.split(*cat, true)};
1878
1879 for (std::unique_ptr<RooAbsData> const &absData : dataList) {
1880 std::string catName(absData->GetName());
1881 std::string dataName = makeValidNameOrError(catName);
1882 absData->SetName((std::string(data.GetName()) + "_" + dataName).c_str());
1883 datamap.components[catName] = absData->GetName();
1884 this->exportData(*absData);
1885 }
1886 return datamap;
1887}
1888
1889// Export a single dataset `data` (binned or unbinned) to the output document.
1891{
1892 RooAbsCategory *cat = findCategoryObservable(data);
1893
1894 if (cat)
1895 return;
1896
1897 HS3::Data output;
1898 output.name = data.GetName();
1899
1900 // This works around a problem in RooStats/HistFactory that was only fixed
1901 // in ROOT 6.30: until then, the weight variable of the observed dataset,
1902 // called "weightVar", was added to the observables. Therefore, it also got
1903 // added to the Asimov dataset. But the Asimov has its own weight variable,
1904 // called "binWeightAsimov", making "weightVar" an actual observable in the
1905 // Asimov data. But this is only by accident and should be removed.
1906 RooArgSet variables = *data.get();
1907 if (auto weightVar = variables.find("weightVar")) {
1908 variables.remove(*weightVar);
1909 }
1910
1911 auto exportBinned = [&](std::size_t n, double const *contents) {
1912 HS3::BinnedData binned;
1913 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1914 recordDomains(_export->domains, *var);
1915 binned.axes.emplace_back(HS3::toBinnedAxis(*var));
1916 }
1917 binned.contents.assign(contents, contents + n);
1918 output.body = std::move(binned);
1919 _export->doc.data.push_back(std::move(output));
1920 };
1921
1922 // this is a regular binned dataset
1923 if (auto dh = dynamic_cast<RooDataHist const *>(&data)) {
1924 return exportBinned(dh->numEntries(), dh->weightArray());
1925 }
1926
1927 // Check if this actually represents a binned dataset, and then import it
1928 // like a RooDataHist. This happens frequently when people create combined
1929 // RooDataSets from binned data to fit HistFactory models. In this case, it
1930 // doesn't make sense to export them like an unbinned dataset, because the
1931 // coordinates are redundant information with the binning. We only do this
1932 // for 1D data for now.
1933 if (data.isWeighted() && variables.size() == 1) {
1934 bool isBinnedData = false;
1935 auto &x = static_cast<RooRealVar const &>(*variables[0]);
1936 std::vector<double> contents;
1937 int i = 0;
1938 for (; i < data.numEntries(); ++i) {
1939 data.get(i);
1940 if (x.getBin() != i)
1941 break;
1942 contents.push_back(data.weight());
1943 }
1944 if (i == x.getBins())
1945 isBinnedData = true;
1946 if (isBinnedData) {
1947 return exportBinned(data.numEntries(), contents.data());
1948 }
1949 }
1950
1951 // this really is an unbinned dataset
1952 HS3::UnbinnedData unbinned;
1953 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1954 recordDomains(_export->domains, *var);
1955 // HS3 forbids binning information on the axes of unbinned data.
1956 testValidName(var->GetName(), false);
1957 unbinned.axes.emplace_back(HS3::ContinuousAxis{var->GetName(), var->getMin(), var->getMax()});
1958 }
1959 std::vector<double> weightVals;
1960 bool hasNonUnityWeights = false;
1961 for (int i = 0; i < data.numEntries(); ++i) {
1962 data.get(i);
1963 for (auto *var : static_range_cast<RooRealVar *>(variables)) {
1964 unbinned.entries.push_back(var->getVal());
1965 }
1966 if (data.isWeighted()) {
1967 weightVals.push_back(data.weight());
1968 if (data.weight() != 1.)
1969 hasNonUnityWeights = true;
1970 }
1971 }
1972 if (data.isWeighted() && hasNonUnityWeights) {
1973 unbinned.weights = std::move(weightVals);
1974 }
1975 output.body = std::move(unbinned);
1976 _export->doc.data.push_back(std::move(output));
1977}
1978
1979/**
1980 * @brief Read axes from the JSONNode and create a RooArgSet representing them.
1981 *
1982 * This function reads axes information from the given JSONNode and
1983 * creates a RooArgSet with variables representing these axes.
1984 *
1985 * @param topNode The JSONNode containing the axes information to be read.
1986 * @return RooArgSet A RooArgSet containing the variables created from the JSONNode.
1987 */
1989{
1990 HS3::Diagnostics diagnostics;
1991 std::vector<HS3::BinnedAxis> axes = HS3::parseBinnedAxes(topNode["axes"], diagnostics);
1992 HS3::report(diagnostics);
1993 return HS3::createObservables(axes);
1994}
1995
1996/**
1997 * @brief Read binned data from the JSONNode and create a RooDataHist object.
1998 *
1999 * This function reads binned data from the given JSONNode and creates a RooDataHist object.
2000 * The binned data is associated with the specified name and variables (RooArgSet) in the workspace.
2001 *
2002 * @param n The JSONNode representing the binned data to be read.
2003 * @param name The name to be associated with the created RooDataHist object.
2004 * @param vars The RooArgSet representing the variables associated with the binned data.
2005 * @return std::unique_ptr<RooDataHist> A unique pointer to the created RooDataHist object.
2006 */
2007std::unique_ptr<RooDataHist>
2008RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std::string &name, RooArgSet const &vars)
2009{
2010 if (!n.has_child("contents"))
2011 RooJSONFactoryWSTool::error("no contents given");
2012
2013 JSONNode const &contents = n["contents"];
2014
2015 if (!contents.is_seq())
2016 RooJSONFactoryWSTool::error("contents are not in list form");
2017
2018 std::vector<double> contentVals;
2019 contentVals << contents;
2020
2021 if (!n.has_child("errors")) {
2022 return HS3::createDataHist(name, vars, contentVals, nullptr);
2023 }
2024 if (!n["errors"].is_seq())
2025 RooJSONFactoryWSTool::error("errors are not in list form");
2026 std::vector<double> errorVals;
2027 errorVals << n["errors"];
2028 return HS3::createDataHist(name, vars, contentVals, &errorVals);
2029}
2030
2031// Import a single variable (RooRealVar or RooConstVar) from the parameter `p` into the workspace.
2033{
2034 // import a RooRealVar object
2036
2037 if (_workspace.arg(p.name))
2038 return;
2039 // A string selects the state of a category, which is done together with the other parameter points.
2040 if (p.value && std::holds_alternative<std::string>(*p.value))
2041 return;
2042 if (!_import->ranges.contains(p.name)) {
2043 // ROOT versions before HS3 0.3 marked named RooConstVars with an attribute.
2044 auto const &attributes = _import->document.rooFit.attributes;
2045 auto found = attributes.find(p.name);
2046 if (found != attributes.end() && found->second.isConstVar) {
2047 if (!p.value) {
2048 RooJSONFactoryWSTool::error("cannot instantiate RooConstVar '" + p.name + "' without \"value\"!");
2049 }
2050 wsEmplace<RooConstVar>(p.name, std::get<double>(*p.value));
2051 return;
2052 }
2053 }
2054 RooRealVar &var = wsEmplace<RooRealVar>(p.name, 1.);
2055 configureVariable(_import->ranges, p, var);
2057}
2058
2059// HS3 requires every parameter to be listed in a domain or constant ("const": true) in the parameter point.
2061{
2062 if (!var.isConstant() && !_import->ranges.contains(var.GetName())) {
2063 warning(std::string("RooFitHS3: the parameter '") + var.GetName() +
2064 "' is not listed in any domain and is not constant: HS3 requires a domain, or \"const\": true. It "
2065 "is read as a free parameter without range.");
2066 }
2067}
2068
2069// Import the objects of a fragment of a document, for which the node `n` plays the role of the document.
2071{
2072 for (const char *section : {"functions", "distributions"}) {
2073 if (auto seq = n.find(section)) {
2074 for (const auto &p : seq->children()) {
2075 this->importFunction(p, true);
2076 }
2077 }
2078 }
2079}
2080
2082 const std::vector<CombinedData> &combDataSets,
2083 const std::vector<RooAbsData *> &singleDataSets)
2084{
2085 auto pdf = mc.GetPdf();
2086 auto simpdf = dynamic_cast<RooSimultaneous const *>(pdf);
2087 if (simpdf) {
2088 for (std::size_t i = 0; i < std::max(combDataSets.size(), std::size_t(1)); ++i) {
2089 const bool hasdata = i < combDataSets.size();
2090 if (hasdata && !matches(combDataSets.at(i), simpdf))
2091 continue;
2092
2093 std::string analysisName(mc.GetName());
2094 if (hasdata)
2095 analysisName += "_" + combDataSets[i].name;
2096
2097 exportSingleModelConfig(mc, analysisName, hasdata ? &combDataSets[i].components : nullptr);
2098 }
2099 } else {
2100 RooArgSet observables(*mc.GetObservables());
2101 int founddata = 0;
2102 for (auto *data : singleDataSets) {
2103 if (observables.equals(*(data->get()))) {
2104 std::map<std::string, std::string> mapping;
2105 mapping[pdf->GetName()] = data->GetName();
2106 exportSingleModelConfig(mc, std::string(mc.GetName()) + "_" + data->GetName(), &mapping);
2107 ++founddata;
2108 }
2109 }
2110 if (founddata == 0) {
2111 exportSingleModelConfig(mc, mc.GetName(), nullptr);
2112 }
2113 }
2114}
2115
2116void RooJSONFactoryWSTool::exportSingleModelConfig(RooFit::ModelConfig const &mc, std::string const &analysisName,
2117 std::map<std::string, std::string> const *dataComponents)
2118{
2119 HS3Document &doc = _export->doc;
2120 auto pdf = mc.GetPdf();
2121
2122 HS3::Analysis analysis;
2123 analysis.name = analysisName;
2124
2125 // HS3 names must be unique across all top-level components, so the likelihood can't have the analysis name.
2126 HS3::Likelihood likelihood;
2127 likelihood.name = analysisName + "_likelihood";
2128 analysis.likelihood.name = likelihood.name;
2129
2130 if (dataComponents) {
2131 auto simPdf = dynamic_cast<RooSimultaneous const *>(pdf);
2132 if (simPdf) {
2133 for (auto const &item : simPdf->indexCat()) {
2134 const auto &dataComp = dataComponents->find(item.first);
2135 likelihood.terms.push_back({{simPdf->getPdf(item.first)->GetName()}, HS3::DataRef{dataComp->second}});
2136 }
2137 } else {
2138 for (auto it : *dataComponents) {
2139 likelihood.terms.push_back({{it.first}, HS3::DataRef{it.second}});
2140 }
2141 }
2142 } else {
2143 likelihood.terms.push_back({{pdf->GetName()}, 0.0});
2144 }
2145
2146 auto addAux = [&](std::string const &name) {
2147 auto &aux = likelihood.auxDistributions;
2148 if (std::find_if(aux.begin(), aux.end(), [&](auto const &ref) { return ref.name == name; }) == aux.end()) {
2149 aux.push_back({name});
2150 }
2151 };
2152 // The constraint terms of HistFactory channels that no modifier names are part of the likelihood.
2153 for (auto const &term : likelihood.terms) {
2154 auto found = doc.rooFit.channelConstraints.find(term.distribution.name);
2155 if (found != doc.rooFit.channelConstraints.end()) {
2156 for (auto const &ref : found->second) {
2157 addAux(ref.name);
2158 }
2159 }
2160 }
2161 if (mc.GetExternalConstraints()) {
2162 for (const auto &constr : *mc.GetExternalConstraints()) {
2163 addAux(constr->GetName());
2164 }
2165 }
2166 doc.likelihoods.push_back(std::move(likelihood));
2167
2168 if (RooArgSet const *pois = mc.GetParametersOfInterest()) {
2169 std::vector<std::string> names;
2170 names.reserve(pois->size());
2171 for (RooAbsArg const *arg : *pois)
2172 names.push_back(arg->GetName());
2173 std::sort(names.begin(), names.end());
2174 for (auto const &name : names) {
2175 analysis.parametersOfInterest.push_back({name});
2176 }
2177 }
2178
2179 auto writeProductDomain = [&](const char *suffix, RooArgSet const *args) {
2180 if (!args || args->empty())
2181 return;
2182 const std::string domainName = analysisName + suffix;
2183 analysis.domains.push_back({domainName});
2184 std::map<std::string, HS3::DomainAxis> axes;
2185 for (auto *var : static_range_cast<const RooRealVar *>(*args)) {
2186 axes[var->GetName()] = toDomainAxis(var->GetName(), var->getBinning());
2187 }
2188 appendDomain(doc.domains, domainName, std::move(axes));
2189 };
2190
2191 writeProductDomain("_nuisance_parameters", mc.GetNuisanceParameters());
2192 writeProductDomain("_global_observables", mc.GetGlobalObservables());
2193 writeProductDomain("_parameters_of_interest", mc.GetParametersOfInterest());
2194
2195 // The snapshots are exported as parameter points. Reference the one of the ModelConfig as the initial values.
2196 if (_workspace.getSnapshot(mc.GetSnapshotName().c_str())) {
2198 }
2199 // The prior is a top-level pdf of the workspace or a component of one, so it is exported already.
2200 if (RooAbsPdf const *prior = mc.GetPriorPdf()) {
2201 analysis.prior = HS3::DistributionRef{prior->GetName()};
2202 }
2203 doc.analyses.push_back(std::move(analysis));
2204
2205 HS3::ModelConfigInfo &info = doc.rooFit.modelConfigs[analysisName];
2206 info.pdfName = pdf->GetName();
2207 info.mcName = mc.GetName();
2208}
2209
2210// Export all top-level pdfs, functions, datasets and ModelConfigs of the workspace into `n`.
2212{
2213 _export = std::make_unique<HS3::ToolExportState>();
2214 _export->root = &n;
2215 // The state of the export must not survive it, not even if there is an exception.
2216 struct ExportScope {
2218 ~ExportScope() { tool._export.reset(); }
2219 } exportScope{*this};
2220
2221 HS3Document &doc = _export->doc;
2223 doc.metadata.packages.push_back({"ROOT", rootVersion()});
2224 doc.parameterPoints.push_back({"default_values", {}});
2225
2226 // export all toplevel pdfs
2227 std::vector<RooAbsPdf *> allpdfs;
2228 for (auto &arg : _workspace.allPdfs()) {
2229 if (isTopLevel(*arg, _workspace)) {
2230 if (auto *pdf = dynamic_cast<RooAbsPdf *>(arg)) {
2231 allpdfs.push_back(pdf);
2232 }
2233 }
2234 }
2235 sortByName(allpdfs);
2236 std::set<std::string> exportedObjectNames;
2237 exportObjects(allpdfs, exportedObjectNames);
2238
2239 // export all toplevel functions
2240 std::vector<RooAbsReal *> allfuncs;
2241 for (auto &arg : _workspace.allFunctions()) {
2242 if (isTopLevel(*arg, _workspace)) {
2243 if (auto *func = dynamic_cast<RooAbsReal *>(arg)) {
2244 allfuncs.push_back(func);
2245 }
2246 }
2247 }
2248 sortByName(allfuncs);
2249 exportObjects(allfuncs, exportedObjectNames);
2250
2251 // export attributes of all objects
2252 for (RooAbsArg *arg : _workspace.components()) {
2253 exportAttributes(arg, doc.rooFit.attributes);
2254 }
2255
2256 // collect all datasets
2257 std::vector<RooAbsData *> alldata;
2258 for (auto &d : _workspace.allData()) {
2259 alldata.push_back(d);
2260 }
2261 sortByName(alldata);
2262 // first, take care of combined datasets
2263 std::vector<RooAbsData *> singleData;
2264 std::vector<RooJSONFactoryWSTool::CombinedData> combData;
2265 for (auto &d : alldata) {
2266 auto data = this->exportCombinedData(*d);
2267 if (!data.components.empty()) {
2268 combData.push_back(data);
2269 } else {
2270 singleData.push_back(d);
2271 }
2272 }
2273 // next, take care datasets
2274 for (auto &d : alldata) {
2275 this->exportData(*d);
2276 }
2277
2278 // export all ModelConfig objects and attached Pdfs
2279 for (TObject *obj : _workspace.allGenericObjects()) {
2280 if (auto mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
2281 exportModelConfig(*mc, combData, singleData);
2282 }
2283 }
2284
2285 exportParameterStepWidths(_workspace, allpdfs, alldata, doc.rooFit.stepWidths);
2286
2287 for (auto *snsh : static_range_cast<RooArgSet const *>(_workspace.getSnapshots())) {
2288 RooArgSet snapshotSorted;
2289 // We only want to add the variables that actually got exported and skip
2290 // the ones that the pdfs encoded implicitly (like in the case of
2291 // HistFactory).
2292 for (RooAbsArg *arg : *snsh) {
2293 bool do_export = false;
2294 for (const auto &pdf : allpdfs) {
2295 if (pdf->dependsOn(*arg)) {
2296 do_export = true;
2297 }
2298 }
2299 if (do_export) {
2301 snapshotSorted.add(*arg);
2302 }
2303 }
2304 snapshotSorted.sort();
2305 std::string name(snsh->GetName());
2306 if (name != "default_values") {
2307 doc.parameterPoints.push_back({name, {}});
2308 this->exportVariables(snapshotSorted, doc.parameterPoints.back(), true);
2309 }
2310 }
2311 // The domains of the analyses take precedence.
2312 for (auto &item : _export->domains) {
2313 if (std::none_of(doc.domains.begin(), doc.domains.end(),
2314 [&](HS3::Domain const &domain) { return domain.name == item.first; })) {
2315 appendDomain(doc.domains, item.first, std::move(item.second));
2316 }
2317 }
2318
2319 // The document that RooFit writes has to pass the same checks as the ones that it reads. Until RooFit writes
2320 // documents that follow the current standard, problems are only reported.
2321 HS3::Diagnostics diagnostics;
2322 HS3::ValidateOptions validateOptions;
2323 validateOptions.isExternal = [](std::string const &) { return false; };
2325 HS3::checkDocument(doc, diagnostics, validateOptions);
2326 for (auto &diagnostic : diagnostics) {
2327 diagnostic.severity = HS3::Diagnostic::Severity::Warning;
2328 diagnostic.message = "the exported document is not valid: " + diagnostic.message;
2329 }
2330 HS3::report(diagnostics);
2331 HS3::writeDocument(n, doc);
2332}
2333
2334/**
2335 * @brief Import the workspace from a JSON string.
2336 *
2337 * @param s The JSON string containing the workspace data.
2338 * @return bool Returns true on successful import, false otherwise.
2339 */
2341{
2342 std::stringstream ss(s);
2343 return importJSON(ss);
2344}
2345
2346/**
2347 * @brief Export the workspace to a JSON string.
2348 *
2349 * @return std::string The JSON string representing the exported workspace.
2350 */
2352{
2353 std::stringstream ss;
2354 exportJSON(ss);
2355 return ss.str();
2356}
2357
2358/**
2359 * @brief Create a new JSON tree with version information.
2360 *
2361 * @return std::unique_ptr<JSONTree> A unique pointer to the created JSON tree.
2362 */
2364{
2365 std::unique_ptr<JSONTree> tree = JSONTree::create();
2366 JSONNode &n = tree->rootnode();
2367 n.set_map();
2368 auto &metadata = n["metadata"].set_map();
2369
2370 // add the mandatory hs3 version number
2371 metadata["hs3_version"] << hs3VersionTag;
2372
2373 appendNamedChild(metadata["packages"], "ROOT")["version"] << rootVersion();
2374
2375 return tree;
2376}
2377
2378/**
2379 * @brief Export the workspace to JSON format and write to the output stream.
2380 *
2381 * @param os The output stream to write the JSON data to.
2382 * @return bool Returns true on successful export, false otherwise.
2383 */
2385{
2386 std::unique_ptr<JSONTree> tree = JSONTree::create();
2387 JSONNode &n = tree->rootnode().set_map();
2388 this->exportAllObjects(n);
2389 n.writeJSON(os);
2390 return true;
2391}
2392
2393/**
2394 * @brief Export the workspace to JSON format and write to the specified file.
2395 *
2396 * @param filename The name of the JSON file to create and write the data to.
2397 * @return bool Returns true on successful export, false otherwise.
2398 */
2400{
2401 std::ofstream out(filename.c_str());
2402 if (!out.is_open())
2403 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() invalid output file '" + filename + "'.");
2404 return this->exportJSON(out);
2405}
2406
2407bool RooJSONFactoryWSTool::hasAttribute(const std::string &obj, const std::string &attrib)
2408{
2409 if (!_import)
2410 return false;
2411 auto const &attributes = _import->document.rooFit.attributes;
2412 auto found = attributes.find(obj);
2413 if (found == attributes.end())
2414 return false;
2415 auto const &tags = found->second.tags;
2416 return std::find(tags.begin(), tags.end(), attrib) != tags.end();
2417}
2418void RooJSONFactoryWSTool::setAttribute(const std::string &obj, const std::string &attrib)
2419{
2420 _export->doc.rooFit.attributes[obj].tags.push_back(attrib);
2421}
2422
2423std::string RooJSONFactoryWSTool::getStringAttribute(const std::string &obj, const std::string &attrib)
2424{
2425 if (!_import)
2426 return "";
2427 auto const &attributes = _import->document.rooFit.attributes;
2428 auto found = attributes.find(obj);
2429 if (found == attributes.end())
2430 return "";
2431 auto value = found->second.dict.find(attrib);
2432 return value != found->second.dict.end() ? value->second : "";
2433}
2434void RooJSONFactoryWSTool::setStringAttribute(const std::string &obj, const std::string &attrib,
2435 const std::string &value)
2436{
2437 _export->doc.rooFit.attributes[obj].dict[attrib] = value;
2438}
2439
2440// Import the JSON document rooted at `n` into the workspace.
2442{
2443 HS3::ParseOptions options;
2444 // A user can replace how HistFactory distributions are imported by registering their own importer.
2445 auto const &importers = RooFit::JSONIO::importers();
2446 auto histFactoryImporters = importers.find("histfactory_dist");
2447 options.typedHistFactory = histFactoryImporters != importers.end() && !histFactoryImporters->second.empty() &&
2448 HS3::isBuiltinHistFactoryImporter(*histFactoryImporters->second.front());
2449
2450 HS3::Diagnostics diagnostics;
2451 HS3::upgradeDocument(n, diagnostics, options);
2452 HS3Document parsed = HS3::parseDocument(n, diagnostics, options);
2453 std::optional<RooFit::ValidatedHS3Document> validated;
2454 if (!HS3::hasErrors(diagnostics)) {
2455 HS3::ValidateOptions validateOptions;
2456 validateOptions.strict = !isHS3VersionBefore(parsed.metadata.hs3Version, 0, 3);
2457 validateOptions.isExternal = [this](std::string const &name) { return _workspace.obj(name) != nullptr; };
2458 validated = HS3::validate(std::move(parsed), diagnostics, validateOptions);
2459 }
2460 HS3::report(diagnostics);
2461 HS3Document const &doc = validated->doc();
2462
2463 // The state of the import refers to the document, so it must not survive it, not even if there is an exception.
2464 struct ImportScope {
2466 ~ImportScope()
2467 {
2468 tool._import.reset();
2469 tool._hs3VersionInput.clear();
2470 }
2471 } importScope{*this};
2472
2474
2475 _import = std::make_unique<HS3::ToolImportState>(doc);
2476
2477 createVariables(_workspace, _import->document.domains, _import->ranges);
2478
2479 if (_import->defaultValues) {
2480 for (auto const &p : _import->defaultValues->parameters) {
2482 }
2483 }
2484 for (auto const &function : doc.functions) {
2485 this->importFunction(*function.body.node, true);
2486 }
2487 for (auto const &distribution : doc.distributions) {
2488 this->importDistribution(distribution);
2489 }
2490
2491 // This creates the index categories, which the parameter points can refer to.
2492 combinePdfs(doc, _workspace);
2493
2494 for (auto const &point : doc.parameterPoints) {
2495 RooJSONFactoryWSTool::testValidName(point.name, true);
2496
2497 RooArgSet vars;
2498 for (auto const &var : point.parameters) {
2499 std::string const *label = var.value ? std::get_if<std::string>(&*var.value) : nullptr;
2500 if (RooCategory *cat = _workspace.cat(var.name)) {
2501 if (label) {
2502 cat->setLabel(label->c_str());
2503 } else if (var.value) {
2504 cat->setIndex(static_cast<int>(std::get<double>(*var.value)));
2505 }
2506 vars.add(*cat);
2507 continue;
2508 }
2509 if (label) {
2510 warning("RooFitHS3: ignoring the string value of parameter '" + var.name + "' in parameter point '" +
2511 point.name + "', because there is no category with that name.");
2512 continue;
2513 }
2514 RooRealVar *rrv = _workspace.var(var.name);
2515 if (!rrv && !_workspace.arg(var.name)) {
2516 // The parameter is not used by any other component: create it. "const": false is the same as no
2517 // "const" at all.
2518 testValidName(var.name, true);
2519 rrv = &wsEmplace<RooRealVar>(var.name, 1.);
2520 configureVariable(_import->ranges, var, *rrv);
2522 } else if (rrv) {
2523 configureVariable(_import->ranges, var, *rrv);
2524 }
2525 if (rrv) {
2526 vars.add(*rrv);
2527 }
2528 }
2529 _workspace.saveSnapshot(point.name, vars);
2530 }
2531
2532 // Import attributes
2533 for (auto const &item : doc.rooFit.attributes) {
2534 if (RooAbsArg *arg = _workspace.arg(item.first))
2535 importAttributes(arg, item.second);
2536 }
2537
2538 // We delay the import of the data to after combineDatasets(), because it
2539 // might be that some datasets are merged to combined datasets there. In
2540 // that case, we will remove the components from the "datasets" vector so they
2541 // don't get imported.
2542 std::vector<std::unique_ptr<RooAbsData>> datasets;
2543 datasets.reserve(doc.data.size());
2544 for (auto const &data : doc.data) {
2545 datasets.push_back(loadData(data, _workspace, &_import->ranges));
2546 }
2547
2548 // Now, read in analyses and likelihoods if there are any
2549
2550 for (auto const &analysis : doc.analyses) {
2551 importAnalysis(doc, analysis, _workspace, datasets);
2552 }
2553
2554 combineDatasets(doc, datasets);
2555
2556 for (auto const &d : datasets) {
2557 if (d) {
2559 for (auto const &obs : *d->get()) {
2560 if (auto *rrv = dynamic_cast<RooRealVar *>(obs)) {
2561 _workspace.var(rrv->GetName())->setBinning(rrv->getBinning());
2562 }
2563 }
2564 }
2565 }
2566
2567 if (_workspace.getSnapshot("default_values")) {
2568 _workspace.loadSnapshot("default_values");
2569 }
2570 importParameterStepWidths(_workspace, doc);
2571}
2572
2573/**
2574 * @brief Imports a JSON file from the given input stream to the workspace.
2575 *
2576 * @param is The input stream containing the JSON data.
2577 * @return bool Returns true on successful import, false otherwise.
2578 */
2580{
2581 // import a JSON file to the workspace
2582 std::unique_ptr<JSONTree> tree = JSONTree::create(is);
2583 this->importAllNodes(tree->rootnode());
2584 return true;
2585}
2586
2587/**
2588 * @brief Imports a JSON file from the given filename to the workspace.
2589 *
2590 * @param filename The name of the JSON file to import.
2591 * @return bool Returns true on successful import, false otherwise.
2592 */
2594{
2595 // import a JSON file to the workspace
2596 std::ifstream infile(filename.c_str());
2597 if (!infile.is_open())
2598 RooJSONFactoryWSTool::error("RooJSONFactoryWSTool() invalid input file '" + filename + "'.");
2599 return this->importJSON(infile);
2600}
2601
2602void RooJSONFactoryWSTool::importJSONElement(const std::string &name, const std::string &jsonString)
2603{
2604 // Create the JSON Tree from the string
2605 std::unique_ptr<RooFit::Detail::JSONTree> tree = RooFit::Detail::JSONTree::create(jsonString);
2606 JSONNode &n = tree->rootnode();
2607
2608 // If the objects containts a parameter of interest, import it as a modelConfig
2609 if (n.find("poi")) {
2610
2611 RooStats::ModelConfig modelConfig{"ModelConfig"};
2612 std::string poi = n.find("poi")->val();
2613 std::string pdname = n.find("pdfName")->val();
2614 modelConfig.SetWS(_workspace);
2615 modelConfig.SetPdf(pdname.c_str());
2616 modelConfig.SetParametersOfInterest(_workspace.argSet(poi));
2617 _workspace.import(modelConfig);
2618
2619 return;
2620 }
2621
2622 n["name"] << name;
2623
2624 bool isVariable = true;
2625 bool isData = false;
2626 // Check for the type of object, if it doesn't contain a type, it must be a variable
2627 if (n.find("type")) {
2628 isVariable = false;
2629 std::string elementType = n.find("type")->val();
2630 if (elementType == "binned" || elementType == "unbinned") {
2631 isData = true;
2632 }
2633 }
2634
2635 // Import the object to the workspace
2636 if (isVariable) {
2637 this->importVariableElement(n);
2638 } else if (isData) {
2639 HS3::Diagnostics diagnostics;
2641 readValue(HS3::Cursor{n, diagnostics}, data);
2642 HS3::report(diagnostics);
2643 _workspace.import(*loadData(data, _workspace, nullptr), RooFit::Silence());
2644 } else {
2645 this->importFunction(n, false);
2646 }
2647}
2648
2650{
2651 // A variable is given by its "value", by its range "min" and "max", or by both. With only a value, it is constant.
2652 JSONNode const *value = elementNode.find("value");
2653 JSONNode const *min = elementNode.find("min");
2654 JSONNode const *max = elementNode.find("max");
2655 if (!value && !(min && max)) {
2656 throw std::invalid_argument("Invalid Syntax: Please provide either 'value' or 'min' and 'max' or both");
2657 }
2658 HS3::DomainAxis axis;
2659 axis.name = elementNode["name"].val();
2660 HS3::ParameterValue parameter;
2661 parameter.name = axis.name;
2662 if (min && max) {
2663 axis.min = min->val_double();
2664 axis.max = max->val_double();
2665 }
2666 parameter.value = value ? value->val_double() : (*axis.max + *axis.min) / 2;
2667 parameter.isConst = value && !(min && max);
2668 HS3Document doc;
2669 doc.domains.push_back({HS3::defaultDomainName, {std::move(axis)}});
2670 doc.parameterPoints.push_back({"default_values", {parameter}});
2671
2672 _import = std::make_unique<HS3::ToolImportState>(doc);
2673 struct ImportScope {
2675 ~ImportScope() { tool._import.reset(); }
2676 } importScope{*this};
2677
2678 importVariable(parameter);
2679 if (RooRealVar *rrv = _workspace.var(parameter.name)) {
2680 configureVariable(_import->ranges, parameter, *rrv);
2681 }
2682}
2683
2684/**
2685 * @brief Writes a warning message to the RooFit message service.
2686 *
2687 * @param str The warning message to be logged.
2688 * @return std::ostream& A reference to the output stream.
2689 */
2690std::ostream &RooJSONFactoryWSTool::warning(std::string const &str)
2691{
2692 return RooMsgService::instance().log(nullptr, RooFit::MsgLevel::WARNING, RooFit::ObjectHandling) << str << std::endl;
2693}
2694
2695/**
2696 * @brief Writes an error message to the RooFit message service and throws a runtime_error.
2697 *
2698 * @param s The error message to be logged and thrown.
2699 * @return void
2700 */
2702{
2703 RooMsgService::instance().log(nullptr, RooFit::MsgLevel::ERROR, RooFit::IO) << s << std::endl;
2704 throw std::runtime_error(s);
2705}
2706
2707/**
2708 * @brief Cleans up names to the HS3 standard
2709 *
2710 * @param str The string to be sanitized.
2711 * @return std::string
2712 */
2713std::string RooJSONFactoryWSTool::sanitizeName(const std::string str)
2714{
2715 std::string result;
2716 if (RooJSONFactoryWSTool::config().allowSanitizeNames) {
2717 for (char c : str) {
2718 switch (c) {
2719 case '[':
2720 case '|':
2721 case ',':
2722 case '(': result += '_'; break;
2723 case ']':
2724 case ')':
2725 // skip these characters entirely
2726 break;
2727 case '.': result += "_dot_"; break;
2728 case '@': result += "at"; break;
2729 case '-': result += "minus"; break;
2730 case '/': result += "_div_"; break;
2731
2732 default: result += c; break;
2733 }
2734 }
2735 return result;
2736 }
2737 return str;
2738}
2739
2741{
2742 // Variables
2743
2744 RooWorkspace tmpWS = RooWorkspace();
2745 if (onlyModelConfig) {
2746 for (auto *obj : ws.allGenericObjects()) {
2747 if (auto *mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
2748 tmpWS.import(*mc->GetPdf(), RooFit::RecycleConflictNodes(true));
2749 }
2750 }
2751
2752 } else {
2753
2754 for (auto *pdf : ws.allPdfs()) {
2755 if (isTopLevel(*pdf, ws)) {
2756 tmpWS.import(*pdf, RooFit::RecycleConflictNodes(true));
2757 }
2758 }
2759
2760 for (auto *func : ws.allFunctions()) {
2761 if (isTopLevel(*func, ws)) {
2762 tmpWS.import(*func, RooFit::RecycleConflictNodes(true));
2763 }
2764 }
2765 }
2766
2767 for (auto *data : ws.allData()) {
2768 tmpWS.import(*data);
2769 }
2770
2771 for (auto *obj : ws.allGenericObjects()) {
2772 tmpWS.import(*obj);
2773 }
2774
2775 for (auto *obj : ws.allResolutionModels()) {
2776 tmpWS.import(*obj);
2777 }
2778
2779 for (auto *snsh : ws.getSnapshots()) {
2780 auto *snshSet = dynamic_cast<RooArgSet *>(snsh);
2781 if (snshSet) {
2782 tmpWS.saveSnapshot(snshSet->GetName(), *snshSet, true);
2783 }
2784 }
2785
2786 return tmpWS;
2787}
2788
2790{
2791 static Config conf;
2792 return conf;
2793}
2794
2795// Sanitize all names in the workspace to be HS3 compliant
2797{
2798 // Variables
2799
2800 RooWorkspace tmpWS = cleanWS(ws, false);
2801
2802 auto sanitizeIfNeeded = [](auto const &list) {
2803 for (auto *obj : list) {
2804 if (!isValidName(obj->GetName())) {
2805 obj->SetName(sanitizeName(obj->GetName()).c_str());
2806 }
2807 }
2808 };
2809 sanitizeIfNeeded(tmpWS.allVars());
2810 sanitizeIfNeeded(tmpWS.allFunctions());
2811 sanitizeIfNeeded(tmpWS.allPdfs());
2812 sanitizeIfNeeded(tmpWS.allResolutionModels());
2813 // Datasets
2814 for (auto *data : tmpWS.allData()) {
2815 // Sanitize dataset name
2816 if (!isValidName(data->GetName())) {
2817 data->SetName(sanitizeName(data->GetName()).c_str());
2818 }
2819 for (auto *obj : *data->get()) {
2820 obj->SetName(sanitizeName(obj->GetName()).c_str());
2821 }
2822 }
2823 for (auto *data : tmpWS.allEmbeddedData()) {
2824 // Sanitize dataset name
2825 data->SetName(sanitizeName(data->GetName()).c_str());
2826 for (auto *obj : *data->get()) {
2827 obj->SetName(sanitizeName(obj->GetName()).c_str());
2828 }
2829 }
2830 for (auto *snshObj : tmpWS.getSnapshots()) {
2831 // Snapshots are stored as TObject*, but really they are RooArgSet*
2832 auto *snsh = dynamic_cast<RooArgSet *>(snshObj);
2833 if (!snsh) {
2834 std::cerr << "Warning: found snapshot that is not a RooArgSet, skipping\n";
2835 continue;
2836 }
2837
2838 // Sanitize snapshot name
2839 if (!isValidName(snsh->GetName())) {
2840 snsh->setName(sanitizeName(snsh->GetName()).c_str());
2841 }
2842
2843 // Sanitize the variables inside the snapshot
2844 for (auto *arg : *snsh) {
2845 if (!isValidName(arg->GetName())) {
2846 arg->SetName(sanitizeName(arg->GetName()).c_str());
2847 }
2848 }
2849 }
2850
2851 // Generic objects (ModelConfigs, attributes, etc.)
2852 for (auto *obj : tmpWS.allGenericObjects()) {
2853 if (!isValidName(obj->GetName())) {
2854 if (auto *named = dynamic_cast<TNamed *>(obj)) {
2855 named->SetName(sanitizeName(named->GetName()).c_str());
2856 } else {
2857 std::cerr << "Warning: object " << obj->GetName() << " is not TNamed, cannot rename.\n";
2858 }
2859 }
2860
2861 if (auto *mc = dynamic_cast<RooFit::ModelConfig *>(obj)) {
2862 // Sanitize ModelConfig name
2863 if (!isValidName(mc->GetName())) {
2864 mc->SetName(sanitizeName(mc->GetName()).c_str());
2865 }
2866
2867 // Sanitize the sets inside ModelConfig
2868 for (auto *obs : mc->GetObservables()->get()) {
2869 if (obs) {
2870 obs->SetName(sanitizeName(obs->GetName()).c_str());
2871 }
2872 }
2873 for (auto *poi : mc->GetParametersOfInterest()->get()) {
2874 if (poi) {
2875 poi->SetName(sanitizeName(poi->GetName()).c_str());
2876 }
2877 }
2878 for (auto *nuis : mc->GetNuisanceParameters()->get()) {
2879 if (nuis) {
2880 nuis->SetName(sanitizeName(nuis->GetName()).c_str());
2881 }
2882 }
2883 for (auto *glob : mc->GetGlobalObservables()->get()) {
2884 if (glob) {
2885 glob->SetName(sanitizeName(glob->GetName()).c_str());
2886 }
2887 }
2888 }
2889 }
2890 std::string wsName = std::string{ws.GetName()} + "_sanitized";
2891 RooWorkspace newWS = cleanWS(tmpWS, false);
2892 newWS.SetName(wsName.c_str());
2893
2894 return newWS;
2895}
bool isHS3VersionBefore(std::string const &version, int major, int minor)
bool startsWith(std::string_view str, std::string_view prefix)
bool endsWith(std::string_view str, std::string_view suffix)
void writeAxisBinning(JSONNode &node, const RooAbsBinning &binning)
#define d(i)
Definition RSha256.hxx:102
#define f(i)
Definition RSha256.hxx:104
#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)
static unsigned int total
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 input
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
Option_t Option_t TPoint TPoint const char text
char name[80]
Definition TGX11.cxx:142
#define gROOT
Definition TROOT.h:417
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:77
bool dependsOn(const RooAbsCollection &serverList, const RooAbsArg *ignoreArg=nullptr, bool valueOnly=false) const
Test whether we depend on (ie, are served by) any object in the specified collection.
TClass * IsA() const override
Definition RooAbsArg.h:661
void setStringAttribute(const Text_t *key, const Text_t *value)
Associate string 'value' to this object under key 'key'.
bool isConstant() const
Check if the "Constant" attribute is set.
Definition RooAbsArg.h:284
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.
void SetName(const char *name) override
Set the name of the TNamed.
const RefCountList_t & servers() const
List of all servers of this object.
Definition RooAbsArg.h:146
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.
Abstract base class for RooRealVar binning definitions.
Int_t numBins() const
Return number of bins.
virtual double binLow(Int_t bin) const =0
virtual bool isUniform() const
virtual double highBound() const =0
virtual double lowBound() const =0
virtual double binHigh(Int_t bin) const =0
A space to attach TBranches.
const std::map< std::string, value_type >::value_type & getOrdinal(unsigned int n) const
Return name and index of the nth defined state.
std::size_t size() const
Number of states defined.
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.
const char * GetName() const override
Returns name of object.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
Storage_t::size_type size() const
virtual bool addOwned(RooAbsArg &var, bool silent=false)
Add an argument and transfer the ownership to the collection.
void sort(bool reverse=false)
Sort collection using std::sort and name comparison.
void setName(const char *name)
RooAbsArg * find(const char *name) const
Find object with given name in list.
Abstract base class for binned and unbinned datasets.
Definition RooAbsData.h:55
Abstract base class for objects that are lvalues, i.e.
virtual Int_t numBins(const char *rangeName=nullptr) const =0
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
std::unique_ptr< RooArgSet > getAllConstraints(const RooArgSet &observables, RooArgSet &constrainedParams, bool stripDisconnected=true) const
This helper function finds and collects all constraints terms of all component p.d....
Abstract interface for proxy classes.
Definition RooAbsProxy.h:37
virtual double getMax(const char *name=nullptr) const
Get maximum of currently defined range.
virtual double getMin(const char *name=nullptr) const
Get minimum of currently defined range.
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
double getVal(const RooArgSet *normalisationSet=nullptr) const
Evaluate object.
Definition RooAbsReal.h:107
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
RooArgSet * snapshot(bool deepCopy=true) const
Use RooAbsCollection::snapshot(), but return as RooArgSet.
Definition RooArgSet.h:159
bool containsInstance(const RooAbsArg &var) const override
Check if this exact instance is in this collection.
Definition RooArgSet.h:132
Implements a RooAbsBinning in terms of an array of boundary values, posing no constraints on the choi...
Definition RooBinning.h:27
Object to represent discrete states.
Definition RooCategory.h:28
bool defineType(const std::string &label)
Define a state with given name.
Represents a constant real-valued object.
Definition RooConstVar.h:23
Container class to hold N-dimensional binned data.
Definition RooDataHist.h:40
std::string val() const
JSONNode const * find(std::string const &key) const
bool has_child(std::string const &) const
std::string key() const
Records which components of a JSON map are read while it is alive, through any node of the tree.
static std::unique_ptr< JSONTree > create()
An HS3 document. It is move-only, because generic objects refer to the JSON tree that the document wa...
std::vector< HS3::Analysis > analyses
std::vector< HS3::Data > data
HS3::Metadata metadata
std::vector< HS3::Likelihood > likelihoods
std::vector< HS3::Function > functions
std::vector< HS3::ParameterPoint > parameterPoints
HS3::RooFitInternal rooFit
std::vector< HS3::Domain > domains
std::vector< HS3::Distribution > distributions
A position in the JSON tree.
Definition HS3Schema.h:32
Where RooFit takes the ranges of variables from: the "default_domain" if there is one,...
DomainAxis const * find(std::string const &name) const
The axis that defines the range of a variable, or null.
bool contains(std::string const &name) const
Whether any domain has the variable.
When using RooFit, statistical models can be conveniently handled and stored as a RooWorkspace.
std::string getStringAttribute(const std::string &obj, const std::string &attrib)
static void fillSeq(RooFit::Detail::JSONNode &node, RooAbsCollection const &coll, size_t nMax=-1)
void exportObjects(T const &args, std::set< std::string > &exportedObjectNames)
void exportCategory(RooAbsCategory const &cat, RooFit::Detail::JSONNode &node)
Export a RooAbsCategory object to a JSONNode.
T * requestArg(const RooFit::Detail::JSONNode &node, const std::string &key)
RooJSONFactoryWSTool(RooWorkspace &ws)
void exportData(RooAbsData const &data)
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.
std::vector< std::string > _importStack
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)
void importDistribution(RooFit::HS3::Distribution const &distribution)
static void exportArray(std::size_t n, double const *contents, RooFit::Detail::JSONNode &output)
Export an array of doubles to a JSONNode.
static bool testValidName(const std::string &str, bool forcError)
static void exportHisto(RooArgSet const &vars, std::size_t n, double const *contents, RooFit::Detail::JSONNode &output)
Export histogram data to a JSONNode.
void exportModelConfig(RooStats::ModelConfig const &mc, const std::vector< RooJSONFactoryWSTool::CombinedData > &combined, const std::vector< RooAbsData * > &single)
std::vector< RooAbsArg const * > _serversToDelete
static std::unique_ptr< RooFit::Detail::JSONTree > createNewJSONTree()
Create a new JSON tree with version information.
std::unique_ptr< RooFit::HS3::ToolExportState > _export
RooJSONFactoryWSTool::CombinedData exportCombinedData(RooAbsData const &data)
std::string exportJSONtoString()
Export the workspace to a JSON string.
static RooWorkspace cleanWS(const RooWorkspace &ws, bool onlyModelConfig=false)
void exportVariable(const RooAbsArg *v, RooFit::HS3::ParameterPoint &point, bool storeConstant)
std::string exportTransformed(const RooAbsReal *original, const std::string &suffix, const std::string &formula)
static bool isValidName(const std::string &str)
Check if a string is a valid name.
void importVariable(RooFit::HS3::ParameterValue const &p)
void importDependants(const RooFit::Detail::JSONNode &n)
static void exportAxis(RooFit::Detail::JSONNode &obsNode, RooRealVar const &var)
Export the name and binning of a RooRealVar to a JSONNode.
void importJSONElement(const std::string &name, const std::string &jsonString)
static RooWorkspace sanitizeWS(const RooWorkspace &ws)
static void error(const char *s)
Writes an error message to the RooFit message service and throws a runtime_error.
void setAttribute(const std::string &obj, const std::string &attrib)
void 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.
std::unique_ptr< RooFit::HS3::ToolImportState > _import
void exportObject(RooAbsArg const &func, std::set< std::string > &exportedObjectNames)
void exportSingleModelConfig(RooStats::ModelConfig const &mc, std::string const &analysisName, std::map< std::string, std::string > const *dataComponents)
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 exportVariables(const RooArgSet &allElems, RooFit::HS3::ParameterPoint &point, bool storeConstant)
static std::string name(const RooFit::Detail::JSONNode &n)
void exportAllObjects(RooFit::Detail::JSONNode &n)
bool exportJSON(std::string const &fileName)
Export the workspace to JSON format and write to the specified file.
static RooFit::Detail::JSONNode const * findNamedChild(RooFit::Detail::JSONNode const &node, std::string const &name)
void importAllNodes(RooFit::Detail::JSONNode &n)
void warnIfFreeWithoutDomain(RooRealVar const &var)
void setStringAttribute(const std::string &obj, const std::string &attrib, const std::string &value)
std::vector< RooAbsArg const * > _serversToExport
static std::ostream & warning(const std::string &s)
Writes a warning message to the RooFit message service.
static RooArgSet readAxes(const RooFit::Detail::JSONNode &node)
Read axes from the JSONNode and create a RooArgSet representing them.
void importVariableElement(const RooFit::Detail::JSONNode &n)
std::ostream & log(const RooAbsArg *self, RooFit::MsgLevel level, RooFit::MsgTopic facility, bool forceSkipPrefix=false)
Log error message associated with RooAbsArg object self at given level and topic.
static RooMsgService & instance()
Return reference to singleton instance.
static constexpr double infinity()
Return internal infinity representation.
Definition RooNumber.h:25
static constexpr int isInfinite(double x)
Return true if x is infinite by RooNumber internal specification.
Definition RooNumber.h:27
Variable that can be changed from the outside.
Definition RooRealVar.h:37
void removeMin(const char *name=nullptr)
Remove lower range limit for binning with given name. Empty name means default range.
void setError(double value)
Definition RooRealVar.h:61
void setMin(const char *name, double value, bool shared=true)
Set minimum of name range to given value.
double getError() const
Definition RooRealVar.h:59
void setRange(const char *name, double min, double max, bool shared=true)
Set a fit or plotting range.
void setBins(Int_t nBins, const char *name=nullptr, bool shared=true)
Create a uniform binning under name 'name' for this variable.
bool hasError(bool allowZero=true) const
Definition RooRealVar.h:60
void setBinning(const RooAbsBinning &binning, const char *name=nullptr, bool shared=true)
Add given binning under name 'name' with this variable.
void setMax(const char *name, double value, bool shared=true)
Set maximum of name range to given value.
void removeMax(const char *name=nullptr)
Remove upper range limit for binning with given name. Empty name means default range.
const RooAbsBinning & getBinning(const char *name=nullptr, bool verbose=true, bool createOnTheFly=false, bool shared=true) const override
Return binning definition with name.
Facilitates simultaneous fitting of multiple PDFs to subsets of a given dataset.
const RooAbsCategoryLValue & indexCat() const
< A class that holds configuration information for a model using a workspace as a store
Definition ModelConfig.h:34
const std::string & GetSnapshotName() const
get the name of the workspace snapshot saved by SetSnapshot() (empty if there is none)
const RooArgSet * GetGlobalObservables() const
get RooArgSet for global observables (return nullptr if not existing)
const RooArgSet * GetParametersOfInterest() const
get RooArgSet containing the parameter of interest (return nullptr if not existing)
const RooArgSet * GetNuisanceParameters() const
get RooArgSet containing the nuisance parameters (return nullptr if not existing)
void SetWS(RooWorkspace &ws) override
Set a workspace that owns all the necessary components for the analysis.
const RooArgSet * GetObservables() const
get RooArgSet for observables (return nullptr if not existing)
const RooArgSet * GetExternalConstraints() const
get RooArgSet for global observables (return nullptr if not existing)
RooAbsPdf * GetPdf() const
get model PDF (return nullptr if pdf has not been specified or does not exist)
RooAbsPdf * GetPriorPdf() const
get parameters prior pdf (return nullptr if not existing)
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.
RooCategory * cat(RooStringView name) const
Retrieve discrete variable (RooCategory) with given name. A null pointer is returned if not found.
RooArgSet allVars() const
Return set with all variable objects.
RooArgSet argSet(RooStringView nameList) const
Return set of RooAbsArgs matching to given list of names.
RooArgSet allResolutionModels() const
Return set with all resolution model objects.
bool saveSnapshot(RooStringView, const char *paramNames)
Save snapshot of values and attributes (including "Constant") of given parameters.
RooArgSet allPdfs() const
Return set with all probability density function objects.
std::list< RooAbsData * > allData() const
Return list of all dataset in the workspace.
RooLinkedList const & getSnapshots() const
std::list< TObject * > allGenericObjects() const
Return list of all generic objects in the workspace.
RooAbsReal * function(RooStringView name) const
Retrieve function (RooAbsReal) with given name. Note that all RooAbsPdfs are also RooAbsReals....
RooAbsArg * arg(RooStringView name) const
Return RooAbsArg with given name. A null pointer is returned if none is found.
const RooArgSet & components() const
RooArgSet allFunctions() const
Return set with all function objects.
RooFactoryWSTool & factory()
Return instance to factory tool.
RooRealVar * var(RooStringView name) const
Retrieve real-valued variable (RooRealVar) with given name. A null pointer is returned if not found.
std::list< RooAbsData * > allEmbeddedData() const
Return list of all dataset in the workspace.
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
virtual void SetName(const char *name)
Set the name of the TNamed.
Definition TNamed.cxx:149
Mother of all ROOT objects.
Definition TObject.h:42
virtual const char * ClassName() const
Returns name of class to which the object belongs.
Definition TObject.cxx:226
virtual Bool_t InheritsFrom(const char *classname) const
Returns kTRUE if object inherits from class "classname".
Definition TObject.cxx:548
const char * Data() const
Definition TString.h:385
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:2460
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)
const Double_t sigma
Double_t x[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
Double_t ex[n]
Definition legend1.C:17
CoordSystem::Scalar get(DisplacementVector2D< CoordSystem, Tag > const &p)
std::string makeValidVarName(std::string const &in)
Types that represent an HS3 document.
std::variant< std::string, double > NameOrNumber
A name, or a number for a constant.
void buildHistFactoryDist(RooJSONFactoryWSTool &tool, std::string const &name, HistFactoryDist const &dist, std::vector< DistributionRef > const *channelConstraints=nullptr)
Create the RooFit objects of a HistFactory distribution in the workspace of the tool.
void checkDocument(HS3Document const &doc, Diagnostics &diagnostics, ValidateOptions const &options={})
The checks of validate(), for a document that is still needed if it doesn't pass them.
std::variant< RegularBinning, EdgesBinning > Binning
Definition HS3Document.h:94
constexpr char const * defaultDomainName
The domain in which RooFit writes the ranges of all variables. It is a convention of RooFit,...
std::optional< ValidatedHS3Document > validate(HS3Document doc, Diagnostics &diagnostics, ValidateOptions const &options)
Checks what can be checked without knowing how the objects are built: unique names,...
void setExportedObject(RooJSONFactoryWSTool &tool, HistFactoryDist dist, std::vector< DistributionRef > channelConstraints)
Called by the exporter of a HistFactory distribution, which is then written by the tool.
void writeDocument(Detail::JSONNode &root, HS3Document const &doc)
Writes everything but the distributions and functions, which are written one by one while they are ex...
RooArgSet createObservables(std::vector< BinnedAxis > const &axes)
Create the variables for the axes of binned data.
void importHistFactoryNode(RooJSONFactoryWSTool &tool, Detail::JSONNode const &node, std::vector< DistributionRef > const *channelConstraints)
What the built-in importer does with a HistFactory distribution, plus the constraint terms of the cha...
bool hasErrors(Diagnostics const &diagnostics)
Whether there is a diagnostic with error severity.
Definition HS3Parser.cxx:94
void writeHistFactoryDist(Detail::JSONNode &node, HistFactoryDist const &dist)
Writes a HistFactory distribution, without its name.
std::vector< Diagnostic > Diagnostics
void report(Diagnostics const &diagnostics)
Forward the warnings to the message service, and throw if there are errors.
NameOrNumber toNameOrNumber(RooAbsArg const &arg)
The name of an object, or the value of a literal constant.
void upgradeDocument(Detail::JSONNode &root, Diagnostics &diagnostics, ParseOptions const &options={})
Rewrites everything that older versions of RooFit wrote in a way that is not part of the standard int...
BinnedAxis toBinnedAxis(RooRealVar const &var)
The axis of binned data for a variable.
std::unique_ptr< RooDataHist > createDataHist(std::string const &name, RooArgSet const &vars, std::vector< double > const &contents, std::vector< double > const *errors)
Create a RooDataHist with the contents and errors. If there are no errors, it doesn't store squared w...
bool isBuiltinHistFactoryImporter(JSONIO::Importer const &importer)
Whether this is the importer that RooFit registers for HistFactory distributions, as opposed to the o...
std::vector< BinnedAxis > parseBinnedAxes(Detail::JSONNode const &axes, Diagnostics &diagnostics)
Reads the axes of binned data that doesn't come with a document.
HS3Document parseDocument(Detail::JSONNode const &root, Diagnostics &diagnostics, ParseOptions const &options={})
Reads a document.
ImportMap & importers()
Definition JSONIO.cxx:59
ExportMap & exporters()
Definition JSONIO.cxx:81
ImportExpressionMap & importExpressions()
Definition JSONIO.cxx:108
ExportKeysMap & exportKeys()
Definition JSONIO.cxx:115
@ ObjectHandling
void initialize(typename Architecture_t::Matrix_t &A, EInitialization m)
Definition Functions.h:282
static const char * what
Definition stlLoader.cc:5
std::optional< DistributionRef > prior
std::optional< ParameterPointRef > init
LikelihoodRef likelihood
std::vector< DomainRef > domains
std::vector< ParameterRef > parametersOfInterest
std::vector< std::string > tags
std::map< std::string, std::string > dict
The axis of binned data.
std::vector< int > indices
std::vector< std::string > labels
std::vector< std::string > labels
std::vector< DistributionRef > distributions
The axis of point data or unbinned data, which can't have a binning.
std::optional< double > max
std::optional< double > min
std::string name
std::variant< PointData, UnbinnedData, BinnedData > body
std::variant< GenericObject, HistFactoryDist > body
The axis of a domain.
Definition HS3Document.h:98
std::optional< double > min
std::optional< double > max
std::optional< Binning > binning
std::optional< double > fixedValue
std::vector< std::variant< DomainAxis, CategoricalAxis > > axes
std::vector< double > edges
Definition HS3Document.h:92
A distribution or function that is not interpreted by the document, but by the importers that are reg...
std::vector< DistributionRef > auxDistributions
std::vector< LikelihoodTerm > terms
std::string hs3Version
As it was written in the document, empty for fragments of a document.
Definition HS3Document.h:80
Version version
The parsed version. Versions that can't be parsed count as the oldest one.
Definition HS3Document.h:81
std::vector< Package > packages
Definition HS3Document.h:82
std::optional< std::string > pdfName
std::optional< std::string > mcName
std::optional< std::string > combinedDataName
std::vector< ParameterValue > parameters
std::optional< std::variant< double, std::string > > value
A string selects the state of a category.
Options for upgradeDocument() and parseDocument().
bool typedHistFactory
If false, HistFactory distributions stay generic objects, because a user registered their own importe...
A name that says what it refers to, so that for example a DataRef can't be used as a DomainRef.
Definition HS3Document.h:63
std::string name
Definition HS3Document.h:64
std::map< std::string, Attributes > attributes
std::map< std::string, std::vector< DistributionRef > > channelConstraints
The constraint terms of HistFactory distributions that no modifier names, by distribution.
std::vector< StepWidth > stepWidths
std::map< std::string, ModelConfigInfo > modelConfigs
The document that the tool is exporting.
std::optional< HistFactoryDist > typedObject
Set by the exporter of an object that is interpreted by the document.
std::map< std::string, std::map< std::string, HS3::DomainAxis > > domains
The ranges and binnings of the exported variables, by domain and variable name.
std::vector< DistributionRef > channelConstraints
What the tool needs to know about the document that it is importing.
ToolImportState(HS3Document const &doc)
std::unordered_map< std::string, HS3::Function const * > functions
HS3::ParameterPoint const * defaultValues
std::unordered_map< std::string, HS3::ParameterValue const * > defaultValuesByName
std::unordered_map< std::string, HS3::Distribution const * > distributions
Options for validate().
bool strict
Whether the document has to follow the current standard, or if older conventions of RooFit are tolera...
std::function< bool(std::string const &) isExternal)
Whether a name that is not defined in the document is known in some other way, e.g....
std::map< std::string, std::string > components
TLine l
Definition textangle.C:4