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 =
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;
407 if (constraint_p)
408 return constraint_p;
410 if (constraint_g)
411 return constraint_g;
413 if (constraint_l)
414 return constraint_l;
415 return nullptr;
416}
417
418inline std::string defaultGammaName(std::string const &sysname, std::size_t i)
419{
420 return "gamma_" + sysname + "_bin_" + std::to_string(i);
421}
422
423/// Export the names of the gamma parameters to the modifier struct if the
424/// names don't match the default gamma parameter names, which is gamma_<sysname>_bin_<i>
425void optionallyExportGammaParameters(JSONNode &mod, std::string const &sysname, std::vector<RooAbsReal *> const &params,
426 bool forceExport = true)
427{
428 std::vector<std::string> paramNames;
429 bool needExport = forceExport;
430 for (std::size_t i = 0; i < params.size(); ++i) {
431 std::string name(params[i]->GetName());
432 paramNames.push_back(name);
433 if (name != defaultGammaName(sysname, i)) {
434 needExport = true;
435 }
436 }
437 if (needExport) {
438 mod["parameters"].fill_seq(paramNames);
439 }
440}
441
442RooRealVar &createNominal(RooWorkspace &ws, std::string const &parname, double val, double min, double max)
443{
444 RooRealVar &nom = getOrCreate<RooRealVar>(ws, "nom_" + parname, val, min, max);
445 nom.setConstant(true);
446 return nom;
447}
448
449/// Get the conventional name of the constraint pdf for a constrained
450/// parameter.
451std::string constraintName(std::string const &paramName)
452{
453 return paramName + "Constraint";
454}
455
456bool isLegacyConstraintType(std::string const &value)
457{
458 return value == "Gauss" || value == "Poisson" || value == "Const" || value == "Lognormal";
459}
460
461RooAbsPdf *findNamedConstraint(RooJSONFactoryWSTool &tool, std::string const &constraintName, std::string const &sample)
462{
463 if (auto *constraint = tool.workspace()->pdf(constraintName)) {
464 return constraint;
465 }
466
467 try {
468 return tool.request<RooAbsPdf>(constraintName, sample);
470 if (err.child() != constraintName) {
471 throw;
472 }
473 }
474
475 return nullptr;
476}
477
479 std::string const &constraintType)
480{
481 if (constraintType == "Gauss") {
482 param.setError(1.0);
483 return getOrCreate<RooGaussian>(*tool.workspace(), constraintName(param.GetName()), param,
484 *tool.workspace()->var(std::string("nom_") + param.GetName()), 1.);
485 }
486
487 RooJSONFactoryWSTool::error("legacy constraint value '" + constraintType + "' for modifier '" +
489 "' is a known constraint type, but it cannot be resolved in this context");
490}
491
492ParamHistFunc &createPHF(const std::string &phfname, std::string const &sysname,
493 const std::vector<std::string> &parnames, const std::vector<double> &vals,
494 RooJSONFactoryWSTool &tool, RooAbsCollection &constraints, const RooArgSet &observables,
495 const std::string &constraintType, double gammaMin, double gammaMax, double minSigma,
496 bool createConstraints = true)
497{
498 RooWorkspace &ws = *tool.workspace();
499
500 size_t n = std::max(vals.size(), parnames.size());
502 for (std::size_t i = 0; i < n; ++i) {
503 const std::string name = parnames.empty() ? defaultGammaName(sysname, i) : parnames[i];
504 auto *e = dynamic_cast<RooAbsReal *>(ws.obj(name.c_str()));
505 if (e)
506 gammas.add(*e);
507 else
509 }
510
511 auto &phf = tool.wsEmplace<ParamHistFunc>(phfname, observables, gammas);
512
513 if (vals.size() > 0) {
514 if (!createConstraints) {
516 } else if (constraintType != "Const") {
518 gammas, vals, minSigma, constraintType == "Poisson" ? Constraint::Poisson : Constraint::Gaussian);
519 for (auto const &term : constraintsInfo.constraints) {
521 constraints.add(*ws.pdf(term->GetName()));
522 }
523 } else {
524 for (auto *gamma : static_range_cast<RooRealVar *>(gammas)) {
525 gamma->setConstant(true);
526 }
527 }
528 }
529
530 return phf;
531}
532
533bool hasStaterror(const JSONNode &comp)
534{
535 if (!comp.has_child("modifiers"))
536 return false;
537 for (const auto &mod : comp["modifiers"].children()) {
538 if (mod["type"].val() == ::Literals::staterror)
539 return true;
540 }
541 return false;
542}
543
544const JSONNode &findStaterror(const JSONNode &comp)
545{
546 if (comp.has_child("modifiers")) {
547 for (const auto &mod : comp["modifiers"].children()) {
548 if (mod["type"].val() == ::Literals::staterror)
549 return mod;
550 }
551 }
552 RooJSONFactoryWSTool::error("sample '" + RooJSONFactoryWSTool::name(comp) + "' does not have a " +
553 ::Literals::staterror + " modifier!");
554}
555
556RooAbsPdf &
557getOrCreateConstraint(RooJSONFactoryWSTool &tool, const JSONNode &mod, RooRealVar &param, const std::string &sample)
558{
559 JSONNode const *constrName = mod.find("constraint_name");
560 if (constrName) {
561 auto constraint_name = constrName->val();
562 auto constraint = findNamedConstraint(tool, constraint_name, sample);
563 if (!constraint) {
564 RooJSONFactoryWSTool::error("unable to find definition of of constraint '" + constraint_name +
565 "' for modifier '" + RooJSONFactoryWSTool::name(mod) + "'");
566 }
567 if (auto gauss = dynamic_cast<RooGaussian *const>(constraint)) {
568 param.setError(gauss->getSigma().getVal());
569 }
570 return *constraint;
571 }
572
573 if (auto constr = mod.find("constraint")) {
574 std::string constraintValue = constr->val();
575 if (auto *constraint = findNamedConstraint(tool, constraintValue, sample)) {
576 if (auto gauss = dynamic_cast<RooGaussian *const>(constraint)) {
577 param.setError(gauss->getSigma().getVal());
578 }
579 return *constraint;
580 }
581
584 }
585
586 RooJSONFactoryWSTool::error("unable to resolve constraint value '" + constraintValue + "' for modifier '" +
588 "': this looks like a legacy workspace where the 'constraint' field is neither a "
589 "constraint pdf name nor a supported legacy constraint type");
590 }
591
592 std::string constraint_type = "Gauss";
593 if (auto constrType = mod.find("constraint_type")) {
595 }
598 }
599 RooJSONFactoryWSTool::error("unknown or invalid constraint for modifier '" + RooJSONFactoryWSTool::name(mod) + "'");
600}
601double poissonTau(RooPoisson const &constraint, RooAbsArg const &gamma)
602{
603 auto const *mean = dynamic_cast<RooProduct const *>(&constraint.getMean());
604 if (!mean) {
605 RooJSONFactoryWSTool::error("Poisson gamma constraint mean is not a RooProduct: " +
606 std::string(constraint.GetName()));
607 }
608
609 for (RooAbsArg *arg : mean->servers()) {
610 if (arg == &gamma) {
611 continue;
612 }
613
614 if (auto const *tau = dynamic_cast<RooConstVar const *>(arg)) {
615 return tau->getVal();
616 }
617
618 // Imported workspaces can sometimes represent
619 // constants as constant RooRealVars.
620 if (auto const *real = dynamic_cast<RooAbsReal const *>(arg)) {
621 if (real->isConstant() || endsWith(std::string(real->GetName()), "_tau")) {
622 return real->getVal();
623 }
624 }
625 }
626
627 RooJSONFactoryWSTool::error("Could not find tau component in Poisson gamma constraint mean: " +
628 std::string(constraint.GetName()));
629 return std::numeric_limits<double>::quiet_NaN();
630}
631
632// Returns the relative uncertainty encoded by a gamma constraint pdf. Only RooPoisson (via its tau) and RooGaussian
633// (via sigma/mean) are supported; anything else raises an error.
634double constraintRelError(RooAbsPdf const &constraint, RooAbsArg const &gamma)
635{
636 if (auto constraintP = dynamic_cast<RooPoisson const *>(&constraint)) {
637 return 1. / std::sqrt(poissonTau(*constraintP, gamma));
638 }
639 if (auto constraintG = dynamic_cast<RooGaussian const *>(&constraint)) {
640 return constraintG->getSigma().getVal() / constraintG->getMean().getVal();
641 }
642 RooJSONFactoryWSTool::error("currently, only RooPoisson and RooGaussian are supported as constraint types");
643 return std::numeric_limits<double>::quiet_NaN();
644}
645
647 RooAbsArg const *mcStatObject, const std::string &fprefix, const JSONNode &p,
648 const std::optional<Interpolation> &defaultInterpolation, RooArgSet &constraints)
649{
650 RooWorkspace &ws = *tool.workspace();
651
653 std::string prefixedName = fprefix + "_" + sampleName;
654
655 std::string channelName = fprefix;
656 erasePrefix(channelName, "model_");
657
658 if (!p.has_child("data")) {
659 RooJSONFactoryWSTool::error("sample '" + sampleName + "' does not define a 'data' key");
660 }
661
662 auto &hf = tool.wsEmplace<RooHistFunc>("hist_" + prefixedName, varlist, dh);
663 hf.SetTitle(RooJSONFactoryWSTool::name(p).c_str());
664
667
668 shapeElems.add(tool.wsEmplace<RooBinWidthFunction>(prefixedName + "_binWidth", hf, true));
669
670 if (hasStaterror(p)) {
672 }
673
674 if (p.has_child("modifiers")) {
676 std::vector<double> overall_low;
677 std::vector<double> overall_high;
678 std::vector<int> overall_interp;
679
683 std::vector<int> histoInterp;
684
685 int idx = 0;
686 for (const auto &mod : p["modifiers"].children()) {
687 std::string const &modtype = mod["type"].val();
688 std::string const &sysname =
689 mod.has_child("name")
690 ? mod["name"].val()
691 : (mod.has_child("parameter") ? mod["parameter"].val() : "syst_" + std::to_string(idx));
692 ++idx;
693 if (modtype == "staterror") {
694 // this is dealt with at a different place, ignore it for now
695 } else if (modtype == "normfactor") {
697 constrParam.setError(0.0);
699 if (mod.has_child("constraint") || mod.has_child("constraint_name") || mod.has_child("constraint_type")) {
700 // for norm factors, constraints are optional
702 }
703 } else if (modtype == "normsys") {
704 auto *parameter = mod.find("parameter");
705 std::string parname(parameter ? parameter->val() : "alpha_" + sysname);
706 createNominal(ws, parname, 0.0, -10, 10);
707 auto &par = getOrCreate<RooRealVar>(ws, parname, 0., -5, 5);
708 overall_nps.add(par);
709 auto &data = mod["data"];
710 const std::string context = "interpolation for normsys modifier '" + sysname + "' in sample '" +
711 sampleName + "' of channel '" + channelName + "'";
712 const int interp = interpolationCode(mod, defaultInterpolation, InterpolationClass::Flexible, context);
713 double low = data["lo"].val_double();
714 double high = data["hi"].val_double();
715
716 // the below contains a a hack to cut off variations that go below 0
717 // This is needed because FlexibleInterpVar code 4 interpolates in log-space. Hence, values <= 0 result in
718 // NaN, which propagates throughout the model and causes evaluations to fail. If you know a nicer way to
719 // solve this, please go ahead and fix the lines below.
720 if (interp == 4 && low <= 0)
721 low = std::numeric_limits<double>::epsilon();
722 if (interp == 4 && high <= 0)
723 high = std::numeric_limits<double>::epsilon();
724
725 overall_low.push_back(low);
726 overall_high.push_back(high);
727 overall_interp.push_back(interp);
728
729 constraints.add(getOrCreateConstraint(tool, mod, par, sampleName));
730 } else if (modtype == "histosys") {
731 auto *parameter = mod.find("parameter");
732 std::string parname(parameter ? parameter->val() : "alpha_" + sysname);
733 createNominal(ws, parname, 0.0, -10, 10);
734 auto &par = getOrCreate<RooRealVar>(ws, parname, 0., -5, 5);
735 histNps.add(par);
736 auto &data = mod["data"];
737 histoLo.add(tool.wsEmplace<RooHistFunc>(
738 sysname + "Low_" + prefixedName, varlist,
740 histoHi.add(tool.wsEmplace<RooHistFunc>(
741 sysname + "High_" + prefixedName, varlist,
742 RooJSONFactoryWSTool::readBinnedData(data["hi"], sysname + "High_" + prefixedName, varlist)));
743 const std::string context = "interpolation for histosys modifier '" + sysname + "' in sample '" +
744 sampleName + "' of channel '" + channelName + "'";
745 histoInterp.push_back(interpolationCode(mod, defaultInterpolation, InterpolationClass::Piecewise, context));
746 constraints.add(getOrCreateConstraint(tool, mod, par, sampleName));
747 } else if (modtype == "shapesys" || modtype == "shapefactor") {
748 std::string funcName = channelName + "_" + sysname + "_ShapeSys";
749 // funcName should be "<channel_name>_<sysname>_ShapeSys"
750 std::vector<double> vals;
751 if (mod["data"].has_child("vals")) {
752 for (const auto &v : mod["data"]["vals"].children()) {
753 vals.push_back(v.val_double());
754 }
755 }
756 std::vector<std::string> parnames;
757 for (const auto &v : mod["parameters"].children()) {
758 parnames.push_back(v.val());
759 }
760 if (vals.empty() && parnames.empty()) {
761 RooJSONFactoryWSTool::error("unable to instantiate shapesys '" + sysname +
762 "' with neither values nor parameters!");
763 }
764 std::string constraint = "unknown";
765 std::vector<RooAbsPdf *> constraintPdfs;
766 bool const hasConstraintList = mod.has_child("constraints");
767 if (hasConstraintList) {
768 for (const auto &v : mod["constraints"].children()) {
769 if (v.is_null()) {
770 constraintPdfs.push_back(nullptr);
771 } else {
772 std::string constraintName = v.val();
774 if (!constraintPdf) {
775 RooJSONFactoryWSTool::error("unable to find definition of constraint '" + constraintName +
776 "' for modifier '" + RooJSONFactoryWSTool::name(mod) + "'");
777 }
778 constraintPdfs.push_back(constraintPdf);
779 }
780 }
781 std::size_t const nGammas = std::max(vals.size(), parnames.size());
782 if (constraintPdfs.size() != nGammas) {
783 std::stringstream ss;
784 ss << "modifier '" << RooJSONFactoryWSTool::name(mod) << "' has " << constraintPdfs.size()
785 << " constraints, but " << nGammas << " parameters";
787 }
788 } else if (mod.has_child("constraint_type")) {
789 constraint = mod["constraint_type"].val();
790 } else if (mod.has_child("constraint")) {
791 std::string constraintValue = mod["constraint"].val();
793 constraint = constraintValue;
794 } else {
795 RooJSONFactoryWSTool::error("unable to resolve constraint value '" + constraintValue +
796 "' for modifier '" + RooJSONFactoryWSTool::name(mod) +
797 "': this looks like a legacy workspace where the 'constraint' field is "
798 "not a supported legacy constraint type");
799 }
800 }
801 shapeElems.add(createPHF(funcName, sysname, parnames, vals, tool, constraints, varlist, constraint,
803 /*createConstraints=*/!hasConstraintList));
804 for (auto *constraintPdf : constraintPdfs) {
805 if (constraintPdf) {
806 constraints.add(*constraintPdf);
807 }
808 }
809 } else if (modtype == "custom") {
810 RooAbsReal *obj = ws.function(sysname);
811 if (!obj) {
812 RooJSONFactoryWSTool::error("unable to find custom modifier '" + sysname + "'");
813 }
814 if (obj->dependsOn(varlist)) {
815 shapeElems.add(*obj);
816 } else {
817 normElems.add(*obj);
818 }
819 } else {
820 RooJSONFactoryWSTool::error("modifier '" + sysname + "' of unknown type '" + modtype + "'");
821 }
822 }
823
824 std::string interpName = sampleName + "_" + channelName + "_epsilon";
825 if (!overall_nps.empty()) {
828 normElems.add(v);
829 }
830 if (!histNps.empty()) {
831 auto &v = tool.wsEmplace<PiecewiseInterpolation>("histoSys_" + prefixedName, hf, histoLo, histoHi, histNps,
834 shapeElems.add(v);
835 } else {
836 shapeElems.add(hf);
837 }
838 }
839
840 tool.wsEmplace<RooProduct>(prefixedName + "_shapes", shapeElems);
841 if (!normElems.empty()) {
842 tool.wsEmplace<RooProduct>(prefixedName + "_scaleFactors", normElems);
843 } else {
844 ws.factory("RooConstVar::" + prefixedName + "_scaleFactors(1.)");
845 }
846
847 return true;
848}
849
850class HistFactoryImporter : public RooFit::JSONIO::Importer {
851public:
852 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
853 {
854 std::string name = RooJSONFactoryWSTool::name(p);
855 if (!p.has_child("samples")) {
856 RooJSONFactoryWSTool::error("no samples in '" + name + "', skipping.");
857 }
858 double statErrThresh = 0;
859 std::string statErrType = "Poisson";
860 std::optional<Interpolation> defaultInterpolation;
861 if (p.has_child("default_interpolation")) {
863 readInterpolation(p["default_interpolation"], "default_interpolation of channel '" + name + "'");
864 }
865 if (p.has_child(::Literals::staterror)) {
866 auto &staterr = p[::Literals::staterror];
867 if (staterr.has_child("relThreshold"))
868 statErrThresh = staterr["relThreshold"].val_double();
869 if (staterr.has_child("constraint_type"))
870 statErrType = staterr["constraint_type"].val();
871 }
872 std::vector<double> sumW;
873 std::vector<double> sumW2;
874 std::vector<std::string> gammaParnames;
876
877 std::string fprefix = name;
878
879 std::vector<std::unique_ptr<RooDataHist>> data;
880 for (const auto &comp : p["samples"].children()) {
881 std::unique_ptr<RooDataHist> dh = RooJSONFactoryWSTool::readBinnedData(
882 comp["data"], fprefix + "_" + RooJSONFactoryWSTool::name(comp) + "_dataHist", observables);
883 size_t nbins = dh->numEntries();
884
885 if (hasStaterror(comp)) {
886 if (sumW.empty()) {
887 sumW.resize(nbins);
888 sumW2.resize(nbins);
889 }
890 for (size_t i = 0; i < nbins; ++i) {
891 sumW[i] += dh->weight(i);
892 sumW2[i] += dh->weightSquared(i);
893 }
894 if (gammaParnames.empty()) {
895 if (auto staterrorParams = findStaterror(comp).find("parameters")) {
896 for (const auto &v : staterrorParams->children()) {
897 gammaParnames.push_back(v.val());
898 }
899 }
900 }
901 }
902 data.emplace_back(std::move(dh));
903 }
904
905 RooAbsArg *mcStatObject = nullptr;
906 RooArgSet constraints;
907 if (!sumW.empty()) {
908 std::string channelName = name;
909 erasePrefix(channelName, "model_");
910
911 std::vector<double> errs(sumW.size());
912 for (size_t i = 0; i < sumW.size(); ++i) {
913 if (sumW[i] == 0.) {
914 errs[i] = 0.;
915 continue;
916 }
917 errs[i] = std::sqrt(sumW2[i]) / sumW[i];
918 // avoid negative sigma. This NP will be set constant anyway later
919 errs[i] = std::max(errs[i], 0.);
920 }
921
923 &createPHF("mc_stat_" + channelName, "stat_" + channelName, gammaParnames, errs, *tool, constraints,
925 }
926
927 int idx = 0;
929 RooArgList coefs;
930 for (const auto &comp : p["samples"].children()) {
932 constraints);
933 ++idx;
934
935 std::string const &compName = RooJSONFactoryWSTool::name(comp);
936 funcs.add(*tool->request<RooAbsReal>(fprefix + "_" + compName + "_shapes", name));
937 coefs.add(*tool->request<RooAbsReal>(fprefix + "_" + compName + "_scaleFactors", name));
938 }
939
940 if (constraints.empty()) {
941 tool->wsEmplace<RooRealSumPdf>(name, funcs, coefs, true);
942 } else {
943 std::string sumName = name + "_model";
944 erasePrefix(sumName, "model_");
945 auto &sum = tool->wsEmplace<RooRealSumPdf>(sumName, funcs, coefs, true);
946 sum.SetTitle(name.c_str());
947 tool->wsEmplace<RooProdPdf>(name, constraints, RooFit::Conditional(sum, observables));
948 }
949 return true;
950 }
951};
952
953class FlexibleInterpVarStreamer : public RooFit::JSONIO::Exporter {
954public:
955 std::string const &key() const override
956 {
957 static const std::string keystring = "interpolation0d";
958 return keystring;
959 }
960 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
961 {
962 auto fip = static_cast<const RooStats::HistFactory::FlexibleInterpVar *>(func);
963 const std::size_t nParameters = fip->variables().size();
964 if (fip->low().size() != nParameters || fip->high().size() != nParameters ||
965 fip->interpolationCodes().size() != nParameters) {
966 RooJSONFactoryWSTool::error("FlexibleInterpVar '" + std::string{fip->GetName()} +
967 "' has non-matching parameter, variation, and interpolation lengths");
968 }
969 elem["type"] << key();
970 writeInterpolations(elem["interpolations"], fip->interpolationCodes(), InterpolationClass::Flexible,
971 "FlexibleInterpVar '" + std::string{fip->GetName()} + "'");
972 RooJSONFactoryWSTool::fillSeq(elem["vars"], fip->variables());
973 elem["nom"] << fip->nominal();
974 elem["high"].fill_seq(fip->high(), fip->variables().size());
975 elem["low"].fill_seq(fip->low(), fip->variables().size());
976 return true;
977 }
978};
979
980class PiecewiseInterpolationStreamer : public RooFit::JSONIO::Exporter {
981public:
982 std::string const &key() const override
983 {
984 static const std::string keystring = "interpolation";
985 return keystring;
986 }
987 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
988 {
989 const PiecewiseInterpolation *pip = static_cast<const PiecewiseInterpolation *>(func);
990 const std::size_t nParameters = pip->paramList().size();
991 if (pip->lowList().size() != nParameters || pip->highList().size() != nParameters ||
992 pip->interpolationCodes().size() != nParameters) {
993 RooJSONFactoryWSTool::error("PiecewiseInterpolation '" + std::string{pip->GetName()} +
994 "' has non-matching parameter, variation, and interpolation lengths");
995 }
996 elem["type"] << key();
997 writeInterpolations(elem["interpolations"], pip->interpolationCodes(), InterpolationClass::Piecewise,
998 "PiecewiseInterpolation '" + std::string{pip->GetName()} + "'");
999 elem["positiveDefinite"] << pip->positiveDefinite();
1000 RooJSONFactoryWSTool::fillSeq(elem["vars"], pip->paramList());
1001 elem["nom"] << pip->nominalHist()->GetName();
1002 RooJSONFactoryWSTool::fillSeq(elem["high"], pip->highList(), pip->paramList().size());
1003 RooJSONFactoryWSTool::fillSeq(elem["low"], pip->lowList(), pip->paramList().size());
1004 return true;
1005 }
1006};
1007
1008class PiecewiseInterpolationFactory : public RooFit::JSONIO::Importer {
1009public:
1010 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
1011 {
1012 std::string name(RooJSONFactoryWSTool::name(p));
1013
1014 RooArgList vars{tool->requestArgList<RooAbsReal>(p, "vars")};
1015 RooArgList low{tool->requestArgList<RooAbsReal>(p, "low")};
1016 RooArgList high{tool->requestArgList<RooAbsReal>(p, "high")};
1017 if (vars.size() != low.size() || vars.size() != high.size()) {
1018 RooJSONFactoryWSTool::error("PiecewiseInterpolation '" + name +
1019 "' has non-matching lengths of 'vars', 'high' and 'low'");
1020 }
1021 const std::vector<int> codes =
1022 readInterpolations(p, vars.size(), InterpolationClass::Piecewise, "PiecewiseInterpolation '" + name + "'");
1023
1024 auto &pip =
1025 tool->wsEmplace<PiecewiseInterpolation>(name, *tool->requestArg<RooAbsReal>(p, "nom"), low, high, vars, codes);
1026
1027 pip.setPositiveDefinite(p["positiveDefinite"].val_bool());
1028
1029 return true;
1030 }
1031};
1032
1033class FlexibleInterpVarFactory : public RooFit::JSONIO::Importer {
1034public:
1035 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
1036 {
1037 std::string name(RooJSONFactoryWSTool::name(p));
1038 if (!p.has_child("high")) {
1039 RooJSONFactoryWSTool::error("no high variations of '" + name + "'");
1040 }
1041 if (!p.has_child("low")) {
1042 RooJSONFactoryWSTool::error("no low variations of '" + name + "'");
1043 }
1044 if (!p.has_child("nom")) {
1045 RooJSONFactoryWSTool::error("no nominal variation of '" + name + "'");
1046 }
1047
1048 double nom(p["nom"].val_double());
1049
1050 RooArgList vars{tool->requestArgList<RooRealVar>(p, "vars")};
1051
1052 std::vector<double> high;
1053 high << p["high"];
1054
1055 std::vector<double> low;
1056 low << p["low"];
1057
1058 if (vars.size() != low.size() || vars.size() != high.size()) {
1059 RooJSONFactoryWSTool::error("FlexibleInterpVar '" + name +
1060 "' has non-matching lengths of 'vars', 'high' and 'low'!");
1061 }
1062 const std::vector<int> codes =
1063 readInterpolations(p, vars.size(), InterpolationClass::Flexible, "FlexibleInterpVar '" + name + "'");
1064
1065 tool->wsEmplace<RooStats::HistFactory::FlexibleInterpVar>(name, vars, nom, low, high, codes);
1066
1067 return true;
1068 }
1069};
1070
1071struct NormFactor {
1072 std::string name;
1073 RooAbsReal const *param = nullptr;
1074 RooAbsPdf const *constraint = nullptr;
1075 NormFactor(RooAbsReal const &par, const RooAbsPdf *constr = nullptr)
1076 : name{par.GetName()}, param{&par}, constraint{constr}
1077 {
1078 }
1079};
1080
1081struct NormSys {
1082 std::string name = "";
1083 RooAbsReal const *param = nullptr;
1084 double low = 1.;
1085 double high = 1.;
1086 Interpolation interpolation = multiplicativePolynomialExponential;
1087 RooAbsPdf const *constraint = nullptr;
1088 NormSys() {};
1089 NormSys(const std::string &n, RooAbsReal *const p, double h, double l, Interpolation i, const RooAbsPdf *c)
1090 : name(n), param(p), low(l), high(h), interpolation(std::move(i)), constraint(c)
1091 {
1092 }
1093};
1094
1095struct HistoSys {
1096 std::string name;
1097 RooAbsReal const *param = nullptr;
1098 std::vector<double> low;
1099 std::vector<double> high;
1100 Interpolation interpolation = additivePolynomialLinear;
1101 RooAbsPdf const *constraint = nullptr;
1102 HistoSys(const std::string &n, RooAbsReal *const p, RooHistFunc *l, RooHistFunc *h, Interpolation i,
1103 const RooAbsPdf *c)
1104 : name(n), param(p), interpolation(std::move(i)), constraint(c)
1105 {
1106 low.assign(l->dataHist().weightArray(), l->dataHist().weightArray() + l->dataHist().numEntries());
1107 high.assign(h->dataHist().weightArray(), h->dataHist().weightArray() + h->dataHist().numEntries());
1108 }
1109};
1110struct ShapeSys {
1111 std::string name;
1112 std::vector<double> constraints;
1113 std::vector<RooAbsPdf const *> constraintPdfs;
1114 std::vector<RooAbsReal *> parameters;
1115 ShapeSys(const std::string &n) : name{n} {}
1116};
1117
1118struct GenericElement {
1119 std::string name;
1120 RooAbsReal *function = nullptr;
1121 GenericElement(RooAbsReal *e) : name(e->GetName()), function(e) {};
1122};
1123
1124std::string stripOuterParens(const std::string &s)
1125{
1126 size_t start = 0;
1127 size_t end = s.size();
1128
1129 while (start < end && s[start] == '(' && s[end - 1] == ')') {
1130 int depth = 0;
1131 bool balanced = true;
1132 for (size_t i = start; i < end - 1; ++i) {
1133 if (s[i] == '(')
1134 ++depth;
1135 else if (s[i] == ')')
1136 --depth;
1137 if (depth == 0 && i < end - 1) {
1138 balanced = false;
1139 break;
1140 }
1141 }
1142 if (balanced) {
1143 ++start;
1144 --end;
1145 } else {
1146 break;
1147 }
1148 }
1149 return s.substr(start, end - start);
1150}
1151
1152std::vector<std::string> splitTopLevelProduct(const std::string &expr)
1153{
1154 std::vector<std::string> parts;
1155 int depth = 0;
1156 size_t start = 0;
1157 bool foundTopLevelStar = false;
1158
1159 for (size_t i = 0; i < expr.size(); ++i) {
1160 char c = expr[i];
1161 if (c == '(') {
1162 ++depth;
1163 } else if (c == ')') {
1164 --depth;
1165 } else if (c == '*' && depth == 0) {
1166 foundTopLevelStar = true;
1167 std::string sub = expr.substr(start, i - start);
1168 parts.push_back(stripOuterParens(sub));
1169 start = i + 1;
1170 }
1171 }
1172
1173 if (!foundTopLevelStar) {
1174 return {}; // Not a top-level product
1175 }
1176
1177 std::string sub = expr.substr(start);
1178 parts.push_back(stripOuterParens(sub));
1179 return parts;
1180}
1181
1182NormSys parseOverallModifierFormula(const std::string &s, RooFormulaVar *formula)
1183{
1184 static const std::regex pattern(
1185 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*$)");
1186
1187 NormSys sys;
1188 double sign = 1.0;
1189
1190 std::smatch match;
1191 if (std::regex_match(s, match, pattern)) {
1192 if (match[1].str() == "-") {
1193 sign = -1.0;
1194 }
1195
1196 std::string token2 = match[2].str();
1197 std::string token3 = match[4].str();
1198
1199 RooAbsReal *p2 = static_cast<RooAbsReal *>(formula->getParameter(token2.c_str()));
1200 RooAbsReal *p3 = static_cast<RooAbsReal *>(formula->getParameter(token3.c_str()));
1201 RooRealVar *v2 = dynamic_cast<RooRealVar *>(p2);
1202 RooRealVar *v3 = dynamic_cast<RooRealVar *>(p3);
1203
1204 auto *constr2 = findConstraint(v2);
1205 auto *constr3 = findConstraint(v3);
1206
1207 if (constr2 && !p3) {
1208 sys.name = p2->GetName();
1209 sys.param = p2;
1210 sys.high = sign * toDouble(token3);
1211 sys.low = -sign * toDouble(token3);
1212 } else if (!p2 && constr3) {
1213 sys.name = p3->GetName();
1214 sys.param = p3;
1215 sys.high = sign * toDouble(token2);
1216 sys.low = -sign * toDouble(token2);
1217 } else if (constr2 && p3 && !constr3) {
1218 sys.name = v2->GetName();
1219 sys.param = v2;
1220 sys.high = sign * p3->getVal();
1221 sys.low = -sign * p3->getVal();
1222 } else if (p2 && !constr2 && constr3) {
1223 sys.name = v3->GetName();
1224 sys.param = v3;
1225 sys.high = sign * p2->getVal();
1226 sys.low = -sign * p2->getVal();
1227 }
1228
1229 // Preserve the legacy export behaviour for recognized explicit formulae.
1230 sys.interpolation = multiplicativePiecewiseExponential;
1231
1232 erasePrefix(sys.name, "alpha_");
1233 }
1234 return sys;
1235}
1236
1237void collectElements(RooArgList &elems, RooAbsArg *arg)
1238{
1239 if (auto prod = dynamic_cast<RooProduct *>(arg)) {
1240 for (const auto &e : prod->components()) {
1241 collectElements(elems, e);
1242 }
1243 } else {
1244 elems.add(*arg);
1245 }
1246}
1247
1248bool allRooRealVar(const RooAbsCollection &list)
1249{
1250 for (auto *var : list) {
1251 if (!dynamic_cast<RooRealVar *>(var)) {
1252 return false;
1253 }
1254 }
1255 return true;
1256}
1257
1258struct Sample {
1259 std::string name;
1260 std::vector<double> hist;
1261 std::vector<double> histError;
1262 std::vector<NormFactor> normfactors;
1263 std::vector<NormSys> normsys;
1264 std::vector<HistoSys> histosys;
1265 std::vector<ShapeSys> shapesys;
1266 std::vector<GenericElement> tmpElements;
1267 std::vector<GenericElement> otherElements;
1268 bool useBarlowBeestonLight = false;
1269 std::vector<RooAbsReal *> staterrorParameters;
1270 Sample(const std::string &n) : name{n} {}
1271};
1272
1273void addNormFactor(RooRealVar const *par, Sample &sample, RooWorkspace *ws)
1274{
1275 std::string parname = par->GetName();
1276 bool isConstrained = false;
1277 for (RooAbsArg const *pdf : ws->allPdfs()) {
1278 if (auto gauss = dynamic_cast<RooGaussian const *>(pdf)) {
1279 if (parname == gauss->getX().GetName()) {
1280 sample.normfactors.emplace_back(*par, gauss);
1281 isConstrained = true;
1282 }
1283 }
1284 }
1285 if (!isConstrained)
1286 sample.normfactors.emplace_back(*par);
1287}
1288
1289struct Channel {
1290 std::string name;
1291 std::vector<Sample> samples;
1292 std::map<int, double> tot_yield;
1293 std::map<int, double> tot_yield2;
1294 std::map<int, double> rel_errors;
1295 RooArgSet const *varSet = nullptr;
1296 long unsigned int nBins = 0;
1297};
1298
1300{
1301 Channel channel;
1302
1303 RooWorkspace *ws = tool->workspace();
1304
1305 channel.name = pdfname;
1306 erasePrefix(channel.name, "model_");
1307 eraseSuffix(channel.name, "_model");
1308
1309 for (size_t sampleidx = 0; sampleidx < sumpdf->funcList().size(); ++sampleidx) {
1310 PiecewiseInterpolation *pip = nullptr;
1311 std::vector<ParamHistFunc *> phfs;
1312
1313 const auto func = sumpdf->funcList().at(sampleidx);
1314 Sample sample(func->GetName());
1315 erasePrefix(sample.name, "L_x_");
1316 eraseSuffix(sample.name, "_shapes");
1317 eraseSuffix(sample.name, "_" + channel.name);
1318 erasePrefix(sample.name, pdfname + "_");
1319
1320 auto updateObservables = [&](RooDataHist const &dataHist) {
1321 if (channel.varSet == nullptr) {
1322 channel.varSet = dataHist.get();
1323 channel.nBins = dataHist.numEntries();
1324 }
1325 if (sample.hist.empty()) {
1326 auto *w = dataHist.weightArray();
1327 sample.hist.assign(w, w + dataHist.numEntries());
1328 }
1329 };
1330 auto processElements = [&](const auto &elements, auto &&self) -> void {
1331 for (RooAbsArg *e : elements) {
1332 if (TString(e->GetName()).Contains("binWidth")) {
1333 // The bin width modifiers are handled separately. We can't just
1334 // check for the RooBinWidthFunction type here, because prior to
1335 // ROOT 6.26, the multiplication with the inverse bin width was
1336 // done in a different way (like a normfactor with a RooRealVar,
1337 // but it was stored in the dataset).
1338 // Fortunately, the name was similar, so we can match the modifier
1339 // name.
1340 } else if (auto constVar = dynamic_cast<RooConstVar *>(e)) {
1341 if (constVar->getVal() != 1.) {
1342 sample.normfactors.emplace_back(*constVar);
1343 }
1344 } else if (auto par = dynamic_cast<RooRealVar *>(e)) {
1345 addNormFactor(par, sample, ws);
1346 } else if (auto hf = dynamic_cast<const RooHistFunc *>(e)) {
1347 updateObservables(hf->dataHist());
1348 } else if (ParamHistFunc *phf = dynamic_cast<ParamHistFunc *>(e); phf && allRooRealVar(phf->paramList())) {
1349 phfs.push_back(phf);
1350 } else if (auto fip = dynamic_cast<RooStats::HistFactory::FlexibleInterpVar *>(e)) {
1351 // some (modified) histfactory models have several instances of FlexibleInterpVar
1352 // we collect and merge them
1353 for (size_t i = 0; i < fip->variables().size(); ++i) {
1354 RooAbsReal *var = static_cast<RooAbsReal *>(fip->variables().at(i));
1355 std::string sysname(var->GetName());
1356 erasePrefix(sysname, "alpha_");
1357 const auto *constraint = findConstraint(var);
1358 if (!constraint && !var->isConstant()) {
1359 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
1360 } else {
1361 const std::string context = "normsys modifier '" + sysname + "' in sample '" + sample.name +
1362 "' of channel '" + channel.name + "'";
1363 sample.normsys.emplace_back(
1364 sysname, var, fip->high()[i], fip->low()[i],
1365 interpolationFromCode(fip->interpolationCodes()[i], InterpolationClass::Flexible, context),
1366 constraint);
1367 }
1368 }
1369 } else if (!pip && (pip = dynamic_cast<PiecewiseInterpolation *>(e))) {
1370 // nothing to do here, already assigned
1371 } else if (RooFormulaVar *formula = dynamic_cast<RooFormulaVar *>(e)) {
1372 // people do a lot of fancy stuff with RooFormulaVar, like including NormSys via explicit formulae.
1373 // let's try to decompose it into building blocks
1374 TString expression(formula->expression());
1375 for (size_t i = formula->nParameters(); i--;) {
1376 const RooAbsArg *p = formula->getParameter(i);
1377 expression.ReplaceAll(("x[" + std::to_string(i) + "]").c_str(), p->GetName());
1378 expression.ReplaceAll(("@" + std::to_string(i)).c_str(), p->GetName());
1379 }
1380 auto components = splitTopLevelProduct(expression.Data());
1381 if (components.size() == 0) {
1382 // it's not a product, let's just treat it as an unknown element
1383 sample.otherElements.push_back(formula);
1384 } else {
1385 // it is a prododuct, we can try to handle the elements separately
1386 std::vector<RooAbsArg *> realComponents;
1387 int idx = 0;
1388 for (auto &comp : components) {
1389 // check if this is a trivial element of a product, we can treat it as its own modifier
1390 auto *part = formula->getParameter(comp.c_str());
1391 if (part) {
1392 realComponents.push_back(part);
1393 continue;
1394 }
1395 // check if this is an attempt at explicitly encoding an overallSys
1396 auto normsys = parseOverallModifierFormula(comp, formula);
1397 if (normsys.param) {
1398 sample.normsys.emplace_back(std::move(normsys));
1399 continue;
1400 }
1401
1402 // this is something non-trivial, let's deal with it separately
1403 std::string name = std::string(formula->GetName()) + "_part" + std::to_string(idx);
1404 ++idx;
1405 auto *var = new RooFormulaVar(name.c_str(), name.c_str(), comp.c_str(), formula->dependents());
1406 sample.tmpElements.push_back({var});
1407 }
1408 self(realComponents, self);
1409 }
1410 } else if (auto real = dynamic_cast<RooAbsReal *>(e)) {
1411 sample.otherElements.push_back(real);
1412 }
1413 }
1414 };
1415
1416 RooArgList elems;
1417 collectElements(elems, func);
1418 collectElements(elems, sumpdf->coefList().at(sampleidx));
1420
1421 // see if we can get the observables
1422 if (pip) {
1423 if (auto nh = dynamic_cast<RooHistFunc const *>(pip->nominalHist())) {
1424 updateObservables(nh->dataHist());
1425 }
1426 }
1427
1428 // sort and configure norms
1429 sortByName(sample.normfactors);
1430 sortByName(sample.normsys);
1431
1432 // sort and configure the histosys
1433 if (pip) {
1434 for (size_t i = 0; i < pip->paramList().size(); ++i) {
1435 RooAbsReal *var = static_cast<RooAbsReal *>(pip->paramList().at(i));
1436 std::string sysname(var->GetName());
1437 erasePrefix(sysname, "alpha_");
1438 if (auto lo = dynamic_cast<RooHistFunc *>(pip->lowList().at(i))) {
1439 if (auto hi = dynamic_cast<RooHistFunc *>(pip->highList().at(i))) {
1440 const auto *constraint = findConstraint(var);
1441 if (!constraint && !var->isConstant()) {
1442 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
1443 } else {
1444 const std::string context = "histosys modifier '" + sysname + "' in sample '" + sample.name +
1445 "' of channel '" + channel.name + "'";
1446 sample.histosys.emplace_back(
1447 sysname, var, lo, hi,
1448 interpolationFromCode(pip->interpolationCodes()[i], InterpolationClass::Piecewise, context),
1449 constraint);
1450 }
1451 }
1452 }
1453 }
1454 sortByName(sample.histosys);
1455 }
1456
1457 for (ParamHistFunc *phf : phfs) {
1458 if (startsWith(std::string(phf->GetName()), "mc_stat_")) { // MC stat uncertainty
1459 int idx = 0;
1460 for (const auto &g : phf->paramList()) {
1461 sample.staterrorParameters.push_back(static_cast<RooRealVar *>(g));
1462 ++idx;
1463 RooAbsPdf *constraint = findConstraint(g);
1464 if (channel.tot_yield.find(idx) == channel.tot_yield.end()) {
1465 channel.tot_yield[idx] = 0;
1466 channel.tot_yield2[idx] = 0;
1467 }
1468 channel.tot_yield[idx] += sample.hist[idx - 1];
1469 channel.tot_yield2[idx] += (sample.hist[idx - 1] * sample.hist[idx - 1]);
1470 if (constraint) {
1471 channel.rel_errors[idx] = constraintRelError(*constraint, *g);
1472 }
1473 }
1474 sample.useBarlowBeestonLight = true;
1475 } else { // other ShapeSys
1476 ShapeSys sys(phf->GetName());
1477 erasePrefix(sys.name, channel.name + "_");
1478 bool isshapesys = eraseSuffix(sys.name, "_ShapeSys") || eraseSuffix(sys.name, "_shapeSys");
1479 bool isshapefactor = eraseSuffix(sys.name, "_ShapeFactor") || eraseSuffix(sys.name, "_shapeFactor");
1480
1481 for (const auto &g : phf->paramList()) {
1482 sys.parameters.push_back(static_cast<RooRealVar *>(g));
1483 RooAbsPdf *constraint = nullptr;
1484 if (isshapesys) {
1485 constraint = findConstraint(g);
1486 if (!constraint)
1487 constraint = ws->pdf(constraintName(g->GetName()));
1488 if (!constraint && !g->isConstant()) {
1489 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(g->GetName()));
1490 }
1491 } else if (!isshapefactor) {
1492 RooJSONFactoryWSTool::error("unknown type of shapesys " + std::string(phf->GetName()));
1493 }
1494 if (!constraint) {
1495 sys.constraints.push_back(0.0);
1496 sys.constraintPdfs.push_back(nullptr);
1497 } else {
1498 sys.constraints.push_back(constraintRelError(*constraint, *g));
1499 sys.constraintPdfs.push_back(constraint);
1500 }
1501 }
1502 sample.shapesys.emplace_back(std::move(sys));
1503 }
1504 }
1505 sortByName(sample.shapesys);
1506
1507 // add the sample
1508 channel.samples.emplace_back(std::move(sample));
1509 }
1510
1511 sortByName(channel.samples);
1512 return channel;
1513}
1514
1515bool hasSameMetadata(const RooAbsArg *lhs, const RooAbsArg *rhs)
1516{
1517 if (!lhs || !rhs) {
1518 return lhs == rhs;
1519 }
1520 return std::string{lhs->GetName()} == rhs->GetName() && lhs->IsA() == rhs->IsA();
1521}
1522
1523[[noreturn]] void duplicateModifierError(const Channel &channel, const Sample &sample, std::string_view type,
1524 std::string_view name, std::string_view reason)
1525{
1526 std::stringstream ss;
1527 ss << "cannot combine duplicate modifier '" << name << "' of type '" << type << "' in sample '" << sample.name
1528 << "' of channel '" << channel.name << "': " << reason;
1529 RooJSONFactoryWSTool::error(ss.str().c_str());
1530}
1531
1532void warnDuplicateModifiersCombined(const Channel &channel, const Sample &sample, std::string_view type,
1533 std::string_view name, std::size_t count)
1534{
1535 std::stringstream ss;
1536 ss << "combined " << count << " duplicate modifiers named '" << name << "' of type '" << type << "' in sample '"
1537 << sample.name << "' of channel '" << channel.name << "'";
1539}
1540
1541// Multiplicatively combining two normsys is only faithful when the interpolation is done in log-space, so that
1542// f1(alpha) * f2(alpha) is again representable by a single normsys with the multiplied lo/hi factors. This holds for
1543// the piecewise-exponential code 1 (exact everywhere) and for the default code 4 (exact at the +-1 sigma anchors and in
1544// the exponential extrapolation region). The linear-space codes (e.g. 0 and 2) would turn the product into a shape that
1545// cannot be represented by a single normsys, so those must not be merged.
1546bool normSysSupportsMultiplicativeMerge(const Interpolation &interpolation)
1547{
1548 return interpolation == multiplicativePiecewiseExponential || interpolation == multiplicativePolynomialExponential;
1549}
1550
1551// Combines runs of adjacent modifiers that share the same name (the container is sorted by name beforehand) into a
1552// single modifier. The shared metadata (constraint, parameter and interpolation behaviour) must be identical across the
1553// duplicates; the type-specific `combine` callable performs the actual merge and any additional validation.
1554template <class Modifiers, class CombineFn>
1555void mergeDuplicateModifiers(const Channel &channel, const Sample &sample, Modifiers &modifiers, std::string_view type,
1557{
1559 mergedModifiers.reserve(modifiers.size());
1560
1561 for (std::size_t begin = 0; begin < modifiers.size();) {
1562 std::size_t end = begin + 1;
1563 while (end < modifiers.size() && modifiers[end].name == modifiers[begin].name) {
1564 ++end;
1565 }
1566
1567 auto merged = modifiers[begin];
1568 for (std::size_t i = begin + 1; i < end; ++i) {
1569 const auto &modifier = modifiers[i];
1570 if (!hasSameMetadata(merged.constraint, modifier.constraint)) {
1571 duplicateModifierError(channel, sample, type, merged.name, "constraint metadata differs");
1572 }
1573 if (!hasSameMetadata(merged.param, modifier.param)) {
1574 duplicateModifierError(channel, sample, type, merged.name, "parameter metadata differs");
1575 }
1576 if (merged.interpolation != modifier.interpolation) {
1577 duplicateModifierError(channel, sample, type, merged.name, "interpolation behaviours differ");
1578 }
1580 }
1581
1582 if (end - begin > 1) {
1583 warnDuplicateModifiersCombined(channel, sample, type, merged.name, end - begin);
1584 }
1585 mergedModifiers.emplace_back(std::move(merged));
1586 begin = end;
1587 }
1588
1589 modifiers = std::move(mergedModifiers);
1590}
1591
1592void mergeDuplicateNormSys(const Channel &channel, Sample &sample)
1593{
1594 mergeDuplicateModifiers(channel, sample, sample.normsys, "normsys", [&](NormSys &merged, const NormSys &modifier) {
1595 if (!normSysSupportsMultiplicativeMerge(merged.interpolation)) {
1596 duplicateModifierError(channel, sample, "normsys", merged.name,
1597 "multiplicative combination is only valid for log-space interpolation");
1598 }
1599 merged.low *= modifier.low;
1600 merged.high *= modifier.high;
1601 });
1602}
1603
1604void mergeDuplicateHistoSys(const Channel &channel, Sample &sample)
1605{
1606 const std::size_t nBins = sample.hist.size();
1608 channel, sample, sample.histosys, "histosys", [&](HistoSys &merged, const HistoSys &modifier) {
1609 if (merged.interpolation != additivePolynomialLinear) {
1610 duplicateModifierError(channel, sample, "histosys", merged.name,
1611 "this interpolation cannot currently be combined for duplicate histosys "
1612 "modifiers");
1613 }
1614 if (merged.low.size() != nBins || merged.high.size() != nBins || modifier.low.size() != nBins ||
1615 modifier.high.size() != nBins) {
1616 duplicateModifierError(channel, sample, "histosys", merged.name, "histogram binning differs");
1617 }
1618 for (std::size_t bin = 0; bin < nBins; ++bin) {
1619 merged.low[bin] += modifier.low[bin] - sample.hist[bin];
1620 merged.high[bin] += modifier.high[bin] - sample.hist[bin];
1621 }
1622 });
1623}
1624
1625void ensureUniqueModifiers(const Channel &channel, const Sample &sample)
1626{
1627 std::set<std::pair<std::string, std::string>> seen;
1628 auto add = [&](std::string type, const std::string &name) {
1629 if (!seen.emplace(type, name).second) {
1631 "this modifier type cannot be combined without changing its meaning");
1632 }
1633 };
1634
1635 for (const auto &modifier : sample.normfactors)
1636 add("normfactor", modifier.name);
1637 for (const auto &modifier : sample.normsys)
1638 add("normsys", modifier.name);
1639 for (const auto &modifier : sample.histosys)
1640 add("histosys", modifier.name);
1641 for (const auto &modifier : sample.shapesys)
1642 add("shapesys", modifier.name);
1643 for (const auto &modifier : sample.otherElements)
1644 add("custom", modifier.name);
1645 for (const auto &modifier : sample.tmpElements)
1646 add("custom", modifier.name);
1647 if (sample.useBarlowBeestonLight)
1648 add(::Literals::staterror, ::Literals::staterror);
1649}
1650
1651void canonicalizeModifiers(Channel &channel)
1652{
1653 for (auto &sample : channel.samples) {
1654 mergeDuplicateNormSys(channel, sample);
1656 ensureUniqueModifiers(channel, sample);
1657 }
1658}
1659
1660void configureStatError(Channel &channel)
1661{
1662 for (auto &sample : channel.samples) {
1663 if (sample.useBarlowBeestonLight) {
1664 sample.histError.resize(sample.hist.size());
1665 for (auto bin : channel.rel_errors) {
1666 // reverse engineering the correct partial error
1667 // the (arbitrary) convention used here is that all samples should have the same relative error
1668 const int i = bin.first;
1669 const double relerr_tot = bin.second;
1670 const double count = sample.hist[i - 1];
1671 // this reconstruction is inherently imprecise, so we truncate it at some decimal places to make sure that
1672 // we don't carry around too many useless digits
1673 sample.histError[i - 1] =
1674 round_prec(relerr_tot * channel.tot_yield[i] / std::sqrt(channel.tot_yield2[i]) * count, 7);
1675 }
1676 }
1677 }
1678}
1679
1680std::optional<Interpolation> defaultInterpolation(const Channel &channel)
1681{
1682 std::map<Interpolation, std::size_t> counts;
1683 for (const auto &sample : channel.samples) {
1684 for (const auto &modifier : sample.normsys) {
1685 ++counts[modifier.interpolation];
1686 }
1687 for (const auto &modifier : sample.histosys) {
1688 ++counts[modifier.interpolation];
1689 }
1690 }
1691 if (counts.empty()) {
1692 return std::nullopt;
1693 }
1694
1695 auto best = counts.begin();
1696 for (auto current = std::next(counts.begin()); current != counts.end(); ++current) {
1697 if (current->second > best->second ||
1698 (current->second == best->second && current->first == multiplicativePolynomialExponential &&
1700 best = current;
1701 }
1702 }
1703 return best->first;
1704}
1705
1707{
1708 // Write the constraint reference for any modifier that supports an
1709 // external Gaussian/Poisson/etc. constraint.
1710 auto writeConstraint = [](JSONNode &mod, auto const &sys) {
1711 if (sys.constraint) {
1712 mod["constraint"] << sys.constraint->GetName();
1713 }
1714 };
1715
1716 elem["type"] << "histfactory_dist";
1719 writeInterpolation(elem["default_interpolation"], *channelDefaultInterpolation);
1720 }
1721
1722 bool observablesWritten = false;
1723 for (const auto &sample : channel.samples) {
1724
1725 auto &s = RooJSONFactoryWSTool::appendNamedChild(elem["samples"], sample.name);
1726
1727 auto &modifiers = s["modifiers"];
1728 modifiers.set_seq();
1729
1730 for (const auto &nf : sample.normfactors) {
1731 auto &mod = modifiers.append_child();
1732 mod.set_map();
1733 mod["name"] << nf.name;
1734 mod["parameter"] << nf.param->GetName();
1735 mod["type"] << "normfactor";
1736 if (nf.constraint) {
1737 mod["constraint"] << nf.constraint->GetName();
1738 tool->queueExport(*nf.constraint);
1739 }
1740 }
1741
1742 for (const auto &sys : sample.normsys) {
1743 auto &mod = modifiers.append_child();
1744 mod.set_map();
1745 mod["name"] << sys.name;
1746 mod["type"] << "normsys";
1747 mod["parameter"] << sys.param->GetName();
1748 if (!channelDefaultInterpolation || sys.interpolation != *channelDefaultInterpolation) {
1749 writeInterpolation(mod["interpolation"], sys.interpolation);
1750 }
1751 writeConstraint(mod, sys);
1752 auto &data = mod["data"].set_map();
1753 data["lo"] << sys.low;
1754 data["hi"] << sys.high;
1755 }
1756
1757 for (const auto &sys : sample.histosys) {
1758 auto &mod = modifiers.append_child();
1759 mod.set_map();
1760 mod["name"] << sys.name;
1761 mod["type"] << "histosys";
1762 mod["parameter"] << sys.param->GetName();
1763 if (!channelDefaultInterpolation || sys.interpolation != *channelDefaultInterpolation) {
1764 writeInterpolation(mod["interpolation"], sys.interpolation);
1765 }
1766 writeConstraint(mod, sys);
1767 auto &data = mod["data"].set_map();
1768 if (channel.nBins != sys.low.size() || channel.nBins != sys.high.size()) {
1769 std::stringstream ss;
1770 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sys.low.size() << "/"
1771 << sys.high.size() << " found in nominal histogram errors!";
1772 RooJSONFactoryWSTool::error(ss.str().c_str());
1773 }
1774 RooJSONFactoryWSTool::exportArray(channel.nBins, sys.low.data(), data["lo"].set_map()["contents"]);
1775 RooJSONFactoryWSTool::exportArray(channel.nBins, sys.high.data(), data["hi"].set_map()["contents"]);
1776 }
1777
1778 for (const auto &sys : sample.shapesys) {
1779 auto &mod = modifiers.append_child();
1780 mod.set_map();
1781 mod["name"] << sys.name;
1782 mod["type"] << "shapesys";
1783 optionallyExportGammaParameters(mod, sys.name, sys.parameters);
1784 if (std::any_of(sys.constraintPdfs.begin(), sys.constraintPdfs.end(),
1785 [](auto *pdf) { return pdf != nullptr; })) {
1786 auto &constraintNames = mod["constraints"].set_seq();
1787 for (auto *constraint : sys.constraintPdfs) {
1788 if (constraint) {
1789 constraintNames.append_child() << constraint->GetName();
1790 } else {
1791 constraintNames.append_child().set_null();
1792 }
1793 }
1794 }
1795 mod["data"].set_map()["vals"].fill_seq(sys.constraints);
1796 }
1797
1798 for (const auto &other : sample.otherElements) {
1799 auto &mod = modifiers.append_child();
1800 mod.set_map();
1801 mod["name"] << other.name;
1802 mod["type"] << "custom";
1803 }
1804 for (const auto &other : sample.tmpElements) {
1805 auto &mod = modifiers.append_child();
1806 mod.set_map();
1807 mod["name"] << other.name;
1808 mod["type"] << "custom";
1809 }
1810
1811 if (sample.useBarlowBeestonLight) {
1812 auto &mod = modifiers.append_child();
1813 mod.set_map();
1814 mod["name"] << ::Literals::staterror;
1815 mod["type"] << ::Literals::staterror;
1816 optionallyExportGammaParameters(mod, "stat_" + channel.name, sample.staterrorParameters);
1817 }
1818
1819 if (!observablesWritten) {
1820 auto &output = elem["axes"].set_seq();
1821 for (auto *obs : static_range_cast<RooRealVar *>(*channel.varSet)) {
1822 RooJSONFactoryWSTool::exportAxis(output.append_child().set_map(), *obs);
1823 }
1824 observablesWritten = true;
1825 }
1826 auto &dataNode = s["data"].set_map();
1827 if (channel.nBins != sample.hist.size()) {
1828 std::stringstream ss;
1829 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sample.hist.size()
1830 << " found in nominal histogram!";
1831 RooJSONFactoryWSTool::error(ss.str().c_str());
1832 }
1833 RooJSONFactoryWSTool::exportArray(channel.nBins, sample.hist.data(), dataNode["contents"]);
1834 if (!sample.histError.empty()) {
1835 if (channel.nBins != sample.histError.size()) {
1836 std::stringstream ss;
1837 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sample.histError.size()
1838 << " found in nominal histogram errors!";
1839 RooJSONFactoryWSTool::error(ss.str().c_str());
1840 }
1841 RooJSONFactoryWSTool::exportArray(channel.nBins, sample.histError.data(), dataNode["errors"]);
1842 }
1843 }
1844
1845 return true;
1846}
1847
1848std::vector<RooAbsPdf *> findLostConstraints(const Channel &channel, const std::vector<RooAbsPdf *> &constraints)
1849{
1850 // collect all the vars that are used by the model
1851 std::set<const RooAbsReal *> vars;
1852 for (const auto &sample : channel.samples) {
1853 for (const auto &nf : sample.normfactors) {
1854 vars.insert(nf.param);
1855 }
1856 for (const auto &sys : sample.normsys) {
1857 vars.insert(sys.param);
1858 }
1859
1860 for (const auto &sys : sample.histosys) {
1861 vars.insert(sys.param);
1862 }
1863 for (const auto &sys : sample.shapesys) {
1864 for (const auto &par : sys.parameters) {
1865 vars.insert(par);
1866 }
1867 }
1868 if (sample.useBarlowBeestonLight) {
1869 for (const auto &par : sample.staterrorParameters) {
1870 vars.insert(par);
1871 }
1872 }
1873 }
1874
1875 // check if there is any constraint present that is unrelated to these vars
1876 std::vector<RooAbsPdf *> lostConstraints;
1877 for (auto *pdf : constraints) {
1878 bool related = false;
1879 for (const auto *var : vars) {
1880 if (pdf->dependsOn(*var)) {
1881 related = true;
1882 }
1883 }
1884 if (!related) {
1885 lostConstraints.push_back(pdf);
1886 }
1887 }
1888 // return the constraints that would be "lost" when exporting the model
1889 return lostConstraints;
1890}
1891
1893 std::vector<RooAbsPdf *> constraints, JSONNode &elem)
1894{
1895 // some preliminary checks
1896 if (!sumpdf) {
1897 return false;
1898 }
1899
1900 for (RooAbsArg *sample : sumpdf->funcList()) {
1901 if (!dynamic_cast<RooProduct *>(sample) && !dynamic_cast<RooRealSumPdf *>(sample)) {
1902 return false;
1903 }
1904 }
1905
1906 auto channel = readChannel(tool, pdfname, sumpdf);
1907
1908 // sanity checks
1909 if (channel.samples.size() == 0)
1910 return false;
1911 for (auto &sample : channel.samples) {
1912 if (sample.hist.empty()) {
1913 return false;
1914 }
1915 }
1916
1917 canonicalizeModifiers(channel);
1918
1919 // stat error handling
1920 configureStatError(channel);
1921
1922 auto lostConstraints = findLostConstraints(channel, constraints);
1923 // Export all the lost constraints
1924 for (const auto *constraint : lostConstraints) {
1926 "losing constraint term '" + std::string(constraint->GetName()) +
1927 "', implicit constraints are not supported by HS3 yet! The term will appear in the HS3 file, but will not be "
1928 "picked up when creating a likelihood from it! You will have to add it manually as an external constraint.");
1929 tool->queueExport(*constraint);
1930 }
1931
1932 // Export all the regular modifiers
1933 for (const auto &sample : channel.samples) {
1934 for (auto &modifier : sample.normfactors) {
1935 if (modifier.constraint) {
1936 tool->queueExport(*modifier.constraint);
1937 }
1938 }
1939 for (auto &modifier : sample.normsys) {
1940 if (modifier.constraint) {
1941 tool->queueExport(*modifier.constraint);
1942 }
1943 }
1944 for (auto &modifier : sample.histosys) {
1945 if (modifier.constraint) {
1946 tool->queueExport(*modifier.constraint);
1947 }
1948 }
1949 for (auto &modifier : sample.shapesys) {
1950 for (auto *constraint : modifier.constraintPdfs) {
1951 if (constraint) {
1952 tool->queueExport(*constraint);
1953 }
1954 }
1955 }
1956 }
1957
1958 // Export all the custom modifiers
1959 for (const auto &sample : channel.samples) {
1960 for (auto &modifier : sample.otherElements) {
1961 tool->queueExport(*modifier.function);
1962 }
1963 for (auto &modifier : sample.tmpElements) {
1964 tool->queueExportTemporary(modifier.function);
1965 }
1966 }
1967
1968 // Export all model parameters
1969 RooArgSet parameters;
1970 sumpdf->getParameters(channel.varSet, parameters);
1971 for (RooAbsArg *param : parameters) {
1972 // This should exclude the global observables
1973 if (!startsWith(std::string{param->GetName()}, "nom_")) {
1974 tool->queueExport(*param);
1975 }
1976 }
1977
1978 return exportChannel(tool, channel, elem);
1979}
1980
1981class HistFactoryStreamer_ProdPdf : public RooFit::JSONIO::Exporter {
1982public:
1983 bool autoExportDependants() const override { return false; }
1985 {
1986 std::vector<RooAbsPdf *> constraints;
1987 RooRealSumPdf *sumpdf = nullptr;
1988 for (auto *pdf : static_range_cast<RooAbsPdf *>(prodpdf->pdfList())) {
1989 auto thispdf = dynamic_cast<RooRealSumPdf *>(pdf);
1990 if (thispdf) {
1991 if (!sumpdf)
1992 sumpdf = thispdf;
1993 else
1994 return false;
1995 } else {
1996 constraints.push_back(pdf);
1997 }
1998 }
1999 if (!sumpdf)
2000 return false;
2001
2002 bool ok = tryExportHistFactory(tool, prodpdf->GetName(), sumpdf, constraints, elem);
2003 return ok;
2004 }
2005 std::string const &key() const override
2006 {
2007 static const std::string keystring = "histfactory_dist";
2008 return keystring;
2009 }
2010 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
2011 {
2012 return tryExport(tool, static_cast<const RooProdPdf *>(p), elem);
2013 }
2014};
2015
2016class HistFactoryStreamer_SumPdf : public RooFit::JSONIO::Exporter {
2017public:
2018 bool autoExportDependants() const override { return false; }
2020 {
2021 std::vector<RooAbsPdf *> constraints;
2022 return tryExportHistFactory(tool, sumpdf->GetName(), sumpdf, constraints, elem);
2023 }
2024 std::string const &key() const override
2025 {
2026 static const std::string keystring = "histfactory_dist";
2027 return keystring;
2028 }
2029 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
2030 {
2031 return tryExport(tool, static_cast<const RooRealSumPdf *>(p), elem);
2032 }
2033};
2034
2035STATIC_EXECUTE([]() {
2036 using namespace RooFit::JSONIO;
2037
2038 registerImporter<HistFactoryImporter>("histfactory_dist", true);
2040 registerImporter<FlexibleInterpVarFactory>("interpolation0d", true);
2045});
2046
2047} // 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:145
#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...
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:31
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.
RooFit Lognormal PDF.
Poisson pdf.
Definition RooPoisson.h:19
RooAbsReal const & getMean() const
Get the mean parameter.
Definition RooPoisson.h:48
Efficient implementation of a product of PDFs of the form.
Definition RooProdPdf.h:36
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:641
RooCmdArg RecycleConflictNodes(bool flag=true)
RooCmdArg Conditional(const RooArgSet &pdfSet, const RooArgSet &depSet, bool depsAreCond=false)
const Int_t n
Definition legend1.C:16
double gamma(double x)
void function(const Char_t *name_, T fun, const Char_t *docstring=0)
Definition RExports.h:168
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:2338
static void output()