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 <charconv>
37#include <iterator>
38#include <map>
39#include <optional>
40#include <regex>
41#include <tuple>
42
43#include "static_execute.h"
44#include "JSONIOUtils.h"
45
47
48using namespace RooStats::HistFactory;
49using namespace RooStats::HistFactory::Detail;
51
52namespace {
53
54double round_prec(double d, int nSig)
55{
56 if (d == 0.0)
57 return 0.0;
58 int ndigits = std::floor(std::log10(std::abs(d))) + 1 - nSig;
59 double sf = std::pow(10, ndigits);
60 if (std::abs(d / sf) < 2)
61 ndigits--;
62 return sf * std::round(d / sf);
63}
64
65// To avoid repeating the same string literals that can potentially get out of
66// sync.
67namespace Literals {
68constexpr auto staterror = "staterror";
69}
70
71struct Interpolation {
72 std::string type;
73 std::string in;
74 std::optional<std::string> out;
75
76 bool operator==(const Interpolation &other) const
77 {
78 return std::tie(type, in, out) == std::tie(other.type, other.in, other.out);
79 }
80
81 bool operator!=(const Interpolation &other) const { return !(*this == other); }
82
83 bool operator<(const Interpolation &other) const
84 {
85 return std::tie(type, in, out) < std::tie(other.type, other.in, other.out);
86 }
87};
88
89const Interpolation additivePiecewiseLinear{"add", "poly1", std::nullopt};
90const Interpolation multiplicativePiecewiseExponential{"mult", "exp", std::nullopt};
91const Interpolation additiveQuadraticLinear{"add", "poly2", "poly1"};
92const Interpolation additivePolynomialLinear{"add", "poly6", "poly1"};
93const Interpolation multiplicativePolynomialExponential{"mult", "poly6", "exp"};
94const Interpolation multiplicativePolynomialLinear{"mult", "poly6", "poly1"};
95
96std::string interpolationString(const Interpolation &interpolation)
97{
98 std::stringstream ss;
99 ss << R"({"type":")" << interpolation.type << R"(","in":")" << interpolation.in << R"(","out":)";
100 if (interpolation.out) {
101 ss << '"' << *interpolation.out << '"';
102 } else {
103 ss << "null";
104 }
105 ss << '}';
106 return ss.str();
107}
108
109bool isInterpolationFunction(std::string_view function)
110{
111 return function == "poly1" || function == "poly2" || function == "poly6" || function == "exp";
112}
113
114Interpolation readInterpolation(const JSONNode &node, const std::string &context)
115{
116 if (!node.is_map()) {
117 RooJSONFactoryWSTool::error(context + " must be a struct with components 'type', 'in', and 'out'");
118 }
119 for (const char *component : {"type", "in", "out"}) {
120 if (!node.has_child(component)) {
121 RooJSONFactoryWSTool::error(context + " does not define the required '" + component + "' component");
122 }
123 }
124
125 const auto &typeNode = node["type"];
126 const auto &inNode = node["in"];
127 const auto &outNode = node["out"];
128 if (typeNode.is_container() || typeNode.is_null() || inNode.is_container() || inNode.is_null()) {
129 RooJSONFactoryWSTool::error(context + " components 'type' and 'in' must be strings");
130 }
131
132 Interpolation interpolation{typeNode.val(), inNode.val(), std::nullopt};
133 if (interpolation.type != "add" && interpolation.type != "mult") {
134 RooJSONFactoryWSTool::error(context + " has unknown interpolation type '" + interpolation.type + "'");
135 }
136 if (!isInterpolationFunction(interpolation.in)) {
137 RooJSONFactoryWSTool::error(context + " has unknown interpolation function '" + interpolation.in + "'");
138 }
139
140 if (!outNode.is_null()) {
141 if (outNode.is_container()) {
142 RooJSONFactoryWSTool::error(context + " component 'out' must be a string or null");
143 }
144 interpolation.out = outNode.val();
145 if (!isInterpolationFunction(*interpolation.out)) {
146 RooJSONFactoryWSTool::error(context + " has unknown extrapolation function '" + *interpolation.out + "'");
147 }
148 }
149
150 return interpolation;
151}
152
153void writeInterpolation(JSONNode &node, const Interpolation &interpolation)
154{
155 node.set_map();
156 node["type"] << interpolation.type;
157 node["in"] << interpolation.in;
158 if (interpolation.out) {
159 node["out"] << *interpolation.out;
160 } else {
161 node["out"].set_null();
162 }
163}
164
165int readLegacyInterpolationCode(const JSONNode &node, const std::string &context)
166{
167 if (node.is_container() || node.is_null() || !node.has_val()) {
168 RooJSONFactoryWSTool::error(context + " must be a structured interpolation or a legacy integer code");
169 }
170
171 const std::string value = node.val();
172 int code = 0;
173 const auto result = std::from_chars(value.data(), value.data() + value.size(), code);
174 if (result.ec != std::errc{} || result.ptr != value.data() + value.size()) {
175 RooJSONFactoryWSTool::error(context + " has invalid legacy interpolation code '" + value + "'");
176 }
177 return code;
178}
179
180enum class InterpolationClass {
181 Piecewise,
182 Flexible
183};
184
185// Single source of truth for the mapping between the structured HS3 interpolation
186// descriptors and the RooFit integer codes of PiecewiseInterpolation and
187// FlexibleInterpVar. A code of `kUnrepresentable` means the descriptor cannot be
188// expressed by that class: FlexibleInterpVar internally remaps code 4 to code 5,
189// so it has no additive-linear or multiplicative-linear poly6 variant.
190struct InterpolationCodes {
191 const Interpolation &descriptor;
192 int piecewise;
193 int flexible;
194};
195
196constexpr int kUnrepresentable = -1;
197
198// clang-format off
199const std::vector<InterpolationCodes> interpolationTable{
206};
207// clang-format on
208
209int codeForClass(const InterpolationCodes &row, InterpolationClass interpolationClass)
210{
211 return interpolationClass == InterpolationClass::Piecewise ? row.piecewise : row.flexible;
212}
213
214const char *interpolationClassName(InterpolationClass interpolationClass)
215{
216 return interpolationClass == InterpolationClass::Piecewise ? "PiecewiseInterpolation" : "FlexibleInterpVar";
217}
218
219Interpolation interpolationFromCode(int code, InterpolationClass interpolationClass, const std::string &context)
220{
221 // Code 3 was historically an unimplemented alias of code 2 for both classes,
222 // and FlexibleInterpVar treats code 5 identically to its canonical code 4.
223 if (code == 3) {
224 code = 2;
225 }
226 if (interpolationClass == InterpolationClass::Flexible && code == 5) {
227 code = 4;
228 }
229 for (const auto &row : interpolationTable) {
230 const int rowCode = codeForClass(row, interpolationClass);
231 if (rowCode != kUnrepresentable && rowCode == code) {
232 return row.descriptor;
233 }
234 }
235 RooJSONFactoryWSTool::error(context + " has unsupported " + interpolationClassName(interpolationClass) + " code " +
236 std::to_string(code));
237}
238
239int codeFromInterpolation(const Interpolation &interpolation, InterpolationClass interpolationClass,
240 const std::string &context)
241{
242 for (const auto &row : interpolationTable) {
243 const int rowCode = codeForClass(row, interpolationClass);
244 if (rowCode != kUnrepresentable && row.descriptor == interpolation) {
245 return rowCode;
246 }
247 }
248 RooJSONFactoryWSTool::error(context + " " + interpolationString(interpolation) + " cannot be represented by " +
250}
251
252void writeInterpolations(JSONNode &node, const std::vector<int> &codes, InterpolationClass interpolationClass,
253 const std::string &context)
254{
255 auto &interpolations = node.set_seq();
256 if (codes.empty()) {
257 return;
258 }
259
260 std::vector<Interpolation> descriptors;
261 descriptors.reserve(codes.size());
262 for (std::size_t i = 0; i < codes.size(); ++i) {
263 descriptors.push_back(
264 interpolationFromCode(codes[i], interpolationClass, context + " at parameter index " + std::to_string(i)));
265 }
266
267 bool allEqual = true;
268 for (std::size_t i = 1; i < descriptors.size(); ++i) {
269 if (descriptors[i] != descriptors.front()) {
270 allEqual = false;
271 break;
272 }
273 }
274
275 const std::size_t outputSize = allEqual ? 1 : descriptors.size();
276 for (std::size_t i = 0; i < outputSize; ++i) {
278 }
279}
280
281std::vector<int> readInterpolations(const JSONNode &object, std::size_t nParameters,
282 InterpolationClass interpolationClass, const std::string &context)
283{
284 if (const auto *interpolations = object.find("interpolations")) {
285 if (!interpolations->is_seq()) {
286 RooJSONFactoryWSTool::error(context + " component 'interpolations' must be an array");
287 }
288
289 const std::size_t size = interpolations->num_children();
290 const bool validSize = nParameters == 0 ? size == 0 : size == 1 || size == nParameters;
291 if (!validSize) {
293 " component 'interpolations' must contain either one descriptor or one "
294 "descriptor per parameter (got " +
295 std::to_string(size) + " for " + std::to_string(nParameters) + " parameters)");
296 }
297
298 std::vector<int> codes;
299 codes.reserve(size);
300 std::size_t i = 0;
301 for (const auto &node : interpolations->children()) {
302 const std::string entryContext = context + " component 'interpolations' at index " + std::to_string(i);
303 codes.push_back(
305 ++i;
306 }
307 if (size == 1) {
308 codes.resize(nParameters, codes.front());
309 }
310 return codes;
311 }
312
313 std::vector<int> codes(nParameters, 0);
314 if (const auto *legacyCodes = object.find("interpolationCodes")) {
315 if (!legacyCodes->is_seq()) {
316 RooJSONFactoryWSTool::error(context + " legacy component 'interpolationCodes' must be an array");
317 }
318 if (legacyCodes->num_children() != nParameters) {
320 " legacy component 'interpolationCodes' must contain one code per "
321 "parameter (got " +
322 std::to_string(legacyCodes->num_children()) + " for " +
323 std::to_string(nParameters) + " parameters)");
324 }
325
326 std::size_t i = 0;
327 for (const auto &node : legacyCodes->children()) {
328 const std::string entryContext =
329 context + " legacy component 'interpolationCodes' at index " + std::to_string(i);
330 const Interpolation interpolation =
332 codes[i] = codeFromInterpolation(interpolation, interpolationClass, entryContext);
333 ++i;
334 }
335 }
336 return codes;
337}
338
339int interpolationCode(const JSONNode &modifier, const std::optional<Interpolation> &defaultInterpolation,
340 InterpolationClass interpolationClass, const std::string &context)
341{
342 const auto toCode = [&](const Interpolation &interpolation) {
343 return codeFromInterpolation(interpolation, interpolationClass, context);
344 };
345
346 if (const auto *interpolationNode = modifier.find("interpolation")) {
347 if (interpolationNode->is_map()) {
348 return toCode(readInterpolation(*interpolationNode, context));
349 }
351 const Interpolation interpolation = interpolationFromCode(legacyCode, interpolationClass, context);
352 return toCode(interpolation);
353 }
356 }
357
358 // Before structured interpolation was introduced, both modifier classes
359 // used the integer code 4 as their implicit default. The meaning of code 4
360 // is class-dependent.
361 return 4;
362}
363
364void erasePrefix(std::string &str, std::string_view prefix)
365{
366 if (startsWith(str, prefix)) {
367 str.erase(0, prefix.size());
368 }
369}
370
371bool eraseSuffix(std::string &str, std::string_view suffix)
372{
373 if (endsWith(str, suffix)) {
374 str.erase(str.size() - suffix.size());
375 return true;
376 } else {
377 return false;
378 }
379}
380
381template <class Coll>
382void sortByName(Coll &coll)
383{
384 std::sort(coll.begin(), coll.end(), [](auto &l, auto &r) { return l.name < r.name; });
385}
386
387template <class T>
388T *findClient(RooAbsArg *gamma)
389{
390 for (const auto &client : gamma->clients()) {
391 if (auto casted = dynamic_cast<T *>(client)) {
392 return casted;
393 } else {
394 T *c = findClient<T>(client);
395 if (c)
396 return c;
397 }
398 }
399 return nullptr;
400}
401
403{
404 if (!g)
405 return nullptr;
406 if (auto *constraint = findClient<RooPoisson>(g))
407 return constraint;
408 if (auto *constraint = findClient<RooGaussian>(g))
409 return constraint;
411}
412
413inline std::string defaultGammaName(std::string const &sysname, std::size_t i)
414{
415 return "gamma_" + sysname + "_bin_" + std::to_string(i);
416}
417
418/// Export the names of the gamma parameters to the modifier struct
419void exportGammaParameters(JSONNode &mod, std::vector<RooAbsReal *> const &params)
420{
421 std::vector<std::string> paramNames;
422 for (RooAbsReal *param : params) {
423 paramNames.emplace_back(param->GetName());
424 }
425 mod["parameters"].fill_seq(paramNames);
426}
427
428RooRealVar &createNominal(RooWorkspace &ws, std::string const &parname, double val, double min, double max)
429{
430 RooRealVar &nom = getOrCreate<RooRealVar>(ws, "nom_" + parname, val, min, max);
431 nom.setConstant(true);
432 return nom;
433}
434
435/// Get the conventional name of the constraint pdf for a constrained
436/// parameter.
437std::string constraintName(std::string const &paramName)
438{
439 return paramName + "Constraint";
440}
441
442bool isLegacyConstraintType(std::string const &value)
443{
444 return value == "Gauss" || value == "Poisson" || value == "Const" || value == "Lognormal";
445}
446
447RooAbsPdf *findNamedConstraint(RooJSONFactoryWSTool &tool, std::string const &constraintName, std::string const &sample)
448{
449 if (auto *constraint = tool.workspace()->pdf(constraintName)) {
450 return constraint;
451 }
452
453 try {
454 return tool.request<RooAbsPdf>(constraintName, sample);
456 if (err.child() != constraintName) {
457 throw;
458 }
459 }
460
461 return nullptr;
462}
463
465 std::string const &constraintType)
466{
467 if (constraintType == "Gauss") {
468 param.setError(1.0);
469 return getOrCreate<RooGaussian>(*tool.workspace(), constraintName(param.GetName()), param,
470 *tool.workspace()->var(std::string("nom_") + param.GetName()), 1.);
471 }
472
473 RooJSONFactoryWSTool::error("legacy constraint value '" + constraintType + "' for modifier '" +
475 "' is a known constraint type, but it cannot be resolved in this context");
476}
477
478ParamHistFunc &createPHF(const std::string &phfname, std::string const &sysname,
479 const std::vector<std::string> &parnames, const std::vector<double> &vals,
480 RooJSONFactoryWSTool &tool, RooAbsCollection &constraints, const RooArgSet &observables,
481 const std::string &constraintType, double gammaMin, double gammaMax, double minSigma,
482 bool createConstraints = true)
483{
484 RooWorkspace &ws = *tool.workspace();
485
486 size_t n = std::max(vals.size(), parnames.size());
488 for (std::size_t i = 0; i < n; ++i) {
489 const std::string name = parnames.empty() ? defaultGammaName(sysname, i) : parnames[i];
490 auto *e = dynamic_cast<RooAbsReal *>(ws.obj(name.c_str()));
491 if (e)
492 gammas.add(*e);
493 else
495 }
496
497 auto &phf = tool.wsEmplace<ParamHistFunc>(phfname, observables, gammas);
498
499 if (vals.size() > 0) {
500 if (!createConstraints) {
502 } else if (constraintType != "Const") {
504 gammas, vals, minSigma, constraintType == "Poisson" ? Constraint::Poisson : Constraint::Gaussian);
505 for (auto const &term : constraintsInfo.constraints) {
507 constraints.add(*ws.pdf(term->GetName()));
508 }
509 } else {
510 for (auto *gamma : static_range_cast<RooRealVar *>(gammas)) {
511 gamma->setConstant(true);
512 }
513 }
514 }
515
516 return phf;
517}
518
519/// Find the staterror modifier of a sample, or return nullptr if there is none.
520const JSONNode *findStaterror(const JSONNode &comp)
521{
522 if (comp.has_child("modifiers")) {
523 for (const auto &mod : comp["modifiers"].children()) {
524 if (mod["type"].val() == ::Literals::staterror)
525 return &mod;
526 }
527 }
528 return nullptr;
529}
530
531RooAbsPdf &
532getOrCreateConstraint(RooJSONFactoryWSTool &tool, const JSONNode &mod, RooRealVar &param, const std::string &sample)
533{
534 JSONNode const *constrName = mod.find("constraint_name");
535 if (constrName) {
536 auto constraint_name = constrName->val();
537 auto constraint = findNamedConstraint(tool, constraint_name, sample);
538 if (!constraint) {
539 RooJSONFactoryWSTool::error("unable to find definition of of constraint '" + constraint_name +
540 "' for modifier '" + RooJSONFactoryWSTool::name(mod) + "'");
541 }
542 if (auto gauss = dynamic_cast<RooGaussian *const>(constraint)) {
543 param.setError(gauss->getSigma().getVal());
544 }
545 return *constraint;
546 }
547
548 if (auto constr = mod.find("constraint")) {
549 std::string constraintValue = constr->val();
550 if (auto *constraint = findNamedConstraint(tool, constraintValue, sample)) {
551 if (auto gauss = dynamic_cast<RooGaussian *const>(constraint)) {
552 param.setError(gauss->getSigma().getVal());
553 }
554 return *constraint;
555 }
556
559 }
560
561 RooJSONFactoryWSTool::error("unable to resolve constraint value '" + constraintValue + "' for modifier '" +
563 "': this looks like a legacy workspace where the 'constraint' field is neither a "
564 "constraint pdf name nor a supported legacy constraint type");
565 }
566
567 std::string constraint_type = "Gauss";
568 if (auto constrType = mod.find("constraint_type")) {
570 }
573 }
574 RooJSONFactoryWSTool::error("unknown or invalid constraint for modifier '" + RooJSONFactoryWSTool::name(mod) + "'");
575}
576double poissonTau(RooPoisson const &constraint, RooAbsArg const &gamma)
577{
578 auto const *mean = dynamic_cast<RooProduct const *>(&constraint.getMean());
579 if (!mean) {
580 RooJSONFactoryWSTool::error("Poisson gamma constraint mean is not a RooProduct: " +
581 std::string(constraint.GetName()));
582 }
583
584 for (RooAbsArg *arg : mean->servers()) {
585 if (arg == &gamma) {
586 continue;
587 }
588
589 if (auto const *tau = dynamic_cast<RooConstVar const *>(arg)) {
590 return tau->getVal();
591 }
592
593 // Imported workspaces can sometimes represent
594 // constants as constant RooRealVars.
595 if (auto const *real = dynamic_cast<RooAbsReal const *>(arg)) {
596 if (real->isConstant() || endsWith(std::string(real->GetName()), "_tau")) {
597 return real->getVal();
598 }
599 }
600 }
601
602 RooJSONFactoryWSTool::error("Could not find tau component in Poisson gamma constraint mean: " +
603 std::string(constraint.GetName()));
604 return std::numeric_limits<double>::quiet_NaN();
605}
606
607// Returns the relative uncertainty encoded by a gamma constraint pdf. Only RooPoisson (via its tau) and RooGaussian
608// (via sigma/mean) are supported; anything else raises an error.
609double constraintRelError(RooAbsPdf const &constraint, RooAbsArg const &gamma)
610{
611 if (auto constraintP = dynamic_cast<RooPoisson const *>(&constraint)) {
612 return 1. / std::sqrt(poissonTau(*constraintP, gamma));
613 }
614 if (auto constraintG = dynamic_cast<RooGaussian const *>(&constraint)) {
615 return constraintG->getSigma().getVal() / constraintG->getMean().getVal();
616 }
617 RooJSONFactoryWSTool::error("currently, only RooPoisson and RooGaussian are supported as constraint types");
618 return std::numeric_limits<double>::quiet_NaN();
619}
620
622 RooAbsArg const *mcStatObject, const std::string &fprefix, const JSONNode &p,
623 const std::optional<Interpolation> &defaultInterpolation, RooArgSet &constraints)
624{
625 RooWorkspace &ws = *tool.workspace();
626
628 std::string prefixedName = fprefix + "_" + sampleName;
629
630 std::string channelName = fprefix;
631 erasePrefix(channelName, "model_");
632
633 if (!p.has_child("data")) {
634 RooJSONFactoryWSTool::error("sample '" + sampleName + "' does not define a 'data' key");
635 }
636
637 auto &hf = tool.wsEmplace<RooHistFunc>("hist_" + prefixedName, varlist, dh);
638 hf.SetTitle(RooJSONFactoryWSTool::name(p).c_str());
639
642
643 shapeElems.add(tool.wsEmplace<RooBinWidthFunction>(prefixedName + "_binWidth", hf, true));
644
645 if (findStaterror(p)) {
647 }
648
649 if (p.has_child("modifiers")) {
651 std::vector<double> overall_low;
652 std::vector<double> overall_high;
653 std::vector<int> overall_interp;
654
658 std::vector<int> histoInterp;
659
660 int idx = 0;
661 for (const auto &mod : p["modifiers"].children()) {
662 std::string const &modtype = mod["type"].val();
663 std::string const &sysname =
664 mod.has_child("name")
665 ? mod["name"].val()
666 : (mod.has_child("parameter") ? mod["parameter"].val() : "syst_" + std::to_string(idx));
667 ++idx;
668 if (modtype == "staterror") {
669 // this is dealt with at a different place, ignore it for now
670 } else if (modtype == "normfactor") {
672 constrParam.setError(0.0);
674 if (mod.has_child("constraint") || mod.has_child("constraint_name") || mod.has_child("constraint_type")) {
675 // for norm factors, constraints are optional
677 }
678 } else if (modtype == "normsys") {
679 auto *parameter = mod.find("parameter");
680 std::string parname(parameter ? parameter->val() : "alpha_" + sysname);
681 createNominal(ws, parname, 0.0, -10, 10);
682 auto &par = getOrCreate<RooRealVar>(ws, parname, 0., -5, 5);
683 overall_nps.add(par);
684 auto &data = mod["data"];
685 const std::string context = "interpolation for normsys modifier '" + sysname + "' in sample '" +
686 sampleName + "' of channel '" + channelName + "'";
687 const int interp = interpolationCode(mod, defaultInterpolation, InterpolationClass::Flexible, context);
688 double low = data["lo"].val_double();
689 double high = data["hi"].val_double();
690
691 // the below contains a a hack to cut off variations that go below 0
692 // This is needed because FlexibleInterpVar code 4 interpolates in log-space. Hence, values <= 0 result in
693 // NaN, which propagates throughout the model and causes evaluations to fail. If you know a nicer way to
694 // solve this, please go ahead and fix the lines below.
695 if (interp == 4 && low <= 0)
696 low = std::numeric_limits<double>::epsilon();
697 if (interp == 4 && high <= 0)
698 high = std::numeric_limits<double>::epsilon();
699
700 overall_low.push_back(low);
701 overall_high.push_back(high);
702 overall_interp.push_back(interp);
703
704 constraints.add(getOrCreateConstraint(tool, mod, par, sampleName));
705 } else if (modtype == "histosys") {
706 auto *parameter = mod.find("parameter");
707 std::string parname(parameter ? parameter->val() : "alpha_" + sysname);
708 createNominal(ws, parname, 0.0, -10, 10);
709 auto &par = getOrCreate<RooRealVar>(ws, parname, 0., -5, 5);
710 histNps.add(par);
711 auto &data = mod["data"];
712 histoLo.add(tool.wsEmplace<RooHistFunc>(
713 sysname + "Low_" + prefixedName, varlist,
715 histoHi.add(tool.wsEmplace<RooHistFunc>(
716 sysname + "High_" + prefixedName, varlist,
717 RooJSONFactoryWSTool::readBinnedData(data["hi"], sysname + "High_" + prefixedName, varlist)));
718 const std::string context = "interpolation for histosys modifier '" + sysname + "' in sample '" +
719 sampleName + "' of channel '" + channelName + "'";
720 histoInterp.push_back(interpolationCode(mod, defaultInterpolation, InterpolationClass::Piecewise, context));
721 constraints.add(getOrCreateConstraint(tool, mod, par, sampleName));
722 } else if (modtype == "shapesys" || modtype == "shapefactor") {
723 std::string funcName = channelName + "_" + sysname + "_ShapeSys";
724 // funcName should be "<channel_name>_<sysname>_ShapeSys"
725 std::vector<double> vals;
726 if (mod["data"].has_child("vals")) {
727 for (const auto &v : mod["data"]["vals"].children()) {
728 vals.push_back(v.val_double());
729 }
730 }
731 std::vector<std::string> parnames;
732 for (const auto &v : mod["parameters"].children()) {
733 parnames.push_back(v.val());
734 }
735 if (vals.empty() && parnames.empty()) {
736 RooJSONFactoryWSTool::error("unable to instantiate shapesys '" + sysname +
737 "' with neither values nor parameters!");
738 }
739 std::string constraint = "unknown";
740 std::vector<RooAbsPdf *> constraintPdfs;
741 bool const hasConstraintList = mod.has_child("constraints");
742 if (hasConstraintList) {
743 for (const auto &v : mod["constraints"].children()) {
744 if (v.is_null()) {
745 constraintPdfs.push_back(nullptr);
746 } else {
747 std::string constraintName = v.val();
749 if (!constraintPdf) {
750 RooJSONFactoryWSTool::error("unable to find definition of constraint '" + constraintName +
751 "' for modifier '" + RooJSONFactoryWSTool::name(mod) + "'");
752 }
753 constraintPdfs.push_back(constraintPdf);
754 }
755 }
756 std::size_t const nGammas = std::max(vals.size(), parnames.size());
757 if (constraintPdfs.size() != nGammas) {
758 std::stringstream ss;
759 ss << "modifier '" << RooJSONFactoryWSTool::name(mod) << "' has " << constraintPdfs.size()
760 << " constraints, but " << nGammas << " parameters";
762 }
763 } else if (mod.has_child("constraint_type")) {
764 constraint = mod["constraint_type"].val();
765 } else if (mod.has_child("constraint")) {
766 std::string constraintValue = mod["constraint"].val();
768 constraint = constraintValue;
769 } else {
770 RooJSONFactoryWSTool::error("unable to resolve constraint value '" + constraintValue +
771 "' for modifier '" + RooJSONFactoryWSTool::name(mod) +
772 "': this looks like a legacy workspace where the 'constraint' field is "
773 "not a supported legacy constraint type");
774 }
775 }
776 shapeElems.add(createPHF(funcName, sysname, parnames, vals, tool, constraints, varlist, constraint,
778 /*createConstraints=*/!hasConstraintList));
779 for (auto *constraintPdf : constraintPdfs) {
780 if (constraintPdf) {
781 constraints.add(*constraintPdf);
782 }
783 }
784 } else if (modtype == "custom") {
785 RooAbsReal *obj = ws.function(sysname);
786 if (!obj) {
787 RooJSONFactoryWSTool::error("unable to find custom modifier '" + sysname + "'");
788 }
789 if (obj->dependsOn(varlist)) {
790 shapeElems.add(*obj);
791 } else {
792 normElems.add(*obj);
793 }
794 } else {
795 RooJSONFactoryWSTool::error("modifier '" + sysname + "' of unknown type '" + modtype + "'");
796 }
797 }
798
799 std::string interpName = sampleName + "_" + channelName + "_epsilon";
800 if (!overall_nps.empty()) {
803 normElems.add(v);
804 }
805 if (!histNps.empty()) {
806 auto &v = tool.wsEmplace<PiecewiseInterpolation>("histoSys_" + prefixedName, hf, histoLo, histoHi, histNps,
809 shapeElems.add(v);
810 } else {
811 shapeElems.add(hf);
812 }
813 }
814
815 tool.wsEmplace<RooProduct>(prefixedName + "_shapes", shapeElems);
816 if (!normElems.empty()) {
817 tool.wsEmplace<RooProduct>(prefixedName + "_scaleFactors", normElems);
818 } else {
819 ws.factory("RooConstVar::" + prefixedName + "_scaleFactors(1.)");
820 }
821
822 return true;
823}
824
825class HistFactoryImporter : public RooFit::JSONIO::Importer {
826public:
827 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
828 {
829 std::string name = RooJSONFactoryWSTool::name(p);
830 if (!p.has_child("samples")) {
831 RooJSONFactoryWSTool::error("no samples in '" + name + "', skipping.");
832 }
833 double statErrThresh = 0;
834 std::string statErrType = "Poisson";
835 std::optional<Interpolation> defaultInterpolation;
836 if (p.has_child("default_interpolation")) {
838 readInterpolation(p["default_interpolation"], "default_interpolation of channel '" + name + "'");
839 }
840 if (p.has_child(::Literals::staterror)) {
841 auto &staterr = p[::Literals::staterror];
842 if (staterr.has_child("relThreshold"))
843 statErrThresh = staterr["relThreshold"].val_double();
844 if (staterr.has_child("constraint_type"))
845 statErrType = staterr["constraint_type"].val();
846 }
847 std::vector<double> sumW;
848 std::vector<double> sumW2;
849 std::vector<std::string> gammaParnames;
851
852 std::string fprefix = name;
853
854 std::vector<std::unique_ptr<RooDataHist>> data;
855 for (const auto &comp : p["samples"].children()) {
856 std::unique_ptr<RooDataHist> dh = RooJSONFactoryWSTool::readBinnedData(
857 comp["data"], fprefix + "_" + RooJSONFactoryWSTool::name(comp) + "_dataHist", observables);
858 size_t nbins = dh->numEntries();
859
860 if (const JSONNode *staterror = findStaterror(comp)) {
861 if (sumW.empty()) {
862 sumW.resize(nbins);
863 sumW2.resize(nbins);
864 }
865 for (size_t i = 0; i < nbins; ++i) {
866 sumW[i] += dh->weight(i);
867 sumW2[i] += dh->weightSquared(i);
868 }
869 if (gammaParnames.empty()) {
870 if (auto staterrorParams = staterror->find("parameters")) {
871 for (const auto &v : staterrorParams->children()) {
872 gammaParnames.push_back(v.val());
873 }
874 }
875 }
876 }
877 data.emplace_back(std::move(dh));
878 }
879
880 RooAbsArg *mcStatObject = nullptr;
881 RooArgSet constraints;
882 if (!sumW.empty()) {
883 std::string channelName = name;
884 erasePrefix(channelName, "model_");
885
886 std::vector<double> errs(sumW.size());
887 for (size_t i = 0; i < sumW.size(); ++i) {
888 if (sumW[i] == 0.) {
889 errs[i] = 0.;
890 continue;
891 }
892 errs[i] = std::sqrt(sumW2[i]) / sumW[i];
893 // avoid negative sigma. This NP will be set constant anyway later
894 errs[i] = std::max(errs[i], 0.);
895 }
896
898 &createPHF("mc_stat_" + channelName, "stat_" + channelName, gammaParnames, errs, *tool, constraints,
900 }
901
902 int idx = 0;
904 RooArgList coefs;
905 for (const auto &comp : p["samples"].children()) {
907 constraints);
908 ++idx;
909
910 std::string const &compName = RooJSONFactoryWSTool::name(comp);
911 funcs.add(*tool->request<RooAbsReal>(fprefix + "_" + compName + "_shapes", name));
912 coefs.add(*tool->request<RooAbsReal>(fprefix + "_" + compName + "_scaleFactors", name));
913 }
914
915 if (constraints.empty()) {
916 tool->wsEmplace<RooRealSumPdf>(name, funcs, coefs, true);
917 } else {
918 std::string sumName = name + "_model";
919 erasePrefix(sumName, "model_");
920 auto &sum = tool->wsEmplace<RooRealSumPdf>(sumName, funcs, coefs, true);
921 sum.SetTitle(name.c_str());
922 tool->wsEmplace<RooProdPdf>(name, constraints, RooFit::Conditional(sum, observables));
923 }
924 return true;
925 }
926};
927
928class FlexibleInterpVarStreamer : public RooFit::JSONIO::Exporter {
929public:
930 std::string const &key() const override
931 {
932 static const std::string keystring = "interpolation0d";
933 return keystring;
934 }
935 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
936 {
937 auto fip = static_cast<const RooStats::HistFactory::FlexibleInterpVar *>(func);
938 const std::size_t nParameters = fip->variables().size();
939 if (fip->low().size() != nParameters || fip->high().size() != nParameters ||
940 fip->interpolationCodes().size() != nParameters) {
941 RooJSONFactoryWSTool::error("FlexibleInterpVar '" + std::string{fip->GetName()} +
942 "' has non-matching parameter, variation, and interpolation lengths");
943 }
944 elem["type"] << key();
945 writeInterpolations(elem["interpolations"], fip->interpolationCodes(), InterpolationClass::Flexible,
946 "FlexibleInterpVar '" + std::string{fip->GetName()} + "'");
947 RooJSONFactoryWSTool::fillSeq(elem["vars"], fip->variables());
948 elem["nom"] << fip->nominal();
949 elem["high"].fill_seq(fip->high(), fip->variables().size());
950 elem["low"].fill_seq(fip->low(), fip->variables().size());
951 return true;
952 }
953};
954
955class PiecewiseInterpolationStreamer : public RooFit::JSONIO::Exporter {
956public:
957 std::string const &key() const override
958 {
959 static const std::string keystring = "interpolation";
960 return keystring;
961 }
962 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
963 {
964 const PiecewiseInterpolation *pip = static_cast<const PiecewiseInterpolation *>(func);
965 const std::size_t nParameters = pip->paramList().size();
966 if (pip->lowList().size() != nParameters || pip->highList().size() != nParameters ||
967 pip->interpolationCodes().size() != nParameters) {
968 RooJSONFactoryWSTool::error("PiecewiseInterpolation '" + std::string{pip->GetName()} +
969 "' has non-matching parameter, variation, and interpolation lengths");
970 }
971 elem["type"] << key();
972 writeInterpolations(elem["interpolations"], pip->interpolationCodes(), InterpolationClass::Piecewise,
973 "PiecewiseInterpolation '" + std::string{pip->GetName()} + "'");
974 elem["positiveDefinite"] << pip->positiveDefinite();
975 RooJSONFactoryWSTool::fillSeq(elem["vars"], pip->paramList());
976 elem["nom"] << pip->nominalHist()->GetName();
977 RooJSONFactoryWSTool::fillSeq(elem["high"], pip->highList(), pip->paramList().size());
978 RooJSONFactoryWSTool::fillSeq(elem["low"], pip->lowList(), pip->paramList().size());
979 return true;
980 }
981};
982
983class PiecewiseInterpolationFactory : public RooFit::JSONIO::Importer {
984public:
985 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
986 {
987 std::string name(RooJSONFactoryWSTool::name(p));
988
989 RooArgList vars{tool->requestArgList<RooAbsReal>(p, "vars")};
990 RooArgList low{tool->requestArgList<RooAbsReal>(p, "low")};
991 RooArgList high{tool->requestArgList<RooAbsReal>(p, "high")};
992 if (vars.size() != low.size() || vars.size() != high.size()) {
993 RooJSONFactoryWSTool::error("PiecewiseInterpolation '" + name +
994 "' has non-matching lengths of 'vars', 'high' and 'low'");
995 }
996 const std::vector<int> codes =
997 readInterpolations(p, vars.size(), InterpolationClass::Piecewise, "PiecewiseInterpolation '" + name + "'");
998
999 auto &pip =
1000 tool->wsEmplace<PiecewiseInterpolation>(name, *tool->requestArg<RooAbsReal>(p, "nom"), low, high, vars, codes);
1001
1002 pip.setPositiveDefinite(p["positiveDefinite"].val_bool());
1003
1004 return true;
1005 }
1006};
1007
1008class FlexibleInterpVarFactory : public RooFit::JSONIO::Importer {
1009public:
1010 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
1011 {
1012 std::string name(RooJSONFactoryWSTool::name(p));
1013 if (!p.has_child("high")) {
1014 RooJSONFactoryWSTool::error("no high variations of '" + name + "'");
1015 }
1016 if (!p.has_child("low")) {
1017 RooJSONFactoryWSTool::error("no low variations of '" + name + "'");
1018 }
1019 if (!p.has_child("nom")) {
1020 RooJSONFactoryWSTool::error("no nominal variation of '" + name + "'");
1021 }
1022
1023 double nom(p["nom"].val_double());
1024
1025 RooArgList vars{tool->requestArgList<RooRealVar>(p, "vars")};
1026
1027 std::vector<double> high;
1028 high << p["high"];
1029
1030 std::vector<double> low;
1031 low << p["low"];
1032
1033 if (vars.size() != low.size() || vars.size() != high.size()) {
1034 RooJSONFactoryWSTool::error("FlexibleInterpVar '" + name +
1035 "' has non-matching lengths of 'vars', 'high' and 'low'!");
1036 }
1037 const std::vector<int> codes =
1038 readInterpolations(p, vars.size(), InterpolationClass::Flexible, "FlexibleInterpVar '" + name + "'");
1039
1040 tool->wsEmplace<RooStats::HistFactory::FlexibleInterpVar>(name, vars, nom, low, high, codes);
1041
1042 return true;
1043 }
1044};
1045
1046struct NormFactor {
1047 std::string name;
1048 RooAbsReal const *param = nullptr;
1049 RooAbsPdf const *constraint = nullptr;
1050 NormFactor(RooAbsReal const &par, const RooAbsPdf *constr = nullptr)
1051 : name{par.GetName()}, param{&par}, constraint{constr}
1052 {
1053 }
1054};
1055
1056struct NormSys {
1057 std::string name = "";
1058 RooAbsReal const *param = nullptr;
1059 double low = 1.;
1060 double high = 1.;
1061 Interpolation interpolation = multiplicativePolynomialExponential;
1062 RooAbsPdf const *constraint = nullptr;
1063 NormSys() {};
1064 NormSys(const std::string &n, RooAbsReal *const p, double h, double l, Interpolation i, const RooAbsPdf *c)
1065 : name(n), param(p), low(l), high(h), interpolation(std::move(i)), constraint(c)
1066 {
1067 }
1068};
1069
1070struct HistoSys {
1071 std::string name;
1072 RooAbsReal const *param = nullptr;
1073 std::vector<double> low;
1074 std::vector<double> high;
1075 Interpolation interpolation = additivePolynomialLinear;
1076 RooAbsPdf const *constraint = nullptr;
1077 HistoSys(const std::string &n, RooAbsReal *const p, RooHistFunc *l, RooHistFunc *h, Interpolation i,
1078 const RooAbsPdf *c)
1079 : name(n), param(p), interpolation(std::move(i)), constraint(c)
1080 {
1081 low.assign(l->dataHist().weightArray(), l->dataHist().weightArray() + l->dataHist().numEntries());
1082 high.assign(h->dataHist().weightArray(), h->dataHist().weightArray() + h->dataHist().numEntries());
1083 }
1084};
1085struct ShapeSys {
1086 std::string name;
1087 std::vector<double> constraints;
1088 std::vector<RooAbsPdf const *> constraintPdfs;
1089 std::vector<RooAbsReal *> parameters;
1090 ShapeSys(const std::string &n) : name{n} {}
1091};
1092
1093struct GenericElement {
1094 std::string name;
1095 RooAbsReal *function = nullptr;
1096 GenericElement(RooAbsReal *e) : name(e->GetName()), function(e) {};
1097};
1098
1099std::string stripOuterParens(const std::string &s)
1100{
1101 size_t start = 0;
1102 size_t end = s.size();
1103
1104 while (start < end && s[start] == '(' && s[end - 1] == ')') {
1105 int depth = 0;
1106 bool balanced = true;
1107 for (size_t i = start; i < end - 1; ++i) {
1108 if (s[i] == '(')
1109 ++depth;
1110 else if (s[i] == ')')
1111 --depth;
1112 if (depth == 0 && i < end - 1) {
1113 balanced = false;
1114 break;
1115 }
1116 }
1117 if (balanced) {
1118 ++start;
1119 --end;
1120 } else {
1121 break;
1122 }
1123 }
1124 return s.substr(start, end - start);
1125}
1126
1127std::vector<std::string> splitTopLevelProduct(const std::string &expr)
1128{
1129 std::vector<std::string> parts;
1130 int depth = 0;
1131 size_t start = 0;
1132 bool foundTopLevelStar = false;
1133
1134 for (size_t i = 0; i < expr.size(); ++i) {
1135 char c = expr[i];
1136 if (c == '(') {
1137 ++depth;
1138 } else if (c == ')') {
1139 --depth;
1140 } else if (c == '*' && depth == 0) {
1141 foundTopLevelStar = true;
1142 std::string sub = expr.substr(start, i - start);
1143 parts.push_back(stripOuterParens(sub));
1144 start = i + 1;
1145 }
1146 }
1147
1148 if (!foundTopLevelStar) {
1149 return {}; // Not a top-level product
1150 }
1151
1152 std::string sub = expr.substr(start);
1153 parts.push_back(stripOuterParens(sub));
1154 return parts;
1155}
1156
1157NormSys parseOverallModifierFormula(const std::string &s, RooFormulaVar *formula)
1158{
1159 static const std::regex pattern(
1160 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*$)");
1161
1162 NormSys sys;
1163 double sign = 1.0;
1164
1165 std::smatch match;
1166 if (std::regex_match(s, match, pattern)) {
1167 if (match[1].str() == "-") {
1168 sign = -1.0;
1169 }
1170
1171 std::string token2 = match[2].str();
1172 std::string token3 = match[4].str();
1173
1174 RooAbsReal *p2 = static_cast<RooAbsReal *>(formula->getParameter(token2.c_str()));
1175 RooAbsReal *p3 = static_cast<RooAbsReal *>(formula->getParameter(token3.c_str()));
1176 RooRealVar *v2 = dynamic_cast<RooRealVar *>(p2);
1177 RooRealVar *v3 = dynamic_cast<RooRealVar *>(p3);
1178
1179 auto *constr2 = findConstraint(v2);
1180 auto *constr3 = findConstraint(v3);
1181
1182 if (constr2 && !p3) {
1183 sys.name = p2->GetName();
1184 sys.param = p2;
1185 sys.high = sign * toDouble(token3);
1186 sys.low = -sign * toDouble(token3);
1187 } else if (!p2 && constr3) {
1188 sys.name = p3->GetName();
1189 sys.param = p3;
1190 sys.high = sign * toDouble(token2);
1191 sys.low = -sign * toDouble(token2);
1192 } else if (constr2 && p3 && !constr3) {
1193 sys.name = v2->GetName();
1194 sys.param = v2;
1195 sys.high = sign * p3->getVal();
1196 sys.low = -sign * p3->getVal();
1197 } else if (p2 && !constr2 && constr3) {
1198 sys.name = v3->GetName();
1199 sys.param = v3;
1200 sys.high = sign * p2->getVal();
1201 sys.low = -sign * p2->getVal();
1202 }
1203
1204 // Preserve the legacy export behaviour for recognized explicit formulae.
1205 sys.interpolation = multiplicativePiecewiseExponential;
1206
1207 erasePrefix(sys.name, "alpha_");
1208 }
1209 return sys;
1210}
1211
1212void collectElements(RooArgList &elems, RooAbsArg *arg)
1213{
1214 if (auto prod = dynamic_cast<RooProduct *>(arg)) {
1215 for (const auto &e : prod->components()) {
1216 collectElements(elems, e);
1217 }
1218 } else {
1219 elems.add(*arg);
1220 }
1221}
1222
1223bool allRooRealVar(const RooAbsCollection &list)
1224{
1225 for (auto *var : list) {
1226 if (!dynamic_cast<RooRealVar *>(var)) {
1227 return false;
1228 }
1229 }
1230 return true;
1231}
1232
1233struct Sample {
1234 std::string name;
1235 std::vector<double> hist;
1236 std::vector<double> histError;
1237 std::vector<NormFactor> normfactors;
1238 std::vector<NormSys> normsys;
1239 std::vector<HistoSys> histosys;
1240 std::vector<ShapeSys> shapesys;
1241 std::vector<GenericElement> tmpElements;
1242 std::vector<GenericElement> otherElements;
1243 bool useBarlowBeestonLight = false;
1244 std::vector<RooAbsReal *> staterrorParameters;
1245 Sample(const std::string &n) : name{n} {}
1246};
1247
1248void addNormFactor(RooRealVar const *par, Sample &sample, RooWorkspace *ws)
1249{
1250 std::string parname = par->GetName();
1251 bool isConstrained = false;
1252 for (RooAbsArg const *pdf : ws->allPdfs()) {
1253 if (auto gauss = dynamic_cast<RooGaussian const *>(pdf)) {
1254 if (parname == gauss->getX().GetName()) {
1255 sample.normfactors.emplace_back(*par, gauss);
1256 isConstrained = true;
1257 }
1258 }
1259 }
1260 if (!isConstrained)
1261 sample.normfactors.emplace_back(*par);
1262}
1263
1264struct Channel {
1265 std::string name;
1266 std::vector<Sample> samples;
1267 std::map<int, double> tot_yield;
1268 std::map<int, double> tot_yield2;
1269 std::map<int, double> rel_errors;
1270 RooArgSet const *varSet = nullptr;
1271 long unsigned int nBins = 0;
1272};
1273
1275{
1276 Channel channel;
1277
1278 RooWorkspace *ws = tool->workspace();
1279
1280 channel.name = pdfname;
1281 erasePrefix(channel.name, "model_");
1282 eraseSuffix(channel.name, "_model");
1283
1284 for (size_t sampleidx = 0; sampleidx < sumpdf->funcList().size(); ++sampleidx) {
1285 PiecewiseInterpolation *pip = nullptr;
1286 std::vector<ParamHistFunc *> phfs;
1287
1288 const auto func = sumpdf->funcList().at(sampleidx);
1289 Sample sample(func->GetName());
1290 erasePrefix(sample.name, "L_x_");
1291 eraseSuffix(sample.name, "_shapes");
1292 eraseSuffix(sample.name, "_" + channel.name);
1293 erasePrefix(sample.name, pdfname + "_");
1294
1295 auto updateObservables = [&](RooDataHist const &dataHist) {
1296 if (channel.varSet == nullptr) {
1297 channel.varSet = dataHist.get();
1298 channel.nBins = dataHist.numEntries();
1299 }
1300 if (sample.hist.empty()) {
1301 auto *w = dataHist.weightArray();
1302 sample.hist.assign(w, w + dataHist.numEntries());
1303 }
1304 };
1305 auto processElements = [&](const auto &elements, auto &&self) -> void {
1306 for (RooAbsArg *e : elements) {
1307 if (TString(e->GetName()).Contains("binWidth")) {
1308 // The bin width modifiers are handled separately. We can't just
1309 // check for the RooBinWidthFunction type here, because prior to
1310 // ROOT 6.26, the multiplication with the inverse bin width was
1311 // done in a different way (like a normfactor with a RooRealVar,
1312 // but it was stored in the dataset).
1313 // Fortunately, the name was similar, so we can match the modifier
1314 // name.
1315 } else if (auto constVar = dynamic_cast<RooConstVar *>(e)) {
1316 if (constVar->getVal() != 1.) {
1317 sample.normfactors.emplace_back(*constVar);
1318 }
1319 } else if (auto par = dynamic_cast<RooRealVar *>(e)) {
1320 addNormFactor(par, sample, ws);
1321 } else if (auto hf = dynamic_cast<const RooHistFunc *>(e)) {
1322 updateObservables(hf->dataHist());
1323 } else if (ParamHistFunc *phf = dynamic_cast<ParamHistFunc *>(e); phf && allRooRealVar(phf->paramList())) {
1324 phfs.push_back(phf);
1325 } else if (auto fip = dynamic_cast<RooStats::HistFactory::FlexibleInterpVar *>(e)) {
1326 // some (modified) histfactory models have several instances of FlexibleInterpVar
1327 // we collect and merge them
1328 for (size_t i = 0; i < fip->variables().size(); ++i) {
1329 RooAbsReal *var = static_cast<RooAbsReal *>(fip->variables().at(i));
1330 std::string sysname(var->GetName());
1331 erasePrefix(sysname, "alpha_");
1332 const auto *constraint = findConstraint(var);
1333 if (!constraint && !var->isConstant()) {
1334 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
1335 } else {
1336 const std::string context = "normsys modifier '" + sysname + "' in sample '" + sample.name +
1337 "' of channel '" + channel.name + "'";
1338 sample.normsys.emplace_back(
1339 sysname, var, fip->high()[i], fip->low()[i],
1340 interpolationFromCode(fip->interpolationCodes()[i], InterpolationClass::Flexible, context),
1341 constraint);
1342 }
1343 }
1344 } else if (!pip && (pip = dynamic_cast<PiecewiseInterpolation *>(e))) {
1345 // nothing to do here, already assigned
1346 } else if (RooFormulaVar *formula = dynamic_cast<RooFormulaVar *>(e)) {
1347 // people do a lot of fancy stuff with RooFormulaVar, like including NormSys via explicit formulae.
1348 // let's try to decompose it into building blocks
1349 TString expression(formula->expression());
1350 for (size_t i = formula->nParameters(); i--;) {
1351 const RooAbsArg *p = formula->getParameter(i);
1352 expression.ReplaceAll(("x[" + std::to_string(i) + "]").c_str(), p->GetName());
1353 expression.ReplaceAll(("@" + std::to_string(i)).c_str(), p->GetName());
1354 }
1355 auto components = splitTopLevelProduct(expression.Data());
1356 if (components.size() == 0) {
1357 // it's not a product, let's just treat it as an unknown element
1358 sample.otherElements.push_back(formula);
1359 } else {
1360 // it is a prododuct, we can try to handle the elements separately
1361 std::vector<RooAbsArg *> realComponents;
1362 int idx = 0;
1363 for (auto &comp : components) {
1364 // check if this is a trivial element of a product, we can treat it as its own modifier
1365 auto *part = formula->getParameter(comp.c_str());
1366 if (part) {
1367 realComponents.push_back(part);
1368 continue;
1369 }
1370 // check if this is an attempt at explicitly encoding an overallSys
1371 auto normsys = parseOverallModifierFormula(comp, formula);
1372 if (normsys.param) {
1373 sample.normsys.emplace_back(std::move(normsys));
1374 continue;
1375 }
1376
1377 // this is something non-trivial, let's deal with it separately
1378 std::string name = std::string(formula->GetName()) + "_part" + std::to_string(idx);
1379 ++idx;
1380 auto *var = new RooFormulaVar(name.c_str(), name.c_str(), comp.c_str(), formula->dependents());
1381 sample.tmpElements.push_back({var});
1382 }
1383 self(realComponents, self);
1384 }
1385 } else if (auto real = dynamic_cast<RooAbsReal *>(e)) {
1386 sample.otherElements.push_back(real);
1387 }
1388 }
1389 };
1390
1391 RooArgList elems;
1392 collectElements(elems, func);
1393 collectElements(elems, sumpdf->coefList().at(sampleidx));
1395
1396 // see if we can get the observables
1397 if (pip) {
1398 if (auto nh = dynamic_cast<RooHistFunc const *>(pip->nominalHist())) {
1399 updateObservables(nh->dataHist());
1400 }
1401 }
1402
1403 // sort and configure norms
1404 sortByName(sample.normfactors);
1405 sortByName(sample.normsys);
1406
1407 // sort and configure the histosys
1408 if (pip) {
1409 for (size_t i = 0; i < pip->paramList().size(); ++i) {
1410 RooAbsReal *var = static_cast<RooAbsReal *>(pip->paramList().at(i));
1411 std::string sysname(var->GetName());
1412 erasePrefix(sysname, "alpha_");
1413 if (auto lo = dynamic_cast<RooHistFunc *>(pip->lowList().at(i))) {
1414 if (auto hi = dynamic_cast<RooHistFunc *>(pip->highList().at(i))) {
1415 const auto *constraint = findConstraint(var);
1416 if (!constraint && !var->isConstant()) {
1417 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
1418 } else {
1419 const std::string context = "histosys modifier '" + sysname + "' in sample '" + sample.name +
1420 "' of channel '" + channel.name + "'";
1421 sample.histosys.emplace_back(
1422 sysname, var, lo, hi,
1423 interpolationFromCode(pip->interpolationCodes()[i], InterpolationClass::Piecewise, context),
1424 constraint);
1425 }
1426 }
1427 }
1428 }
1429 sortByName(sample.histosys);
1430 }
1431
1432 for (ParamHistFunc *phf : phfs) {
1433 if (startsWith(std::string(phf->GetName()), "mc_stat_")) { // MC stat uncertainty
1434 int idx = 0;
1435 for (const auto &g : phf->paramList()) {
1436 sample.staterrorParameters.push_back(static_cast<RooRealVar *>(g));
1437 ++idx;
1438 RooAbsPdf *constraint = findConstraint(g);
1439 channel.tot_yield[idx] += sample.hist[idx - 1];
1440 channel.tot_yield2[idx] += (sample.hist[idx - 1] * sample.hist[idx - 1]);
1441 if (constraint) {
1442 channel.rel_errors[idx] = constraintRelError(*constraint, *g);
1443 }
1444 }
1445 sample.useBarlowBeestonLight = true;
1446 } else { // other ShapeSys
1447 ShapeSys sys(phf->GetName());
1448 erasePrefix(sys.name, channel.name + "_");
1449 bool isshapesys = eraseSuffix(sys.name, "_ShapeSys") || eraseSuffix(sys.name, "_shapeSys");
1450 bool isshapefactor = eraseSuffix(sys.name, "_ShapeFactor") || eraseSuffix(sys.name, "_shapeFactor");
1451
1452 for (const auto &g : phf->paramList()) {
1453 sys.parameters.push_back(static_cast<RooRealVar *>(g));
1454 RooAbsPdf *constraint = nullptr;
1455 if (isshapesys) {
1456 constraint = findConstraint(g);
1457 if (!constraint)
1458 constraint = ws->pdf(constraintName(g->GetName()));
1459 if (!constraint && !g->isConstant()) {
1460 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(g->GetName()));
1461 }
1462 } else if (!isshapefactor) {
1463 RooJSONFactoryWSTool::error("unknown type of shapesys " + std::string(phf->GetName()));
1464 }
1465 if (!constraint) {
1466 sys.constraints.push_back(0.0);
1467 sys.constraintPdfs.push_back(nullptr);
1468 } else {
1469 sys.constraints.push_back(constraintRelError(*constraint, *g));
1470 sys.constraintPdfs.push_back(constraint);
1471 }
1472 }
1473 sample.shapesys.emplace_back(std::move(sys));
1474 }
1475 }
1476 sortByName(sample.shapesys);
1477
1478 // add the sample
1479 channel.samples.emplace_back(std::move(sample));
1480 }
1481
1482 sortByName(channel.samples);
1483 return channel;
1484}
1485
1486bool hasSameMetadata(const RooAbsArg *lhs, const RooAbsArg *rhs)
1487{
1488 if (!lhs || !rhs) {
1489 return lhs == rhs;
1490 }
1491 return std::string{lhs->GetName()} == rhs->GetName() && lhs->IsA() == rhs->IsA();
1492}
1493
1494[[noreturn]] void duplicateModifierError(const Channel &channel, const Sample &sample, std::string_view type,
1495 std::string_view name, std::string_view reason)
1496{
1497 std::stringstream ss;
1498 ss << "cannot combine duplicate modifier '" << name << "' of type '" << type << "' in sample '" << sample.name
1499 << "' of channel '" << channel.name << "': " << reason;
1500 RooJSONFactoryWSTool::error(ss.str().c_str());
1501}
1502
1503void warnDuplicateModifiersCombined(const Channel &channel, const Sample &sample, std::string_view type,
1504 std::string_view name, std::size_t count)
1505{
1506 std::stringstream ss;
1507 ss << "combined " << count << " duplicate modifiers named '" << name << "' of type '" << type << "' in sample '"
1508 << sample.name << "' of channel '" << channel.name << "'";
1510}
1511
1512// Multiplicatively combining two normsys is only faithful when the interpolation is done in log-space, so that
1513// f1(alpha) * f2(alpha) is again representable by a single normsys with the multiplied lo/hi factors. This holds for
1514// the piecewise-exponential code 1 (exact everywhere) and for the default code 4 (exact at the +-1 sigma anchors and in
1515// the exponential extrapolation region). The linear-space codes (e.g. 0 and 2) would turn the product into a shape that
1516// cannot be represented by a single normsys, so those must not be merged.
1517bool normSysSupportsMultiplicativeMerge(const Interpolation &interpolation)
1518{
1519 return interpolation == multiplicativePiecewiseExponential || interpolation == multiplicativePolynomialExponential;
1520}
1521
1522// Combines runs of adjacent modifiers that share the same name (the container is sorted by name beforehand) into a
1523// single modifier. The shared metadata (constraint, parameter and interpolation behaviour) must be identical across the
1524// duplicates; the type-specific `combine` callable performs the actual merge and any additional validation.
1525template <class Modifiers, class CombineFn>
1526void mergeDuplicateModifiers(const Channel &channel, const Sample &sample, Modifiers &modifiers, std::string_view type,
1528{
1530 mergedModifiers.reserve(modifiers.size());
1531
1532 for (std::size_t begin = 0; begin < modifiers.size();) {
1533 std::size_t end = begin + 1;
1534 while (end < modifiers.size() && modifiers[end].name == modifiers[begin].name) {
1535 ++end;
1536 }
1537
1538 auto merged = modifiers[begin];
1539 for (std::size_t i = begin + 1; i < end; ++i) {
1540 const auto &modifier = modifiers[i];
1541 if (!hasSameMetadata(merged.constraint, modifier.constraint)) {
1542 duplicateModifierError(channel, sample, type, merged.name, "constraint metadata differs");
1543 }
1544 if (!hasSameMetadata(merged.param, modifier.param)) {
1545 duplicateModifierError(channel, sample, type, merged.name, "parameter metadata differs");
1546 }
1547 if (merged.interpolation != modifier.interpolation) {
1548 duplicateModifierError(channel, sample, type, merged.name, "interpolation behaviours differ");
1549 }
1551 }
1552
1553 if (end - begin > 1) {
1554 warnDuplicateModifiersCombined(channel, sample, type, merged.name, end - begin);
1555 }
1556 mergedModifiers.emplace_back(std::move(merged));
1557 begin = end;
1558 }
1559
1560 modifiers = std::move(mergedModifiers);
1561}
1562
1563void mergeDuplicateNormSys(const Channel &channel, Sample &sample)
1564{
1565 mergeDuplicateModifiers(channel, sample, sample.normsys, "normsys", [&](NormSys &merged, const NormSys &modifier) {
1566 if (!normSysSupportsMultiplicativeMerge(merged.interpolation)) {
1567 duplicateModifierError(channel, sample, "normsys", merged.name,
1568 "multiplicative combination is only valid for log-space interpolation");
1569 }
1570 merged.low *= modifier.low;
1571 merged.high *= modifier.high;
1572 });
1573}
1574
1575void mergeDuplicateHistoSys(const Channel &channel, Sample &sample)
1576{
1577 const std::size_t nBins = sample.hist.size();
1579 channel, sample, sample.histosys, "histosys", [&](HistoSys &merged, const HistoSys &modifier) {
1580 if (merged.interpolation != additivePolynomialLinear) {
1581 duplicateModifierError(channel, sample, "histosys", merged.name,
1582 "this interpolation cannot currently be combined for duplicate histosys "
1583 "modifiers");
1584 }
1585 if (merged.low.size() != nBins || merged.high.size() != nBins || modifier.low.size() != nBins ||
1586 modifier.high.size() != nBins) {
1587 duplicateModifierError(channel, sample, "histosys", merged.name, "histogram binning differs");
1588 }
1589 for (std::size_t bin = 0; bin < nBins; ++bin) {
1590 merged.low[bin] += modifier.low[bin] - sample.hist[bin];
1591 merged.high[bin] += modifier.high[bin] - sample.hist[bin];
1592 }
1593 });
1594}
1595
1596void ensureUniqueModifiers(const Channel &channel, const Sample &sample)
1597{
1598 std::set<std::pair<std::string, std::string>> seen;
1599 auto add = [&](std::string type, const std::string &name) {
1600 if (!seen.emplace(type, name).second) {
1602 "this modifier type cannot be combined without changing its meaning");
1603 }
1604 };
1605
1606 for (const auto &modifier : sample.normfactors)
1607 add("normfactor", modifier.name);
1608 for (const auto &modifier : sample.normsys)
1609 add("normsys", modifier.name);
1610 for (const auto &modifier : sample.histosys)
1611 add("histosys", modifier.name);
1612 for (const auto &modifier : sample.shapesys)
1613 add("shapesys", modifier.name);
1614 for (const auto &modifier : sample.otherElements)
1615 add("custom", modifier.name);
1616 for (const auto &modifier : sample.tmpElements)
1617 add("custom", modifier.name);
1618 if (sample.useBarlowBeestonLight)
1619 add(::Literals::staterror, ::Literals::staterror);
1620}
1621
1622void canonicalizeModifiers(Channel &channel)
1623{
1624 for (auto &sample : channel.samples) {
1625 mergeDuplicateNormSys(channel, sample);
1627 ensureUniqueModifiers(channel, sample);
1628 }
1629}
1630
1631void configureStatError(Channel &channel)
1632{
1633 for (auto &sample : channel.samples) {
1634 if (sample.useBarlowBeestonLight) {
1635 sample.histError.resize(sample.hist.size());
1636 for (auto bin : channel.rel_errors) {
1637 // reverse engineering the correct partial error
1638 // the (arbitrary) convention used here is that all samples should have the same relative error
1639 const int i = bin.first;
1640 const double relerr_tot = bin.second;
1641 const double count = sample.hist[i - 1];
1642 // this reconstruction is inherently imprecise, so we truncate it at some decimal places to make sure that
1643 // we don't carry around too many useless digits
1644 sample.histError[i - 1] =
1645 round_prec(relerr_tot * channel.tot_yield[i] / std::sqrt(channel.tot_yield2[i]) * count, 7);
1646 }
1647 }
1648 }
1649}
1650
1651std::optional<Interpolation> defaultInterpolation(const Channel &channel)
1652{
1653 std::map<Interpolation, std::size_t> counts;
1654 for (const auto &sample : channel.samples) {
1655 for (const auto &modifier : sample.normsys) {
1656 ++counts[modifier.interpolation];
1657 }
1658 for (const auto &modifier : sample.histosys) {
1659 ++counts[modifier.interpolation];
1660 }
1661 }
1662 if (counts.empty()) {
1663 return std::nullopt;
1664 }
1665
1666 auto best = counts.begin();
1667 for (auto current = std::next(counts.begin()); current != counts.end(); ++current) {
1668 if (current->second > best->second ||
1669 (current->second == best->second && current->first == multiplicativePolynomialExponential &&
1671 best = current;
1672 }
1673 }
1674 return best->first;
1675}
1676
1678{
1679 // Write the constraint reference for any modifier that supports an
1680 // external Gaussian/Poisson/etc. constraint.
1681 auto writeConstraint = [](JSONNode &mod, auto const &sys) {
1682 if (sys.constraint) {
1683 mod["constraint"] << sys.constraint->GetName();
1684 }
1685 };
1686 auto addModifier = [](JSONNode &modifiers, std::string const &name, const char *type) -> JSONNode & {
1687 auto &mod = modifiers.append_child();
1688 mod.set_map();
1689 mod["name"] << name;
1690 mod["type"] << type;
1691 return mod;
1692 };
1693
1694 elem["type"] << "histfactory_dist";
1697 writeInterpolation(elem["default_interpolation"], *channelDefaultInterpolation);
1698 }
1699
1700 bool observablesWritten = false;
1701 for (const auto &sample : channel.samples) {
1702
1703 auto &s = RooJSONFactoryWSTool::appendNamedChild(elem["samples"], sample.name);
1704
1705 auto &modifiers = s["modifiers"];
1706 modifiers.set_seq();
1707
1708 for (const auto &nf : sample.normfactors) {
1709 auto &mod = modifiers.append_child();
1710 mod.set_map();
1711 mod["name"] << nf.name;
1712 mod["parameter"] << nf.param->GetName();
1713 mod["type"] << "normfactor";
1714 if (nf.constraint) {
1715 mod["constraint"] << nf.constraint->GetName();
1716 tool->queueExport(*nf.constraint);
1717 }
1718 }
1719
1720 for (const auto &sys : sample.normsys) {
1721 auto &mod = addModifier(modifiers, sys.name, "normsys");
1722 mod["parameter"] << sys.param->GetName();
1723 if (!channelDefaultInterpolation || sys.interpolation != *channelDefaultInterpolation) {
1724 writeInterpolation(mod["interpolation"], sys.interpolation);
1725 }
1726 writeConstraint(mod, sys);
1727 auto &data = mod["data"].set_map();
1728 data["lo"] << sys.low;
1729 data["hi"] << sys.high;
1730 }
1731
1732 for (const auto &sys : sample.histosys) {
1733 auto &mod = addModifier(modifiers, sys.name, "histosys");
1734 mod["parameter"] << sys.param->GetName();
1735 if (!channelDefaultInterpolation || sys.interpolation != *channelDefaultInterpolation) {
1736 writeInterpolation(mod["interpolation"], sys.interpolation);
1737 }
1738 writeConstraint(mod, sys);
1739 auto &data = mod["data"].set_map();
1740 if (channel.nBins != sys.low.size() || channel.nBins != sys.high.size()) {
1741 RooJSONFactoryWSTool::error("inconsistent binning: " + std::to_string(channel.nBins) +
1742 " bins expected, but " + std::to_string(sys.low.size()) + "/" +
1743 std::to_string(sys.high.size()) + " found in nominal histogram errors!");
1744 }
1745 RooJSONFactoryWSTool::exportArray(channel.nBins, sys.low.data(), data["lo"].set_map()["contents"]);
1746 RooJSONFactoryWSTool::exportArray(channel.nBins, sys.high.data(), data["hi"].set_map()["contents"]);
1747 }
1748
1749 for (const auto &sys : sample.shapesys) {
1750 auto &mod = addModifier(modifiers, sys.name, "shapesys");
1751 exportGammaParameters(mod, sys.parameters);
1752 if (std::any_of(sys.constraintPdfs.begin(), sys.constraintPdfs.end(),
1753 [](auto *pdf) { return pdf != nullptr; })) {
1754 auto &constraintNames = mod["constraints"].set_seq();
1755 for (auto *constraint : sys.constraintPdfs) {
1756 if (constraint) {
1757 constraintNames.append_child() << constraint->GetName();
1758 } else {
1759 constraintNames.append_child().set_null();
1760 }
1761 }
1762 }
1763 mod["data"].set_map()["vals"].fill_seq(sys.constraints);
1764 }
1765
1766 for (const auto &other : sample.otherElements) {
1767 addModifier(modifiers, other.name, "custom");
1768 }
1769 for (const auto &other : sample.tmpElements) {
1770 addModifier(modifiers, other.name, "custom");
1771 }
1772
1773 if (sample.useBarlowBeestonLight) {
1774 auto &mod = addModifier(modifiers, ::Literals::staterror, ::Literals::staterror);
1775 exportGammaParameters(mod, sample.staterrorParameters);
1776 }
1777
1778 if (!observablesWritten) {
1779 auto &output = elem["axes"].set_seq();
1780 for (auto *obs : static_range_cast<RooRealVar *>(*channel.varSet)) {
1781 RooJSONFactoryWSTool::exportAxis(output.append_child().set_map(), *obs);
1782 }
1783 observablesWritten = true;
1784 }
1785 auto &dataNode = s["data"].set_map();
1786 if (channel.nBins != sample.hist.size()) {
1787 RooJSONFactoryWSTool::error("inconsistent binning: " + std::to_string(channel.nBins) + " bins expected, but " +
1788 std::to_string(sample.hist.size()) + " found in nominal histogram!");
1789 }
1790 RooJSONFactoryWSTool::exportArray(channel.nBins, sample.hist.data(), dataNode["contents"]);
1791 if (!sample.histError.empty()) {
1792 if (channel.nBins != sample.histError.size()) {
1793 RooJSONFactoryWSTool::error("inconsistent binning: " + std::to_string(channel.nBins) +
1794 " bins expected, but " + std::to_string(sample.histError.size()) +
1795 " found in nominal histogram errors!");
1796 }
1797 RooJSONFactoryWSTool::exportArray(channel.nBins, sample.histError.data(), dataNode["errors"]);
1798 }
1799 }
1800
1801 return true;
1802}
1803
1804std::vector<RooAbsPdf *> findLostConstraints(const Channel &channel, const std::vector<RooAbsPdf *> &constraints)
1805{
1806 // collect all the vars that are used by the model
1807 std::set<const RooAbsReal *> vars;
1808 for (const auto &sample : channel.samples) {
1809 for (const auto &nf : sample.normfactors) {
1810 vars.insert(nf.param);
1811 }
1812 for (const auto &sys : sample.normsys) {
1813 vars.insert(sys.param);
1814 }
1815
1816 for (const auto &sys : sample.histosys) {
1817 vars.insert(sys.param);
1818 }
1819 for (const auto &sys : sample.shapesys) {
1820 for (const auto &par : sys.parameters) {
1821 vars.insert(par);
1822 }
1823 }
1824 if (sample.useBarlowBeestonLight) {
1825 for (const auto &par : sample.staterrorParameters) {
1826 vars.insert(par);
1827 }
1828 }
1829 }
1830
1831 // check if there is any constraint present that is unrelated to these vars
1832 std::vector<RooAbsPdf *> lostConstraints;
1833 for (auto *pdf : constraints) {
1834 bool related = false;
1835 for (const auto *var : vars) {
1836 if (pdf->dependsOn(*var)) {
1837 related = true;
1838 }
1839 }
1840 if (!related) {
1841 lostConstraints.push_back(pdf);
1842 }
1843 }
1844 // return the constraints that would be "lost" when exporting the model
1845 return lostConstraints;
1846}
1847
1849 std::vector<RooAbsPdf *> constraints, JSONNode &elem)
1850{
1851 // some preliminary checks
1852 if (!sumpdf) {
1853 return false;
1854 }
1855
1856 for (RooAbsArg *sample : sumpdf->funcList()) {
1857 if (!dynamic_cast<RooProduct *>(sample) && !dynamic_cast<RooRealSumPdf *>(sample)) {
1858 return false;
1859 }
1860 }
1861
1862 auto channel = readChannel(tool, pdfname, sumpdf);
1863
1864 // sanity checks
1865 if (channel.samples.size() == 0)
1866 return false;
1867 for (auto &sample : channel.samples) {
1868 if (sample.hist.empty()) {
1869 return false;
1870 }
1871 }
1872
1873 canonicalizeModifiers(channel);
1874
1875 // stat error handling
1876 configureStatError(channel);
1877
1878 auto lostConstraints = findLostConstraints(channel, constraints);
1879 // Export all the lost constraints
1880 for (const auto *constraint : lostConstraints) {
1882 "losing constraint term '" + std::string(constraint->GetName()) +
1883 "', implicit constraints are not supported by HS3 yet! The term will appear in the HS3 file, but will not be "
1884 "picked up when creating a likelihood from it! You will have to add it manually as an external constraint.");
1885 tool->queueExport(*constraint);
1886 }
1887
1888 // Export all the regular modifiers
1889 auto queueConstraints = [&](auto const &modifiers) {
1890 for (auto &modifier : modifiers) {
1891 if (modifier.constraint) {
1892 tool->queueExport(*modifier.constraint);
1893 }
1894 }
1895 };
1896 for (const auto &sample : channel.samples) {
1897 queueConstraints(sample.normfactors);
1898 queueConstraints(sample.normsys);
1899 queueConstraints(sample.histosys);
1900 for (auto &modifier : sample.shapesys) {
1901 for (auto *constraint : modifier.constraintPdfs) {
1902 if (constraint) {
1903 tool->queueExport(*constraint);
1904 }
1905 }
1906 }
1907 }
1908
1909 // Export all the custom modifiers
1910 for (const auto &sample : channel.samples) {
1911 for (auto &modifier : sample.otherElements) {
1912 tool->queueExport(*modifier.function);
1913 }
1914 for (auto &modifier : sample.tmpElements) {
1915 tool->queueExportTemporary(modifier.function);
1916 }
1917 }
1918
1919 // Export all model parameters
1920 RooArgSet parameters;
1921 sumpdf->getParameters(channel.varSet, parameters);
1922 for (RooAbsArg *param : parameters) {
1923 // This should exclude the global observables
1924 if (!startsWith(std::string{param->GetName()}, "nom_")) {
1925 tool->queueExport(*param);
1926 }
1927 }
1928
1929 return exportChannel(tool, channel, elem);
1930}
1931
1932class HistFactoryStreamer_ProdPdf : public RooFit::JSONIO::Exporter {
1933public:
1934 bool autoExportDependants() const override { return false; }
1936 {
1937 std::vector<RooAbsPdf *> constraints;
1938 RooRealSumPdf *sumpdf = nullptr;
1939 for (auto *pdf : static_range_cast<RooAbsPdf *>(prodpdf->pdfList())) {
1940 auto thispdf = dynamic_cast<RooRealSumPdf *>(pdf);
1941 if (thispdf) {
1942 if (!sumpdf)
1943 sumpdf = thispdf;
1944 else
1945 return false;
1946 } else {
1947 constraints.push_back(pdf);
1948 }
1949 }
1950 if (!sumpdf)
1951 return false;
1952
1953 bool ok = tryExportHistFactory(tool, prodpdf->GetName(), sumpdf, constraints, elem);
1954 return ok;
1955 }
1956 std::string const &key() const override
1957 {
1958 static const std::string keystring = "histfactory_dist";
1959 return keystring;
1960 }
1961 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
1962 {
1963 return tryExport(tool, static_cast<const RooProdPdf *>(p), elem);
1964 }
1965};
1966
1967class HistFactoryStreamer_SumPdf : public RooFit::JSONIO::Exporter {
1968public:
1969 bool autoExportDependants() const override { return false; }
1971 {
1972 std::vector<RooAbsPdf *> constraints;
1973 return tryExportHistFactory(tool, sumpdf->GetName(), sumpdf, constraints, elem);
1974 }
1975 std::string const &key() const override
1976 {
1977 static const std::string keystring = "histfactory_dist";
1978 return keystring;
1979 }
1980 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
1981 {
1982 return tryExport(tool, static_cast<const RooRealSumPdf *>(p), elem);
1983 }
1984};
1985
1986STATIC_EXECUTE([]() {
1987 using namespace RooFit::JSONIO;
1988
1989 registerImporter<HistFactoryImporter>("histfactory_dist", true);
1991 registerImporter<FlexibleInterpVarFactory>("interpolation0d", true);
1996});
1997
1998} // namespace
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 c(i)
Definition RSha256.hxx:101
#define g(i)
Definition RSha256.hxx:105
#define h(i)
Definition RSha256.hxx:106
#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
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Bool_t operator!=(const TDatime &d1, const TDatime &d2)
Definition TDatime.h:104
Bool_t operator<(const TDatime &d1, const TDatime &d2)
Definition TDatime.h:106
Bool_t operator==(const TDatime &d1, const TDatime &d2)
Definition TDatime.h:102
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 char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void 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...
The PiecewiseInterpolation is a class that can morph distributions into each other,...
static TClass * Class()
void setPositiveDefinite(bool flag=true)
const_iterator begin() const
const_iterator end() const
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:76
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:283
Abstract container object that can hold multiple RooAbsArg objects.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
RooArgList is a container object that can hold multiple RooAbsArg objects.
Definition RooArgList.h:22
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
virtual std::string val() const =0
virtual JSONNode & set_map()=0
virtual JSONNode & set_null()=0
virtual JSONNode & set_seq()=0
virtual bool is_container() const =0
virtual bool is_map() const =0
virtual bool has_child(std::string const &) const =0
virtual bool is_null() const =0
virtual bool has_val() const =0
A RooFormulaVar is a generic implementation of a real-valued object, which takes a RooArgList of serv...
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.
static void fillSeq(RooFit::Detail::JSONNode &node, RooAbsCollection const &coll, size_t nMax=-1)
static std::unique_ptr< RooDataHist > readBinnedData(const RooFit::Detail::JSONNode &n, const std::string &namecomp, RooArgSet const &vars)
Read binned data from the JSONNode and create a RooDataHist object.
static RooFit::Detail::JSONNode & appendNamedChild(RooFit::Detail::JSONNode &node, std::string const &name)
static void exportArray(std::size_t n, double const *contents, RooFit::Detail::JSONNode &output)
Export an array of doubles to a JSONNode.
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.
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.
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:
static TClass * Class()
Variable that can be changed from the outside.
Definition RooRealVar.h:37
void setError(double value)
Definition RooRealVar.h:61
This class encapsulates all information for the statistical interpretation of one experiment.
Configuration for a constrained, coherent shape variation of affected samples.
Configuration for an un- constrained overall systematic to scale sample normalisations.
Definition Measurement.h:60
Constrained bin-by-bin variation of affected histogram.
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.
RooAbsReal * function(RooStringView name) const
Retrieve function (RooAbsReal) with given name. Note that all RooAbsPdfs are also RooAbsReals....
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.
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
Basic string class.
Definition TString.h:138
Bool_t Contains(const char *pat, ECaseCompare cmp=kExact) const
Definition TString.h:643
RooCmdArg RecycleConflictNodes(bool flag=true)
RooCmdArg Conditional(const RooArgSet &pdfSet, const RooArgSet &depSet, bool depsAreCond=false)
const Int_t n
Definition legend1.C:16
for(Int_t i=0;i< n;i++)
Definition legend1.C:18
double gamma(double x)
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)
#define STATIC_EXECUTE(MY_FUNC)
TLine l
Definition textangle.C:4
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2335