Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
JSONFactories_HistFactory.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#include <RooFitHS3/JSONIO.h>
16
21#include <RooConstVar.h>
22#include <RooRealVar.h>
23#include <RooDataHist.h>
24#include <RooHistFunc.h>
25#include <RooRealSumPdf.h>
26#include <RooBinWidthFunction.h>
27#include <RooProdPdf.h>
28#include <RooPoisson.h>
29#include <RooFormulaVar.h>
30#include <RooLognormal.h>
31#include <RooGaussian.h>
32#include <RooProduct.h>
33#include <RooWorkspace.h>
34#include <RooFitImplHelpers.h>
35
36#include <algorithm>
37#include <cctype>
38#include <charconv>
39#include <cmath>
40#include <iterator>
41#include <limits>
42#include <map>
43#include <optional>
44#include <regex>
45#include <set>
46#include <sstream>
47#include <variant>
48
49#include "static_execute.h"
50#include "HS3Builder.h"
51#include "JSONIOUtils.h"
52
59
60namespace HS3 = RooFit::HS3;
61
62using namespace RooStats::HistFactory;
63using namespace RooStats::HistFactory::Detail;
65
66namespace {
67
68double round_prec(double d, int nSig)
69{
70 if (d == 0.0)
71 return 0.0;
72 int ndigits = std::floor(std::log10(std::abs(d))) + 1 - nSig;
73 double sf = std::pow(10, ndigits);
74 if (std::abs(d / sf) < 2)
75 ndigits--;
76 return sf * std::round(d / sf);
77}
78
79std::string interpolationString(const Interpolation &interpolation)
80{
81 std::stringstream ss;
82 ss << R"({"type":")" << toString(interpolation.type) << R"(","in":")" << toString(interpolation.in) << R"(","out":)";
83 if (interpolation.out) {
84 ss << '"' << toString(*interpolation.out) << '"';
85 } else {
86 ss << "null";
87 }
88 ss << '}';
89 return ss.str();
90}
91
92const char *interpolationClassName(InterpolationClass interpolationClass)
93{
94 return interpolationClass == InterpolationClass::Piecewise ? "PiecewiseInterpolation" : "FlexibleInterpVar";
95}
96
97Interpolation interpolationFromCode(int code, InterpolationClass interpolationClass, const std::string &context)
98{
99 if (auto interpolation = HS3::interpolationFromCode(code, interpolationClass)) {
100 return *interpolation;
101 }
102 RooJSONFactoryWSTool::error(context + " has unsupported " + interpolationClassName(interpolationClass) + " code " +
103 std::to_string(code));
104}
105
106int codeFromInterpolation(const Interpolation &interpolation, InterpolationClass interpolationClass,
107 const std::string &context)
108{
109 if (auto code = HS3::codeFromInterpolation(interpolation, interpolationClass)) {
110 return *code;
111 }
112 RooJSONFactoryWSTool::error(context + " " + interpolationString(interpolation) + " cannot be represented by " +
113 interpolationClassName(interpolationClass));
114}
115
116// The interpolations of the parameters of a PiecewiseInterpolation or FlexibleInterpVar.
117std::vector<Interpolation>
118toInterpolations(std::vector<int> const &codes, InterpolationClass interpolationClass, std::string const &context)
119{
120 std::vector<Interpolation> out;
121 out.reserve(codes.size());
122 for (std::size_t i = 0; i < codes.size(); ++i) {
123 out.push_back(
124 interpolationFromCode(codes[i], interpolationClass, context + " at parameter index " + std::to_string(i)));
125 }
126 return out;
127}
128
129std::vector<int> toCodes(std::vector<Interpolation> const &interpolations, InterpolationClass interpolationClass,
130 std::string const &context)
131{
132 std::vector<int> out;
133 out.reserve(interpolations.size());
134 for (std::size_t i = 0; i < interpolations.size(); ++i) {
135 out.push_back(codeFromInterpolation(interpolations[i], interpolationClass,
136 context + " component 'interpolations' at index " + std::to_string(i)));
137 }
138 return out;
139}
140
141// Reports the problems with an object that doesn't come with a document.
142void report(std::string const &name, HS3::Diagnostics &diagnostics)
143{
144 for (auto &diagnostic : diagnostics) {
145 diagnostic.path = name + (diagnostic.path.empty() ? "" : "." + diagnostic.path);
146 }
147 HS3::report(diagnostics);
148}
149
150int interpolationCode(std::optional<Interpolation> const &interpolation,
151 std::optional<Interpolation> const &defaultInterpolation, InterpolationClass interpolationClass,
152 std::string const &context)
153{
154 if (interpolation) {
155 return codeFromInterpolation(*interpolation, interpolationClass, context);
156 }
157 // HS3 forbids implementation-specific default interpolations.
158 if (!defaultInterpolation) {
159 RooJSONFactoryWSTool::error(context + " is not given, and the channel has no \"default_interpolation\"");
160 }
161 return codeFromInterpolation(*defaultInterpolation, interpolationClass, context);
162}
163
164void erasePrefix(std::string &str, std::string_view prefix)
165{
166 if (startsWith(str, prefix)) {
167 str.erase(0, prefix.size());
168 }
169}
170
171bool eraseSuffix(std::string &str, std::string_view suffix)
172{
173 if (endsWith(str, suffix)) {
174 str.erase(str.size() - suffix.size());
175 return true;
176 } else {
177 return false;
178 }
179}
180
181template <class Coll>
182void sortByName(Coll &coll)
183{
184 std::sort(coll.begin(), coll.end(), [](auto &l, auto &r) { return l.name < r.name; });
185}
186
187template <class T>
188T *findClient(RooAbsArg *gamma)
189{
190 for (const auto &client : gamma->clients()) {
191 if (auto casted = dynamic_cast<T *>(client)) {
192 return casted;
193 } else {
194 T *c = findClient<T>(client);
195 if (c)
196 return c;
197 }
198 }
199 return nullptr;
200}
201
202RooAbsPdf *findConstraint(RooAbsArg *g)
203{
204 if (!g)
205 return nullptr;
206 if (auto *constraint = findClient<RooPoisson>(g))
207 return constraint;
208 if (auto *constraint = findClient<RooGaussian>(g))
209 return constraint;
210 return findClient<RooLognormal>(g);
211}
212
213inline std::string defaultGammaName(std::string const &sysname, std::size_t i)
214{
215 return "gamma_" + sysname + "_bin_" + std::to_string(i);
216}
217
218RooRealVar &createNominal(RooWorkspace &ws, std::string const &parname, double val, double min, double max)
219{
220 RooRealVar &nom = getOrCreate<RooRealVar>(ws, "nom_" + parname, val, min, max);
221 nom.setConstant(true);
222 return nom;
223}
224
225/// Get the conventional name of the constraint pdf for a constrained
226/// parameter.
227std::string constraintName(std::string const &paramName)
228{
229 return paramName + "Constraint";
230}
231
232RooAbsPdf *findNamedConstraint(RooJSONFactoryWSTool &tool, std::string const &constraintName, std::string const &sample)
233{
234 if (auto *constraint = tool.workspace()->pdf(constraintName)) {
235 return constraint;
236 }
237
238 try {
239 return tool.request<RooAbsPdf>(constraintName, sample);
241 if (err.child() != constraintName) {
242 throw;
243 }
244 }
245
246 return nullptr;
247}
248
249// The parameter of a modifier, which is created if the document doesn't define it. It's a variable, or a constant if
250// the document doesn't give it a domain.
251RooAbsReal &getOrCreateParameter(RooJSONFactoryWSTool &tool, std::string const &name, double val, double min,
252 double max, std::string const &modifier)
253{
254 RooWorkspace &ws = *tool.workspace();
255 TObject *existing = ws.obj(name);
256 if (!existing) {
257 return getOrCreate<RooRealVar>(ws, name, val, min, max);
258 }
259 if (dynamic_cast<RooRealVar *>(existing) || dynamic_cast<RooConstVar *>(existing)) {
260 return *static_cast<RooAbsReal *>(existing);
261 }
262 RooJSONFactoryWSTool::error("the parameter '" + name + "' of the modifier '" + modifier +
263 "' is neither a variable nor a constant");
264}
265
266void setErrorIfVariable(RooAbsReal &param, double error)
267{
268 if (auto *var = dynamic_cast<RooRealVar *>(&param)) {
269 var->setError(error);
270 }
271}
272
273// All constraints of parameters that control a whole sample are created here. Following the HS3 standard, the nominal
274// value is defined by the type of the modifier, and the standard deviation is one.
275void addConstraint(RooJSONFactoryWSTool &tool, HS3::Constraint const &constraint, RooAbsReal &param, double nominal,
276 std::string const &modifier, std::string const &sample, RooArgSet &constraints)
277{
278 RooWorkspace &ws = *tool.workspace();
279 std::visit(HS3::overloaded{
280 [](HS3::Unconstrained) {},
282 if (type != HS3::ConstraintType::Gauss) {
283 RooJSONFactoryWSTool::error(std::string("legacy constraint value '") + toString(type) +
284 "' for modifier '" + modifier +
285 "' is a known constraint type, but it cannot be resolved in this "
286 "context");
287 }
288 RooRealVar &nom = createNominal(ws, param.GetName(), nominal, -10, 10);
289 setErrorIfVariable(param, 1.0);
290 const std::string name = constraintName(param.GetName());
291 RooAbsPdf *pdf = ws.pdf(name);
292 constraints.add(pdf ? *pdf : tool.wsEmplace<RooGaussian>(name, param, nom, 1.));
293 },
294 [&](HS3::ConstraintPdf const &constraintPdf) {
295 RooAbsPdf *pdf = findNamedConstraint(tool, constraintPdf.pdf.name, sample);
296 if (!pdf) {
297 RooJSONFactoryWSTool::error("unable to find definition of of constraint '" +
298 constraintPdf.pdf.name + "' for modifier '" + modifier + "'");
299 }
300 if (auto gauss = dynamic_cast<RooGaussian *>(pdf)) {
301 setErrorIfVariable(param, gauss->getSigma().getVal());
302 }
303 constraints.add(*pdf);
304 },
305 },
306 constraint);
307}
308
309// Create the ParamHistFunc of the gamma parameters, together with their constraints.
310ParamHistFunc &createPHF(RooJSONFactoryWSTool &tool, std::string const &phfname, std::string const &sysname,
311 std::vector<HS3::ParameterRef> const &parnames, std::vector<double> const &vals,
312 HS3::GammaConstraint const &constraint, RooArgSet &constraints, RooArgSet const &observables,
313 double gammaMax, double minSigma, std::size_t nBins)
314{
315 RooWorkspace &ws = *tool.workspace();
316
317 // The validation made sure that there is a value, parameter and constraint for each bin, if they are given.
318 RooArgList gammas;
319 for (std::size_t i = 0; i < nBins; ++i) {
320 const std::string name = parnames.empty() ? defaultGammaName(sysname, i) : parnames[i].name;
321 if (auto *existing = dynamic_cast<RooAbsReal *>(ws.obj(name))) {
322 gammas.add(*existing);
323 } else {
324 gammas.add(getOrCreate<RooRealVar>(ws, name, 1., defaultGammaMin, gammaMax));
325 }
326 }
327
328 // Shape modifiers of the same name in a channel share the function, so they need the same parameters.
329 if (RooAbsArg *existing = ws.arg(phfname.c_str())) {
330 auto *existingPHF = dynamic_cast<ParamHistFunc *>(existing);
331 bool same = existingPHF && existingPHF->paramList().size() == gammas.size();
332 for (std::size_t i = 0; same && i < gammas.size(); ++i) {
333 same = &existingPHF->paramList()[i] == &gammas[i];
334 }
335 if (!same) {
336 RooJSONFactoryWSTool::error("the modifier '" + sysname + "' has other parameters than another modifier " +
337 "of this name in the channel");
338 }
339 }
340 auto &phf = tool.wsEmplace<ParamHistFunc>(phfname, observables, gammas);
341
342 auto addGammaConstraints = [&](Constraint::Type type) {
343 for (auto const &term : createGammaConstraints(gammas, vals, minSigma, type).constraints) {
345 constraints.add(*ws.pdf(term->GetName()));
346 }
347 };
348
349 if (auto const *pdfs = std::get_if<HS3::GammaConstraintPdfs>(&constraint)) {
350 for (auto const &pdfName : pdfs->pdfs) {
351 if (!pdfName) {
352 continue;
353 }
354 auto *pdf = findNamedConstraint(tool, pdfName->name, sysname);
355 if (!pdf) {
356 RooJSONFactoryWSTool::error("unable to find definition of constraint '" + pdfName->name +
357 "' for modifier '" + sysname + "'");
358 }
359 constraints.add(*pdf);
360 }
361 }
362
363 if (vals.empty()) {
364 return phf;
365 }
366
367 std::visit(HS3::overloaded{
368 [&](HS3::Unconstrained) { configureConstrainedGammas(gammas, vals, minSigma); },
369 [&](HS3::GammaConstraintPdfs const &) { configureConstrainedGammas(gammas, vals, minSigma); },
371 for (auto *gamma : static_range_cast<RooRealVar *>(gammas)) {
372 gamma->setConstant(true);
373 }
374 },
376 switch (type) {
377 case HS3::ConstraintType::Gauss: addGammaConstraints(Constraint::Gaussian); break;
378 case HS3::ConstraintType::Poisson: addGammaConstraints(Constraint::Poisson); break;
379 case HS3::ConstraintType::LogNormal:
380 RooJSONFactoryWSTool::error(std::string("unsupported constraint '") + toString(type) +
381 "' for the gamma parameters of '" + sysname +
382 "', supported are \"Poisson\" and \"Gauss\"");
383 }
384 },
385 },
386 constraint);
387
388 return phf;
389}
390
391double poissonTau(RooPoisson const &constraint, RooAbsArg const &gamma)
392{
393 auto const *mean = dynamic_cast<RooProduct const *>(&constraint.getMean());
394 if (!mean) {
395 RooJSONFactoryWSTool::error("Poisson gamma constraint mean is not a RooProduct: " +
396 std::string(constraint.GetName()));
397 }
398
399 for (RooAbsArg *arg : mean->servers()) {
400 if (arg == &gamma) {
401 continue;
402 }
403
404 if (auto const *tau = dynamic_cast<RooConstVar const *>(arg)) {
405 return tau->getVal();
406 }
407
408 // Imported workspaces can sometimes represent
409 // constants as constant RooRealVars.
410 if (auto const *real = dynamic_cast<RooAbsReal const *>(arg)) {
411 if (real->isConstant() || endsWith(std::string(real->GetName()), "_tau")) {
412 return real->getVal();
413 }
414 }
415 }
416
417 RooJSONFactoryWSTool::error("Could not find tau component in Poisson gamma constraint mean: " +
418 std::string(constraint.GetName()));
419 return std::numeric_limits<double>::quiet_NaN();
420}
421
422// Returns the relative uncertainty encoded by a gamma constraint pdf. Only RooPoisson (via its tau) and RooGaussian
423// (via sigma/mean) are supported; anything else raises an error.
424double constraintRelError(RooAbsPdf const &constraint, RooAbsArg const &gamma)
425{
426 if (auto constraintP = dynamic_cast<RooPoisson const *>(&constraint)) {
427 return 1. / std::sqrt(poissonTau(*constraintP, gamma));
428 }
429 if (auto constraintG = dynamic_cast<RooGaussian const *>(&constraint)) {
430 return constraintG->getSigma().getVal() / constraintG->getMean().getVal();
431 }
432 RooJSONFactoryWSTool::error("currently, only RooPoisson and RooGaussian are supported as constraint types");
433 return std::numeric_limits<double>::quiet_NaN();
434}
435
436struct ChannelContext {
439 RooArgSet const &observables;
440 std::string fprefix;
441 std::string channelName;
442 std::size_t nBins;
443 RooAbsArg const *mcStatObject;
444 RooArgSet &constraints;
445};
446
447// Collects what the modifiers of a sample contribute to the RooFit objects.
448struct SampleElements {
449 RooArgList shapeElems;
450 RooArgList normElems;
451
452 RooArgList overallNps;
453 std::vector<double> overallLow;
454 std::vector<double> overallHigh;
455 std::vector<int> overallInterp;
456
457 RooArgList histNps;
458 RooArgList histoLo;
459 RooArgList histoHi;
460 std::vector<int> histoInterp;
461};
462
463void buildSample(ChannelContext const &ctx, HS3::Sample const &sample, RooDataHist &dh)
464{
465 RooJSONFactoryWSTool &tool = ctx.tool;
466 RooWorkspace &ws = *tool.workspace();
467
468 std::string const &sampleName = sample.name;
469 std::string prefixedName = ctx.fprefix + "_" + sampleName;
470
471 // The objects of a sample are not shared, so an existing object of the same name is a name clash.
472 auto newName = [&](std::string name) {
473 if (ws.arg(name.c_str())) {
474 RooJSONFactoryWSTool::error("the object '" + name + "' for sample '" + sampleName + "' of channel '" +
475 ctx.channelName + "' has the name of an existing object");
476 }
477 return name;
478 };
479
480 auto &hf = tool.wsEmplace<RooHistFunc>(newName("hist_" + prefixedName), ctx.observables, dh);
481 hf.SetTitle(sampleName.c_str());
482
483 SampleElements elems;
484 elems.shapeElems.add(tool.wsEmplace<RooBinWidthFunction>(newName(prefixedName + "_binWidth"), hf, true));
485
486 const bool hasStatError = std::any_of(sample.modifiers.begin(), sample.modifiers.end(),
487 [](auto const &m) { return std::holds_alternative<HS3::StatError>(m); });
488 if (hasStatError) {
489 elems.shapeElems.add(*ctx.mcStatObject);
490 }
491
492 auto context = [&](const char *type, std::string const &name) {
493 return std::string("interpolation for ") + type + " modifier '" + name + "' in sample '" + sampleName +
494 "' of channel '" + ctx.channelName + "'";
495 };
496
497 for (HS3::Modifier const &modifier : sample.modifiers) {
498 std::visit(
500 [&](HS3::StatError const &) {
501 // this is dealt with at a different place, ignore it for now
502 },
503 [&](HS3::NormFactor const &mod) {
504 RooAbsReal &param = getOrCreateParameter(tool, mod.parameter.name, 1., -3, 5, mod.name);
505 setErrorIfVariable(param, 0.0);
506 elems.normElems.add(param);
507 // for norm factors, constraints are optional
508 addConstraint(tool, mod.constraint, param, 1.0, mod.name, sampleName, ctx.constraints);
509 },
510 [&](HS3::NormSys const &mod) {
511 RooAbsReal &param = getOrCreateParameter(tool, mod.parameter.name, 0., -5, 5, mod.name);
512 elems.overallNps.add(param);
513 const int interp = interpolationCode(mod.interpolation, ctx.dist.defaultInterpolation,
514 InterpolationClass::Flexible, context("normsys", mod.name));
515 double low = mod.lo;
516 double high = mod.hi;
517
518 // the below contains a a hack to cut off variations that go below 0
519 // This is needed because FlexibleInterpVar code 4 interpolates in log-space. Hence, values <= 0 result
520 // in NaN, which propagates throughout the model and causes evaluations to fail. If you know a nicer way
521 // to solve this, please go ahead and fix the lines below.
522 if (interp == 4 && low <= 0)
523 low = std::numeric_limits<double>::epsilon();
524 if (interp == 4 && high <= 0)
525 high = std::numeric_limits<double>::epsilon();
526
527 elems.overallLow.push_back(low);
528 elems.overallHigh.push_back(high);
529 elems.overallInterp.push_back(interp);
530
531 addConstraint(tool, mod.constraint, param, 0.0, mod.name, sampleName, ctx.constraints);
532 },
533 [&](HS3::HistoSys const &mod) {
534 RooAbsReal &param = getOrCreateParameter(tool, mod.parameter.name, 0., -5, 5, mod.name);
535 elems.histNps.add(param);
536 auto addVariation = [&](RooArgList &list, HS3::HistoSysVariation const &variation, const char *suffix) {
537 const std::string name = newName(mod.name + suffix + prefixedName);
538 list.add(
539 tool.wsEmplace<RooHistFunc>(name, ctx.observables,
540 HS3::createDataHist(name, ctx.observables, variation.contents,
541 variation.errors ? &*variation.errors : nullptr)));
542 };
543 addVariation(elems.histoLo, mod.lo, "Low_");
544 addVariation(elems.histoHi, mod.hi, "High_");
545 elems.histoInterp.push_back(interpolationCode(mod.interpolation, ctx.dist.defaultInterpolation,
546 InterpolationClass::Piecewise,
547 context("histosys", mod.name)));
548 addConstraint(tool, mod.constraint, param, 0.0, mod.name, sampleName, ctx.constraints);
549 },
550 [&](HS3::ShapeSys const &mod) {
551 const bool constrained = std::visit(
552 HS3::overloaded{[](HS3::Unconstrained) { return false; }, [](HS3::ConstantGammas) { return false; },
553 [&](HS3::ConstraintType) { return !mod.vals.empty(); },
554 [](HS3::GammaConstraintPdfs const &constraints) {
555 return std::any_of(constraints.pdfs.begin(), constraints.pdfs.end(),
556 [](auto const &pdf) { return pdf.has_value(); });
557 }},
558 mod.constraint);
559 // Follow the naming of HistFactory: "<channel>_<name>_ShapeSys" for constrained and
560 // "<channel>_<name>_shapeFactor" for free parameters.
561 const std::string funcName =
562 ctx.channelName + "_" + mod.name + (constrained ? "_ShapeSys" : "_shapeFactor");
563 elems.shapeElems.add(createPHF(tool, funcName, mod.name, mod.parameters, mod.vals, mod.constraint,
564 ctx.constraints, ctx.observables, defaultShapeSysGammaMax,
565 minShapeUncertainty, ctx.nBins));
566 },
567 [&](HS3::CustomModifier const &mod) {
568 // The function can come later in the document.
569 RooAbsReal *obj = tool.request<RooAbsReal>(mod.name, ctx.channelName);
570 if (obj->dependsOn(ctx.observables)) {
571 elems.shapeElems.add(*obj);
572 } else {
573 elems.normElems.add(*obj);
574 }
575 },
576 },
577 modifier);
578 }
579
580 if (!elems.overallNps.empty()) {
581 std::string interpName = newName(sampleName + "_" + ctx.channelName + "_epsilon");
583 interpName, elems.overallNps, 1., elems.overallLow, elems.overallHigh, elems.overallInterp));
584 }
585 if (!elems.histNps.empty()) {
586 auto &v = tool.wsEmplace<PiecewiseInterpolation>(newName("histoSys_" + prefixedName), hf, elems.histoLo,
587 elems.histoHi, elems.histNps, elems.histoInterp);
589 elems.shapeElems.add(v);
590 } else {
591 elems.shapeElems.add(hf);
592 }
593
594 tool.wsEmplace<RooProduct>(newName(prefixedName + "_shapes"), elems.shapeElems);
595 if (!elems.normElems.empty()) {
596 tool.wsEmplace<RooProduct>(newName(prefixedName + "_scaleFactors"), elems.normElems);
597 } else {
598 ws.factory("RooConstVar::" + newName(prefixedName + "_scaleFactors") + "(1.)");
599 }
600}
601
602// The constraint of the gamma parameters of the staterror modifiers. Without one, the gamma parameters are free.
603HS3::GammaConstraint statErrorConstraint(HS3::HistFactoryDist const &dist)
604{
605 // The constraint distributions named by a staterror modifier take precedence over a constraint type, and a type
606 // given on a staterror modifier over the channel-level setting.
607 HS3::GammaConstraint const *modifierConstraint = nullptr;
608 for (auto const &sample : dist.samples) {
609 for (auto const &modifier : sample.modifiers) {
610 auto const *statError = std::get_if<HS3::StatError>(&modifier);
611 if (!statError) {
612 continue;
613 }
614 if (std::holds_alternative<HS3::GammaConstraintPdfs>(statError->constraint)) {
615 return statError->constraint;
616 }
617 if (!modifierConstraint && !std::holds_alternative<HS3::Unconstrained>(statError->constraint)) {
618 modifierConstraint = &statError->constraint;
619 }
620 }
621 }
622 return modifierConstraint ? *modifierConstraint : dist.statError.constraint;
623}
624
625// Reject constraints of staterror modifiers that make no sense for gamma parameters.
626void checkStatErrorConstraints(HS3::HistFactoryDist const &dist, std::string const &name)
627{
628 for (auto const &sample : dist.samples) {
629 for (auto const &modifier : sample.modifiers) {
630 auto const *statError = std::get_if<HS3::StatError>(&modifier);
631 auto const *type = statError ? std::get_if<HS3::ConstraintType>(&statError->constraint) : nullptr;
632 if (type && *type == HS3::ConstraintType::LogNormal) {
633 RooJSONFactoryWSTool::error(std::string("unsupported constraint '") + toString(*type) +
634 "' of the staterror modifier in channel '" + name +
635 "', supported are \"Poisson\" and \"Gauss\"");
636 }
637 }
638 }
639}
640
641class HistFactoryImporter : public RooFit::JSONIO::Importer {
642public:
643 // This is only used for HistFactory distributions that don't come with a document.
644 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
645 {
646 RooFit::HS3::importHistFactoryNode(*tool, p, nullptr);
647 return true;
648 }
649};
650
651class FlexibleInterpVarStreamer : public RooFit::JSONIO::Exporter {
652public:
653 std::string const &key() const override
654 {
655 static const std::string keystring = "interpolation0d";
656 return keystring;
657 }
658 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
659 {
660 auto fip = static_cast<const RooStats::HistFactory::FlexibleInterpVar *>(func);
661 const std::string context = "FlexibleInterpVar '" + std::string{fip->GetName()} + "'";
662 const std::size_t nParameters = fip->variables().size();
663 if (fip->low().size() != nParameters || fip->high().size() != nParameters ||
664 fip->interpolationCodes().size() != nParameters) {
665 RooJSONFactoryWSTool::error(context + " has non-matching parameter, variation, and interpolation lengths");
666 }
668 for (RooAbsArg const *var : fip->variables()) {
669 out.parameters.push_back(var->GetName());
670 }
671 out.nominal = fip->nominal();
672 out.high = fip->high();
673 out.low = fip->low();
674 out.interpolations = toInterpolations(fip->interpolationCodes(), InterpolationClass::Flexible, context);
676 return true;
677 }
678};
679
680class PiecewiseInterpolationStreamer : public RooFit::JSONIO::Exporter {
681public:
682 std::string const &key() const override
683 {
684 static const std::string keystring = "interpolation";
685 return keystring;
686 }
687 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
688 {
689 const PiecewiseInterpolation *pip = static_cast<const PiecewiseInterpolation *>(func);
690 const std::string context = "PiecewiseInterpolation '" + std::string{pip->GetName()} + "'";
691 const std::size_t nParameters = pip->paramList().size();
692 if (pip->lowList().size() != nParameters || pip->highList().size() != nParameters ||
693 pip->interpolationCodes().size() != nParameters) {
694 RooJSONFactoryWSTool::error(context + " has non-matching parameter, variation, and interpolation lengths");
695 }
697 out.nominal = pip->nominalHist()->GetName();
698 for (std::size_t i = 0; i < nParameters; ++i) {
699 out.parameters.push_back(HS3::toNameOrNumber(pip->paramList()[i]));
700 out.high.push_back(HS3::toNameOrNumber(pip->highList()[i]));
701 out.low.push_back(HS3::toNameOrNumber(pip->lowList()[i]));
702 }
703 out.interpolations = toInterpolations(pip->interpolationCodes(), InterpolationClass::Piecewise, context);
706 return true;
707 }
708};
709
710class PiecewiseInterpolationFactory : public RooFit::JSONIO::Importer {
711public:
712 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
713 {
714 std::string name(RooJSONFactoryWSTool::name(p));
715 HS3::Diagnostics diagnostics;
717 report(name, diagnostics);
718
719 auto request = [&](std::vector<HS3::NameOrNumber> const &refs) {
720 RooArgList out;
721 for (auto const &ref : refs) {
722 auto const *value = std::get_if<double>(&ref);
723 out.add(value ? RooFit::RooConst(*value) : *tool->request<RooAbsReal>(std::get<std::string>(ref), name));
724 }
725 return out;
726 };
727 RooArgList vars{request(in.parameters)};
728 RooArgList low{request(in.low)};
729 RooArgList high{request(in.high)};
730 auto &pip = tool->wsEmplace<PiecewiseInterpolation>(
731 name, *tool->request<RooAbsReal>(in.nominal, name), low, high, vars,
732 toCodes(in.interpolations, InterpolationClass::Piecewise, "PiecewiseInterpolation '" + name + "'"));
734 return true;
735 }
736};
737
738class FlexibleInterpVarFactory : public RooFit::JSONIO::Importer {
739public:
740 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
741 {
742 std::string name(RooJSONFactoryWSTool::name(p));
743 HS3::Diagnostics diagnostics;
745 report(name, diagnostics);
746
747 RooArgList vars;
748 for (std::string const &var : in.parameters) {
749 vars.add(*tool->request<RooRealVar>(var, name));
750 }
752 name, vars, in.nominal, in.low, in.high,
753 toCodes(in.interpolations, InterpolationClass::Flexible, "FlexibleInterpVar '" + name + "'"));
754 return true;
755 }
756};
757
758std::string stripOuterParens(const std::string &s)
759{
760 size_t start = 0;
761 size_t end = s.size();
762
763 while (start < end && s[start] == '(' && s[end - 1] == ')') {
764 int depth = 0;
765 bool balanced = true;
766 for (size_t i = start; i < end - 1; ++i) {
767 if (s[i] == '(') {
768 ++depth;
769 } else if (s[i] == ')') {
770 --depth;
771 }
772 if (depth == 0 && i < end - 1) {
773 balanced = false;
774 break;
775 }
776 }
777 if (balanced) {
778 ++start;
779 --end;
780 } else {
781 break;
782 }
783 }
784 return s.substr(start, end - start);
785}
786
787/// Whether the `+` or `-` at position `i` of `expr` is a binary operator, i.e. not a sign and not part of the exponent
788/// of a number like `1e-5`.
789bool isBinaryOperator(const std::string &expr, std::size_t i)
790{
791 std::size_t j = i;
792 while (j > 0 && std::isspace(static_cast<unsigned char>(expr[j - 1]))) {
793 --j;
794 }
795 if (j == 0) {
796 return false;
797 }
798 const char prev = expr[j - 1];
799 if (prev == 'e' || prev == 'E') {
800 // An exponent if the "e" directly follows the digits of a number, which don't start a name.
801 std::size_t k = j - 1;
802 while (k > 0 && (std::isdigit(static_cast<unsigned char>(expr[k - 1])) || expr[k - 1] == '.')) {
803 --k;
804 }
805 const bool startsName = k > 0 && (std::isalpha(static_cast<unsigned char>(expr[k - 1])) || expr[k - 1] == '_');
806 if (k < j - 1 && !startsName && i == j) {
807 return false;
808 }
809 }
810 return std::isalnum(static_cast<unsigned char>(prev)) || prev == '_' || prev == '.' || prev == ')' || prev == ']';
811}
812
813std::vector<std::string> splitTopLevelProduct(const std::string &expr)
814{
815 std::vector<std::string> parts;
816 int depth = 0;
817 size_t start = 0;
818 bool foundTopLevelStar = false;
819
820 for (size_t i = 0; i < expr.size(); ++i) {
821 char c = expr[i];
822 if (c == '(') {
823 ++depth;
824 } else if (c == ')') {
825 --depth;
826 } else if ((c == '+' || c == '-') && depth == 0 && isBinaryOperator(expr, i)) {
827 return {}; // A sum or difference at the top level is not a product, e.g. "1 + 0.2 * k"
828 } else if (c == '*' && depth == 0) {
829 foundTopLevelStar = true;
830 std::string sub = expr.substr(start, i - start);
831 parts.push_back(stripOuterParens(sub));
832 start = i + 1;
833 }
834 }
835
836 if (!foundTopLevelStar) {
837 return {}; // Not a top-level product
838 }
839
840 std::string sub = expr.substr(start);
841 parts.push_back(stripOuterParens(sub));
842 return parts;
843}
844
845// A normsys that is written as the formula "1 +- a * b", where one of a and b is a constrained parameter.
846struct FormulaNormSys {
847 RooAbsReal *param = nullptr;
848 RooAbsPdf *constraint = nullptr;
849 double variation = 0.0;
850};
851
852FormulaNormSys parseOverallModifierFormula(const std::string &s, RooFormulaVar *formula)
853{
854 static const std::regex pattern(
855 R"(^\s*1(?:\.0)?\s*([\+\-])\s*([a-zA-Z_][a-zA-Z0-9_]*|[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*\*\s*([a-zA-Z_][a-zA-Z0-9_]*|[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*$)");
856
857 FormulaNormSys sys;
858 std::smatch match;
859 if (std::regex_match(s, match, pattern)) {
860 const double sign = match[1].str() == "-" ? -1.0 : 1.0;
861
862 std::string token2 = match[2].str();
863 std::string token3 = match[4].str();
864
865 RooAbsReal *p2 = static_cast<RooAbsReal *>(formula->getParameter(token2.c_str()));
866 RooAbsReal *p3 = static_cast<RooAbsReal *>(formula->getParameter(token3.c_str()));
867 RooRealVar *v2 = dynamic_cast<RooRealVar *>(p2);
868 RooRealVar *v3 = dynamic_cast<RooRealVar *>(p3);
869
870 auto *constr2 = findConstraint(v2);
871 auto *constr3 = findConstraint(v3);
872
873 if (constr2 && !p3) {
874 sys.param = p2;
875 sys.variation = sign * toDouble(token3);
876 } else if (!p2 && constr3) {
877 sys.param = p3;
878 sys.variation = sign * toDouble(token2);
879 } else if (constr2 && p3 && !constr3) {
880 sys.param = v2;
881 sys.variation = sign * p3->getVal();
882 } else if (p2 && !constr2 && constr3) {
883 sys.param = v3;
884 sys.variation = sign * p2->getVal();
885 }
886
887 // A recognized formula has exactly one constrained parameter.
888 sys.constraint = constr2 ? constr2 : constr3;
889 }
890 return sys;
891}
892
893void collectElements(RooArgList &elems, RooAbsArg *arg)
894{
895 if (auto prod = dynamic_cast<RooProduct *>(arg)) {
896 for (const auto &e : prod->components()) {
897 collectElements(elems, e);
898 }
899 } else {
900 elems.add(*arg);
901 }
902}
903
904bool allRooRealVar(const RooAbsCollection &list)
905{
906 for (auto *var : list) {
907 if (!dynamic_cast<RooRealVar *>(var)) {
908 return false;
909 }
910 }
911 return true;
912}
913
914// The parameters of normsys and histosys modifiers must be variables.
915void checkModifierParameter(RooAbsReal const &param, RooAbsPdf const *constraint)
916{
917 if (!constraint && !param.isConstant() && !dynamic_cast<RooRealVar const *>(&param)) {
918 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(param.GetName()) +
919 ": functions are not supported as parameters of this modifier");
920 }
921}
922
923std::vector<double> contents(RooDataHist const &dataHist)
924{
925 return {dataHist.weightArray(), dataHist.weightArray() + dataHist.numEntries()};
926}
927
928// A channel as it is exported, and what the exporter needs to know about its RooFit objects.
929struct ExportedChannel {
930 std::string name;
932 RooArgSet const *observables = nullptr;
933 /// The pdfs that the modifiers name as constraints, by name.
934 std::map<std::string, RooAbsPdf const *> constraints;
935 std::vector<RooAbsReal *> customModifiers;
936 /// Parts of formulas that are exported as custom modifiers. They are created for the export.
937 std::vector<std::unique_ptr<RooAbsReal>> temporaries;
938 // To reconstruct the statistical uncertainties, by bin starting at one.
939 std::map<int, double> totYield;
940 std::map<int, double> totYield2;
941 std::map<int, double> relErrors;
942 // Constant inverse bin widths of HistFactory before ROOT 6.26, which histfactory_dist implies.
943 std::set<RooAbsArg const *> legacyBinWidthFactors;
944};
945
946/// The volume of the first bin and the average bin volume of every histogram of a channel. HistFactory before ROOT
947/// 6.26 took the inverse bin width as the number of bins over the range of each observable.
948std::vector<double> binVolumes(RooRealSumPdf const &sumpdf)
949{
950 RooArgSet nodes;
951 sumpdf.treeNodeServerList(&nodes);
952 std::vector<double> out;
953 for (RooAbsArg *node : nodes) {
954 auto *hf = dynamic_cast<RooHistFunc const *>(node);
955 if (!hf || hf->dataHist().numEntries() == 0) {
956 continue;
957 }
958 out.push_back(hf->dataHist().binVolume(0));
959 double average = 1.;
960 for (RooAbsArg *obs : *hf->dataHist().get()) {
961 if (auto *real = dynamic_cast<RooRealVar const *>(obs)) {
962 average *= (real->getMax() - real->getMin()) / real->numBins();
963 }
964 }
965 out.push_back(average);
966 }
967 return out;
968}
969
970/// Whether a constant factor of a sample is the inverse bin width that HistFactory before ROOT 6.26 multiplied the
971/// samples with (a constant `RooRealVar` `binWidth_<observables>_<i>` in the coefficients of the `RooRealSumPdf`).
972/// `histfactory_dist` implies this factor, so it is not written as a modifier. It is identified by its structure, not
973/// by its name: a constant that is not floating in any snapshot, used only by the sum or the products of the channel,
974/// with the inverse of the volume of the first bin, or of the average bin volume, of one of the histograms of the
975/// channel as value.
976bool isLegacyBinWidthFactor(RooAbsArg const &factor, RooRealSumPdf const &sumpdf, RooArgSet const &channelNodes,
977 std::vector<double> const &volumes, RooWorkspace const *ws)
978{
979 if (!RooJSONFactoryWSTool::config().identifyLegacyBinWidthFactors) {
980 return false;
981 }
982 auto const *var = dynamic_cast<RooRealVar const *>(&factor);
983 if (!dynamic_cast<RooConstVar const *>(&factor) && !(var && var->isConstant())) {
984 return false;
985 }
986 if (var && ws) {
987 for (auto *snapshot : static_range_cast<RooArgSet const *>(ws->getSnapshots())) {
988 auto const *inSnapshot = dynamic_cast<RooRealVar const *>(snapshot->find(*var));
989 if (inSnapshot && !inSnapshot->isConstant()) {
990 return false;
991 }
992 }
993 }
994 for (RooAbsArg const *client : factor.clients()) {
995 const bool isChannelProduct = dynamic_cast<RooProduct const *>(client) && channelNodes.find(*client) == client;
996 if (client != &sumpdf && !isChannelProduct) {
997 return false;
998 }
999 }
1000 // The value was stored with 7 significant digits.
1001 const double value = static_cast<RooAbsReal const &>(factor).getVal();
1002 return std::any_of(volumes.begin(), volumes.end(),
1003 [&](double volume) { return volume > 0. && std::abs(value * volume - 1.) < 1e-6; });
1004}
1005
1006ExportedChannel readChannel(RooJSONFactoryWSTool *tool, const std::string &pdfname, const RooRealSumPdf *sumpdf)
1007{
1008 ExportedChannel channel;
1009
1010 RooWorkspace *ws = tool->workspace();
1011
1012 channel.name = pdfname;
1013 erasePrefix(channel.name, "model_");
1014 eraseSuffix(channel.name, "_model");
1015
1016 RooArgSet channelNodes;
1017 sumpdf->treeNodeServerList(&channelNodes);
1018 const std::vector<double> volumes = binVolumes(*sumpdf);
1019
1020 auto constraint = [&](RooAbsPdf const *pdf) -> HS3::Constraint {
1021 if (!pdf) {
1022 return HS3::Unconstrained{};
1023 }
1024 channel.constraints.emplace(pdf->GetName(), pdf);
1025 return HS3::ConstraintPdf{{pdf->GetName()}};
1026 };
1027 auto gammaConstraint = [&](RooAbsPdf const *pdf) -> std::optional<HS3::DistributionRef> {
1028 if (!pdf) {
1029 return std::nullopt;
1030 }
1031 channel.constraints.emplace(pdf->GetName(), pdf);
1032 return HS3::DistributionRef{pdf->GetName()};
1033 };
1034
1035 for (size_t sampleidx = 0; sampleidx < sumpdf->funcList().size(); ++sampleidx) {
1036 PiecewiseInterpolation *pip = nullptr;
1037 std::vector<ParamHistFunc *> phfs;
1038
1039 const auto func = sumpdf->funcList().at(sampleidx);
1040 HS3::Sample sample;
1041 sample.name = func->GetName();
1042 erasePrefix(sample.name, "L_x_");
1043 eraseSuffix(sample.name, "_shapes");
1044 eraseSuffix(sample.name, "_" + channel.name);
1045 erasePrefix(sample.name, pdfname + "_");
1046
1047 // The modifiers by type, which are written in this order.
1048 std::vector<HS3::NormFactor> normFactors;
1049 std::vector<HS3::NormSys> normSys;
1050 std::vector<HS3::HistoSys> histoSys;
1051 std::vector<HS3::ShapeSys> shapeSys;
1052 std::vector<HS3::CustomModifier> custom;
1053 std::vector<HS3::CustomModifier> temporaries;
1054 std::optional<HS3::StatError> statError;
1055
1056 auto addNormSys = [&](RooAbsReal &param, double hi, double lo, HS3::Interpolation interpolation,
1057 RooAbsPdf const *pdf) {
1058 checkModifierParameter(param, pdf);
1059 std::string sysname(param.GetName());
1060 erasePrefix(sysname, "alpha_");
1061 normSys.push_back({sysname, {param.GetName()}, hi, lo, constraint(pdf), interpolation});
1062 };
1063
1064 auto updateObservables = [&](RooDataHist const &dataHist) {
1065 if (channel.observables == nullptr) {
1066 channel.observables = dataHist.get();
1067 }
1068 if (sample.contents.empty()) {
1069 sample.contents = contents(dataHist);
1070 }
1071 };
1072 auto processElements = [&](const auto &elements, auto &&self) -> void {
1073 for (RooAbsArg *e : elements) {
1074 if (auto *binWidth = dynamic_cast<RooBinWidthFunction *>(e); binWidth && binWidth->divideByBinWidth()) {
1075 // The division by the bin width is implied by histfactory_dist.
1076 } else if (isLegacyBinWidthFactor(*e, *sumpdf, channelNodes, volumes, ws)) {
1077 // Before ROOT 6.26, HistFactory multiplied the samples with a constant inverse bin width instead.
1078 channel.legacyBinWidthFactors.insert(e);
1079 if (!TString(e->GetName()).Contains("binWidth")) {
1080 oocoutW(nullptr, ObjectHandling)
1081 << "RooFitHS3: the constant factor '" << e->GetName() << "' of sample '" << sample.name
1082 << "' in channel '" << channel.name
1083 << "' is used like the inverse bin width of HistFactory before ROOT 6.26 (its value is the "
1084 "inverse bin volume), so it is not written: histfactory_dist implies it. Set "
1085 "RooJSONFactoryWSTool::config().identifyLegacyBinWidthFactors = false to write such factors "
1086 "as normfactors."
1087 << std::endl;
1088 }
1089 } else if (auto constVar = dynamic_cast<RooConstVar *>(e)) {
1090 if (constVar->getVal() != 1.) {
1091 normFactors.push_back({constVar->GetName(), {constVar->GetName()}, HS3::Unconstrained{}});
1092 }
1093 } else if (auto par = dynamic_cast<RooRealVar *>(e)) {
1094 // Every Gaussian of the parameter is a candidate for its constraint.
1095 bool isConstrained = false;
1096 for (RooAbsArg const *pdf : ws->allPdfs()) {
1097 if (auto gauss = dynamic_cast<RooGaussian const *>(pdf)) {
1098 if (std::string(par->GetName()) == gauss->getX().GetName()) {
1099 normFactors.push_back({par->GetName(), {par->GetName()}, constraint(gauss)});
1100 isConstrained = true;
1101 }
1102 }
1103 }
1104 if (!isConstrained) {
1105 normFactors.push_back({par->GetName(), {par->GetName()}, HS3::Unconstrained{}});
1106 }
1107 } else if (auto hf = dynamic_cast<const RooHistFunc *>(e)) {
1108 updateObservables(hf->dataHist());
1109 } else if (ParamHistFunc *phf = dynamic_cast<ParamHistFunc *>(e); phf && allRooRealVar(phf->paramList())) {
1110 phfs.push_back(phf);
1111 } else if (auto fip = dynamic_cast<RooStats::HistFactory::FlexibleInterpVar *>(e)) {
1112 // some (modified) histfactory models have several instances of FlexibleInterpVar
1113 // we collect and merge them
1114 for (size_t i = 0; i < fip->variables().size(); ++i) {
1115 RooAbsReal *var = static_cast<RooAbsReal *>(fip->variables().at(i));
1116 std::string sysname(var->GetName());
1117 erasePrefix(sysname, "alpha_");
1118 const std::string context = "normsys modifier '" + sysname + "' in sample '" + sample.name +
1119 "' of channel '" + channel.name + "'";
1120 // A parameter without constraint is unconstrained, see checkUnconstrainedModifiers().
1121 addNormSys(*var, fip->high()[i], fip->low()[i],
1122 interpolationFromCode(fip->interpolationCodes()[i], InterpolationClass::Flexible, context),
1123 findConstraint(var));
1124 }
1125 } else if (!pip && (pip = dynamic_cast<PiecewiseInterpolation *>(e))) {
1126 // nothing to do here, already assigned
1127 } else if (RooFormulaVar *formula = dynamic_cast<RooFormulaVar *>(e)) {
1128 // people do a lot of fancy stuff with RooFormulaVar, like including NormSys via explicit formulae.
1129 // let's try to decompose it into building blocks
1130 TString expression(formula->expression());
1131 for (size_t i = formula->nParameters(); i--;) {
1132 const RooAbsArg *p = formula->getParameter(i);
1133 expression.ReplaceAll(("x[" + std::to_string(i) + "]").c_str(), p->GetName());
1134 expression.ReplaceAll(("@" + std::to_string(i)).c_str(), p->GetName());
1135 }
1136 auto components = splitTopLevelProduct(expression.Data());
1137 if (components.empty()) {
1138 // it's not a product, let's just treat it as an unknown element
1139 custom.push_back({formula->GetName()});
1140 channel.customModifiers.push_back(formula);
1141 } else {
1142 // it is a prododuct, we can try to handle the elements separately
1143 std::vector<RooAbsArg *> realComponents;
1144 int idx = 0;
1145 for (auto &comp : components) {
1146 // check if this is a trivial element of a product, we can treat it as its own modifier
1147 auto *part = formula->getParameter(comp.c_str());
1148 if (part) {
1149 realComponents.push_back(part);
1150 continue;
1151 }
1152 // check if this is an attempt at explicitly encoding an overallSys
1153 FormulaNormSys sys = parseOverallModifierFormula(comp, formula);
1154 if (sys.param) {
1155 // Preserve the legacy export behaviour for recognized explicit formulae.
1156 addNormSys(*sys.param, 1.0 + sys.variation, 1.0 - sys.variation,
1157 multiplicativePiecewiseExponential, sys.constraint);
1158 continue;
1159 }
1160
1161 // this is something non-trivial, let's deal with it separately
1162 std::string name = std::string(formula->GetName()) + "_part" + std::to_string(idx);
1163 ++idx;
1164 temporaries.push_back({name});
1165 channel.temporaries.push_back(std::make_unique<RooFormulaVar>(
1166 name.c_str(), name.c_str(), comp.c_str(), formula->dependents()));
1167 }
1168 self(realComponents, self);
1169 }
1170 } else if (auto real = dynamic_cast<RooAbsReal *>(e)) {
1171 custom.push_back({real->GetName()});
1172 channel.customModifiers.push_back(real);
1173 }
1174 }
1175 };
1176
1177 RooArgList elems;
1178 collectElements(elems, func);
1179 collectElements(elems, sumpdf->coefList().at(sampleidx));
1180 processElements(elems, processElements);
1181
1182 // see if we can get the observables
1183 if (pip) {
1184 if (auto nh = dynamic_cast<RooHistFunc const *>(pip->nominalHist())) {
1185 updateObservables(nh->dataHist());
1186 }
1187 }
1188
1189 if (pip) {
1190 for (size_t i = 0; i < pip->paramList().size(); ++i) {
1191 RooAbsReal *var = static_cast<RooAbsReal *>(pip->paramList().at(i));
1192 std::string sysname(var->GetName());
1193 erasePrefix(sysname, "alpha_");
1194 if (auto lo = dynamic_cast<RooHistFunc *>(pip->lowList().at(i))) {
1195 if (auto hi = dynamic_cast<RooHistFunc *>(pip->highList().at(i))) {
1196 // A parameter without constraint is unconstrained, see checkUnconstrainedModifiers().
1197 const auto *pdf = findConstraint(var);
1198 checkModifierParameter(*var, pdf);
1199 const std::string context = "histosys modifier '" + sysname + "' in sample '" + sample.name +
1200 "' of channel '" + channel.name + "'";
1201 histoSys.push_back(
1202 {sysname,
1203 {var->GetName()},
1204 {contents(hi->dataHist()), std::nullopt},
1205 {contents(lo->dataHist()), std::nullopt},
1206 constraint(pdf),
1207 interpolationFromCode(pip->interpolationCodes()[i], InterpolationClass::Piecewise, context)});
1208 }
1209 }
1210 }
1211 }
1212
1213 for (ParamHistFunc *phf : phfs) {
1214 if (startsWith(std::string(phf->GetName()), "mc_stat_")) { // MC stat uncertainty
1215 statError.emplace();
1216 statError->name = "staterror";
1218 int idx = 0;
1219 for (const auto &g : phf->paramList()) {
1220 statError->parameters.push_back({g->GetName()});
1221 ++idx;
1222 RooAbsPdf *pdf = findConstraint(g);
1223 pdfs.pdfs.push_back(gammaConstraint(pdf));
1224 channel.totYield[idx] += sample.contents[idx - 1];
1225 channel.totYield2[idx] += (sample.contents[idx - 1] * sample.contents[idx - 1]);
1226 if (pdf) {
1227 channel.relErrors[idx] = constraintRelError(*pdf, *g);
1228 }
1229 }
1230 statError->constraint = std::move(pdfs);
1231 } else { // ShapeSys or ShapeFactor
1232 HS3::ShapeSys &sys = shapeSys.emplace_back();
1233 sys.name = phf->GetName();
1234 erasePrefix(sys.name, channel.name + "_");
1235 for (std::string_view suffix : {"_ShapeSys", "_shapeSys", "_ShapeFactor", "_shapeFactor"}) {
1236 if (eraseSuffix(sys.name, suffix))
1237 break;
1238 }
1239
1240 // Both are written as "shapefactor": the constrained parameters name their constraint distribution in
1241 // "constraints", the others are free.
1243 for (const auto &g : phf->paramList()) {
1244 sys.parameters.push_back({g->GetName()});
1245 RooAbsPdf *pdf = findConstraint(g);
1246 if (!pdf)
1247 pdf = ws->pdf(constraintName(g->GetName()));
1248 pdfs.pdfs.push_back(gammaConstraint(pdf));
1249 }
1250 sys.constraint = std::move(pdfs);
1251 }
1252 }
1253
1254 sortByName(normFactors);
1255 sortByName(normSys);
1256 sortByName(histoSys);
1257 sortByName(shapeSys);
1258 auto append = [&](auto &modifiers) {
1259 sample.modifiers.insert(sample.modifiers.end(), std::make_move_iterator(modifiers.begin()),
1260 std::make_move_iterator(modifiers.end()));
1261 };
1262 append(normFactors);
1263 append(normSys);
1264 append(histoSys);
1265 append(shapeSys);
1266 append(custom);
1267 append(temporaries);
1268 if (statError) {
1269 sample.modifiers.push_back(std::move(*statError));
1270 }
1271
1272 channel.dist.samples.push_back(std::move(sample));
1273 }
1274
1275 sortByName(channel.dist.samples);
1276 return channel;
1277}
1278
1279// The statistical uncertainties of the samples are reconstructed from the constraints of the gamma parameters, with
1280// the (arbitrary) convention that all samples have the same relative uncertainty.
1281void configureStatError(ExportedChannel &channel)
1282{
1283 for (HS3::Sample &sample : channel.dist.samples) {
1284 if (std::none_of(sample.modifiers.begin(), sample.modifiers.end(),
1285 [](auto const &m) { return std::holds_alternative<HS3::StatError>(m); })) {
1286 continue;
1287 }
1288 std::vector<double> errors(sample.contents.size());
1289 for (auto const &bin : channel.relErrors) {
1290 const int i = bin.first;
1291 // this reconstruction is inherently imprecise, so we truncate it at some decimal places to make sure that
1292 // we don't carry around too many useless digits
1293 errors[i - 1] =
1294 round_prec(bin.second * channel.totYield[i] / std::sqrt(channel.totYield2[i]) * sample.contents[i - 1], 7);
1295 }
1296 sample.errors = std::move(errors);
1297 }
1298}
1299
1300// The modifiers name only the constraint terms of the channel. A pdf of a parameter that is not, like an external
1301// constraint or a prior, stays where it is. Returns the named constraint terms.
1302std::set<RooAbsPdf const *>
1303nameChannelConstraints(ExportedChannel &channel, std::vector<RooAbsPdf *> const &constraints)
1304{
1305 std::set<RooAbsPdf const *> named;
1306 auto isNamed = [&](std::string const &name) {
1307 RooAbsPdf const *pdf = channel.constraints.at(name);
1308 if (std::find(constraints.begin(), constraints.end(), pdf) == constraints.end()) {
1309 return false;
1310 }
1311 named.insert(pdf);
1312 return true;
1313 };
1314 auto filter = [&](HS3::Constraint &constraint) {
1315 auto const *pdf = std::get_if<HS3::ConstraintPdf>(&constraint);
1316 if (pdf && !isNamed(pdf->pdf.name)) {
1317 constraint = HS3::Unconstrained{};
1318 }
1319 };
1320 auto filterGammas = [&](HS3::GammaConstraint &constraint) {
1321 auto *pdfs = std::get_if<HS3::GammaConstraintPdfs>(&constraint);
1322 if (!pdfs) {
1323 return;
1324 }
1325 bool any = false;
1326 for (auto &pdf : pdfs->pdfs) {
1327 if (pdf && !isNamed(pdf->name)) {
1328 pdf.reset();
1329 }
1330 any = any || pdf;
1331 }
1332 // Nothing is written if all parameters are unconstrained.
1333 if (!any) {
1334 constraint = HS3::Unconstrained{};
1335 }
1336 };
1337 for (HS3::Sample &sample : channel.dist.samples) {
1338 for (HS3::Modifier &modifier : sample.modifiers) {
1339 std::visit(HS3::overloaded{
1340 [&](HS3::NormFactor &m) { filter(m.constraint); },
1341 [&](HS3::NormSys &m) { filter(m.constraint); }, [&](HS3::HistoSys &m) { filter(m.constraint); },
1342 [&](HS3::ShapeSys &m) { filterGammas(m.constraint); },
1343 [&](HS3::StatError &m) { filterGammas(m.constraint); }, [](HS3::CustomModifier &) {}},
1344 modifier);
1345 }
1346 }
1347 return named;
1348}
1349
1350bool tryExportHistFactory(RooJSONFactoryWSTool *tool, const std::string &pdfname, const RooRealSumPdf *sumpdf,
1351 std::vector<RooAbsPdf *> constraints)
1352{
1353 // some preliminary checks
1354 if (!sumpdf) {
1355 return false;
1356 }
1357
1358 for (RooAbsArg *sample : sumpdf->funcList()) {
1359 if (!dynamic_cast<RooProduct *>(sample) && !dynamic_cast<RooRealSumPdf *>(sample)) {
1360 return false;
1361 }
1362 }
1363
1364 ExportedChannel channel = readChannel(tool, pdfname, sumpdf);
1365 HS3::HistFactoryDist &dist = channel.dist;
1366
1367 // sanity checks
1368 if (dist.samples.empty())
1369 return false;
1370 for (auto &sample : dist.samples) {
1371 if (sample.contents.empty()) {
1372 return false;
1373 }
1374 }
1375
1376 HS3::Diagnostics diagnostics;
1377 HS3::mergeDuplicateModifiers(dist, channel.name, diagnostics);
1378 HS3::report(diagnostics);
1379 configureStatError(channel);
1381
1382 for (auto *obs : static_range_cast<RooRealVar *>(*channel.observables)) {
1383 dist.axes.push_back(HS3::toBinnedAxis(*obs));
1384 }
1385
1386 std::set<RooAbsPdf const *> namedConstraints = nameChannelConstraints(channel, constraints);
1387 for (RooAbsPdf const *pdf : namedConstraints) {
1388 tool->queueExport(*pdf);
1389 }
1390
1391 for (RooAbsReal *modifier : channel.customModifiers) {
1392 tool->queueExport(*modifier);
1393 }
1394 for (auto &modifier : channel.temporaries) {
1395 tool->queueExportTemporary(modifier.release());
1396 }
1397
1398 // Export all model parameters
1399 RooArgSet parameters;
1400 sumpdf->getParameters(channel.observables, parameters);
1401 for (RooAbsArg *param : parameters) {
1402 // This should exclude the global observables. The legacy inverse bin widths are not written at all.
1403 if (!startsWith(std::string{param->GetName()}, "nom_") && !channel.legacyBinWidthFactors.count(param)) {
1404 tool->queueExport(*param);
1405 }
1406 }
1407
1408 // All other constraint terms of the channel are written separately.
1409 std::vector<HS3::DistributionRef> channelConstraintNames;
1410 for (RooAbsPdf const *pdf : constraints) {
1411 if (!namedConstraints.count(pdf)) {
1412 tool->queueExport(*pdf);
1413 channelConstraintNames.push_back({pdf->GetName()});
1414 }
1415 }
1416
1417 HS3::setExportedObject(*tool, std::move(dist), std::move(channelConstraintNames));
1418 return true;
1419}
1420
1421class HistFactoryStreamer_ProdPdf : public RooFit::JSONIO::Exporter {
1422public:
1423 bool autoExportDependants() const override { return false; }
1424 bool tryExport(RooJSONFactoryWSTool *tool, const RooProdPdf *prodpdf) const
1425 {
1426 std::vector<RooAbsPdf *> constraints;
1427 RooRealSumPdf *sumpdf = nullptr;
1428 for (auto *pdf : static_range_cast<RooAbsPdf *>(prodpdf->pdfList())) {
1429 auto thispdf = dynamic_cast<RooRealSumPdf *>(pdf);
1430 if (thispdf) {
1431 if (!sumpdf) {
1432 sumpdf = thispdf;
1433 } else {
1434 return false;
1435 }
1436 } else {
1437 constraints.push_back(pdf);
1438 }
1439 }
1440 if (!sumpdf)
1441 return false;
1442
1443 return tryExportHistFactory(tool, prodpdf->GetName(), sumpdf, constraints);
1444 }
1445 std::string const &key() const override
1446 {
1447 static const std::string keystring = "histfactory_dist";
1448 return keystring;
1449 }
1450 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &) const override
1451 {
1452 return tryExport(tool, static_cast<const RooProdPdf *>(p));
1453 }
1454};
1455
1456class HistFactoryStreamer_SumPdf : public RooFit::JSONIO::Exporter {
1457public:
1458 bool autoExportDependants() const override { return false; }
1459 bool tryExport(RooJSONFactoryWSTool *tool, const RooRealSumPdf *sumpdf) const
1460 {
1461 std::vector<RooAbsPdf *> constraints;
1462 return tryExportHistFactory(tool, sumpdf->GetName(), sumpdf, constraints);
1463 }
1464 std::string const &key() const override
1465 {
1466 static const std::string keystring = "histfactory_dist";
1467 return keystring;
1468 }
1469 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &) const override
1470 {
1471 return tryExport(tool, static_cast<const RooRealSumPdf *>(p));
1472 }
1473};
1474
1475STATIC_EXECUTE([]() {
1476 using namespace RooFit::JSONIO;
1477
1478 registerImporter<HistFactoryImporter>("histfactory_dist", true);
1479 registerImporter<PiecewiseInterpolationFactory>("interpolation", true);
1480 registerImporter<FlexibleInterpVarFactory>("interpolation0d", true);
1481 registerExporter<FlexibleInterpVarStreamer>(RooStats::HistFactory::FlexibleInterpVar::Class(), true);
1482 registerExporter<PiecewiseInterpolationStreamer>(PiecewiseInterpolation::Class(), true);
1483 registerExporter<HistFactoryStreamer_ProdPdf>(RooProdPdf::Class(), true);
1484 registerExporter<HistFactoryStreamer_SumPdf>(RooRealSumPdf::Class(), true);
1485});
1486
1487} // namespace
1488
1490 std::vector<DistributionRef> const *channelConstraints)
1491{
1492 checkStatErrorConstraints(dist, name);
1493
1494 RooArgSet observables = createObservables(dist.axes);
1495
1496 std::vector<std::unique_ptr<RooDataHist>> data;
1497 data.reserve(dist.samples.size());
1498 std::vector<double> sumW;
1499 std::vector<double> sumW2;
1500 std::vector<ParameterRef> gammaParnames;
1501 for (auto const &sample : dist.samples) {
1502 auto dh = createDataHist(name + "_" + sample.name + "_dataHist", observables, sample.contents,
1503 sample.errors ? &*sample.errors : nullptr);
1504 const std::size_t nbins = dh->numEntries();
1505
1506 auto found = std::find_if(sample.modifiers.begin(), sample.modifiers.end(),
1507 [](auto const &m) { return std::holds_alternative<StatError>(m); });
1508 if (found != sample.modifiers.end()) {
1509 if (sumW.empty()) {
1510 sumW.resize(nbins);
1511 sumW2.resize(nbins);
1512 }
1513 // A sample without "errors" contributes no statistical uncertainty, unless Poisson uncertainties are implied.
1514 const bool hasErrors = sample.errors || dist.statError.impliedPoissonErrors;
1515 for (std::size_t i = 0; i < nbins; ++i) {
1516 sumW[i] += dh->weight(i);
1517 sumW2[i] += hasErrors ? dh->weightSquared(i) : 0.;
1518 }
1519 if (gammaParnames.empty()) {
1520 gammaParnames = std::get<StatError>(*found).parameters;
1521 }
1522 }
1523 data.emplace_back(std::move(dh));
1524 }
1525
1526 std::string channelName = name;
1527 erasePrefix(channelName, "model_");
1528
1529 std::size_t nBins = 1;
1530 for (auto const &axis : dist.axes) {
1531 nBins *= std::visit(overloaded{[](RegularBinning const &b) { return static_cast<std::size_t>(b.nbins); },
1532 [](EdgesBinning const &b) { return b.edges.size() - 1; }},
1533 axis.binning);
1534 }
1535
1536 RooAbsArg *mcStatObject = nullptr;
1537 RooArgSet constraints;
1538 if (!sumW.empty()) {
1539 std::vector<double> errs(sumW.size());
1540 for (std::size_t i = 0; i < sumW.size(); ++i) {
1541 if (sumW[i] == 0.) {
1542 errs[i] = 0.;
1543 continue;
1544 }
1545 errs[i] = std::sqrt(sumW2[i]) / sumW[i];
1546 // avoid negative sigma. This NP will be set constant anyway later
1547 errs[i] = std::max(errs[i], 0.);
1548 }
1549
1550 mcStatObject = &createPHF(tool, "mc_stat_" + channelName, "stat_" + channelName, gammaParnames, errs,
1551 statErrorConstraint(dist), constraints, observables, defaultStatErrorGammaMax,
1552 dist.statError.relThreshold, nBins);
1553 }
1554
1555 ChannelContext ctx{tool, dist, observables, name, channelName, nBins, mcStatObject, constraints};
1556
1558 RooArgList coefs;
1559 for (std::size_t i = 0; i < dist.samples.size(); ++i) {
1560 HS3::Sample const &sample = dist.samples[i];
1561 buildSample(ctx, sample, *data[i]);
1562 funcs.add(*tool.request<RooAbsReal>(name + "_" + sample.name + "_shapes", name));
1563 coefs.add(*tool.request<RooAbsReal>(name + "_" + sample.name + "_scaleFactors", name));
1564 }
1565
1566 if (channelConstraints) {
1567 for (auto const &ref : *channelConstraints) {
1568 RooAbsPdf *pdf = findNamedConstraint(tool, ref.name, name);
1569 if (!pdf) {
1570 RooJSONFactoryWSTool::error("unable to find the constraint '" + ref.name + "' of '" + name + "'");
1571 }
1572 constraints.add(*pdf);
1573 }
1574 }
1575
1576 // A histfactory_dist is a product of Poisson distributions of the bin counts. RooFit's binned likelihood
1577 // computes exactly that (it differs from the event-wise likelihood only by a constant), and binned generation
1578 // generates bin counts, so both are enabled, as HistFactory does by default.
1579 auto enableBinned = [](RooRealSumPdf &sum) {
1580 sum.setAttribute("BinnedLikelihood");
1581 sum.setAttribute("GenerateBinned");
1582 };
1583 if (constraints.empty()) {
1584 enableBinned(tool.wsEmplace<RooRealSumPdf>(name, funcs, coefs, true));
1585 } else {
1586 std::string sumName = name + "_model";
1587 erasePrefix(sumName, "model_");
1588 auto &sum = tool.wsEmplace<RooRealSumPdf>(sumName, funcs, coefs, true);
1589 sum.SetTitle(name.c_str());
1590 enableBinned(sum);
1591 tool.wsEmplace<RooProdPdf>(name, constraints, RooFit::Conditional(sum, observables));
1592 }
1593}
1594
1596{
1597 return dynamic_cast<HistFactoryImporter const *>(&importer) != nullptr;
1598}
1599
1600// The upgrade rewrites the JSON, so it has to work on a copy.
1602 std::vector<DistributionRef> const *channelConstraints)
1603{
1604 std::string name = RooJSONFactoryWSTool::name(p);
1605 if (!p.has_child("samples")) {
1606 RooJSONFactoryWSTool::error("no samples in '" + name + "', skipping.");
1607 }
1608 std::stringstream ss;
1609 p.writeJSON(ss);
1610 std::unique_ptr<RooFit::Detail::JSONTree> tree = RooFit::Detail::JSONTree::create(ss.str());
1611
1612 HS3::Version version;
1613 HS3::parseVersion(tool.hs3VersionInput(), version);
1614
1615 HS3::Diagnostics diagnostics;
1616 HS3::upgradeHistFactoryDist(tree->rootnode(), version, diagnostics);
1617 HS3::HistFactoryDist dist = HS3::parseHistFactoryDist(tree->rootnode(), diagnostics);
1618 if (!HS3::hasErrors(diagnostics)) {
1619 HS3::validateHistFactoryDist(dist, name, diagnostics);
1620 }
1621 HS3::report(diagnostics);
1622 HS3::buildHistFactoryDist(tool, name, dist, channelConstraints);
1623}
std::string channelName
bool startsWith(std::string_view str, std::string_view prefix)
bool endsWith(std::string_view str, std::string_view suffix)
#define d(i)
Definition RSha256.hxx:102
#define b(i)
Definition RSha256.hxx:100
#define c(i)
Definition RSha256.hxx:101
#define g(i)
Definition RSha256.hxx:105
#define e(i)
Definition RSha256.hxx:103
ROOT::RRangeCast< T, false, Range_t > static_range_cast(Range_t &&coll)
double toDouble(const char *s)
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
#define oocoutW(o, a)
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t r
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 funcs
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t modifier
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
#define hi
A class which maps the current values of a RooRealVar (or a set of RooRealVars) to one of a number of...
const RooArgList & paramList() const
The PiecewiseInterpolation is a class that can morph distributions into each other,...
const RooArgList & highList() const
const RooAbsReal * nominalHist() const
Return pointer to the nominal hist function.
static TClass * Class()
const RooArgList & lowList() const
void setPositiveDefinite(bool flag=true)
const RooArgList & paramList() const
const std::vector< int > & interpolationCodes() const
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.
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...
void treeNodeServerList(RooAbsCollection *list, const RooAbsArg *arg=nullptr, bool doBranch=true, bool doLeaf=true, bool valueOnly=false, bool recurseNonDerived=false) const
Fill supplied list with nodes of the arg tree, following all server links, starting with ourself as t...
Abstract container object that can hold multiple RooAbsArg objects.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
const_iterator end() const
Storage_t::size_type size() const
const_iterator begin() const
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
void setConstant(bool value=true)
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
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:24
Returns the bin width (or volume) given a RooHistFunc.
Represents a constant real-valued object.
Definition RooConstVar.h:23
Container class to hold N-dimensional binned data.
Definition RooDataHist.h:40
double const * weightArray() const
double weight(std::size_t i) const
Return weight of i-th bin.
double weightSquared(std::size_t i) const
Return squared weight sum of i-th bin.
const RooArgSet * get() const override
Get bin centre of current bin.
Definition RooDataHist.h:82
static std::unique_ptr< JSONTree > create()
A RooFormulaVar is a generic implementation of a real-valued object, which takes a RooArgList of serv...
RooAbsArg * getParameter(const char *name) const
Return pointer to parameter with given name.
const char * expression() const
const RooArgList & dependents() const
size_t nParameters() const
Return the number of parameters.
Plain Gaussian p.d.f.
Definition RooGaussian.h:24
A real-valued function sampled from a multidimensional histogram.
Definition RooHistFunc.h:29
When using RooFit, statistical models can be conveniently handled and stored as a RooWorkspace.
T * request(const std::string &objname, const std::string &requestAuthor)
void queueExportTemporary(RooAbsArg *arg)
void queueExport(RooAbsArg const &arg)
std::string const & hs3VersionInput() const
The "hs3_version" of the document that is currently being imported, or an empty string outside of an ...
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)
Poisson pdf.
Definition RooPoisson.h:18
RooAbsReal const & getMean() const
Get the mean parameter.
Definition RooPoisson.h:47
Efficient implementation of a product of PDFs of the form.
Definition RooProdPdf.h:35
static TClass * Class()
Represents the product of a given set of RooAbsReal objects.
Definition RooProduct.h:29
Implements a PDF constructed from a sum of functions:
const RooArgList & funcList() const
static TClass * Class()
const RooArgList & coefList() const
Variable that can be changed from the outside.
Definition RooRealVar.h:37
Persistable container for RooFit projects.
TObject * obj(RooStringView name) const
Return any type of object (RooAbsArg, RooAbsData or generic object) with given name)
RooAbsPdf * pdf(RooStringView name) const
Retrieve p.d.f (RooAbsPdf) with given name. A null pointer is returned if not found.
RooAbsArg * arg(RooStringView name) const
Return RooAbsArg with given name. A null pointer is returned if none is found.
RooFactoryWSTool & factory()
Return instance to factory tool.
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.
virtual void SetTitle(const char *title="")
Set the title of the TNamed.
Definition TNamed.cxx:173
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
Mother of all ROOT objects.
Definition TObject.h:42
virtual const char * GetName() const
Returns name of object.
Definition TObject.cxx:461
Basic string class.
Definition TString.h:137
Bool_t Contains(const char *pat, ECaseCompare cmp=kExact) const
Definition TString.h:642
RooCmdArg RecycleConflictNodes(bool flag=true)
RooCmdArg Conditional(const RooArgSet &pdfSet, const RooArgSet &depSet, bool depsAreCond=false)
RooConstVar & RooConst(double val)
RooCmdArg Silence(bool flag=true)
double gamma(double x)
CoordSystem::Scalar get(DisplacementVector2D< CoordSystem, Tag > const &p)
double dist(Rotation3D const &r1, Rotation3D const &r2)
Types that represent an HS3 document.
HistFactoryDist parseHistFactoryDist(Detail::JSONNode const &distribution, Diagnostics &diagnostics)
Reads a HistFactory distribution that doesn't come with a document.
void validateHistFactoryDist(HistFactoryDist const &dist, std::string const &name, Diagnostics &diagnostics)
Same as validate(), but for a single HistFactory distribution that doesn't come with a document.
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 writeInterpolatedFunction(Detail::JSONNode &node, InterpolatedFunction const &function)
Writes the functions "interpolation" and "interpolation0d", with a single interpolation if they are a...
std::variant< NormFactor, NormSys, HistoSys, ShapeSys, StatError, CustomModifier > Modifier
InterpolationClass
The RooFit classes that implement the interpolations, which encode them with integer codes.
constexpr Interpolation multiplicativePolynomialExponential
constexpr Interpolation additivePolynomialLinear
void mergeDuplicateModifiers(HistFactoryDist &dist, std::string const &channel, Diagnostics &diagnostics)
Combines the modifiers of a sample that have the same type and name, which HistFactory models can hav...
ConstraintType
The constraint types that older documents named, for which RooFit creates the constraint terms.
std::optional< Interpolation > interpolationFromCode(int code, InterpolationClass interpolationClass)
The interpolation for an integer code, or nothing if the class doesn't support the code.
Definition HS3Parser.cxx:64
void factorOutDefaultInterpolation(HistFactoryDist &dist)
Makes the most common interpolation of the modifiers the "default_interpolation", which they then don...
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.
char const * toString(InterpolationType type)
InterpolatedNumber parseInterpolatedNumber(Detail::JSONNode const &node, Diagnostics &diagnostics)
void upgradeHistFactoryDist(Detail::JSONNode &distribution, Version const &version, Diagnostics &diagnostics)
Same as upgradeDocument(), but for a single HistFactory distribution that doesn't come with a documen...
InterpolatedFunction parseInterpolatedFunction(Detail::JSONNode const &node, Diagnostics &diagnostics)
Reads the functions "interpolation" and "interpolation0d", which don't come with a document.
RooArgSet createObservables(std::vector< BinnedAxis > const &axes)
Create the variables for the axes of binned data.
std::optional< int > codeFromInterpolation(Interpolation const &interpolation, InterpolationClass interpolationClass)
The integer code of an interpolation, or nothing if the class can't represent the interpolation.
Definition HS3Parser.cxx:83
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 writeInterpolatedNumber(Detail::JSONNode &node, InterpolatedNumber const &function)
constexpr Interpolation multiplicativePiecewiseExponential
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.
std::variant< Unconstrained, ConstraintType, ConstantGammas, GammaConstraintPdfs > GammaConstraint
bool parseVersion(std::string const &version, Version &out)
Reads the leading "MAJOR.MINOR" and the optional ".PATCH" of an HS3 version string.
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...
std::variant< Unconstrained, ConstraintType, ConstraintPdf > Constraint
bool isBuiltinHistFactoryImporter(JSONIO::Importer const &importer)
Whether this is the importer that RooFit registers for HistFactory distributions, as opposed to the o...
void configureConstrainedGammas(RooArgList const &gammas, std::span< const double > relSigmas, double minSigma)
Configure constrained gamma parameters for fitting.
CreateGammaConstraintsOutput createGammaConstraints(RooArgList const &paramList, std::span< const double > relSigmas, double minSigma, Constraint::Type type)
void variables(TString dataset, TString fin="TMVA.root", TString dirName="InputVariables_Id", TString title="TMVA Input Variables", Bool_t isRegression=kFALSE, Bool_t useTMVAStyle=kTRUE)
#define STATIC_EXECUTE(MY_FUNC)
The gamma parameters are constant, which is a RooFit extension.
The constraint term is a distribution of the document, named in "constraints".
Multiplies the sample with a function of the document.
Every gamma parameter has its own constraint term, or none, as named in "constraints".
std::vector< std::optional< DistributionRef > > pdfs
std::optional< Interpolation > defaultInterpolation
std::vector< Sample > samples
The function "interpolation" of RooFit, implemented by PiecewiseInterpolation: a nominal function plu...
std::vector< Interpolation > interpolations
One per parameter.
std::vector< NameOrNumber > parameters
std::vector< NameOrNumber > low
std::vector< NameOrNumber > high
The function "interpolation0d" of RooFit, implemented by FlexibleInterpVar: a nominal value plus the ...
std::vector< double > high
std::vector< double > low
std::vector< std::string > parameters
std::vector< Interpolation > interpolations
One per parameter.
How a modifier of a HistFactory distribution interpolates and extrapolates its effect.
InterpolationType type
InterpolationFunction in
std::optional< InterpolationFunction > out
If not set, the function inside the bounds is used outside as well.
std::vector< double > contents
std::optional< std::vector< double > > errors
std::vector< Modifier > modifiers
Scales each bin by its own parameter. The modifier type "shapesys" is a deprecated alias of "shapefac...
Scales each bin by a parameter, that is constrained by the statistical uncertainty of all samples tha...
The parameter of the modifier is not constrained.
Combines lambdas into one visitor for std::visit(). The compiler complains about the alternatives wit...
Definition HS3Document.h:55
TMarker m
Definition textangle.C:8
TLine l
Definition textangle.C:4
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2335