Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
JSONFactories_RooFitCore.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
14
15#include <RooAddPdf.h>
16#include <RooAddModel.h>
17#include <RooBinning.h>
18#include <RooBinSamplingPdf.h>
19#include <RooBinWidthFunction.h>
20#include <RooConstVar.h>
21#include <RooCrystalBall.h>
22#include <RooDataHist.h>
23#include <RooDecay.h>
24#include <RooDerivative.h>
25#include <RooExponential.h>
26#include <RooExtendPdf.h>
27#include <RooFFTConvPdf.h>
29#include <RooFitHS3/JSONIO.h>
30#include <RooFormulaVar.h>
31#include <RooGenericPdf.h>
32#include <RooHistFunc.h>
33#include <RooHistPdf.h>
34#include <RooLegacyExpPoly.h>
35#include <RooLognormal.h>
36#include <RooMultiVarGaussian.h>
38#include <RooAddition.h>
39#include <RooProduct.h>
40#include <RooProdPdf.h>
41#include <RooPoisson.h>
42#include <RooPolynomial.h>
43#include <RooPolyVar.h>
44#include <RooAbsRealLValue.h>
45#include <RooRealSumFunc.h>
46#include <RooRealSumPdf.h>
47#include <RooRealVar.h>
48#include <RooResolutionModel.h>
49#include <RooTFnBinding.h>
50#include <RooTruthModel.h>
51#include <RooGaussModel.h>
52#include <RooWrapperPdf.h>
53#include <RooRealIntegral.h>
54#include <RooSpline.h>
55#include <RooUniformBinning.h>
56#include <TSpline.h>
57
58#include <TF1.h>
59
60#include "JSONIOUtils.h"
61
62#include <type_traits>
63
64#include "static_execute.h"
65
66#include <algorithm>
67#include <cctype>
68#include <cmath>
69#include <limits>
70#include <memory>
71#include <initializer_list>
72#include <set>
73#include <sstream>
74#include <string_view>
75#include <vector>
76
78
79///////////////////////////////////////////////////////////////////////////////////////////////////////
80// individually implemented importers
81///////////////////////////////////////////////////////////////////////////////////////////////////////
82
83namespace {
84bool isReservedExpressionIdentifier(const std::string &arg)
85{
86 return arg == "PI" || arg == "EULER" || arg == "TMath";
87}
88
89/**
90 * Extracts arguments from a mathematical expression.
91 *
92 * This function takes a string representing a mathematical
93 * expression and extracts the arguments from it. The arguments are
94 * defined as sequences of characters that do not contain digits,
95 * spaces, or parentheses, and that start with a letter. Function
96 * calls such as "exp( ... )", identified as being followed by an
97 * opening parenthesis, are not treated as arguments. The extracted
98 * arguments are returned as a vector of strings.
99 *
100 * @param expr A string representing a mathematical expression.
101 * @return A set of unique strings representing the extracted arguments.
102 */
103std::set<std::string> extractArguments(std::string expr)
104{
105 // Get rid of whitespaces
106 expr.erase(std::remove_if(expr.begin(), expr.end(), [](unsigned char c) { return std::isspace(c); }), expr.end());
107
108 std::set<std::string> arguments;
109 size_t startidx = expr.size();
110 for (size_t i = 0; i < expr.size(); ++i) {
111 if (startidx >= expr.size()) {
112 if (isalpha(expr[i])) {
113 startidx = i;
114 // check this character is not part of scientific notation, e.g. 2e-5
115 if (TFormula::IsScientificNotation(expr, i)) {
116 // if it is, we ignore this character
117 startidx = expr.size();
118 }
119 }
120 } else {
121 if (!isdigit(expr[i]) && !isalpha(expr[i]) && expr[i] != '_') {
122 if (expr[i] == '(') {
123 startidx = expr.size();
124 continue;
125 }
126 std::string arg(expr.substr(startidx, i - startidx));
127 startidx = expr.size();
128 if (!isReservedExpressionIdentifier(arg)) {
129 arguments.insert(arg);
130 }
131 }
132 }
133 }
134 if (startidx < expr.size()) {
135 std::string arg(expr.substr(startidx));
136 if (!isReservedExpressionIdentifier(arg)) {
137 arguments.insert(arg);
138 }
139 }
140 return arguments;
141}
142
143void replaceIdentifier(TString &expr, std::string_view identifier, std::string_view replacement)
144{
145 std::string in(expr.Data());
146 std::string out;
147 out.reserve(in.size());
148
149 for (std::size_t pos = 0; pos < in.size();) {
150 const bool matches = in.compare(pos, identifier.size(), identifier) == 0;
151 const bool beforeIdentifier =
152 pos > 0 && (std::isalnum(static_cast<unsigned char>(in[pos - 1])) || in[pos - 1] == '_');
153 const std::size_t end = pos + identifier.size();
154 const bool afterIdentifier =
155 end < in.size() && (std::isalnum(static_cast<unsigned char>(in[end])) || in[end] == '_');
156 if (matches && !beforeIdentifier && !afterIdentifier) {
157 out.append(replacement);
158 pos = end;
159 } else {
160 out.push_back(in[pos]);
161 ++pos;
162 }
163 }
164
165 expr = out.c_str();
166}
167
168bool isIdentifierChar(char c)
169{
170 return std::isalnum(static_cast<unsigned char>(c)) || c == '_';
171}
172
173std::string trimmed(std::string const &str)
174{
175 const auto begin = str.find_first_not_of(" \t\n");
176 if (begin == std::string::npos) {
177 return "";
178 }
179 return str.substr(begin, str.find_last_not_of(" \t\n") - begin + 1);
180}
181
182// Index of the bracket that closes the one at `open`, or std::string::npos if it is not closed.
183std::size_t findClosingBracket(std::string const &str, std::size_t open)
184{
185 int depth = 0;
186 for (std::size_t i = open; i < str.size(); ++i) {
187 if (str[i] == '(' || str[i] == '[') {
188 ++depth;
189 } else if ((str[i] == ')' || str[i] == ']') && --depth == 0) {
190 return i;
191 }
192 }
193 return std::string::npos;
194}
195
196// Split `str` at the commas that are not enclosed in brackets.
197std::vector<std::string> splitTopLevelCommas(std::string const &str)
198{
199 std::vector<std::string> parts;
200 int depth = 0;
201 std::size_t start = 0;
202 for (std::size_t i = 0; i < str.size(); ++i) {
203 if (str[i] == '(' || str[i] == '[') {
204 ++depth;
205 } else if (str[i] == ')' || str[i] == ']') {
206 --depth;
207 } else if (str[i] == ',' && depth == 0) {
208 parts.push_back(str.substr(start, i - start));
209 start = i + 1;
210 }
211 }
212 parts.push_back(str.substr(start));
213 return parts;
214}
215
216// Remove parentheses that enclose the whole string, e.g. "((a+b))" becomes "a+b".
217std::string stripEnclosingParentheses(std::string str)
218{
219 str = trimmed(str);
220 while (str.size() >= 2 && str.front() == '(' && findClosingBracket(str, 0) == str.size() - 1) {
221 str = trimmed(str.substr(1, str.size() - 2));
222 }
223 return str;
224}
225
226// Rewrite the HS3 conditional "ifelse(c,a,b)" into the C-style ternary "((c)?(a):(b))" that TFormula understands.
227// Malformed calls are left unchanged, so that TFormula reports them.
228std::string ifElseToTernary(std::string const &expr)
229{
230 static const std::string keyword = "ifelse";
231 std::string out;
232 std::size_t pos = 0;
233 while (pos < expr.size()) {
234 const std::size_t found = expr.find(keyword, pos);
235 if (found == std::string::npos) {
236 break;
237 }
238 const std::size_t open = expr.find_first_not_of(" \t", found + keyword.size());
239 const bool isCall =
240 (found == 0 || !isIdentifierChar(expr[found - 1])) && open != std::string::npos && expr[open] == '(';
241 const std::size_t close = isCall ? findClosingBracket(expr, open) : std::string::npos;
242 const auto args = close != std::string::npos ? splitTopLevelCommas(expr.substr(open + 1, close - open - 1))
243 : std::vector<std::string>{};
244 if (args.size() != 3) {
245 out += expr.substr(pos, found + keyword.size() - pos);
246 pos = found + keyword.size();
247 continue;
248 }
249 out += expr.substr(pos, found - pos);
250 out += "((" + ifElseToTernary(trimmed(args[0])) + ")?(" + ifElseToTernary(trimmed(args[1])) + "):(" +
251 ifElseToTernary(trimmed(args[2])) + "))";
252 pos = close + 1;
253 }
254 return out + expr.substr(pos);
255}
256
257void translateImportedExpression(TString &expr)
258{
259 replaceIdentifier(expr, "PI", "TMath::Pi()");
260 replaceIdentifier(expr, "EULER", "TMath::E()");
261 if (std::string(expr.Data()).find("ifelse") != std::string::npos) {
262 expr = ifElseToTernary(expr.Data()).c_str();
263 }
264}
265
266int readPositiveInteger(const JSONNode &node, const std::string &context)
267{
268 // Read through val_double() so an integer encoded as a JSON float (e.g. 1e6,
269 // whose textual form is "1e+06") is accepted like elsewhere in HS3, while
270 // fractional, non-finite, out-of-range or non-numeric values are rejected.
271 const double value = node.is_number() ? node.val_double() : std::numeric_limits<double>::quiet_NaN();
272 if (!std::isfinite(value) || value < 1.0 || value != std::floor(value) ||
273 value > static_cast<double>(std::numeric_limits<int>::max())) {
274 RooJSONFactoryWSTool::error("\"nbins\" in " + context + " must be a positive integer");
275 }
276 return static_cast<int>(value);
277}
278
279std::unique_ptr<RooAbsBinning>
280readFormulaAxisBinning(const JSONNode &axis, const std::string &axisName, const std::string &formulaName)
281{
282 const std::string context = "axis '" + axisName + "' of generic formula '" + formulaName + "'";
283 const bool hasEdges = axis.has_child("edges");
284 const bool hasMin = axis.has_child("min");
285 const bool hasMax = axis.has_child("max");
286 const bool hasNBins = axis.has_child("nbins");
287
288 if (hasEdges && hasNBins) {
289 RooJSONFactoryWSTool::error(context + " must use either \"edges\" or \"nbins\"");
290 }
291
292 if (hasEdges) {
293 const JSONNode &edgesNode = axis["edges"];
294 if (!edgesNode.is_seq()) {
295 RooJSONFactoryWSTool::error("\"edges\" in " + context + " must be a sequence");
296 }
297
298 std::vector<double> edges;
299 edges.reserve(edgesNode.num_children());
300 for (const JSONNode &edgeNode : edgesNode.children()) {
301 if (!edgeNode.is_number()) {
302 RooJSONFactoryWSTool::error("\"edges\" in " + context + " must contain only finite values");
303 }
304 const double edge = edgeNode.val_double();
305 if (!std::isfinite(edge)) {
306 RooJSONFactoryWSTool::error("\"edges\" in " + context + " must contain only finite values");
307 }
308 if (!edges.empty() && edge <= edges.back()) {
309 RooJSONFactoryWSTool::error("\"edges\" in " + context + " must be strictly increasing");
310 }
311 edges.push_back(edge);
312 }
313 if (edges.size() < 2) {
314 RooJSONFactoryWSTool::error("\"edges\" in " + context + " must contain at least two values");
315 }
316 // The HS3 standard requires "min" and "max" also for irregular binnings, which older versions of RooFit didn't
317 // write.
318 if ((hasMin && (!axis["min"].is_number() || axis["min"].val_double() != edges.front())) ||
319 (hasMax && (!axis["max"].is_number() || axis["max"].val_double() != edges.back()))) {
320 RooJSONFactoryWSTool::error("\"min\" and \"max\" in " + context + " must agree with the \"edges\"");
321 }
322 return std::make_unique<RooBinning>(static_cast<int>(edges.size() - 1), edges.data());
323 }
324
325 if (!hasMin || !hasMax || !hasNBins) {
326 RooJSONFactoryWSTool::error(context + " must define \"min\", \"max\", and \"nbins\"");
327 }
328
329 if (!axis["min"].is_number() || !axis["max"].is_number()) {
330 RooJSONFactoryWSTool::error("\"min\" and \"max\" in " + context + " must be finite and increasing");
331 }
332 const double min = axis["min"].val_double();
333 const double max = axis["max"].val_double();
334 if (!std::isfinite(min) || !std::isfinite(max) || max <= min) {
335 RooJSONFactoryWSTool::error("\"min\" and \"max\" in " + context + " must be finite and increasing");
336 }
337 return std::make_unique<RooUniformBinning>(min, max, readPositiveInteger(axis["nbins"], context));
338}
339
340template <class RooArg_t>
341void importFormulaBinnings(RooArg_t &arg, const JSONNode &node)
342{
343 if (!node.has_child("axes")) {
344 return;
345 }
346
347 const JSONNode &axes = node["axes"];
348 if (!axes.is_seq()) {
349 RooJSONFactoryWSTool::error("\"axes\" in generic formula '" + std::string(arg.GetName()) +
350 "' must be a sequence");
351 }
352
353 std::set<std::string> axisNames;
354 for (const JSONNode &axis : axes.children()) {
355 if (!axis.is_map() || !axis.has_child("name")) {
356 RooJSONFactoryWSTool::error("each axis in generic formula '" + std::string(arg.GetName()) +
357 "' must be a map with a \"name\"");
358 }
359 const std::string axisName = axis["name"].val();
360 if (!axisNames.insert(axisName).second) {
361 RooJSONFactoryWSTool::error("duplicate axis '" + axisName + "' in generic formula '" + arg.GetName() + "'");
362 }
363
364 auto *observable = dynamic_cast<RooAbsRealLValue *>(arg.getParameter(axisName.c_str()));
365 if (!observable) {
367 "axis '" + axisName + "' is not a real-valued formula variable of generic formula '" + arg.GetName() + "'");
368 }
369
370 std::unique_ptr<RooAbsBinning> binning = readFormulaAxisBinning(axis, axisName, arg.GetName());
371 arg.setBinning(*observable, *binning, /*checkFlatness=*/false);
372 }
373}
374
375template <class RooArg_t>
376bool importFormulaArg(RooJSONFactoryWSTool *tool, const JSONNode &p)
377{
378 std::string name(RooJSONFactoryWSTool::name(p));
379 if (!p.has_child("expression")) {
380 RooJSONFactoryWSTool::error("no expression given for '" + name + "'");
381 }
382 TString formula(p["expression"].val());
383 translateImportedExpression(formula);
384 RooArgList dependents;
385 for (const auto &d : extractArguments(formula.Data())) {
386 dependents.add(*tool->request<RooAbsReal>(d, name));
387 }
388 RooArg_t arg{name.c_str(), formula, dependents};
389 importFormulaBinnings(arg, p);
390 tool->wsImport(arg);
391 return true;
392}
393
394// Fast-path importers for RooProduct, RooAddition, and RooProdPdf that
395// bypass the generic factory-expression mechanism. The default path
396// generates a string expression and passes it to gROOT->ProcessLineFast(),
397// which invokes the Cling JIT for every single call. For workspaces with
398// thousands of product/sum nodes (a common shape for HistFactory models)
399// that JIT cost dominates JSON import time. Constructing the RooFit object
400// directly here keeps the work O(N) of cheap C++ calls.
401bool importProduct(RooJSONFactoryWSTool *tool, const JSONNode &p)
402{
403 std::string name(RooJSONFactoryWSTool::name(p));
404 tool->wsEmplace<RooProduct>(name, tool->requestArgList<RooAbsReal>(p, "factors"));
405 return true;
406}
407
408bool importProdPdf(RooJSONFactoryWSTool *tool, const JSONNode &p)
409{
410 std::string name(RooJSONFactoryWSTool::name(p));
411 tool->wsEmplace<RooProdPdf>(name, tool->requestArgList<RooAbsPdf>(p, "factors"));
412 return true;
413}
414
415bool importAddition(RooJSONFactoryWSTool *tool, const JSONNode &p)
416{
417 std::string name(RooJSONFactoryWSTool::name(p));
418 tool->wsEmplace<RooAddition>(name, tool->requestArgList<RooAbsReal>(p, "summands"));
419 return true;
420}
421
422bool importAddPdf(RooJSONFactoryWSTool *tool, const JSONNode &p)
423{
424 std::string name(RooJSONFactoryWSTool::name(p));
425 if (p.has_child("extended")) {
426 // Check the explicit "extended" flag against the number of coefficients, which determines the extended mode of
427 // the RooAddPdf.
428 const bool extended = p["extended"].val_bool();
429 const std::size_t nSummands = p.has_child("summands") ? p["summands"].num_children() : 0;
430 const std::size_t nCoefficients = p.has_child("coefficients") ? p["coefficients"].num_children() : 0;
431 const std::string counts =
432 std::to_string(nCoefficients) + " coefficients for " + std::to_string(nSummands) + " summands";
433 if (extended && nCoefficients > 0 && nCoefficients + 1 == nSummands) {
434 RooJSONFactoryWSTool::error("mixture_dist '" + name + "' is declared as extended, but it has " + counts +
435 ", which defines a non-extended mixture");
436 }
437 if (!extended && nCoefficients == 0 && nSummands > 0) {
438 RooJSONFactoryWSTool::error("mixture_dist '" + name + "' is declared as not extended, but it has " + counts +
439 ", which defines an extended mixture of extended distributions");
440 }
441 if (!extended && nCoefficients == nSummands) {
442 RooJSONFactoryWSTool::warning("RooFitHS3: mixture_dist '" + name + "' is declared as not extended, but " +
443 "RooFit can use it as extended because it has one coefficient per summand. " +
444 "Don't use the extended likelihood term when fitting it.");
445 }
446 }
447 if (!tool->requestArgList<RooAbsReal>(p, "coefficients").empty()) {
448 tool->wsEmplace<RooAddPdf>(name, tool->requestArgList<RooAbsPdf>(p, "summands"),
449 tool->requestArgList<RooAbsReal>(p, "coefficients"));
450 return true;
451 }
452 tool->wsEmplace<RooAddPdf>(name, tool->requestArgList<RooAbsPdf>(p, "summands"));
453 return true;
454}
455
456bool importAddModel(RooJSONFactoryWSTool *tool, const JSONNode &p)
457{
458 std::string name(RooJSONFactoryWSTool::name(p));
459 tool->wsEmplace<RooAddModel>(name, tool->requestArgList<RooAbsPdf>(p, "summands"),
460 tool->requestArgList<RooAbsReal>(p, "coefficients"));
461 return true;
462}
463
464template <bool DivideByBinWidth>
465bool importBinWidthFunction(RooJSONFactoryWSTool *tool, const JSONNode &p)
466{
467 std::string name(RooJSONFactoryWSTool::name(p));
468 RooHistFunc *hf = dynamic_cast<RooHistFunc *>(tool->request<RooAbsReal>(p["histogram"].val(), name));
469 if (!hf) {
470 RooJSONFactoryWSTool::error("histogram '" + p["histogram"].val() + "' of '" + name + "' is not a RooHistFunc");
471 }
472 tool->wsEmplace<RooBinWidthFunction>(name, *hf, DivideByBinWidth);
473 return true;
474}
475
476bool importBinSamplingPdf(RooJSONFactoryWSTool *tool, const JSONNode &p)
477{
478 std::string name(RooJSONFactoryWSTool::name(p));
479
480 RooAbsPdf *pdf = tool->requestArg<RooAbsPdf>(p, "pdf");
481 RooRealVar *obs = tool->requestArg<RooRealVar>(p, "observable");
482
483 if (!pdf->dependsOn(*obs)) {
484 RooJSONFactoryWSTool::error(std::string("pdf '") + pdf->GetName() + "' does not depend on observable '" +
485 obs->GetName() + "' as indicated by parent RooBinSamplingPdf '" + name +
486 "', please check!");
487 }
488
489 if (!p.has_child("epsilon")) {
490 RooJSONFactoryWSTool::error("no epsilon given in '" + name + "'");
491 }
492 double epsilon(p["epsilon"].val_double());
493
494 tool->wsEmplace<RooBinSamplingPdf>(name, *obs, *pdf, epsilon);
495
496 return true;
497}
498
499template <class RooArg_t>
500bool importRealSum(RooJSONFactoryWSTool *tool, const JSONNode &p)
501{
502 std::string name(RooJSONFactoryWSTool::name(p));
503 RooArgList samples = tool->requestArgList<RooAbsReal>(p, "samples");
504 RooArgList coefs = tool->requestArgList<RooAbsReal>(p, "coefficients");
505 if constexpr (std::is_same_v<RooArg_t, RooRealSumPdf>) {
506 const bool extended = p.has_child("extended") && p["extended"].val_bool();
507 tool->wsEmplace<RooRealSumPdf>(name, samples, coefs, extended);
508 } else {
509 tool->wsEmplace<RooArg_t>(name, samples, coefs);
510 }
511 return true;
512}
513
514template <class RooArg_t>
515bool importPolynomial(RooJSONFactoryWSTool *tool, const JSONNode &p)
516{
517 std::string name(RooJSONFactoryWSTool::name(p));
518 if (!p.has_child("coefficients")) {
519 RooJSONFactoryWSTool::error("no coefficients given in '" + name + "'");
520 }
521 RooAbsReal *x = tool->requestArg<RooAbsReal>(p, "x");
522 RooArgList coefs;
523 int order = 0;
524 int lowestOrder = 0;
525 for (const auto &coef : p["coefficients"].children()) {
526 // As long as the coefficients match the default coefficients in
527 // RooFit, we don't have to instantiate RooFit objects but can
528 // increase the lowestOrder flag.
529 if (order == 0 && (coef.val() == "1.0" || coef.val() == "1")) {
530 ++lowestOrder;
531 } else if (coefs.empty() && (coef.val() == "0.0" || coef.val() == "0")) {
532 ++lowestOrder;
533 } else {
534 coefs.add(*tool->request<RooAbsReal>(coef.val(), name));
535 }
536 ++order;
537 }
538
539 tool->wsEmplace<RooArg_t>(name, *x, coefs, lowestOrder);
540 return true;
541}
542
543bool importPoisson(RooJSONFactoryWSTool *tool, const JSONNode &p)
544{
545 std::string name(RooJSONFactoryWSTool::name(p));
546 RooAbsReal *x = tool->requestArg<RooAbsReal>(p, "x");
547 RooAbsReal *mean = tool->requestArg<RooAbsReal>(p, "mean");
548 tool->wsEmplace<RooPoisson>(name, *x, *mean, !p["integer"].val_bool());
549 return true;
550}
551
552bool importDecay(RooJSONFactoryWSTool *tool, const JSONNode &p)
553{
554 std::string name(RooJSONFactoryWSTool::name(p));
555 RooRealVar *t = tool->requestArg<RooRealVar>(p, "t");
556 RooAbsReal *tau = tool->requestArg<RooAbsReal>(p, "tau");
557 // HS3 lists some resolution models as functions.
559 if (!model) {
560 RooJSONFactoryWSTool::error("resolutionModel of '" + name + "' is not a RooResolutionModel");
561 }
562 RooDecay::DecayType decayType = static_cast<RooDecay::DecayType>(p["decayType"].val_int());
563 tool->wsEmplace<RooDecay>(name, *t, *tau, *model, decayType);
564 return true;
565}
566
567bool importTruthModel(RooJSONFactoryWSTool *tool, const JSONNode &p)
568{
569 std::string name(RooJSONFactoryWSTool::name(p));
570 RooRealVar *x = tool->requestArg<RooRealVar>(p, "x");
571 tool->wsEmplace<RooTruthModel>(name, *x);
572 return true;
573}
574
575bool importGaussModel(RooJSONFactoryWSTool *tool, const JSONNode &p)
576{
577 std::string name(RooJSONFactoryWSTool::name(p));
578 RooRealVar *x = tool->requestArg<RooRealVar>(p, "x");
579 RooRealVar *mean = tool->requestArg<RooRealVar>(p, "mean");
580 RooRealVar *sigma = tool->requestArg<RooRealVar>(p, "sigma");
581 tool->wsEmplace<RooGaussModel>(name, *x, *mean, *sigma);
582 return true;
583}
584
585bool importRealIntegral(RooJSONFactoryWSTool *tool, const JSONNode &p)
586{
587 std::string name(RooJSONFactoryWSTool::name(p));
588 RooAbsReal *func = tool->requestArg<RooAbsReal>(p, "integrand");
589 auto vars = tool->requestArgList<RooAbsReal>(p, "variables");
590 RooArgSet normSet;
591 RooArgSet const *normSetPtr = nullptr;
592 if (p.has_child("normalization")) {
593 normSet.add(tool->requestArgSet<RooAbsReal>(p, "normalization"));
594 normSetPtr = &normSet;
595 }
596 std::string domain;
597 bool hasDomain = p.has_child("domain");
598 if (hasDomain) {
599 domain = p["domain"].val();
600 }
601 // todo: at some point, take care of integrator configurations
602 tool->wsEmplace<RooRealIntegral>(name, *func, vars, normSetPtr, static_cast<RooNumIntConfig *>(nullptr),
603 hasDomain ? domain.c_str() : nullptr);
604 return true;
605}
606
607bool importDerivative(RooJSONFactoryWSTool *tool, const JSONNode &p)
608{
609 std::string name(RooJSONFactoryWSTool::name(p));
610 RooAbsReal *func = tool->requestArg<RooAbsReal>(p, "function");
611 RooRealVar *x = tool->requestArg<RooRealVar>(p, "x");
612 Int_t order = p["order"].val_int();
613 double eps = p["eps"].val_double();
614 if (p.has_child("normalization")) {
615 RooArgSet normSet;
616 normSet.add(tool->requestArgSet<RooAbsReal>(p, "normalization"));
617 tool->wsEmplace<RooDerivative>(name, *func, *x, normSet, order, eps);
618 return true;
619 }
620 tool->wsEmplace<RooDerivative>(name, *func, *x, order, eps);
621 return true;
622}
623
624bool importFFTConvPdf(RooJSONFactoryWSTool *tool, const JSONNode &p)
625{
626 std::string name(RooJSONFactoryWSTool::name(p));
627 RooRealVar *convVar = tool->requestArg<RooRealVar>(p, "conv_var");
628 Int_t order = p["ipOrder"].val_int();
629 RooAbsPdf *pdf1 = tool->requestArg<RooAbsPdf>(p, "pdf1");
630 RooAbsPdf *pdf2 = tool->requestArg<RooAbsPdf>(p, "pdf2");
631 if (p.has_child("conv_func")) {
632 RooAbsReal *convFunc = tool->requestArg<RooAbsReal>(p, "conv_func");
633 tool->wsEmplace<RooFFTConvPdf>(name, *convFunc, *convVar, *pdf1, *pdf2, order);
634 return true;
635 }
636 tool->wsEmplace<RooFFTConvPdf>(name, *convVar, *pdf1, *pdf2, order);
637 return true;
638}
639
640bool importExtendPdf(RooJSONFactoryWSTool *tool, const JSONNode &p)
641{
642 std::string name(RooJSONFactoryWSTool::name(p));
643 // HS3 calls the fields "dist" and "rate". RooFit used to write "pdf" and "norm", which are still accepted.
644 RooAbsPdf *pdf = tool->requestArg<RooAbsPdf>(p, p.has_child("pdf") && !p.has_child("dist") ? "pdf" : "dist");
645 RooAbsReal *norm = tool->requestArg<RooAbsReal>(p, p.has_child("norm") && !p.has_child("rate") ? "norm" : "rate");
646 if (p.has_child("range")) {
647 std::string rangeName = p["range"].val();
648 tool->wsEmplace<RooExtendPdf>(name, *pdf, *norm, rangeName.c_str());
649 return true;
650 }
651 tool->wsEmplace<RooExtendPdf>(name, *pdf, *norm);
652 return true;
653}
654
655bool importLogNormal(RooJSONFactoryWSTool *tool, const JSONNode &p)
656{
657 std::string name(RooJSONFactoryWSTool::name(p));
658 RooAbsReal *x = tool->requestArg<RooAbsReal>(p, "x");
659
660 // Same mechanism to undo the parameter transformation as in the
661 // importExponential() function (see comments in that function for more info).
662 const std::string muName = p["mu"].val();
663 const std::string sigmaName = p["sigma"].val();
664 const bool isTransformed = endsWith(muName, "_lognormal_log");
665 const std::string suffixToRemove = isTransformed ? "_lognormal_log" : "";
666 RooAbsReal *mu = tool->request<RooAbsReal>(removeSuffix(muName, suffixToRemove), name);
667 RooAbsReal *sigma = tool->request<RooAbsReal>(removeSuffix(sigmaName, suffixToRemove), name);
668
669 tool->wsEmplace<RooLognormal>(name, *x, *mu, *sigma, !isTransformed);
670
671 return true;
672}
673
674bool importExponential(RooJSONFactoryWSTool *tool, const JSONNode &p)
675{
676 std::string name(RooJSONFactoryWSTool::name(p));
677 RooAbsReal *x = tool->requestArg<RooAbsReal>(p, "x");
678
679 // If the parameter name ends with the "_exponential_inverted" suffix,
680 // this means that it was exported from a RooFit object where the
681 // parameter first needed to be transformed on export to match the HS3
682 // specification. But when re-importing such a parameter, we can simply
683 // skip the transformation and use the original RooFit parameter without
684 // the suffix.
685 //
686 // A concrete example: take the following RooFit pdf in the factory language:
687 //
688 // "Exponential::exponential_1(x[0, 10], c[-0.1])"
689 //
690 // It defines en exponential exp(c * x). However, in HS3 the exponential
691 // is defined as exp(-c * x), to RooFit would export these dictionaries
692 // to the JSON:
693 //
694 // {
695 // "name": "exponential_1", // HS3 exponential_dist with transformed parameter
696 // "type": "exponential_dist",
697 // "x": "x",
698 // "c": "c_exponential_inverted"
699 // },
700 // {
701 // "name": "c_exponential_inverted", // transformation function created on-the-fly on export
702 // "type": "generic",
703 // "expression": "-c"
704 // }
705 //
706 // On import, we can directly take the non-transformed parameter, which is
707 // we check for the suffix and optionally remove it from the requested
708 // name next:
709
710 const std::string constParamName = p["c"].val();
711 const bool isInverted = endsWith(constParamName, "_exponential_inverted");
712 const std::string suffixToRemove = isInverted ? "_exponential_inverted" : "";
713 RooAbsReal *c = tool->request<RooAbsReal>(removeSuffix(constParamName, suffixToRemove), name);
714
715 tool->wsEmplace<RooExponential>(name, *x, *c, !isInverted);
716
717 return true;
718}
719
720bool importMultiVarGaussian(RooJSONFactoryWSTool *tool, const JSONNode &p)
721{
722 std::string name(RooJSONFactoryWSTool::name(p));
723 bool has_cov = p.has_child("covariances");
724 bool has_corr = p.has_child("correlations") && p.has_child("standard_deviations");
725 if (!has_cov && !has_corr) {
726 RooJSONFactoryWSTool::error("no covariances or correlations+standard_deviations given in '" + name + "'");
727 }
728
729 TMatrixDSym covmat;
730
731 // The matrix must be square, otherwise the entries would be written out of bounds.
732 auto checkSquare = [&](const char *key, std::size_t n) {
733 if (p[key].num_children() != n) {
734 RooJSONFactoryWSTool::error("\"" + std::string(key) + "\" in '" + name + "' must have " + std::to_string(n) +
735 " rows");
736 }
737 for (const auto &row : p[key].children()) {
738 if (!row.is_seq() || row.num_children() != n) {
739 RooJSONFactoryWSTool::error("\"" + std::string(key) + "\" in '" + name + "' must be a square matrix");
740 }
741 }
742 };
743
744 if (has_cov) {
745 int n = p["covariances"].num_children();
746 checkSquare("covariances", n);
747 int i = 0;
748 covmat.ResizeTo(n, n);
749 for (const auto &row : p["covariances"].children()) {
750 int j = 0;
751 for (const auto &val : row.children()) {
752 covmat(i, j) = val.val_double();
753 ++j;
754 }
755 ++i;
756 }
757 } else {
758 std::vector<double> variances;
759 variances << p["standard_deviations"];
760 checkSquare("correlations", variances.size());
761 covmat.ResizeTo(variances.size(), variances.size());
762 int i = 0;
763 for (const auto &row : p["correlations"].children()) {
764 int j = 0;
765 for (const auto &val : row.children()) {
766 covmat(i, j) = val.val_double() * variances[i] * variances[j];
767 ++j;
768 }
769 ++i;
770 }
771 }
773 tool->requestArgList<RooAbsReal>(p, "mean"), covmat);
774 return true;
775}
776
777/// Read the binning variables from the "axes" node, ordered like in `varList`.
778RooArgList readBinning(const JSONNode &topNode, const RooArgList &varList)
779{
781 RooArgList vars;
782 for (RooAbsArg *refVar : varList) {
783 if (RooAbsArg *axis = axes.find(*refVar)) {
784 vars.addClone(*axis);
785 }
786 }
787 return vars;
788}
789
790bool importParamHistFunc(RooJSONFactoryWSTool *tool, const JSONNode &p)
791{
792 if (!p.has_child("parameters")) {
793 return false;
794 }
795 std::string name(RooJSONFactoryWSTool::name(p));
796 RooArgList varList = tool->requestArgList<RooRealVar>(p, "variables");
797 if (!p.has_child("axes")) {
798 std::stringstream ss;
799 ss << "No axes given in '" << name << "'"
800 << ". Using default binning (uniform; nbins=100). If needed, export the Workspace to JSON with a newer "
801 << "Root version that supports custom ParamHistFunc binnings(>=6.38.00)." << std::endl;
803 tool->wsEmplace<ParamHistFunc>(name, varList, tool->requestArgList<RooAbsReal>(p, "parameters"));
804 return true;
805 }
806 tool->wsEmplace<ParamHistFunc>(name, readBinning(p, varList), tool->requestArgList<RooAbsReal>(p, "parameters"));
807 return true;
808}
809
810bool importSpline(RooJSONFactoryWSTool *tool, const JSONNode &p)
811{
812 const std::string name(RooJSONFactoryWSTool::name(p));
813
814 // Mandatory fields
815 if (!p.has_child("x")) {
816 RooJSONFactoryWSTool::error("no x given in '" + name + "'");
817 }
818 if (!p.has_child("x0") || !p.has_child("y0")) {
819 RooJSONFactoryWSTool::error("no x0/y0 given in '" + name + "'");
820 }
821
822 RooAbsReal *x = tool->requestArg<RooAbsReal>(p, "x");
823
824 // Optional fields (defaults follow RooSpline ctor defaults)
825 std::string algo = p.has_child("interpolation") ? p["interpolation"].val() : "poly3";
826 int order = 0;
827 if (algo == "poly3") {
828 order = 3;
829 } else if (algo == "poly5") {
830 order = 5;
831 } else {
832 RooJSONFactoryWSTool::error("unsupported algo '" + algo + "' for RooSpline in '" + name +
833 "': allowed are 'poly3' and 'poly5'");
834 }
835 const bool logx = p.has_child("logx") ? p["logx"].val_bool() : false;
836 const bool logy = p.has_child("logy") ? p["logy"].val_bool() : false;
837
838 // Read knots
839 std::vector<double> x0;
840 std::vector<double> y0;
841 x0 << p["x0"];
842 y0 << p["y0"];
843
844 if (x0.size() != y0.size()) {
845 RooJSONFactoryWSTool::error("x0/y0 size mismatch in '" + name + "': x0 has " + std::to_string(x0.size()) +
846 ", y0 has " + std::to_string(y0.size()));
847 }
848 if (x0.size() < 2) {
849 RooJSONFactoryWSTool::error("need at least 2 knots in '" + name + "'");
850 }
851
852 // Construct RooSpline(name,title, x, x0, y0, order, logx, logy)
853 tool->wsEmplace<::RooSpline>(name.c_str(), *x, std::span<const double>(x0.data(), x0.size()),
854 std::span<const double>(y0.data(), y0.size()), order, logx, logy);
855
856 return true;
857}
858
859// Importer that is registered with top priority for a deprecated type key. It warns once, and then lets the importers
860// or the factory expression that are registered for the deprecated key do the actual work.
861class DeprecatedKeyImporter : public RooFit::JSONIO::Importer {
862public:
863 DeprecatedKeyImporter(std::string key, std::string replacement)
864 : _key{std::move(key)}, _replacement{std::move(replacement)}
865 {
866 }
867 bool importArg(RooJSONFactoryWSTool *, const JSONNode &) const override
868 {
869 if (!_warned) {
870 RooJSONFactoryWSTool::warning("RooFitHS3: the type \"" + _key + "\" is deprecated, please use \"" +
871 _replacement + "\" instead.");
872 _warned = true;
873 }
874 return false;
875 }
876
877private:
878 const std::string _key;
879 const std::string _replacement;
880 mutable bool _warned = false;
881};
882
883namespace CrystalBall {
884
885constexpr const char *tailKeys[] = {"alpha_L", "n_L", "alpha_R", "n_R"};
886
887bool hasAny(const JSONNode &p, std::initializer_list<const char *> keys)
888{
889 for (const char *key : keys) {
890 if (p.has_child(key)) {
891 return true;
892 }
893 }
894 return false;
895}
896
897bool hasDoubleSidedKeys(const JSONNode &p)
898{
899 return hasAny(p, {"alpha_L", "n_L", "alpha_R", "n_R", "sigma_L", "sigma_R"});
900}
901
902// Create a RooCrystalBall with two tails. With `symmetricShorthand`, the tails may also be given as "alpha" and "n"
903// for both sides, as in the "crystalball_dist" of HS3 versions before 0.3.
904void importDoubleSided(RooJSONFactoryWSTool *tool, const JSONNode &p, std::string const &type, bool symmetricShorthand)
905{
906 const std::string name(RooJSONFactoryWSTool::name(p));
907 const std::string context = type + " '" + name + "'";
908
909 const bool asymmetricTails = hasAny(p, {"alpha_L", "n_L", "alpha_R", "n_R"});
910 const bool symmetricTails = hasAny(p, {"alpha", "n"});
911 if (symmetricTails && !symmetricShorthand) {
912 RooJSONFactoryWSTool::error(context + " must not have \"alpha\" or \"n\", the tails are given by \"alpha_L\", " +
913 "\"n_L\", \"alpha_R\" and \"n_R\"");
914 }
915 if (symmetricTails && asymmetricTails) {
917 " must not combine \"alpha\"/\"n\" with \"alpha_L\"/\"n_L\"/\"alpha_R\"/\"n_R\"");
918 }
919 if (asymmetricTails || !symmetricTails) {
920 for (const char *key : tailKeys) {
921 if (!p.has_child(key)) {
922 RooJSONFactoryWSTool::error(context + " is missing \"" + key +
923 "\": \"alpha_L\", \"n_L\", \"alpha_R\" and \"n_R\" are all required");
924 }
925 }
926 } else if (!p.has_child("alpha") || !p.has_child("n")) {
927 RooJSONFactoryWSTool::error(context + " needs both \"alpha\" and \"n\"");
928 }
929
930 const bool asymmetricWidths = hasAny(p, {"sigma_L", "sigma_R"});
931 if (asymmetricWidths && p.has_child("sigma")) {
932 RooJSONFactoryWSTool::error(context + " must not combine \"sigma\" with \"sigma_L\"/\"sigma_R\"");
933 }
934 if (asymmetricWidths && !(p.has_child("sigma_L") && p.has_child("sigma_R"))) {
935 RooJSONFactoryWSTool::error(context + " needs both \"sigma_L\" and \"sigma_R\"");
936 }
937 if (!asymmetricWidths && !p.has_child("sigma")) {
938 RooJSONFactoryWSTool::error(context + " needs either \"sigma\" or both \"sigma_L\" and \"sigma_R\"");
939 }
940
941 RooAbsReal *m = tool->requestArg<RooAbsReal>(p, "m");
942 RooAbsReal *m0 = tool->requestArg<RooAbsReal>(p, "m0");
943 RooAbsReal *sigmaL = tool->requestArg<RooAbsReal>(p, asymmetricWidths ? "sigma_L" : "sigma");
944 RooAbsReal *sigmaR = tool->requestArg<RooAbsReal>(p, asymmetricWidths ? "sigma_R" : "sigma");
945 RooAbsReal *alphaL = tool->requestArg<RooAbsReal>(p, symmetricTails ? "alpha" : "alpha_L");
946 RooAbsReal *nL = tool->requestArg<RooAbsReal>(p, symmetricTails ? "n" : "n_L");
947 RooAbsReal *alphaR = tool->requestArg<RooAbsReal>(p, symmetricTails ? "alpha" : "alpha_R");
948 RooAbsReal *nR = tool->requestArg<RooAbsReal>(p, symmetricTails ? "n" : "n_R");
949 tool->wsEmplace<RooCrystalBall>(name, *m, *m0, *sigmaL, *sigmaR, *alphaL, *nL, *alphaR, *nR);
950}
951
952} // namespace CrystalBall
953
954// "crystalball_dist" is the single-sided Crystal Ball, which is left to the factory expression (RooCBShape). Before
955// HS3 0.3, it was also the double-sided Crystal Ball, recognizable by the "_L"/"_R" fields. Such documents are still
956// read, with a deprecation warning.
957class CrystalBallImporter : public RooFit::JSONIO::Importer {
958public:
959 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
960 {
961 if (!CrystalBall::hasDoubleSidedKeys(p)) {
962 return false;
963 }
964 if (!_warned) {
965 RooJSONFactoryWSTool::warning("RooFitHS3: \"crystalball_dist\" with the fields \"alpha_L\", \"n_L\", "
966 "\"alpha_R\", \"n_R\", \"sigma_L\" or \"sigma_R\" is deprecated, please use "
967 "\"crystalball_doublesided_dist\" for the double-sided Crystal Ball.");
968 _warned = true;
969 }
970 CrystalBall::importDoubleSided(tool, p, "crystalball_dist", /*symmetricShorthand=*/true);
971 return true;
972 }
973
974private:
975 mutable bool _warned = false;
976};
977
978bool importCrystalBallDoubleSided(RooJSONFactoryWSTool *tool, const JSONNode &p)
979{
980 CrystalBall::importDoubleSided(tool, p, "crystalball_doublesided_dist", /*symmetricShorthand=*/false);
981 return true;
982}
983
984///////////////////////////////////////////////////////////////////////////////////////////////////////
985// specialized exporter implementations
986///////////////////////////////////////////////////////////////////////////////////////////////////////
987// Write a reference to `arg`: the value if it is a literal constant, and the name otherwise.
988void writeReference(JSONNode &node, RooAbsArg const &arg)
989{
990 auto const *constVar = dynamic_cast<RooConstVar const *>(&arg);
991 std::istringstream stream(arg.GetName());
992 double value = 0.0;
993 if (constVar && (stream >> value) && stream.eof()) {
994 node << constVar->getVal();
995 } else {
996 node << arg.GetName();
997 }
998}
999
1000// A RooCrystalBall with one tail is the single-sided "crystalball_dist" (read back as RooCBShape, which follows the
1001// same convention for the sign of alpha). With two tails, it is a "crystalball_doublesided_dist", whose tails are
1002// always written as "alpha_L"/"n_L"/"alpha_R"/"n_R": there is no shorthand for symmetric tails.
1003bool exportCrystalBall(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1004{
1005 auto const *pdf = static_cast<const RooCrystalBall *>(func);
1006 writeReference(elem["m"], pdf->x());
1007 writeReference(elem["m0"], pdf->x0());
1008 if (!pdf->alphaR() || !pdf->nR()) {
1009 elem["type"] << "crystalball_dist";
1010 writeReference(elem["sigma"], pdf->sigmaL());
1011 writeReference(elem["alpha"], pdf->alphaL());
1012 writeReference(elem["n"], pdf->nL());
1013 return true;
1014 }
1015 elem["type"] << key;
1016 if (&pdf->sigmaL() == &pdf->sigmaR()) {
1017 writeReference(elem["sigma"], pdf->sigmaL());
1018 } else {
1019 writeReference(elem["sigma_L"], pdf->sigmaL());
1020 writeReference(elem["sigma_R"], pdf->sigmaR());
1021 }
1022 writeReference(elem["alpha_L"], pdf->alphaL());
1023 writeReference(elem["n_L"], pdf->nL());
1024 writeReference(elem["alpha_R"], *pdf->alphaR());
1025 writeReference(elem["n_R"], *pdf->nR());
1026 return true;
1027}
1028
1029template <class RooArg_t>
1030bool exportAddPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1031{
1032 const RooArg_t *pdf = static_cast<const RooArg_t *>(func);
1033 elem["type"] << key;
1034 RooJSONFactoryWSTool::fillSeq(elem["summands"], pdf->pdfList());
1035 RooJSONFactoryWSTool::fillSeq(elem["coefficients"], pdf->coefList());
1036 elem["extended"] << (pdf->extendMode() != RooArg_t::CanNotBeExtended);
1037 return true;
1038}
1039
1040template <class RooArg_t>
1041bool exportRealSum(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1042{
1043 auto const *pdf = static_cast<const RooArg_t *>(func);
1044 elem["type"] << key;
1045 RooJSONFactoryWSTool::fillSeq(elem["samples"], pdf->funcList());
1046 RooJSONFactoryWSTool::fillSeq(elem["coefficients"], pdf->coefList());
1047 if constexpr (std::is_same_v<RooArg_t, RooRealSumPdf>) {
1048 elem["extended"] << (pdf->extendMode() != RooAbsPdf::CanNotBeExtended);
1049 }
1050 return true;
1051}
1052
1053template <class RooArg_t>
1054bool exportHist(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1055{
1056 const RooArg_t *hf = static_cast<const RooArg_t *>(func);
1057 elem["type"] << key;
1058 RooDataHist const &dh = hf->dataHist();
1059 tool->exportHisto(*dh.get(), dh.numEntries(), dh.weightArray(), elem["data"].set_map());
1060 return true;
1061}
1062
1063template <class RooArg_t>
1064bool importHist(RooJSONFactoryWSTool *tool, const JSONNode &p)
1065{
1066 std::string name(RooJSONFactoryWSTool::name(p));
1067 if (!p.has_child("data")) {
1068 return false;
1069 }
1070 std::unique_ptr<RooDataHist> dataHist =
1072 tool->wsEmplace<RooArg_t>(name, *dataHist->get(), *dataHist);
1073 return true;
1074}
1075
1076bool exportBinSamplingPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1077{
1078 const RooBinSamplingPdf *pdf = static_cast<const RooBinSamplingPdf *>(func);
1079 elem["type"] << key;
1080 elem["pdf"] << pdf->pdf().GetName();
1081 elem["observable"] << pdf->observable().GetName();
1082 elem["epsilon"] << pdf->epsilon();
1083 return true;
1084}
1085
1086bool exportBinWidthFunction(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &)
1087{
1088 const RooBinWidthFunction *pdf = static_cast<const RooBinWidthFunction *>(func);
1089 elem["type"] << (pdf->divideByBinWidth() ? "inverse_binvolume" : "binvolume");
1090 elem["histogram"] << pdf->histFunc().GetName();
1091 return true;
1092}
1093
1094void cleanExpression(TString &expr)
1095{
1096 // Plain substring replacement would also hit longer identifiers that
1097 // share a prefix (e.g. "TMath::Tan" in "TMath::TanH", or "TMath::Pi" in
1098 // "TMath::PiOver2"), corrupting the exported expression. Identifiers
1099 // without a replacement are kept as-is.
1100 replaceIdentifier(expr, "TMath::Exp", "exp");
1101 replaceIdentifier(expr, "TMath::Min", "min");
1102 replaceIdentifier(expr, "TMath::Max", "max");
1103 replaceIdentifier(expr, "TMath::Log", "log");
1104 replaceIdentifier(expr, "TMath::Log10", "log10");
1105 replaceIdentifier(expr, "TMath::Cos", "cos");
1106 replaceIdentifier(expr, "TMath::CosH", "cosh");
1107 replaceIdentifier(expr, "TMath::Sin", "sin");
1108 replaceIdentifier(expr, "TMath::SinH", "sinh");
1109 replaceIdentifier(expr, "TMath::Sqrt", "sqrt");
1110 replaceIdentifier(expr, "TMath::Power", "pow");
1111 replaceIdentifier(expr, "TMath::Erf", "erf");
1112 replaceIdentifier(expr, "TMath::Erfc", "erfc");
1113 replaceIdentifier(expr, "TMath::Floor", "floor");
1114 replaceIdentifier(expr, "TMath::Ceil", "ceil");
1115 replaceIdentifier(expr, "TMath::Abs", "abs");
1116 replaceIdentifier(expr, "TMath::Tan", "tan");
1117 replaceIdentifier(expr, "TMath::TanH", "tanh");
1118 replaceIdentifier(expr, "TMath::ASin", "asin");
1119 replaceIdentifier(expr, "TMath::ACos", "acos");
1120 replaceIdentifier(expr, "TMath::ATan", "atan");
1121 replaceIdentifier(expr, "TMath::ATan2", "atan2");
1122 replaceIdentifier(expr, "TMath::Pi()", "PI");
1123 replaceIdentifier(expr, "TMath::E()", "EULER");
1124}
1125
1126bool ternariesToIfElse(std::string const &in, std::string &out);
1127
1128// Rewrite one comma-free part of an expression, see ternariesToIfElse().
1129bool ternaryPartToIfElse(std::string const &in, std::string &out)
1130{
1131 // Find the first '?' that is not enclosed in brackets. A ':' that is not part of "::" can only follow a '?'.
1132 std::size_t question = std::string::npos;
1133 int depth = 0;
1134 for (std::size_t i = 0; i < in.size() && question == std::string::npos; ++i) {
1135 const char c = in[i];
1136 if (c == '(' || c == '[') {
1137 ++depth;
1138 } else if (c == ')' || c == ']') {
1139 --depth;
1140 } else if (c == ':' && i + 1 < in.size() && in[i + 1] == ':') {
1141 ++i;
1142 } else if (depth == 0 && c == ':') {
1143 return false;
1144 } else if (depth == 0 && c == '?') {
1145 question = i;
1146 } else if (depth == 0 && c == ',') {
1147 return false;
1148 }
1149 }
1150
1151 if (question == std::string::npos) {
1152 // No ternary on this level: copy the expression and rewrite the ternaries inside of brackets.
1153 out.clear();
1154 for (std::size_t i = 0; i < in.size(); ++i) {
1155 if (in[i] == ')' || in[i] == ']') {
1156 return false;
1157 }
1158 if (in[i] != '(' && in[i] != '[') {
1159 out += in[i];
1160 continue;
1161 }
1162 const std::size_t close = findClosingBracket(in, i);
1163 if (close == std::string::npos) {
1164 return false;
1165 }
1166 const std::string inner = in.substr(i + 1, close - i - 1);
1167 std::string innerOut;
1168 if (!ternariesToIfElse(inner, innerOut)) {
1169 return false;
1170 }
1171 // Parentheses that only group a ternary are redundant around the ifelse() function call.
1172 const std::string before = trimmed(in.substr(0, i));
1173 const bool isGrouping = in[i] == '(' && (before.empty() || !isIdentifierChar(before.back()));
1174 if (isGrouping && innerOut != trimmed(inner) && startsWith(innerOut, "ifelse(") &&
1175 findClosingBracket(innerOut, std::string("ifelse").size()) == innerOut.size() - 1) {
1176 out += innerOut;
1177 } else {
1178 out += in[i] + innerOut + in[close];
1179 }
1180 i = close;
1181 }
1182 return true;
1183 }
1184
1185 // Find the ':' that belongs to this '?', taking nested ternaries into account.
1186 std::size_t colon = std::string::npos;
1187 int nested = 0;
1188 depth = 0;
1189 for (std::size_t i = question + 1; i < in.size() && colon == std::string::npos; ++i) {
1190 const char c = in[i];
1191 if (c == '(' || c == '[') {
1192 ++depth;
1193 } else if (c == ')' || c == ']') {
1194 --depth;
1195 } else if (c == ':' && i + 1 < in.size() && in[i + 1] == ':') {
1196 ++i;
1197 } else if (depth == 0 && c == '?') {
1198 ++nested;
1199 } else if (depth == 0 && c == ':') {
1200 if (nested == 0) {
1201 colon = i;
1202 } else {
1203 --nested;
1204 }
1205 }
1206 }
1207 if (colon == std::string::npos) {
1208 return false;
1209 }
1210
1211 std::string condition;
1212 std::string ifTrue;
1213 std::string ifFalse;
1214 if (!ternaryPartToIfElse(stripEnclosingParentheses(in.substr(0, question)), condition) ||
1215 !ternaryPartToIfElse(stripEnclosingParentheses(in.substr(question + 1, colon - question - 1)), ifTrue) ||
1216 !ternaryPartToIfElse(stripEnclosingParentheses(in.substr(colon + 1)), ifFalse)) {
1217 return false;
1218 }
1219 if (condition.empty() || ifTrue.empty() || ifFalse.empty()) {
1220 return false;
1221 }
1222 out = "ifelse(" + condition + "," + ifTrue + "," + ifFalse + ")";
1223 return true;
1224}
1225
1226// Rewrite the C-style ternaries "c ? a : b" in a TFormula expression into the HS3 conditional "ifelse(c,a,b)". Returns
1227// false if this is not safely possible, e.g. because of unbalanced brackets.
1228bool ternariesToIfElse(std::string const &in, std::string &out)
1229{
1230 out.clear();
1231 bool first = true;
1232 for (std::string const &part : splitTopLevelCommas(in)) {
1233 std::string partOut;
1234 if (!ternaryPartToIfElse(part, partOut)) {
1235 return false;
1236 }
1237 out += (first ? "" : ",") + partOut;
1238 first = false;
1239 }
1240 return true;
1241}
1242
1243template <class RooArg_t>
1244bool exportFormulaArg(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1245{
1246 const RooArg_t *pdf = static_cast<const RooArg_t *>(func);
1247 elem["type"] << key;
1248 TString expression(pdf->expression());
1249 cleanExpression(expression);
1250 // If the tokens follow the "x[#]" convention, the square braces enclosing each number
1251 // ensures that there is a unique mapping between the token and parameter name
1252 // If the tokens follow the "@#" convention, the numbers are not enclosed by braces.
1253 // So there may be tokens with numbers whose lower place value forms a subset string of ones with a higher place
1254 // value, e.g. "@1" is a subset of "@10". So the names of these parameters must be applied descending from the
1255 // highest place value in order to ensure each parameter name is uniquely applied to its token.
1256 for (size_t idx = pdf->nParameters(); idx--;) {
1257 const RooAbsArg *par = pdf->getParameter(idx);
1258 expression.ReplaceAll(("x[" + std::to_string(idx) + "]").c_str(), par->GetName());
1259 expression.ReplaceAll(("@" + std::to_string(idx)).c_str(), par->GetName());
1260 }
1261 if (expression.Contains("?")) {
1262 std::string rewritten;
1263 if (ternariesToIfElse(expression.Data(), rewritten)) {
1264 expression = rewritten.c_str();
1265 } else {
1266 RooJSONFactoryWSTool::warning(std::string("RooFitHS3: could not translate the ternary operators in the "
1267 "expression of '") +
1268 func->GetName() + "' to ifelse(), exporting the expression unchanged.");
1269 }
1270 }
1271 elem["expression"] << expression.Data();
1272
1273 for (const RooAbsArg *dependent : pdf->dependents()) {
1274 auto const *observable = dynamic_cast<const RooAbsRealLValue *>(dependent);
1275 if (!observable) {
1276 continue;
1277 }
1278 const RooAbsBinning *binning = pdf->getBinning(*observable);
1279 if (!binning) {
1280 continue;
1281 }
1282
1283 auto &axes = elem["axes"];
1284 if (!axes.is_seq()) {
1285 axes.set_seq();
1286 }
1287 auto &axis = axes.append_child().set_map();
1288 axis["name"] << observable->GetName();
1289 writeAxisBinning(axis, *binning);
1290 }
1291 return true;
1292}
1293
1294// Write the "x" reference and the coefficient list for polynomial-like
1295// pdfs/funcs, including the implicit defaults below "lowestOrder" so that the
1296// output is self-documenting.
1297template <class RooArg_t>
1298bool exportPolynomial(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1299{
1300 auto const *pdf = static_cast<const RooArg_t *>(func);
1301 elem["type"] << key;
1302 elem["x"] << pdf->x().GetName();
1303 auto &coefs = elem["coefficients"].set_seq();
1304 for (int i = 0; i < pdf->lowestOrder(); ++i) {
1305 coefs.append_child() << (i == 0 ? 1.0 : 0.0);
1306 }
1307 for (const auto &coef : pdf->coefList()) {
1308 coefs.append_child() << coef->GetName();
1309 }
1310 return true;
1311}
1312
1313bool exportPoisson(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1314{
1315 auto *pdf = static_cast<const RooPoisson *>(func);
1316 elem["type"] << key;
1317 elem["x"] << pdf->getX().GetName();
1318 elem["mean"] << pdf->getMean().GetName();
1319 elem["integer"] << !pdf->getNoRounding();
1320 return true;
1321}
1322
1323// The RooDecay servers are the internal resModel-times-basis convolutions,
1324// which are implementation details that should not leak into the JSON. We
1325// only export the actual dependents (t, tau and the original resolution
1326// model) explicitly.
1327bool exportDecay(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1328{
1329 auto *pdf = static_cast<const RooDecay *>(func);
1330 elem["type"] << key;
1331 elem["t"] << pdf->getT().GetName();
1332 elem["tau"] << pdf->getTau().GetName();
1333 elem["resolutionModel"] << pdf->getModel().GetName();
1334 elem["decayType"] << pdf->getDecayType();
1335
1336 tool->queueExport(pdf->getT());
1337 tool->queueExport(pdf->getTau());
1338 tool->queueExport(pdf->getModel());
1339
1340 return true;
1341}
1342
1343bool exportTruthModel(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1344{
1345 auto *pdf = static_cast<const RooTruthModel *>(func);
1346 elem["type"] << key;
1347 elem["x"] << pdf->convVar().GetName();
1348
1349 return true;
1350}
1351
1352bool exportGaussModel(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1353{
1354 auto *pdf = static_cast<const RooGaussModel *>(func);
1355 elem["type"] << key;
1356 elem["x"] << pdf->convVar().GetName();
1357 elem["mean"] << pdf->getMean().GetName();
1358 elem["sigma"] << pdf->getSigma().GetName();
1359 return true;
1360}
1361
1362bool exportLogNormal(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1363{
1364 auto *pdf = static_cast<const RooLognormal *>(func);
1365
1366 elem["type"] << key;
1367 elem["x"] << pdf->getX().GetName();
1368
1369 auto &m0 = pdf->getMedian();
1370 auto &k = pdf->getShapeK();
1371
1372 if (pdf->useStandardParametrization()) {
1373 elem["mu"] << m0.GetName();
1374 elem["sigma"] << k.GetName();
1375 } else {
1376 elem["mu"] << tool->exportTransformed(&m0, "_lognormal_log", "log(%s)");
1377 elem["sigma"] << tool->exportTransformed(&k, "_lognormal_log", "log(%s)");
1378 }
1379
1380 return true;
1381}
1382
1383bool exportExponential(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1384{
1385 auto *pdf = static_cast<const RooExponential *>(func);
1386 elem["type"] << key;
1387 elem["x"] << pdf->variable().GetName();
1388 auto &c = pdf->coefficient();
1389 if (pdf->negateCoefficient()) {
1390 elem["c"] << c.GetName();
1391 } else {
1392 elem["c"] << tool->exportTransformed(&c, "_exponential_inverted", "-%s");
1393 }
1394
1395 return true;
1396}
1397
1398bool exportMultiVarGaussian(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1399{
1400 auto *pdf = static_cast<const RooMultiVarGaussian *>(func);
1401 elem["type"] << key;
1402 RooJSONFactoryWSTool::fillSeq(elem["x"], pdf->xVec());
1403 RooJSONFactoryWSTool::fillSeq(elem["mean"], pdf->muVec());
1404 elem["covariances"].fill_mat(pdf->covarianceMatrix());
1405 return true;
1406}
1407
1408bool exportTFnBinding(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1409{
1410 auto *pdf = static_cast<const RooTFnBinding *>(func);
1411 elem["type"] << key;
1412
1413 TString formula(pdf->function().GetExpFormula());
1414 formula.ReplaceAll("x", pdf->observables()[0].GetName());
1415 formula.ReplaceAll("y", pdf->observables()[1].GetName());
1416 formula.ReplaceAll("z", pdf->observables()[2].GetName());
1417 for (size_t i = 0; i < pdf->parameters().size(); ++i) {
1418 TString pname(TString::Format("[%d]", (int)i));
1419 formula.ReplaceAll(pname, pdf->parameters()[i].GetName());
1420 }
1421 elem["expression"] << formula.Data();
1422 return true;
1423}
1424
1425bool exportDerivative(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1426{
1427 auto *pdf = static_cast<const RooDerivative *>(func);
1428 elem["type"] << key;
1429 elem["x"] << pdf->getX().GetName();
1430 elem["function"] << pdf->getFunc().GetName();
1431 if (!pdf->getNset().empty()) {
1432 RooJSONFactoryWSTool::fillSeq(elem["normalization"], pdf->getNset());
1433 }
1434 elem["order"] << pdf->order();
1435 elem["eps"] << pdf->eps();
1436 return true;
1437}
1438
1439bool exportRealIntegral(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1440{
1441 auto *integral = static_cast<const RooRealIntegral *>(func);
1442 elem["type"] << key;
1443 std::string integrand = integral->integrand().GetName();
1444 elem["integrand"] << integrand;
1445 if (integral->intRange()) {
1446 elem["domain"] << integral->intRange();
1447 }
1448 RooJSONFactoryWSTool::fillSeq(elem["variables"], integral->intVars());
1449 if (RooArgSet const *funcNormSet = integral->funcNormSet()) {
1450 RooJSONFactoryWSTool::fillSeq(elem["normalization"], *funcNormSet);
1451 }
1452 return true;
1453}
1454
1455bool exportFFTConvPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1456{
1457 auto *pdf = static_cast<const RooFFTConvPdf *>(func);
1458 elem["type"] << key;
1459 if (auto convFunc = pdf->getPdfConvVar()) {
1460 elem["conv_func"] << convFunc->GetName();
1461 }
1462 elem["conv_var"] << pdf->getConvVar().GetName();
1463 elem["pdf1"] << pdf->getPdf1().GetName();
1464 elem["pdf2"] << pdf->getPdf2().GetName();
1465 elem["ipOrder"] << pdf->getInterpolationOrder();
1466 return true;
1467}
1468
1469bool exportExtendPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1470{
1471 auto *pdf = static_cast<const RooExtendPdf *>(func);
1472 elem["type"] << key;
1473 if (auto rangeName = pdf->getRangeName()) {
1474 elem["range"] << rangeName->GetName();
1475 }
1476 elem["dist"] << pdf->pdf().GetName();
1477 elem["rate"] << pdf->getN().GetName();
1478 return true;
1479}
1480
1481bool exportParamHistFunc(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1482{
1483 auto *pdf = static_cast<const ParamHistFunc *>(func);
1484 elem["type"] << key;
1485 RooJSONFactoryWSTool::fillSeq(elem["variables"], pdf->dataVars());
1486 RooJSONFactoryWSTool::fillSeq(elem["parameters"], pdf->paramList());
1487 auto &observablesNode = elem["axes"].set_seq();
1488 // axes have to be ordered to get consistent bin indices
1489 for (auto *var : static_range_cast<RooRealVar *>(pdf->dataVars())) {
1490 RooJSONFactoryWSTool::exportAxis(observablesNode.append_child().set_map(), *var);
1491 }
1492 return true;
1493}
1494
1495bool exportSpline(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key)
1496{
1497 auto const *rs = static_cast<RooSpline const *>(func);
1498
1499 elem["type"] << key;
1500
1501 // Independent variable
1502 elem["x"] << rs->x().GetName();
1503
1504 // Spline configuration
1505 // Canonical algo for RooSpline
1506 elem["interpolation"] << (rs->order() == 5 ? "poly5" : "poly3");
1507 elem["logx"] << rs->logx();
1508 elem["logy"] << rs->logy();
1509
1510 // Serialize knots as primitive arrays
1511 TSpline const &sp = rs->spline();
1512 auto &x0 = elem["x0"].set_seq();
1513 auto &y0 = elem["y0"].set_seq();
1514
1515 const int np = sp.GetNp();
1516 for (int i = 0; i < np; ++i) {
1517 double xk = 0.0;
1518 double yk = 0.0;
1519 sp.GetKnot(i, xk, yk);
1520 x0.append_child() << xk;
1521 y0.append_child() << yk;
1522 }
1523
1524 return true;
1525}
1526
1527bool importWrapperPdf(RooJSONFactoryWSTool *tool, const JSONNode &node)
1528{
1529 if (node["type"].val() != "density_function_dist")
1530 return false;
1531
1532 auto name = RooJSONFactoryWSTool::name(node);
1533 auto *func = tool->requestArg<RooAbsReal>(node, "function");
1534
1535 bool selfNormalized = false;
1536
1537 auto sn = node.find("self_normalized");
1538 // ROOT previously exported this key without an underscore.
1539 if (!sn)
1540 sn = node.find("selfnormalized");
1541 if (sn)
1542 selfNormalized = sn->val_bool();
1543
1544 tool->wsEmplace<RooWrapperPdf>(name, *func, selfNormalized);
1545 return true;
1546}
1547
1548bool exportWrapperPdf(RooJSONFactoryWSTool *, const RooAbsArg *arg, JSONNode &node, std::string const &key)
1549{
1550 auto const *pdf = dynamic_cast<RooWrapperPdf const *>(arg);
1551 if (!pdf)
1552 return false;
1553
1554 node["type"] << key;
1555
1556 // Proxy name in RooWrapperPdf is "_func" / "func" depending on accessor/proxy export.
1557 // Prefer a public accessor if one exists; otherwise inspect proxies as below.
1558 auto const *funcProxy = dynamic_cast<RooRealProxy const *>(pdf->getProxy(0));
1559 if (!funcProxy || !funcProxy->absArg())
1560 return false;
1561
1562 node["function"] << funcProxy->absArg()->GetName();
1563 if (pdf->selfNormalized())
1564 node["self_normalized"] << true;
1565
1566 return true;
1567}
1568
1569///////////////////////////////////////////////////////////////////////////////////////////////////////
1570// instantiate all importers and exporters
1571///////////////////////////////////////////////////////////////////////////////////////////////////////
1572
1573// Adapters that wrap the plain import/export functions above into the
1574// RooFit::JSONIO::Importer/Exporter interface. The exporter also owns the HS3
1575// type key, which is passed at registration time.
1576template <auto Func>
1577class FuncImporter : public RooFit::JSONIO::Importer {
1578public:
1579 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override { return Func(tool, p); }
1580};
1581
1582template <auto Func>
1583class FuncExporter : public RooFit::JSONIO::Exporter {
1584public:
1585 FuncExporter(std::string key, bool autoExportDependants)
1586 : _key{std::move(key)}, _autoExportDependants{autoExportDependants}
1587 {
1588 }
1589 std::string const &key() const override { return _key; }
1590 bool autoExportDependants() const override { return _autoExportDependants; }
1591 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem) const override
1592 {
1593 return Func(tool, func, elem, _key);
1594 }
1595
1596private:
1597 const std::string _key;
1598 const bool _autoExportDependants;
1599};
1600
1601template <auto Func>
1602void registerImporter(const std::string &key, bool topPriority = true)
1603{
1604 RooFit::JSONIO::registerImporter(key, std::make_unique<FuncImporter<Func>>(), topPriority);
1605}
1606
1607template <auto Func>
1608void registerExporter(TClass const *cl, std::string key, bool topPriority = true, bool autoExportDependants = true)
1609{
1610 RooFit::JSONIO::registerExporter(cl, std::make_unique<FuncExporter<Func>>(std::move(key), autoExportDependants),
1611 topPriority);
1612}
1613
1614STATIC_EXECUTE([]() {
1615 registerImporter<importWrapperPdf>("density_function_dist");
1616 registerImporter<importExtendPdf>("rate_extended_dist");
1617 RooFit::JSONIO::registerImporter("crystalball_dist", std::make_unique<CrystalBallImporter>());
1618 registerImporter<importCrystalBallDoubleSided>("crystalball_doublesided_dist");
1619 RooFit::JSONIO::registerImporter("ARGUS_dist", std::make_unique<DeprecatedKeyImporter>("ARGUS_dist", "argus_dist"));
1621 std::make_unique<DeprecatedKeyImporter>("binsampling", "binsampling_dist"));
1623 "mixture_resolution_model", std::make_unique<DeprecatedKeyImporter>("mixture_resolution_model", "mixture_model"));
1624 RooFit::JSONIO::registerImporter("delta_resolution_model", std::make_unique<DeprecatedKeyImporter>(
1625 "delta_resolution_model", "truth_model_function"));
1626 RooFit::JSONIO::registerImporter("gauss_resolution_model", std::make_unique<DeprecatedKeyImporter>(
1627 "gauss_resolution_model", "gauss_model_function"));
1628 registerImporter<importProduct>("product", false);
1629 registerImporter<importProdPdf>("product_dist", false);
1630 registerImporter<importAddition>("sum", false);
1631 registerImporter<importAddPdf>("mixture_dist", false);
1632 registerImporter<importAddModel>("mixture_model", false);
1633 registerImporter<importAddModel>("mixture_resolution_model", false);
1634 registerImporter<importBinSamplingPdf>("binsampling_dist", false);
1635 registerImporter<importBinSamplingPdf>("binsampling", false);
1636 registerImporter<importBinWidthFunction<false>>("binvolume", false);
1637 registerImporter<importBinWidthFunction<true>>("inverse_binvolume", false);
1638 registerImporter<importPolynomial<RooLegacyExpPoly>>("legacy_exp_poly_dist", false);
1639 registerImporter<importExponential>("exponential_dist", false);
1640 registerImporter<importFormulaArg<RooFormulaVar>>("generic", false);
1641 registerImporter<importFormulaArg<RooFormulaVar>>("generic_function", false);
1642 registerImporter<importFormulaArg<RooGenericPdf>>("generic_dist", false);
1643 registerImporter<importHist<RooHistFunc>>("histogram", false);
1644 registerImporter<importHist<RooHistFunc>>("step", false);
1645 registerImporter<importHist<RooHistPdf>>("histogram_dist", false);
1646 registerImporter<importLogNormal>("lognormal_dist", false);
1647 registerImporter<importMultiVarGaussian>("multivariate_normal_dist", false);
1648 registerImporter<importPoisson>("poisson_dist", false);
1649 registerImporter<importDecay>("decay_dist", false);
1650 registerImporter<importTruthModel>("truth_model_function", false);
1651 registerImporter<importTruthModel>("delta_resolution_model", false);
1652 registerImporter<importGaussModel>("gauss_model_function", false);
1653 registerImporter<importGaussModel>("gauss_resolution_model", false);
1654 registerImporter<importPolynomial<RooPolynomial>>("polynomial_dist", false);
1655 registerImporter<importPolynomial<RooPolyVar>>("polynomial", false);
1656 registerImporter<importRealSum<RooRealSumPdf>>("weighted_sum_dist", false);
1657 registerImporter<importRealSum<RooRealSumFunc>>("weighted_sum", false);
1658 registerImporter<importRealIntegral>("integral", false);
1659 registerImporter<importDerivative>("derivative", false);
1660 registerImporter<importFFTConvPdf>("fft_convolution_dist", false);
1661 registerImporter<importExtendPdf>("extend_pdf", false);
1662 registerImporter<importParamHistFunc>("step", false);
1663 registerImporter<importSpline>("spline", false);
1664
1665 registerExporter<exportWrapperPdf>(RooWrapperPdf::Class(), "density_function_dist");
1666 registerExporter<exportAddPdf<RooAddPdf>>(RooAddPdf::Class(), "mixture_dist", false);
1667 registerExporter<exportAddPdf<RooAddModel>>(RooAddModel::Class(), "mixture_model", false);
1668 registerExporter<exportBinSamplingPdf>(RooBinSamplingPdf::Class(), "binsampling_dist", false);
1669 registerExporter<exportCrystalBall>(RooCrystalBall::Class(), "crystalball_doublesided_dist", false);
1670 registerExporter<exportBinWidthFunction>(RooBinWidthFunction::Class(), "binvolume", false);
1671 registerExporter<exportPolynomial<RooLegacyExpPoly>>(RooLegacyExpPoly::Class(), "legacy_exp_poly_dist", false);
1672 registerExporter<exportExponential>(RooExponential::Class(), "exponential_dist", false);
1673 registerExporter<exportFormulaArg<RooFormulaVar>>(RooFormulaVar::Class(), "generic", false);
1674 registerExporter<exportFormulaArg<RooGenericPdf>>(RooGenericPdf::Class(), "generic_dist", false);
1675 registerExporter<exportHist<RooHistFunc>>(RooHistFunc::Class(), "step", false);
1676 registerExporter<exportHist<RooHistPdf>>(RooHistPdf::Class(), "histogram_dist", false);
1677 registerExporter<exportLogNormal>(RooLognormal::Class(), "lognormal_dist", false);
1678 registerExporter<exportMultiVarGaussian>(RooMultiVarGaussian::Class(), "multivariate_normal_dist", false);
1679 registerExporter<exportPoisson>(RooPoisson::Class(), "poisson_dist", false);
1680 registerExporter<exportDecay>(RooDecay::Class(), "decay_dist", false, /*autoExportDependants=*/false);
1681 registerExporter<exportTruthModel>(RooTruthModel::Class(), "truth_model_function", false);
1682 registerExporter<exportGaussModel>(RooGaussModel::Class(), "gauss_model_function", false);
1683 registerExporter<exportPolynomial<RooPolynomial>>(RooPolynomial::Class(), "polynomial_dist", false);
1684 registerExporter<exportPolynomial<RooPolyVar>>(RooPolyVar::Class(), "polynomial", false);
1685 registerExporter<exportRealSum<RooRealSumFunc>>(RooRealSumFunc::Class(), "weighted_sum", false);
1686 registerExporter<exportRealSum<RooRealSumPdf>>(RooRealSumPdf::Class(), "weighted_sum_dist", false);
1687 registerExporter<exportTFnBinding>(RooTFnBinding::Class(), "generic", false);
1688 registerExporter<exportRealIntegral>(RooRealIntegral::Class(), "integral", false);
1689 registerExporter<exportDerivative>(RooDerivative::Class(), "derivative", false);
1690 registerExporter<exportFFTConvPdf>(RooFFTConvPdf::Class(), "fft_convolution_dist", false);
1691 registerExporter<exportExtendPdf>(RooExtendPdf::Class(), "rate_extended_dist", false);
1692 registerExporter<exportParamHistFunc>(ParamHistFunc::Class(), "step", false);
1693 registerExporter<exportSpline>(RooSpline::Class(), "spline", false);
1694});
1695
1696} // namespace
bool startsWith(std::string_view str, std::string_view prefix)
bool endsWith(std::string_view str, std::string_view suffix)
std::string removeSuffix(std::string_view str, std::string_view suffix)
void writeAxisBinning(JSONNode &node, const RooAbsBinning &binning)
#define d(i)
Definition RSha256.hxx:102
#define c(i)
Definition RSha256.hxx:101
ROOT::RRangeCast< T, false, Range_t > static_range_cast(Range_t &&coll)
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t np
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 Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t type
char name[80]
Definition TGX11.cxx:142
A class which maps the current values of a RooRealVar (or a set of RooRealVars) to one of a number of...
static TClass * Class()
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.
Abstract base class for RooRealVar binning definitions.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
virtual RooAbsArg * addClone(const RooAbsArg &var, bool silent=false)
Add a clone of the specified argument to list.
RooAbsArg * find(const char *name) const
Find object with given name in list.
virtual Int_t numEntries() const
Return number of entries in dataset, i.e., count unweighted entries.
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
@ CanNotBeExtended
Definition RooAbsPdf.h:208
Abstract base class for objects that represent a real value that may appear on the left hand side of ...
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
RooAddModel is an efficient implementation of a sum of PDFs of the form.
Definition RooAddModel.h:26
static TClass * Class()
Efficient implementation of a sum of PDFs of the form.
Definition RooAddPdf.h:32
static TClass * Class()
Calculates the sum of a set of RooAbsReal terms, or when constructed with two sets,...
Definition RooAddition.h:27
RooArgList is a container object that can hold multiple RooAbsArg objects.
Definition RooArgList.h:22
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:24
The RooBinSamplingPdf is supposed to be used as an adapter between a continuous PDF and a binned dist...
static TClass * Class()
double epsilon() const
const RooAbsPdf & pdf() const
const RooAbsReal & observable() const
Returns the bin width (or volume) given a RooHistFunc.
const RooHistFunc & histFunc() const
static TClass * Class()
Represents a constant real-valued object.
Definition RooConstVar.h:23
PDF implementing the generalized Asymmetrical Double-Sided Crystall Ball line shape.
static TClass * Class()
Container class to hold N-dimensional binned data.
Definition RooDataHist.h:40
double const * weightArray() const
const RooArgSet * get() const override
Get bin centre of current bin.
Definition RooDataHist.h:82
Single or double sided decay function that can be analytically convolved with any RooResolutionModel ...
Definition RooDecay.h:22
static TClass * Class()
Represents the first, second, or third order derivative of any RooAbsReal as calculated (numerically)...
static TClass * Class()
Exponential PDF.
static TClass * Class()
RooExtendPdf is a wrapper around an existing PDF that adds a parameteric extended likelihood term to ...
static TClass * Class()
PDF for the numerical (FFT) convolution of two PDFs.
static TClass * Class()
std::string val() const
void fill_mat(Matrix const &mat)
bool has_child(std::string const &) const
static TClass * Class()
Class RooGaussModel implements a RooResolutionModel that models a Gaussian distribution.
static TClass * Class()
static TClass * Class()
A real-valued function sampled from a multidimensional histogram.
Definition RooHistFunc.h:29
static TClass * Class()
static TClass * Class()
When using RooFit, statistical models can be conveniently handled and stored as a RooWorkspace.
static void fillSeq(RooFit::Detail::JSONNode &node, RooAbsCollection const &coll, size_t nMax=-1)
T * requestArg(const RooFit::Detail::JSONNode &node, const std::string &key)
T * request(const std::string &objname, const std::string &requestAuthor)
Obj_t & wsImport(Obj_t const &obj)
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 void exportHisto(RooArgSet const &vars, std::size_t n, double const *contents, RooFit::Detail::JSONNode &output)
Export histogram data to a JSONNode.
void queueExport(RooAbsArg const &arg)
RooArgSet requestArgSet(const RooFit::Detail::JSONNode &node, const std::string &seqName)
RooArgList requestArgList(const RooFit::Detail::JSONNode &node, const std::string &seqName)
std::string exportTransformed(const RooAbsReal *original, const std::string &suffix, const std::string &formula)
static void exportAxis(RooFit::Detail::JSONNode &obsNode, RooRealVar const &var)
Export the name and binning of a RooRealVar to a JSONNode.
static void error(const char *s)
Writes an error message to the RooFit message service and throws a runtime_error.
Obj_t & wsEmplace(RooStringView name, Args_t &&...args)
static std::string name(const RooFit::Detail::JSONNode &n)
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.
static TClass * Class()
RooFit Lognormal PDF.
static TClass * Class()
Multivariate Gaussian p.d.f.
static TClass * Class()
Holds the configuration parameters of the various numeric integrators used by RooRealIntegral.
Poisson pdf.
Definition RooPoisson.h:18
static TClass * Class()
static TClass * Class()
static TClass * Class()
Efficient implementation of a product of PDFs of the form.
Definition RooProdPdf.h:35
Represents the product of a given set of RooAbsReal objects.
Definition RooProduct.h:29
Performs hybrid numerical/analytical integrals of RooAbsReal objects.
static TClass * Class()
static TClass * Class()
Implements a PDF constructed from a sum of functions:
static TClass * Class()
Variable that can be changed from the outside.
Definition RooRealVar.h:37
RooResolutionModel is the base class for PDFs that represent a resolution model that can be convolute...
A RooFit class for creating spline functions.
Definition RooSpline.h:27
static TClass * Class()
Use TF1, TF2, TF3 functions as RooFit objects.
static TClass * Class()
Implements a RooResolution model that corresponds to a delta function.
static TClass * Class()
The RooWrapperPdf is a class that can be used to convert a function into a PDF.
static TClass * Class()
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
static Bool_t IsScientificNotation(const TString &formula, int ipos)
Definition TFormula.cxx:383
TMatrixTBase< Element > & ResizeTo(Int_t nrows, Int_t ncols, Int_t=-1) override
Set size of the matrix to nrows x ncols New dynamic elements are created, the overlapping part of the...
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
Base class for spline implementation containing the Draw/Paint methods.
Definition TSpline.h:31
virtual void GetKnot(Int_t i, Double_t &x, Double_t &y) const =0
virtual Int_t GetNp() const
Definition TSpline.h:60
Basic string class.
Definition TString.h:137
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
const Double_t sigma
Double_t x[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
static bool registerImporter(const std::string &key, bool topPriority=true)
Definition JSONIO.h:85
bool registerImporter(const std::string &key, std::unique_ptr< const Importer > f, bool topPriority=true)
Definition JSONIO.cxx:122
static bool registerExporter(const TClass *key, bool topPriority=true)
Definition JSONIO.h:90
bool registerExporter(const TClass *key, std::unique_ptr< const Exporter > f, bool topPriority=true)
Definition JSONIO.cxx:129
#define STATIC_EXECUTE(MY_FUNC)
TMarker m
Definition textangle.C:8