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 <regex>
37
38#include "static_execute.h"
39#include "JSONIOUtils.h"
40
42
43using namespace RooStats::HistFactory;
44using namespace RooStats::HistFactory::Detail;
46
47namespace {
48
49inline void writeAxis(JSONNode &axis, RooRealVar const &obs)
50{
51 auto &binning = obs.getBinning();
52 if (binning.isUniform()) {
53 axis["nbins"] << obs.numBins();
54 axis["min"] << obs.getMin();
55 axis["max"] << obs.getMax();
56 } else {
57 auto &edges = axis["edges"];
58 edges.set_seq();
59 double val = binning.binLow(0);
60 edges.append_child() << val;
61 for (int i = 0; i < binning.numBins(); ++i) {
62 val = binning.binHigh(i);
63 edges.append_child() << val;
64 }
65 }
66}
67
68double round_prec(double d, int nSig)
69{
70 if (d == 0.0)
71 return 0.0;
72 int ndigits = std::floor(std::log10(std::abs(d))) + 1 - nSig;
73 double sf = std::pow(10, ndigits);
74 if (std::abs(d / sf) < 2)
75 ndigits--;
76 return sf * std::round(d / sf);
77}
78
79// To avoid repeating the same string literals that can potentially get out of
80// sync.
81namespace Literals {
82constexpr auto staterror = "staterror";
83}
84
85void erasePrefix(std::string &str, std::string_view prefix)
86{
87 if (startsWith(str, prefix)) {
88 str.erase(0, prefix.size());
89 }
90}
91
92bool eraseSuffix(std::string &str, std::string_view suffix)
93{
94 if (endsWith(str, suffix)) {
95 str.erase(str.size() - suffix.size());
96 return true;
97 } else {
98 return false;
99 }
100}
101
102template <class Coll>
103void sortByName(Coll &coll)
104{
105 std::sort(coll.begin(), coll.end(), [](auto &l, auto &r) { return l.name < r.name; });
106}
107
108template <class T>
109T *findClient(RooAbsArg *gamma)
110{
111 for (const auto &client : gamma->clients()) {
112 if (auto casted = dynamic_cast<T *>(client)) {
113 return casted;
114 } else {
115 T *c = findClient<T>(client);
116 if (c)
117 return c;
118 }
119 }
120 return nullptr;
121}
122
124{
125 if (!g)
126 return nullptr;
128 if (constraint_p)
129 return constraint_p;
131 if (constraint_g)
132 return constraint_g;
134 if (constraint_l)
135 return constraint_l;
136 return nullptr;
137}
138
139std::string toString(TClass *c)
140{
141 if (!c) {
142 return "Const";
143 }
144 if (c == RooPoisson::Class()) {
145 return "Poisson";
146 }
147 if (c == RooGaussian::Class()) {
148 return "Gauss";
149 }
150 if (c == RooLognormal::Class()) {
151 return "Lognormal";
152 }
153 return "unknown";
154}
155
156inline std::string defaultGammaName(std::string const &sysname, std::size_t i)
157{
158 return "gamma_" + sysname + "_bin_" + std::to_string(i);
159}
160
161/// Export the names of the gamma parameters to the modifier struct if the
162/// names don't match the default gamma parameter names, which is gamma_<sysname>_bin_<i>
163void optionallyExportGammaParameters(JSONNode &mod, std::string const &sysname, std::vector<RooAbsReal *> const &params,
164 bool forceExport = true)
165{
166 std::vector<std::string> paramNames;
167 bool needExport = forceExport;
168 for (std::size_t i = 0; i < params.size(); ++i) {
169 std::string name(params[i]->GetName());
170 paramNames.push_back(name);
171 if (name != defaultGammaName(sysname, i)) {
172 needExport = true;
173 }
174 }
175 if (needExport) {
176 mod["parameters"].fill_seq(paramNames);
177 }
178}
179
180RooRealVar &createNominal(RooWorkspace &ws, std::string const &parname, double val, double min, double max)
181{
182 RooRealVar &nom = getOrCreate<RooRealVar>(ws, "nom_" + parname, val, min, max);
183 nom.setConstant(true);
184 return nom;
185}
186
187/// Get the conventional name of the constraint pdf for a constrained
188/// parameter.
189std::string constraintName(std::string const &paramName)
190{
191 return paramName + "Constraint";
192}
193
194ParamHistFunc &createPHF(const std::string &phfname, std::string const &sysname,
195 const std::vector<std::string> &parnames, const std::vector<double> &vals,
196 RooJSONFactoryWSTool &tool, RooAbsCollection &constraints, const RooArgSet &observables,
197 const std::string &constraintType, double gammaMin, double gammaMax, double minSigma)
198{
199 RooWorkspace &ws = *tool.workspace();
200
201 size_t n = std::max(vals.size(), parnames.size());
203 for (std::size_t i = 0; i < n; ++i) {
204 const std::string name = parnames.empty() ? defaultGammaName(sysname, i) : parnames[i];
205 auto *e = dynamic_cast<RooAbsReal *>(ws.obj(name.c_str()));
206 if (e)
207 gammas.add(*e);
208 else
210 }
211
212 auto &phf = tool.wsEmplace<ParamHistFunc>(phfname, observables, gammas);
213
214 if (vals.size() > 0) {
215 if (constraintType != "Const") {
217 gammas, vals, minSigma, constraintType == "Poisson" ? Constraint::Poisson : Constraint::Gaussian);
218 for (auto const &term : constraintsInfo.constraints) {
220 constraints.add(*ws.pdf(term->GetName()));
221 }
222 } else {
223 for (auto *gamma : static_range_cast<RooRealVar *>(gammas)) {
224 gamma->setConstant(true);
225 }
226 }
227 }
228
229 return phf;
230}
231
232bool hasStaterror(const JSONNode &comp)
233{
234 if (!comp.has_child("modifiers"))
235 return false;
236 for (const auto &mod : comp["modifiers"].children()) {
237 if (mod["type"].val() == ::Literals::staterror)
238 return true;
239 }
240 return false;
241}
242
243const JSONNode &findStaterror(const JSONNode &comp)
244{
245 if (comp.has_child("modifiers")) {
246 for (const auto &mod : comp["modifiers"].children()) {
247 if (mod["type"].val() == ::Literals::staterror)
248 return mod;
249 }
250 }
251 RooJSONFactoryWSTool::error("sample '" + RooJSONFactoryWSTool::name(comp) + "' does not have a " +
252 ::Literals::staterror + " modifier!");
253}
254
255RooAbsPdf &
256getOrCreateConstraint(RooJSONFactoryWSTool &tool, const JSONNode &mod, RooRealVar &param, const std::string &sample)
257{
258 if (auto constrName = mod.find("constraint_name")) {
259 auto constraint_name = constrName->val();
260 auto constraint = tool.workspace()->pdf(constraint_name);
261 if (!constraint) {
262 constraint = tool.request<RooAbsPdf>(constrName->val(), sample);
263 }
264 if (!constraint) {
265 RooJSONFactoryWSTool::error("unable to find definition of of constraint '" + constraint_name +
266 "' for modifier '" + RooJSONFactoryWSTool::name(mod) + "'");
267 }
268 if (auto gauss = dynamic_cast<RooGaussian *const>(constraint)) {
269 param.setError(gauss->getSigma().getVal());
270 }
271 return *constraint;
272 } else {
273 std::string constraint_type = "Gauss";
274 if (auto constrType = mod.find("constraint_type")) {
276 }
277 if (constraint_type == "Gauss") {
278 param.setError(1.0);
279 return getOrCreate<RooGaussian>(*tool.workspace(), constraintName(param.GetName()), param,
280 *tool.workspace()->var(std::string("nom_") + param.GetName()), 1.);
281 }
282 RooJSONFactoryWSTool::error("unknown or invalid constraint for modifier '" + RooJSONFactoryWSTool::name(mod) +
283 "'");
284 }
285}
286
288 RooAbsArg const *mcStatObject, const std::string &fprefix, const JSONNode &p,
289 RooArgSet &constraints)
290{
291 RooWorkspace &ws = *tool.workspace();
292
294 std::string prefixedName = fprefix + "_" + sampleName;
295
296 std::string channelName = fprefix;
297 erasePrefix(channelName, "model_");
298
299 if (!p.has_child("data")) {
300 RooJSONFactoryWSTool::error("sample '" + sampleName + "' does not define a 'data' key");
301 }
302
303 auto &hf = tool.wsEmplace<RooHistFunc>("hist_" + prefixedName, varlist, dh);
304 hf.SetTitle(RooJSONFactoryWSTool::name(p).c_str());
305
308
309 shapeElems.add(tool.wsEmplace<RooBinWidthFunction>(prefixedName + "_binWidth", hf, true));
310
311 if (hasStaterror(p)) {
313 }
314
315 if (p.has_child("modifiers")) {
317 std::vector<double> overall_low;
318 std::vector<double> overall_high;
319 std::vector<int> overall_interp;
320
324
325 int idx = 0;
326 for (const auto &mod : p["modifiers"].children()) {
327 std::string const &modtype = mod["type"].val();
328 std::string const &sysname =
329 mod.has_child("name")
330 ? mod["name"].val()
331 : (mod.has_child("parameter") ? mod["parameter"].val() : "syst_" + std::to_string(idx));
332 ++idx;
333 if (modtype == "staterror") {
334 // this is dealt with at a different place, ignore it for now
335 } else if (modtype == "normfactor") {
338 if (mod.has_child("constraint_name") || mod.has_child("constraint_type")) {
339 // for norm factors, constraints are optional
341 }
342 } else if (modtype == "normsys") {
343 auto *parameter = mod.find("parameter");
344 std::string parname(parameter ? parameter->val() : "alpha_" + sysname);
345 createNominal(ws, parname, 0.0, -10, 10);
346 auto &par = getOrCreate<RooRealVar>(ws, parname, 0., -5, 5);
347 overall_nps.add(par);
348 auto &data = mod["data"];
349 int interp = 4;
350 if (mod.has_child("interpolation")) {
351 interp = mod["interpolation"].val_int();
352 }
353 double low = data["lo"].val_double();
354 double high = data["hi"].val_double();
355
356 // the below contains a a hack to cut off variations that go below 0
357 // this is needed because with interpolation code 4, which is the default, interpolation is done in
358 // log-space. hence, values <= 0 result in NaN which propagate throughout the model and cause evaluations to
359 // fail if you know a nicer way to solve this, please go ahead and fix the lines below
360 if (interp == 4 && low <= 0)
361 low = std::numeric_limits<double>::epsilon();
362 if (interp == 4 && high <= 0)
363 high = std::numeric_limits<double>::epsilon();
364
365 overall_low.push_back(low);
366 overall_high.push_back(high);
367 overall_interp.push_back(interp);
368
369 constraints.add(getOrCreateConstraint(tool, mod, par, sampleName));
370 } else if (modtype == "histosys") {
371 auto *parameter = mod.find("parameter");
372 std::string parname(parameter ? parameter->val() : "alpha_" + sysname);
373 createNominal(ws, parname, 0.0, -10, 10);
374 auto &par = getOrCreate<RooRealVar>(ws, parname, 0., -5, 5);
375 histNps.add(par);
376 auto &data = mod["data"];
377 histoLo.add(tool.wsEmplace<RooHistFunc>(
378 sysname + "Low_" + prefixedName, varlist,
380 histoHi.add(tool.wsEmplace<RooHistFunc>(
381 sysname + "High_" + prefixedName, varlist,
382 RooJSONFactoryWSTool::readBinnedData(data["hi"], sysname + "High_" + prefixedName, varlist)));
383 constraints.add(getOrCreateConstraint(tool, mod, par, sampleName));
384 } else if (modtype == "shapesys" || modtype == "shapefactor") {
385 std::string funcName = channelName + "_" + sysname + "_ShapeSys";
386 // funcName should be "<channel_name>_<sysname>_ShapeSys"
387 std::vector<double> vals;
388 if (mod["data"].has_child("vals")) {
389 for (const auto &v : mod["data"]["vals"].children()) {
390 vals.push_back(v.val_double());
391 }
392 }
393 std::vector<std::string> parnames;
394 for (const auto &v : mod["parameters"].children()) {
395 parnames.push_back(v.val());
396 }
397 if (vals.empty() && parnames.empty()) {
398 RooJSONFactoryWSTool::error("unable to instantiate shapesys '" + sysname +
399 "' with neither values nor parameters!");
400 }
401 std::string constraint(mod.has_child("constraint_type") ? mod["constraint_type"].val()
402 : mod.has_child("constraint") ? mod["constraint"].val()
403 : "unknown");
404 shapeElems.add(createPHF(funcName, sysname, parnames, vals, tool, constraints, varlist, constraint,
406 } else if (modtype == "custom") {
407 RooAbsReal *obj = ws.function(sysname);
408 if (!obj) {
409 RooJSONFactoryWSTool::error("unable to find custom modifier '" + sysname + "'");
410 }
411 if (obj->dependsOn(varlist)) {
412 shapeElems.add(*obj);
413 } else {
414 normElems.add(*obj);
415 }
416 } else {
417 RooJSONFactoryWSTool::error("modifier '" + sysname + "' of unknown type '" + modtype + "'");
418 }
419 }
420
421 std::string interpName = sampleName + "_" + channelName + "_epsilon";
422 if (!overall_nps.empty()) {
425 normElems.add(v);
426 }
427 if (!histNps.empty()) {
428 auto &v = tool.wsEmplace<PiecewiseInterpolation>("histoSys_" + prefixedName, hf, histoLo, histoHi, histNps);
430 v.setAllInterpCodes(4); // default interpCode for HistFactory
431 shapeElems.add(v);
432 } else {
433 shapeElems.add(hf);
434 }
435 }
436
437 tool.wsEmplace<RooProduct>(prefixedName + "_shapes", shapeElems);
438 if (!normElems.empty()) {
439 tool.wsEmplace<RooProduct>(prefixedName + "_scaleFactors", normElems);
440 } else {
441 ws.factory("RooConstVar::" + prefixedName + "_scaleFactors(1.)");
442 }
443
444 return true;
445}
446
447class HistFactoryImporter : public RooFit::JSONIO::Importer {
448public:
449 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
450 {
451 std::string name = RooJSONFactoryWSTool::name(p);
452 if (!p.has_child("samples")) {
453 RooJSONFactoryWSTool::error("no samples in '" + name + "', skipping.");
454 }
455 double statErrThresh = 0;
456 std::string statErrType = "Poisson";
457 if (p.has_child(::Literals::staterror)) {
458 auto &staterr = p[::Literals::staterror];
459 if (staterr.has_child("relThreshold"))
460 statErrThresh = staterr["relThreshold"].val_double();
461 if (staterr.has_child("constraint_type"))
462 statErrType = staterr["constraint_type"].val();
463 }
464 std::vector<double> sumW;
465 std::vector<double> sumW2;
466 std::vector<std::string> gammaParnames;
468
469 std::string fprefix = name;
470
471 std::vector<std::unique_ptr<RooDataHist>> data;
472 for (const auto &comp : p["samples"].children()) {
473 std::unique_ptr<RooDataHist> dh = RooJSONFactoryWSTool::readBinnedData(
474 comp["data"], fprefix + "_" + RooJSONFactoryWSTool::name(comp) + "_dataHist", observables);
475 size_t nbins = dh->numEntries();
476
477 if (hasStaterror(comp)) {
478 if (sumW.empty()) {
479 sumW.resize(nbins);
480 sumW2.resize(nbins);
481 }
482 for (size_t i = 0; i < nbins; ++i) {
483 sumW[i] += dh->weight(i);
484 sumW2[i] += dh->weightSquared(i);
485 }
486 if (gammaParnames.empty()) {
487 if (auto staterrorParams = findStaterror(comp).find("parameters")) {
488 for (const auto &v : staterrorParams->children()) {
489 gammaParnames.push_back(v.val());
490 }
491 }
492 }
493 }
494 data.emplace_back(std::move(dh));
495 }
496
497 RooAbsArg *mcStatObject = nullptr;
498 RooArgSet constraints;
499 if (!sumW.empty()) {
500 std::string channelName = name;
501 erasePrefix(channelName, "model_");
502
503 std::vector<double> errs(sumW.size());
504 for (size_t i = 0; i < sumW.size(); ++i) {
505 if (sumW[i] == 0.) {
506 errs[i] = 0.;
507 continue;
508 }
509 errs[i] = std::sqrt(sumW2[i]) / sumW[i];
510 // avoid negative sigma. This NP will be set constant anyway later
511 errs[i] = std::max(errs[i], 0.);
512 }
513
515 &createPHF("mc_stat_" + channelName, "stat_" + channelName, gammaParnames, errs, *tool, constraints,
517 }
518
519 int idx = 0;
521 RooArgList coefs;
522 for (const auto &comp : p["samples"].children()) {
523 importHistSample(*tool, *data[idx], observables, mcStatObject, fprefix, comp, constraints);
524 ++idx;
525
526 std::string const &compName = RooJSONFactoryWSTool::name(comp);
527 funcs.add(*tool->request<RooAbsReal>(fprefix + "_" + compName + "_shapes", name));
528 coefs.add(*tool->request<RooAbsReal>(fprefix + "_" + compName + "_scaleFactors", name));
529 }
530
531 if (constraints.empty()) {
532 tool->wsEmplace<RooRealSumPdf>(name, funcs, coefs, true);
533 } else {
534 std::string sumName = name + "_model";
535 erasePrefix(sumName, "model_");
536 auto &sum = tool->wsEmplace<RooRealSumPdf>(sumName, funcs, coefs, true);
537 sum.SetTitle(name.c_str());
538 tool->wsEmplace<RooProdPdf>(name, constraints, RooFit::Conditional(sum, observables));
539 }
540 return true;
541 }
542};
543
544class FlexibleInterpVarStreamer : public RooFit::JSONIO::Exporter {
545public:
546 std::string const &key() const override
547 {
548 static const std::string keystring = "interpolation0d";
549 return keystring;
550 }
551 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
552 {
553 auto fip = static_cast<const RooStats::HistFactory::FlexibleInterpVar *>(func);
554 elem["type"] << key();
555 elem["interpolationCodes"].fill_seq(fip->interpolationCodes());
556 RooJSONFactoryWSTool::fillSeq(elem["vars"], fip->variables());
557 elem["nom"] << fip->nominal();
558 elem["high"].fill_seq(fip->high(), fip->variables().size());
559 elem["low"].fill_seq(fip->low(), fip->variables().size());
560 return true;
561 }
562};
563
564class PiecewiseInterpolationStreamer : public RooFit::JSONIO::Exporter {
565public:
566 std::string const &key() const override
567 {
568 static const std::string keystring = "interpolation";
569 return keystring;
570 }
571 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
572 {
573 const PiecewiseInterpolation *pip = static_cast<const PiecewiseInterpolation *>(func);
574 elem["type"] << key();
575 elem["interpolationCodes"].fill_seq(pip->interpolationCodes());
576 elem["positiveDefinite"] << pip->positiveDefinite();
577 RooJSONFactoryWSTool::fillSeq(elem["vars"], pip->paramList());
578 elem["nom"] << pip->nominalHist()->GetName();
579 RooJSONFactoryWSTool::fillSeq(elem["high"], pip->highList(), pip->paramList().size());
580 RooJSONFactoryWSTool::fillSeq(elem["low"], pip->lowList(), pip->paramList().size());
581 return true;
582 }
583};
584
585class PiecewiseInterpolationFactory : public RooFit::JSONIO::Importer {
586public:
587 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
588 {
589 std::string name(RooJSONFactoryWSTool::name(p));
590
591 RooArgList vars{tool->requestArgList<RooAbsReal>(p, "vars")};
592
593 auto &pip = tool->wsEmplace<PiecewiseInterpolation>(name, *tool->requestArg<RooAbsReal>(p, "nom"),
594 tool->requestArgList<RooAbsReal>(p, "low"),
595 tool->requestArgList<RooAbsReal>(p, "high"), vars);
596
597 pip.setPositiveDefinite(p["positiveDefinite"].val_bool());
598
599 if (p.has_child("interpolationCodes")) {
600 std::size_t i = 0;
601 for (auto const &node : p["interpolationCodes"].children()) {
602 pip.setInterpCode(*static_cast<RooAbsReal *>(vars.at(i)), node.val_int(), true);
603 ++i;
604 }
605 }
606
607 return true;
608 }
609};
610
611class FlexibleInterpVarFactory : public RooFit::JSONIO::Importer {
612public:
613 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
614 {
615 std::string name(RooJSONFactoryWSTool::name(p));
616 if (!p.has_child("high")) {
617 RooJSONFactoryWSTool::error("no high variations of '" + name + "'");
618 }
619 if (!p.has_child("low")) {
620 RooJSONFactoryWSTool::error("no low variations of '" + name + "'");
621 }
622 if (!p.has_child("nom")) {
623 RooJSONFactoryWSTool::error("no nominal variation of '" + name + "'");
624 }
625
626 double nom(p["nom"].val_double());
627
628 RooArgList vars{tool->requestArgList<RooRealVar>(p, "vars")};
629
630 std::vector<double> high;
631 high << p["high"];
632
633 std::vector<double> low;
634 low << p["low"];
635
636 if (vars.size() != low.size() || vars.size() != high.size()) {
637 RooJSONFactoryWSTool::error("FlexibleInterpVar '" + name +
638 "' has non-matching lengths of 'vars', 'high' and 'low'!");
639 }
640
641 auto &fip = tool->wsEmplace<RooStats::HistFactory::FlexibleInterpVar>(name, vars, nom, low, high);
642
643 if (p.has_child("interpolationCodes")) {
644 size_t i = 0;
645 for (auto const &node : p["interpolationCodes"].children()) {
646 fip.setInterpCode(*static_cast<RooAbsReal *>(vars.at(i)), node.val_int());
647 ++i;
648 }
649 }
650
651 return true;
652 }
653};
654
655struct NormFactor {
656 std::string name;
657 RooAbsReal const *param = nullptr;
658 RooAbsPdf const *constraint = nullptr;
659 TClass *constraintType = RooGaussian::Class();
660 NormFactor(RooAbsReal const &par, const RooAbsPdf *constr = nullptr)
661 : name{par.GetName()}, param{&par}, constraint{constr}
662 {
663 }
664};
665
666struct NormSys {
667 std::string name = "";
668 RooAbsReal const *param = nullptr;
669 double low = 1.;
670 double high = 1.;
671 int interpolationCode = 4;
672 RooAbsPdf const *constraint = nullptr;
673 TClass *constraintType = RooGaussian::Class();
674 NormSys() {};
675 NormSys(const std::string &n, RooAbsReal *const p, double h, double l, int i, const RooAbsPdf *c)
676 : name(n), param(p), low(l), high(h), interpolationCode(i), constraint(c), constraintType(c->IsA())
677 {
678 }
679};
680
681struct HistoSys {
682 std::string name;
683 RooAbsReal const *param = nullptr;
684 std::vector<double> low;
685 std::vector<double> high;
686 RooAbsPdf const *constraint = nullptr;
687 TClass *constraintType = RooGaussian::Class();
688 HistoSys(const std::string &n, RooAbsReal *const p, RooHistFunc *l, RooHistFunc *h, const RooAbsPdf *c)
689 : name(n), param(p), constraint(c), constraintType(c->IsA())
690 {
691 low.assign(l->dataHist().weightArray(), l->dataHist().weightArray() + l->dataHist().numEntries());
692 high.assign(h->dataHist().weightArray(), h->dataHist().weightArray() + h->dataHist().numEntries());
693 }
694};
695struct ShapeSys {
696 std::string name;
697 std::vector<double> constraints;
698 std::vector<RooAbsReal *> parameters;
699 RooAbsPdf const *constraint = nullptr;
700 TClass *constraintType = RooGaussian::Class();
701 ShapeSys(const std::string &n) : name{n} {}
702};
703
704struct GenericElement {
705 std::string name;
706 RooAbsReal *function = nullptr;
707 GenericElement(RooAbsReal *e) : name(e->GetName()), function(e) {};
708};
709
710std::string stripOuterParens(const std::string &s)
711{
712 size_t start = 0;
713 size_t end = s.size();
714
715 while (start < end && s[start] == '(' && s[end - 1] == ')') {
716 int depth = 0;
717 bool balanced = true;
718 for (size_t i = start; i < end - 1; ++i) {
719 if (s[i] == '(')
720 ++depth;
721 else if (s[i] == ')')
722 --depth;
723 if (depth == 0 && i < end - 1) {
724 balanced = false;
725 break;
726 }
727 }
728 if (balanced) {
729 ++start;
730 --end;
731 } else {
732 break;
733 }
734 }
735 return s.substr(start, end - start);
736}
737
738std::vector<std::string> splitTopLevelProduct(const std::string &expr)
739{
740 std::vector<std::string> parts;
741 int depth = 0;
742 size_t start = 0;
743 bool foundTopLevelStar = false;
744
745 for (size_t i = 0; i < expr.size(); ++i) {
746 char c = expr[i];
747 if (c == '(') {
748 ++depth;
749 } else if (c == ')') {
750 --depth;
751 } else if (c == '*' && depth == 0) {
752 foundTopLevelStar = true;
753 std::string sub = expr.substr(start, i - start);
754 parts.push_back(stripOuterParens(sub));
755 start = i + 1;
756 }
757 }
758
759 if (!foundTopLevelStar) {
760 return {}; // Not a top-level product
761 }
762
763 std::string sub = expr.substr(start);
764 parts.push_back(stripOuterParens(sub));
765 return parts;
766}
767
768#include <regex>
769#include <string>
770#include <cctype>
771#include <cstdlib>
772#include <iostream>
773
774NormSys parseOverallModifierFormula(const std::string &s, RooFormulaVar *formula)
775{
776 static const std::regex pattern(
777 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*$)");
778
779 NormSys sys;
780 double sign = 1.0;
781
782 std::smatch match;
783 if (std::regex_match(s, match, pattern)) {
784 if (match[1].str() == "-") {
785 sign = -1.0;
786 }
787
788 std::string token2 = match[2].str();
789 std::string token3 = match[4].str();
790
791 RooAbsReal *p2 = static_cast<RooAbsReal *>(formula->getParameter(token2.c_str()));
792 RooAbsReal *p3 = static_cast<RooAbsReal *>(formula->getParameter(token3.c_str()));
793 RooRealVar *v2 = dynamic_cast<RooRealVar *>(p2);
794 RooRealVar *v3 = dynamic_cast<RooRealVar *>(p3);
795
796 auto *constr2 = findConstraint(v2);
797 auto *constr3 = findConstraint(v3);
798
799 if (constr2 && !p3) {
800 sys.name = p2->GetName();
801 sys.param = p2;
802 sys.high = sign * toDouble(token3);
803 sys.low = -sign * toDouble(token3);
804 } else if (!p2 && constr3) {
805 sys.name = p3->GetName();
806 sys.param = p3;
807 sys.high = sign * toDouble(token2);
808 sys.low = -sign * toDouble(token2);
809 } else if (constr2 && p3 && !constr3) {
810 sys.name = v2->GetName();
811 sys.param = v2;
812 sys.high = sign * p3->getVal();
813 sys.low = -sign * p3->getVal();
814 } else if (p2 && !constr2 && constr3) {
815 sys.name = v3->GetName();
816 sys.param = v3;
817 sys.high = sign * p2->getVal();
818 sys.low = -sign * p2->getVal();
819 }
820
821 // interpolation code 1 means linear, which is what we have here
822 sys.interpolationCode = 1;
823
824 erasePrefix(sys.name, "alpha_");
825 }
826 return sys;
827}
828
829void collectElements(RooArgSet &elems, RooAbsArg *arg)
830{
831 if (auto prod = dynamic_cast<RooProduct *>(arg)) {
832 for (const auto &e : prod->components()) {
833 collectElements(elems, e);
834 }
835 } else {
836 elems.add(*arg);
837 }
838}
839
840bool allRooRealVar(const RooAbsCollection &list)
841{
842 for (auto *var : list) {
843 if (!dynamic_cast<RooRealVar *>(var)) {
844 return false;
845 }
846 }
847 return true;
848}
849
850struct Sample {
851 std::string name;
852 std::vector<double> hist;
853 std::vector<double> histError;
854 std::vector<NormFactor> normfactors;
855 std::vector<NormSys> normsys;
856 std::vector<HistoSys> histosys;
857 std::vector<ShapeSys> shapesys;
858 std::vector<GenericElement> tmpElements;
859 std::vector<GenericElement> otherElements;
860 bool useBarlowBeestonLight = false;
861 std::vector<RooAbsReal *> staterrorParameters;
862 TClass *barlowBeestonLightConstraintType = RooPoisson::Class();
863 Sample(const std::string &n) : name{n} {}
864};
865
866void addNormFactor(RooRealVar const *par, Sample &sample, RooWorkspace *ws)
867{
868 std::string parname = par->GetName();
869 bool isConstrained = false;
870 for (RooAbsArg const *pdf : ws->allPdfs()) {
871 if (auto gauss = dynamic_cast<RooGaussian const *>(pdf)) {
872 if (parname == gauss->getX().GetName()) {
873 sample.normfactors.emplace_back(*par, gauss);
874 isConstrained = true;
875 }
876 }
877 }
878 if (!isConstrained)
879 sample.normfactors.emplace_back(*par);
880}
881
882namespace {
883
884bool verbose = false;
885
886}
887
888struct Channel {
889 std::string name;
890 std::vector<Sample> samples;
891 std::map<int, double> tot_yield;
892 std::map<int, double> tot_yield2;
893 std::map<int, double> rel_errors;
894 RooArgSet const *varSet = nullptr;
895 long unsigned int nBins = 0;
896};
897
899{
900 Channel channel;
901
902 RooWorkspace *ws = tool->workspace();
903
904 channel.name = pdfname;
905 erasePrefix(channel.name, "model_");
906 eraseSuffix(channel.name, "_model");
907
908 for (size_t sampleidx = 0; sampleidx < sumpdf->funcList().size(); ++sampleidx) {
909 PiecewiseInterpolation *pip = nullptr;
910 std::vector<ParamHistFunc *> phfs;
911
912 const auto func = sumpdf->funcList().at(sampleidx);
913 Sample sample(func->GetName());
914 erasePrefix(sample.name, "L_x_");
915 eraseSuffix(sample.name, "_shapes");
916 eraseSuffix(sample.name, "_" + channel.name);
917 erasePrefix(sample.name, pdfname + "_");
918
919 auto updateObservables = [&](RooDataHist const &dataHist) {
920 if (channel.varSet == nullptr) {
921 channel.varSet = dataHist.get();
922 channel.nBins = dataHist.numEntries();
923 }
924 if (sample.hist.empty()) {
925 auto *w = dataHist.weightArray();
926 sample.hist.assign(w, w + dataHist.numEntries());
927 }
928 };
929 auto processElements = [&](const auto &elements, auto &&self) -> void {
930 for (RooAbsArg *e : elements) {
931 if (TString(e->GetName()).Contains("binWidth")) {
932 // The bin width modifiers are handled separately. We can't just
933 // check for the RooBinWidthFunction type here, because prior to
934 // ROOT 6.26, the multiplication with the inverse bin width was
935 // done in a different way (like a normfactor with a RooRealVar,
936 // but it was stored in the dataset).
937 // Fortunately, the name was similar, so we can match the modifier
938 // name.
939 } else if (auto constVar = dynamic_cast<RooConstVar *>(e)) {
940 if (constVar->getVal() != 1.) {
941 sample.normfactors.emplace_back(*constVar);
942 }
943 } else if (auto par = dynamic_cast<RooRealVar *>(e)) {
944 addNormFactor(par, sample, ws);
945 } else if (auto hf = dynamic_cast<const RooHistFunc *>(e)) {
946 updateObservables(hf->dataHist());
947 } else if (ParamHistFunc *phf = dynamic_cast<ParamHistFunc *>(e); phf && allRooRealVar(phf->paramList())) {
948 phfs.push_back(phf);
949 } else if (auto fip = dynamic_cast<RooStats::HistFactory::FlexibleInterpVar *>(e)) {
950 // some (modified) histfactory models have several instances of FlexibleInterpVar
951 // we collect and merge them
952 for (size_t i = 0; i < fip->variables().size(); ++i) {
953 RooAbsReal *var = static_cast<RooAbsReal *>(fip->variables().at(i));
954 std::string sysname(var->GetName());
955 erasePrefix(sysname, "alpha_");
956 const auto *constraint = findConstraint(var);
957 if (!constraint && !var->isConstant()) {
958 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
959 } else {
960 sample.normsys.emplace_back(sysname, var, fip->high()[i], fip->low()[i],
961 fip->interpolationCodes()[i], constraint);
962 }
963 }
964 } else if (!pip && (pip = dynamic_cast<PiecewiseInterpolation *>(e))) {
965 // nothing to do here, already assigned
966 } else if (RooFormulaVar *formula = dynamic_cast<RooFormulaVar *>(e)) {
967 // people do a lot of fancy stuff with RooFormulaVar, like including NormSys via explicit formulae.
968 // let's try to decompose it into building blocks
969 TString expression(formula->expression());
970 for (size_t i = formula->nParameters(); i--;) {
971 const RooAbsArg *p = formula->getParameter(i);
972 expression.ReplaceAll(("x[" + std::to_string(i) + "]").c_str(), p->GetName());
973 expression.ReplaceAll(("@" + std::to_string(i)).c_str(), p->GetName());
974 }
975 auto components = splitTopLevelProduct(expression.Data());
976 if (components.size() == 0) {
977 // it's not a product, let's just treat it as an unknown element
978 sample.otherElements.push_back(formula);
979 } else {
980 // it is a prododuct, we can try to handle the elements separately
981 std::vector<RooAbsArg *> realComponents;
982 int idx = 0;
983 for (auto &comp : components) {
984 // check if this is a trivial element of a product, we can treat it as its own modifier
985 auto *part = formula->getParameter(comp.c_str());
986 if (part) {
987 realComponents.push_back(part);
988 continue;
989 }
990 // check if this is an attempt at explicitly encoding an overallSys
991 auto normsys = parseOverallModifierFormula(comp, formula);
992 if (normsys.param) {
993 sample.normsys.emplace_back(std::move(normsys));
994 continue;
995 }
996
997 // this is something non-trivial, let's deal with it separately
998 std::string name = std::string(formula->GetName()) + "_part" + std::to_string(idx);
999 ++idx;
1000 auto *var = new RooFormulaVar(name.c_str(), name.c_str(), comp.c_str(), formula->dependents());
1001 sample.tmpElements.push_back({var});
1002 }
1003 self(realComponents, self);
1004 }
1005 } else if (auto real = dynamic_cast<RooAbsReal *>(e)) {
1006 sample.otherElements.push_back(real);
1007 }
1008 }
1009 };
1010
1011 RooArgSet elems;
1012 collectElements(elems, func);
1013 collectElements(elems, sumpdf->coefList().at(sampleidx));
1015
1016 // see if we can get the observables
1017 if (pip) {
1018 if (auto nh = dynamic_cast<RooHistFunc const *>(pip->nominalHist())) {
1019 updateObservables(nh->dataHist());
1020 }
1021 }
1022
1023 // sort and configure norms
1024 sortByName(sample.normfactors);
1025 sortByName(sample.normsys);
1026
1027 // sort and configure the histosys
1028 if (pip) {
1029 for (size_t i = 0; i < pip->paramList().size(); ++i) {
1030 RooAbsReal *var = static_cast<RooAbsReal *>(pip->paramList().at(i));
1031 std::string sysname(var->GetName());
1032 erasePrefix(sysname, "alpha_");
1033 if (auto lo = dynamic_cast<RooHistFunc *>(pip->lowList().at(i))) {
1034 if (auto hi = dynamic_cast<RooHistFunc *>(pip->highList().at(i))) {
1035 const auto *constraint = findConstraint(var);
1036 if (!constraint && !var->isConstant()) {
1037 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
1038 } else {
1039 sample.histosys.emplace_back(sysname, var, lo, hi, constraint);
1040 }
1041 }
1042 }
1043 }
1044 sortByName(sample.histosys);
1045 }
1046
1047 for (ParamHistFunc *phf : phfs) {
1048 if (startsWith(std::string(phf->GetName()), "mc_stat_")) { // MC stat uncertainty
1049 int idx = 0;
1050 for (const auto &g : phf->paramList()) {
1051 sample.staterrorParameters.push_back(static_cast<RooRealVar *>(g));
1052 ++idx;
1053 RooAbsPdf *constraint = findConstraint(g);
1054 if (channel.tot_yield.find(idx) == channel.tot_yield.end()) {
1055 channel.tot_yield[idx] = 0;
1056 channel.tot_yield2[idx] = 0;
1057 }
1058 channel.tot_yield[idx] += sample.hist[idx - 1];
1059 channel.tot_yield2[idx] += (sample.hist[idx - 1] * sample.hist[idx - 1]);
1060 if (constraint) {
1061 sample.barlowBeestonLightConstraintType = constraint->IsA();
1062 if (RooPoisson *constraint_p = dynamic_cast<RooPoisson *>(constraint)) {
1063 double erel = 1. / std::sqrt(constraint_p->getX().getVal());
1064 channel.rel_errors[idx] = erel;
1065 } else if (RooGaussian *constraint_g = dynamic_cast<RooGaussian *>(constraint)) {
1066 double erel = constraint_g->getSigma().getVal() / constraint_g->getMean().getVal();
1067 channel.rel_errors[idx] = erel;
1068 } else {
1070 "currently, only RooPoisson and RooGaussian are supported as constraint types");
1071 }
1072 }
1073 }
1074 sample.useBarlowBeestonLight = true;
1075 } else { // other ShapeSys
1076 ShapeSys sys(phf->GetName());
1077 erasePrefix(sys.name, channel.name + "_");
1078 bool isshapesys = eraseSuffix(sys.name, "_ShapeSys") || eraseSuffix(sys.name, "_shapeSys");
1079 bool isshapefactor = eraseSuffix(sys.name, "_ShapeFactor") || eraseSuffix(sys.name, "_shapeFactor");
1080
1081 for (const auto &g : phf->paramList()) {
1082 sys.parameters.push_back(static_cast<RooRealVar *>(g));
1083 RooAbsPdf *constraint = nullptr;
1084 if (isshapesys) {
1085 constraint = findConstraint(g);
1086 if (!constraint)
1087 constraint = ws->pdf(constraintName(g->GetName()));
1088 if (!constraint && !g->isConstant()) {
1089 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(g->GetName()));
1090 }
1091 } else if (!isshapefactor) {
1092 RooJSONFactoryWSTool::error("unknown type of shapesys " + std::string(phf->GetName()));
1093 }
1094 if (!constraint) {
1095 sys.constraints.push_back(0.0);
1096 } else if (auto constraint_p = dynamic_cast<RooPoisson *>(constraint)) {
1097 sys.constraints.push_back(1. / std::sqrt(constraint_p->getX().getVal()));
1098 if (!sys.constraint) {
1099 sys.constraintType = RooPoisson::Class();
1100 }
1101 } else if (auto constraint_g = dynamic_cast<RooGaussian *>(constraint)) {
1102 sys.constraints.push_back(constraint_g->getSigma().getVal() / constraint_g->getMean().getVal());
1103 if (!sys.constraint) {
1104 sys.constraintType = RooGaussian::Class();
1105 }
1106 }
1107 }
1108 sample.shapesys.emplace_back(std::move(sys));
1109 }
1110 }
1111 sortByName(sample.shapesys);
1112
1113 // add the sample
1114 channel.samples.emplace_back(std::move(sample));
1115 }
1116
1117 sortByName(channel.samples);
1118 return channel;
1119}
1120
1121void configureStatError(Channel &channel)
1122{
1123 for (auto &sample : channel.samples) {
1124 if (sample.useBarlowBeestonLight) {
1125 sample.histError.resize(sample.hist.size());
1126 for (auto bin : channel.rel_errors) {
1127 // reverse engineering the correct partial error
1128 // the (arbitrary) convention used here is that all samples should have the same relative error
1129 const int i = bin.first;
1130 const double relerr_tot = bin.second;
1131 const double count = sample.hist[i - 1];
1132 // this reconstruction is inherently imprecise, so we truncate it at some decimal places to make sure that
1133 // we don't carry around too many useless digits
1134 sample.histError[i - 1] =
1135 round_prec(relerr_tot * channel.tot_yield[i] / std::sqrt(channel.tot_yield2[i]) * count, 7);
1136 }
1137 }
1138 }
1139}
1140
1142{
1143 // Write the constraint reference (either by name or by type) for any
1144 // modifier that supports an external Gaussian/Poisson/etc. constraint.
1145 auto writeConstraint = [](JSONNode &mod, auto const &sys) {
1146 if (sys.constraint) {
1147 mod["constraint_name"] << sys.constraint->GetName();
1148 } else if (sys.constraintType) {
1149 mod["constraint_type"] << toString(sys.constraintType);
1150 }
1151 };
1152
1153 bool observablesWritten = false;
1154 for (const auto &sample : channel.samples) {
1155
1156 elem["type"] << "histfactory_dist";
1157
1158 auto &s = RooJSONFactoryWSTool::appendNamedChild(elem["samples"], sample.name);
1159
1160 auto &modifiers = s["modifiers"];
1161 modifiers.set_seq();
1162
1163 for (const auto &nf : sample.normfactors) {
1164 auto &mod = modifiers.append_child();
1165 mod.set_map();
1166 mod["name"] << nf.name;
1167 mod["parameter"] << nf.param->GetName();
1168 mod["type"] << "normfactor";
1169 if (nf.constraint) {
1170 mod["constraint_name"] << nf.constraint->GetName();
1171 tool->queueExport(*nf.constraint);
1172 }
1173 }
1174
1175 for (const auto &sys : sample.normsys) {
1176 auto &mod = modifiers.append_child();
1177 mod.set_map();
1178 mod["name"] << sys.name;
1179 mod["type"] << "normsys";
1180 mod["parameter"] << sys.param->GetName();
1181 if (sys.interpolationCode != 4) {
1182 mod["interpolation"] << sys.interpolationCode;
1183 }
1184 writeConstraint(mod, sys);
1185 auto &data = mod["data"].set_map();
1186 data["lo"] << sys.low;
1187 data["hi"] << sys.high;
1188 }
1189
1190 for (const auto &sys : sample.histosys) {
1191 auto &mod = modifiers.append_child();
1192 mod.set_map();
1193 mod["name"] << sys.name;
1194 mod["type"] << "histosys";
1195 mod["parameter"] << sys.param->GetName();
1196 writeConstraint(mod, sys);
1197 auto &data = mod["data"].set_map();
1198 if (channel.nBins != sys.low.size() || channel.nBins != sys.high.size()) {
1199 std::stringstream ss;
1200 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sys.low.size() << "/"
1201 << sys.high.size() << " found in nominal histogram errors!";
1202 RooJSONFactoryWSTool::error(ss.str().c_str());
1203 }
1204 RooJSONFactoryWSTool::exportArray(channel.nBins, sys.low.data(), data["lo"].set_map()["contents"]);
1205 RooJSONFactoryWSTool::exportArray(channel.nBins, sys.high.data(), data["hi"].set_map()["contents"]);
1206 }
1207
1208 for (const auto &sys : sample.shapesys) {
1209 auto &mod = modifiers.append_child();
1210 mod.set_map();
1211 mod["name"] << sys.name;
1212 mod["type"] << "shapesys";
1213 optionallyExportGammaParameters(mod, sys.name, sys.parameters);
1214 writeConstraint(mod, sys);
1215 auto &vals = mod["data"].set_map()["vals"];
1216 if (sys.constraint || sys.constraintType) {
1217 vals.fill_seq(sys.constraints);
1218 } else {
1219 vals.fill_seq(std::vector<double>(sys.parameters.size(), 0.0));
1220 }
1221 }
1222
1223 for (const auto &other : sample.otherElements) {
1224 auto &mod = modifiers.append_child();
1225 mod.set_map();
1226 mod["name"] << other.name;
1227 mod["type"] << "custom";
1228 }
1229 for (const auto &other : sample.tmpElements) {
1230 auto &mod = modifiers.append_child();
1231 mod.set_map();
1232 mod["name"] << other.name;
1233 mod["type"] << "custom";
1234 }
1235
1236 if (sample.useBarlowBeestonLight) {
1237 auto &mod = modifiers.append_child();
1238 mod.set_map();
1239 mod["name"] << ::Literals::staterror;
1240 mod["type"] << ::Literals::staterror;
1241 optionallyExportGammaParameters(mod, "stat_" + channel.name, sample.staterrorParameters);
1242 mod["constraint_type"] << toString(sample.barlowBeestonLightConstraintType);
1243 }
1244
1245 if (!observablesWritten) {
1246 auto &output = elem["axes"].set_seq();
1247 for (auto *obs : static_range_cast<RooRealVar *>(*channel.varSet)) {
1248 auto &out = output.append_child().set_map();
1249 std::string name = obs->GetName();
1251 out["name"] << name;
1252 writeAxis(out, *obs);
1253 }
1254 observablesWritten = true;
1255 }
1256 auto &dataNode = s["data"].set_map();
1257 if (channel.nBins != sample.hist.size()) {
1258 std::stringstream ss;
1259 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sample.hist.size()
1260 << " found in nominal histogram!";
1261 RooJSONFactoryWSTool::error(ss.str().c_str());
1262 }
1263 RooJSONFactoryWSTool::exportArray(channel.nBins, sample.hist.data(), dataNode["contents"]);
1264 if (!sample.histError.empty()) {
1265 if (channel.nBins != sample.histError.size()) {
1266 std::stringstream ss;
1267 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sample.histError.size()
1268 << " found in nominal histogram errors!";
1269 RooJSONFactoryWSTool::error(ss.str().c_str());
1270 }
1271 RooJSONFactoryWSTool::exportArray(channel.nBins, sample.histError.data(), dataNode["errors"]);
1272 }
1273 }
1274
1275 return true;
1276}
1277
1278std::vector<RooAbsPdf *> findLostConstraints(const Channel &channel, const std::vector<RooAbsPdf *> &constraints)
1279{
1280 // collect all the vars that are used by the model
1281 std::set<const RooAbsReal *> vars;
1282 for (const auto &sample : channel.samples) {
1283 for (const auto &nf : sample.normfactors) {
1284 vars.insert(nf.param);
1285 }
1286 for (const auto &sys : sample.normsys) {
1287 vars.insert(sys.param);
1288 }
1289
1290 for (const auto &sys : sample.histosys) {
1291 vars.insert(sys.param);
1292 }
1293 for (const auto &sys : sample.shapesys) {
1294 for (const auto &par : sys.parameters) {
1295 vars.insert(par);
1296 }
1297 }
1298 if (sample.useBarlowBeestonLight) {
1299 for (const auto &par : sample.staterrorParameters) {
1300 vars.insert(par);
1301 }
1302 }
1303 }
1304
1305 // check if there is any constraint present that is unrelated to these vars
1306 std::vector<RooAbsPdf *> lostConstraints;
1307 for (auto *pdf : constraints) {
1308 bool related = false;
1309 for (const auto *var : vars) {
1310 if (pdf->dependsOn(*var)) {
1311 related = true;
1312 }
1313 }
1314 if (!related) {
1315 lostConstraints.push_back(pdf);
1316 }
1317 }
1318 // return the constraints that would be "lost" when exporting the model
1319 return lostConstraints;
1320}
1321
1323 std::vector<RooAbsPdf *> constraints, JSONNode &elem)
1324{
1325 // some preliminary checks
1326 if (!sumpdf) {
1327 if (verbose) {
1328 std::cout << pdfname << " is not a sumpdf" << std::endl;
1329 }
1330 return false;
1331 }
1332
1333 for (RooAbsArg *sample : sumpdf->funcList()) {
1334 if (!dynamic_cast<RooProduct *>(sample) && !dynamic_cast<RooRealSumPdf *>(sample)) {
1335 if (verbose)
1336 std::cout << "sample " << sample->GetName() << " is no RooProduct or RooRealSumPdf in " << pdfname
1337 << std::endl;
1338 return false;
1339 }
1340 }
1341
1342 auto channel = readChannel(tool, pdfname, sumpdf);
1343
1344 // sanity checks
1345 if (channel.samples.size() == 0)
1346 return false;
1347 for (auto &sample : channel.samples) {
1348 if (sample.hist.empty()) {
1349 return false;
1350 }
1351 }
1352
1353 // stat error handling
1354 configureStatError(channel);
1355
1356 auto lostConstraints = findLostConstraints(channel, constraints);
1357 // Export all the lost constraints
1358 for (const auto *constraint : lostConstraints) {
1360 "losing constraint term '" + std::string(constraint->GetName()) +
1361 "', implicit constraints are not supported by HS3 yet! The term will appear in the HS3 file, but will not be "
1362 "picked up when creating a likelihood from it! You will have to add it manually as an external constraint.");
1363 tool->queueExport(*constraint);
1364 }
1365
1366 // Export all the regular modifiers
1367 for (const auto &sample : channel.samples) {
1368 for (auto &modifier : sample.normfactors) {
1369 if (modifier.constraint) {
1370 tool->queueExport(*modifier.constraint);
1371 }
1372 }
1373 for (auto &modifier : sample.normsys) {
1374 if (modifier.constraint) {
1375 tool->queueExport(*modifier.constraint);
1376 }
1377 }
1378 for (auto &modifier : sample.histosys) {
1379 if (modifier.constraint) {
1380 tool->queueExport(*modifier.constraint);
1381 }
1382 }
1383 }
1384
1385 // Export all the custom modifiers
1386 for (const auto &sample : channel.samples) {
1387 for (auto &modifier : sample.otherElements) {
1388 tool->queueExport(*modifier.function);
1389 }
1390 for (auto &modifier : sample.tmpElements) {
1391 tool->queueExportTemporary(modifier.function);
1392 }
1393 }
1394
1395 // Export all model parameters
1396 RooArgSet parameters;
1397 sumpdf->getParameters(channel.varSet, parameters);
1398 for (RooAbsArg *param : parameters) {
1399 // This should exclude the global observables
1400 if (!startsWith(std::string{param->GetName()}, "nom_")) {
1401 tool->queueExport(*param);
1402 }
1403 }
1404
1405 return exportChannel(tool, channel, elem);
1406}
1407
1408class HistFactoryStreamer_ProdPdf : public RooFit::JSONIO::Exporter {
1409public:
1410 bool autoExportDependants() const override { return false; }
1412 {
1413 std::vector<RooAbsPdf *> constraints;
1414 RooRealSumPdf *sumpdf = nullptr;
1415 for (RooAbsArg *v : prodpdf->pdfList()) {
1416 RooAbsPdf *pdf = static_cast<RooAbsPdf *>(v);
1417 auto thispdf = dynamic_cast<RooRealSumPdf *>(pdf);
1418 if (thispdf) {
1419 if (!sumpdf)
1420 sumpdf = thispdf;
1421 else
1422 return false;
1423 } else {
1424 constraints.push_back(pdf);
1425 }
1426 }
1427 if (!sumpdf)
1428 return false;
1429
1430 bool ok = tryExportHistFactory(tool, prodpdf->GetName(), sumpdf, constraints, elem);
1431 return ok;
1432 }
1433 std::string const &key() const override
1434 {
1435 static const std::string keystring = "histfactory_dist";
1436 return keystring;
1437 }
1438 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
1439 {
1440 return tryExport(tool, static_cast<const RooProdPdf *>(p), elem);
1441 }
1442};
1443
1444class HistFactoryStreamer_SumPdf : public RooFit::JSONIO::Exporter {
1445public:
1446 bool autoExportDependants() const override { return false; }
1448 {
1449 std::vector<RooAbsPdf *> constraints;
1450 return tryExportHistFactory(tool, sumpdf->GetName(), sumpdf, constraints, elem);
1451 }
1452 std::string const &key() const override
1453 {
1454 static const std::string keystring = "histfactory_dist";
1455 return keystring;
1456 }
1457 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
1458 {
1459 return tryExport(tool, static_cast<const RooRealSumPdf *>(p), elem);
1460 }
1461};
1462
1463STATIC_EXECUTE([]() {
1464 using namespace RooFit::JSONIO;
1465
1466 registerImporter<HistFactoryImporter>("histfactory_dist", true);
1468 registerImporter<FlexibleInterpVarFactory>("interpolation0d", true);
1473});
1474
1475} // 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.
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 funcs
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t modifier
char name[80]
Definition TGX11.cxx:145
#define hi
TClass * IsA() const override
Definition TStringLong.h:20
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
TClass * IsA() const override
Definition RooAbsPdf.h:345
Int_t numBins(const char *rangeName=nullptr) const override
virtual double getMax(const char *name=nullptr) const
Get maximum of currently defined range.
virtual double getMin(const char *name=nullptr) const
Get minimum of currently defined range.
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
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 JSONNode & set_seq()=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
static TClass * Class()
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 bool testValidName(const std::string &str, bool forcError)
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.
static TClass * Class()
Poisson pdf.
Definition RooPoisson.h:19
static TClass * Class()
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
const RooAbsBinning & getBinning(const char *name=nullptr, bool verbose=true, bool createOnTheFly=false, bool shared=true) const override
Return binning definition with name.
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:69
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.
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
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
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()