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 <RooLognormal.h>
30#include <RooGaussian.h>
31#include <RooProduct.h>
32#include <RooWorkspace.h>
33
34#include "static_execute.h"
35#include "JSONIOUtils.h"
36
38
39using namespace RooStats::HistFactory;
40using namespace RooStats::HistFactory::Detail;
42
43namespace {
44
45inline void writeAxis(JSONNode &axis, RooRealVar const &obs)
46{
47 auto &binning = obs.getBinning();
48 if (binning.isUniform()) {
49 axis["nbins"] << obs.numBins();
50 axis["min"] << obs.getMin();
51 axis["max"] << obs.getMax();
52 } else {
53 auto &edges = axis["edges"];
54 edges.set_seq();
55 double val = binning.binLow(0);
56 edges.append_child() << val;
57 for (int i = 0; i < binning.numBins(); ++i) {
58 val = binning.binHigh(i);
59 edges.append_child() << val;
60 }
61 }
62}
63
64double round_prec(double d, int nSig)
65{
66 if (d == 0.0)
67 return 0.0;
68 int ndigits = std::floor(std::log10(std::abs(d))) + 1 - nSig;
69 double sf = std::pow(10, ndigits);
70 if (std::abs(d / sf) < 2)
71 ndigits--;
72 return sf * std::round(d / sf);
73}
74
75// To avoid repeating the same string literals that can potentially get out of
76// sync.
77namespace Literals {
78constexpr auto staterror = "staterror";
79}
80
81void erasePrefix(std::string &str, std::string_view prefix)
82{
83 if (startsWith(str, prefix)) {
84 str.erase(0, prefix.size());
85 }
86}
87
88bool eraseSuffix(std::string &str, std::string_view suffix)
89{
90 if (endsWith(str, suffix)) {
91 str.erase(str.size() - suffix.size());
92 return true;
93 } else {
94 return false;
95 }
96}
97
98template <class Coll>
99void sortByName(Coll &coll)
100{
101 std::sort(coll.begin(), coll.end(), [](auto &l, auto &r) { return l.name < r.name; });
102}
103
104template <class T>
105T *findClient(RooAbsArg *gamma)
106{
107 for (const auto &client : gamma->clients()) {
108 if (auto casted = dynamic_cast<T *>(client)) {
109 return casted;
110 } else {
111 T *c = findClient<T>(client);
112 if (c)
113 return c;
114 }
115 }
116 return nullptr;
117}
118
120{
122 if (constraint_p)
123 return constraint_p;
125 if (constraint_g)
126 return constraint_g;
128 if (constraint_l)
129 return constraint_l;
130 return nullptr;
131}
132
133std::string toString(TClass *c)
134{
135 if (!c) {
136 return "Const";
137 }
138 if (c == RooPoisson::Class()) {
139 return "Poisson";
140 }
141 if (c == RooGaussian::Class()) {
142 return "Gauss";
143 }
144 if (c == RooLognormal::Class()) {
145 return "Lognormal";
146 }
147 return "unknown";
148}
149
150inline std::string defaultGammaName(std::string const &sysname, std::size_t i)
151{
152 return "gamma_" + sysname + "_bin_" + std::to_string(i);
153}
154
155/// Export the names of the gamma parameters to the modifier struct if the
156/// names don't match the default gamma parameter names, which is gamma_<sysname>_bin_<i>
157void optionallyExportGammaParameters(JSONNode &mod, std::string const &sysname,
158 std::vector<std::string> const &paramNames)
159{
160 for (std::size_t i = 0; i < paramNames.size(); ++i) {
161 if (paramNames[i] != defaultGammaName(sysname, i)) {
162 mod["parameters"].fill_seq(paramNames);
163 return;
164 }
165 }
166}
167
168RooRealVar &createNominal(RooWorkspace &ws, std::string const &parname, double val, double min, double max)
169{
170 RooRealVar &nom = getOrCreate<RooRealVar>(ws, "nom_" + parname, val, min, max);
171 nom.setConstant(true);
172 return nom;
173}
174
175/// Get the conventional name of the constraint pdf for a constrained
176/// parameter.
177std::string constraintName(std::string const &paramName)
178{
179 return paramName + "Constraint";
180}
181
182RooAbsPdf &getConstraint(RooWorkspace &ws, const std::string &pname)
183{
185 constrParam->setError(1.0);
186 return getOrCreate<RooGaussian>(ws, constraintName(pname), *constrParam, *ws.var("nom_" + pname), 1.);
187}
188
189ParamHistFunc &createPHF(const std::string &phfname, std::string const &sysname,
190 const std::vector<std::string> &parnames, const std::vector<double> &vals,
191 RooJSONFactoryWSTool &tool, RooArgList &constraints, const RooArgSet &observables,
192 const std::string &constraintType, double gammaMin, double gammaMax, double minSigma)
193{
194 RooWorkspace &ws = *tool.workspace();
195
197 for (std::size_t i = 0; i < vals.size(); ++i) {
198 const std::string name = parnames.empty() ? defaultGammaName(sysname, i) : parnames[i];
200 }
201
202 auto &phf = tool.wsEmplace<ParamHistFunc>(phfname, observables, gammas);
203
204 if (constraintType != "Const") {
206 gammas, vals, minSigma, constraintType == "Poisson" ? Constraint::Poisson : Constraint::Gaussian);
207 for (auto const &term : constraintsInfo.constraints) {
209 constraints.add(*ws.pdf(term->GetName()));
210 }
211 } else {
212 for (auto *gamma : static_range_cast<RooRealVar *>(gammas)) {
213 gamma->setConstant(true);
214 }
215 }
216
217 return phf;
218}
219
220bool hasStaterror(const JSONNode &comp)
221{
222 if (!comp.has_child("modifiers"))
223 return false;
224 for (const auto &mod : comp["modifiers"].children()) {
225 if (mod["type"].val() == ::Literals::staterror)
226 return true;
227 }
228 return false;
229}
230
231const JSONNode &findStaterror(const JSONNode &comp)
232{
233 if (comp.has_child("modifiers")) {
234 for (const auto &mod : comp["modifiers"].children()) {
235 if (mod["type"].val() == ::Literals::staterror)
236 return mod;
237 }
238 }
239 RooJSONFactoryWSTool::error("sample '" + RooJSONFactoryWSTool::name(comp) + "' does not have a " +
240 ::Literals::staterror + " modifier!");
241}
242
244 RooAbsArg const *mcStatObject, const std::string &fprefix, const JSONNode &p,
245 RooArgList &constraints)
246{
247 RooWorkspace &ws = *tool.workspace();
248
250 std::string prefixedName = fprefix + "_" + sampleName;
251
252 std::string channelName = fprefix;
253 erasePrefix(channelName, "model_");
254
255 if (!p.has_child("data")) {
256 RooJSONFactoryWSTool::error("sample '" + sampleName + "' does not define a 'data' key");
257 }
258
259 auto &hf = tool.wsEmplace<RooHistFunc>("hist_" + prefixedName, varlist, dh);
260 hf.SetTitle(RooJSONFactoryWSTool::name(p).c_str());
261
264
265 shapeElems.add(tool.wsEmplace<RooBinWidthFunction>(prefixedName + "_binWidth", hf, true));
266
267 if (hasStaterror(p)) {
269 }
270
271 if (p.has_child("modifiers")) {
273 std::vector<double> overall_low;
274 std::vector<double> overall_high;
275
279
280 int idx = 0;
281 for (const auto &mod : p["modifiers"].children()) {
282 std::string const &modtype = mod["type"].val();
283 std::string const &sysname =
284 mod.has_child("name")
285 ? mod["name"].val()
286 : (mod.has_child("parameter") ? mod["parameter"].val() : "syst_" + std::to_string(idx));
287 ++idx;
288 if (modtype == "staterror") {
289 // this is dealt with at a different place, ignore it for now
290 } else if (modtype == "normfactor") {
293 if (auto constrInfo = mod.find("constraint_name")) {
294 auto constraint = tool.request<RooAbsReal>(constrInfo->val(), sampleName);
295 if (auto gauss = dynamic_cast<RooGaussian const *>(constraint)) {
296 constrParam.setError(gauss->getSigma().getVal());
297 }
298 constraints.add(*constraint);
299 }
300 } else if (modtype == "normsys") {
301 auto *parameter = mod.find("parameter");
302 std::string parname(parameter ? parameter->val() : "alpha_" + sysname);
303 createNominal(ws, parname, 0.0, -10, 10);
304 overall_nps.add(getOrCreate<RooRealVar>(ws, parname, 0., -5, 5));
305 auto &data = mod["data"];
306 // the below contains a a hack to cut off variations that go below 0
307 // this is needed because with interpolation code 4, which is the default, interpolation is done in
308 // log-space. hence, values <= 0 result in NaN which propagate throughout the model and cause evaluations to
309 // fail if you know a nicer way to solve this, please go ahead and fix the lines below
310 overall_low.push_back(data["lo"].val_double() > 0 ? data["lo"].val_double()
311 : std::numeric_limits<double>::epsilon());
312 overall_high.push_back(data["hi"].val_double() > 0 ? data["hi"].val_double()
313 : std::numeric_limits<double>::epsilon());
314 constraints.add(getConstraint(ws, parname));
315 } else if (modtype == "histosys") {
316 auto *parameter = mod.find("parameter");
317 std::string parname(parameter ? parameter->val() : "alpha_" + sysname);
318 createNominal(ws, parname, 0.0, -10, 10);
319 histNps.add(getOrCreate<RooRealVar>(ws, parname, 0., -5, 5));
320 auto &data = mod["data"];
321 histoLo.add(tool.wsEmplace<RooHistFunc>(
322 sysname + "Low_" + prefixedName, varlist,
324 histoHi.add(tool.wsEmplace<RooHistFunc>(
325 sysname + "High_" + prefixedName, varlist,
326 RooJSONFactoryWSTool::readBinnedData(data["hi"], sysname + "High_" + prefixedName, varlist)));
327 constraints.add(getConstraint(ws, parname));
328 } else if (modtype == "shapesys") {
329 std::string funcName = channelName + "_" + sysname + "_ShapeSys";
330 // funcName should be "<channel_name>_<sysname>_ShapeSys"
331 std::vector<double> vals;
332 for (const auto &v : mod["data"]["vals"].children()) {
333 vals.push_back(v.val_double());
334 }
335 std::vector<std::string> parnames;
336 for (const auto &v : mod["parameters"].children()) {
337 parnames.push_back(v.val());
338 }
339 if (vals.empty()) {
340 RooJSONFactoryWSTool::error("unable to instantiate shapesys '" + sysname + "' with 0 values!");
341 }
342 std::string constraint(mod["constraint"].val());
343 shapeElems.add(createPHF(funcName, sysname, parnames, vals, tool, constraints, varlist, constraint,
345 } else if (modtype == "custom") {
346 RooAbsReal *obj = ws.function(sysname);
347 if (!obj) {
348 RooJSONFactoryWSTool::error("unable to find custom modifier '" + sysname + "'");
349 }
350 if (obj->dependsOn(varlist)) {
351 shapeElems.add(*obj);
352 } else {
353 normElems.add(*obj);
354 }
355 } else {
356 RooJSONFactoryWSTool::error("modifier '" + sysname + "' of unknown type '" + modtype + "'");
357 }
358 }
359
360 std::string interpName = sampleName + "_" + channelName + "_epsilon";
361 if (!overall_nps.empty()) {
364 v.setAllInterpCodes(4); // default HistFactory interpCode
365 normElems.add(v);
366 }
367 if (!histNps.empty()) {
368 auto &v = tool.wsEmplace<PiecewiseInterpolation>("histoSys_" + prefixedName, hf, histoLo, histoHi, histNps);
370 v.setAllInterpCodes(4); // default interpCode for HistFactory
371 shapeElems.add(v);
372 } else {
373 shapeElems.add(hf);
374 }
375 }
376
377 tool.wsEmplace<RooProduct>(prefixedName + "_shapes", shapeElems);
378 if (!normElems.empty()) {
379 tool.wsEmplace<RooProduct>(prefixedName + "_scaleFactors", normElems);
380 } else {
381 ws.factory("RooConstVar::" + prefixedName + "_scaleFactors(1.)");
382 }
383
384 return true;
385}
386
387class HistFactoryImporter : public RooFit::JSONIO::Importer {
388public:
389 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
390 {
391 std::string name = RooJSONFactoryWSTool::name(p);
392 if (!p.has_child("samples")) {
393 RooJSONFactoryWSTool::error("no samples in '" + name + "', skipping.");
394 }
395 double statErrThresh = 0;
396 std::string statErrType = "Poisson";
397 if (p.has_child(::Literals::staterror)) {
398 auto &staterr = p[::Literals::staterror];
399 if (staterr.has_child("relThreshold"))
400 statErrThresh = staterr["relThreshold"].val_double();
401 if (staterr.has_child("constraint"))
402 statErrType = staterr["constraint"].val();
403 }
404 std::vector<double> sumW;
405 std::vector<double> sumW2;
406 std::vector<std::string> gammaParnames;
408
409 std::string fprefix = name;
410
411 std::vector<std::unique_ptr<RooDataHist>> data;
412 for (const auto &comp : p["samples"].children()) {
413 std::unique_ptr<RooDataHist> dh = RooJSONFactoryWSTool::readBinnedData(
414 comp["data"], fprefix + "_" + RooJSONFactoryWSTool::name(comp) + "_dataHist", observables);
415 size_t nbins = dh->numEntries();
416
417 if (hasStaterror(comp)) {
418 if (sumW.empty()) {
419 sumW.resize(nbins);
420 sumW2.resize(nbins);
421 }
422 for (size_t i = 0; i < nbins; ++i) {
423 sumW[i] += dh->weight(i);
424 sumW2[i] += dh->weightSquared(i);
425 }
426 if (gammaParnames.empty()) {
427 if (auto staterrorParams = findStaterror(comp).find("parameters")) {
428 for (const auto &v : staterrorParams->children()) {
429 gammaParnames.push_back(v.val());
430 }
431 }
432 }
433 }
434 data.emplace_back(std::move(dh));
435 }
436
437 RooAbsArg *mcStatObject = nullptr;
438 RooArgList constraints;
439 if (!sumW.empty()) {
440 std::string channelName = name;
441 erasePrefix(channelName, "model_");
442
443 std::vector<double> errs(sumW.size());
444 for (size_t i = 0; i < sumW.size(); ++i) {
445 errs[i] = std::sqrt(sumW2[i]) / sumW[i];
446 // avoid negative sigma. This NP will be set constant anyway later
447 errs[i] = std::max(errs[i], 0.);
448 }
449
451 &createPHF("mc_stat_" + channelName, "stat_" + channelName, gammaParnames, errs, *tool, constraints,
453 }
454
455 int idx = 0;
457 RooArgList coefs;
458 for (const auto &comp : p["samples"].children()) {
459 importHistSample(*tool, *data[idx], observables, mcStatObject, fprefix, comp, constraints);
460 ++idx;
461
462 std::string const &compName = RooJSONFactoryWSTool::name(comp);
463 funcs.add(*tool->request<RooAbsReal>(fprefix + "_" + compName + "_shapes", name));
464 coefs.add(*tool->request<RooAbsReal>(fprefix + "_" + compName + "_scaleFactors", name));
465 }
466
467 if (constraints.empty()) {
468 tool->wsEmplace<RooRealSumPdf>(name, funcs, coefs, true);
469 } else {
470 std::string sumName = name + "_model";
471 erasePrefix(sumName, "model_");
472 auto &sum = tool->wsEmplace<RooRealSumPdf>(sumName, funcs, coefs, true);
473 sum.SetTitle(name.c_str());
474 tool->wsEmplace<RooProdPdf>(name, constraints, RooFit::Conditional(sum, observables));
475 }
476 return true;
477 }
478};
479
480class FlexibleInterpVarStreamer : public RooFit::JSONIO::Exporter {
481public:
482 std::string const &key() const override
483 {
484 static const std::string keystring = "interpolation0d";
485 return keystring;
486 }
487 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
488 {
489 auto fip = static_cast<const RooStats::HistFactory::FlexibleInterpVar *>(func);
490 elem["type"] << key();
491 elem["interpolationCodes"].fill_seq(fip->interpolationCodes());
492 RooJSONFactoryWSTool::fillSeq(elem["vars"], fip->variables());
493 elem["nom"] << fip->nominal();
494 elem["high"].fill_seq(fip->high(), fip->variables().size());
495 elem["low"].fill_seq(fip->low(), fip->variables().size());
496 return true;
497 }
498};
499
500class PiecewiseInterpolationStreamer : public RooFit::JSONIO::Exporter {
501public:
502 std::string const &key() const override
503 {
504 static const std::string keystring = "interpolation";
505 return keystring;
506 }
507 bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override
508 {
509 const PiecewiseInterpolation *pip = static_cast<const PiecewiseInterpolation *>(func);
510 elem["type"] << key();
511 elem["interpolationCodes"].fill_seq(pip->interpolationCodes());
512 elem["positiveDefinite"] << pip->positiveDefinite();
513 RooJSONFactoryWSTool::fillSeq(elem["vars"], pip->paramList());
514 elem["nom"] << pip->nominalHist()->GetName();
515 RooJSONFactoryWSTool::fillSeq(elem["high"], pip->highList(), pip->paramList().size());
516 RooJSONFactoryWSTool::fillSeq(elem["low"], pip->lowList(), pip->paramList().size());
517 return true;
518 }
519};
520
521class PiecewiseInterpolationFactory : public RooFit::JSONIO::Importer {
522public:
523 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
524 {
525 std::string name(RooJSONFactoryWSTool::name(p));
526
527 RooArgList vars{tool->requestArgList<RooRealVar>(p, "vars")};
528
529 auto &pip = tool->wsEmplace<PiecewiseInterpolation>(name, *tool->requestArg<RooAbsReal>(p, "nom"),
530 tool->requestArgList<RooAbsReal>(p, "low"),
531 tool->requestArgList<RooAbsReal>(p, "high"), vars);
532
533 pip.setPositiveDefinite(p["positiveDefinite"].val_bool());
534
535 if (p.has_child("interpolationCodes")) {
536 std::size_t i = 0;
537 for (auto const &node : p["interpolationCodes"].children()) {
538 pip.setInterpCode(*static_cast<RooAbsReal *>(vars.at(i)), node.val_int(), true);
539 ++i;
540 }
541 }
542
543 return true;
544 }
545};
546
547class FlexibleInterpVarFactory : public RooFit::JSONIO::Importer {
548public:
549 bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
550 {
551 std::string name(RooJSONFactoryWSTool::name(p));
552 if (!p.has_child("high")) {
553 RooJSONFactoryWSTool::error("no high variations of '" + name + "'");
554 }
555 if (!p.has_child("low")) {
556 RooJSONFactoryWSTool::error("no low variations of '" + name + "'");
557 }
558 if (!p.has_child("nom")) {
559 RooJSONFactoryWSTool::error("no nominal variation of '" + name + "'");
560 }
561
562 double nom(p["nom"].val_double());
563
564 RooArgList vars{tool->requestArgList<RooRealVar>(p, "vars")};
565
566 std::vector<double> high;
567 high << p["high"];
568
569 std::vector<double> low;
570 low << p["low"];
571
572 if (vars.size() != low.size() || vars.size() != high.size()) {
573 RooJSONFactoryWSTool::error("FlexibleInterpVar '" + name +
574 "' has non-matching lengths of 'vars', 'high' and 'low'!");
575 }
576
577 auto &fip = tool->wsEmplace<RooStats::HistFactory::FlexibleInterpVar>(name, vars, nom, low, high);
578
579 if (p.has_child("interpolationCodes")) {
580 size_t i = 0;
581 for (auto const &node : p["interpolationCodes"].children()) {
582 fip.setInterpCode(*static_cast<RooAbsReal *>(vars.at(i)), node.val_int());
583 ++i;
584 }
585 }
586
587 return true;
588 }
589};
590
591void collectElements(RooArgSet &elems, RooAbsArg *arg)
592{
593 if (auto prod = dynamic_cast<RooProduct *>(arg)) {
594 for (const auto &e : prod->components()) {
595 collectElements(elems, e);
596 }
597 } else {
598 elems.add(*arg);
599 }
600}
601
602struct NormFactor {
603 std::string name;
604 RooAbsArg const *param = nullptr;
605 RooAbsPdf const *constraint = nullptr;
606 NormFactor(RooAbsArg const &par, RooAbsPdf const *constr = nullptr)
607 : name{par.GetName()}, param{&par}, constraint{constr}
608 {
609 }
610};
611
612struct NormSys {
613 std::string name;
614 RooAbsArg const *param = nullptr;
615 double low;
616 double high;
617 TClass *constraint = RooGaussian::Class();
618 NormSys(const std::string &n, RooAbsArg *const p, double h, double l, TClass *c)
619 : name(n), param(p), low(l), high(h), constraint(c)
620 {
621 }
622};
623struct HistoSys {
624 std::string name;
625 RooAbsArg const *param = nullptr;
626 std::vector<double> low;
627 std::vector<double> high;
628 TClass *constraint = RooGaussian::Class();
629 HistoSys(const std::string &n, RooAbsArg *const p, RooHistFunc *l, RooHistFunc *h, TClass *c)
630 : name(n), param(p), constraint(c)
631 {
632 low.assign(l->dataHist().weightArray(), l->dataHist().weightArray() + l->dataHist().numEntries());
633 high.assign(h->dataHist().weightArray(), h->dataHist().weightArray() + h->dataHist().numEntries());
634 }
635};
636struct ShapeSys {
637 std::string name;
638 std::vector<double> constraints;
639 std::vector<std::string> parameters;
640 TClass *constraint = nullptr;
641 ShapeSys(const std::string &n) : name{n} {}
642};
643struct Sample {
644 std::string name;
645 std::vector<double> hist;
646 std::vector<double> histError;
647 std::vector<NormFactor> normfactors;
648 std::vector<NormSys> normsys;
649 std::vector<HistoSys> histosys;
650 std::vector<ShapeSys> shapesys;
651 std::vector<RooAbsReal *> otherElements;
652 bool useBarlowBeestonLight = false;
653 std::vector<std::string> staterrorParameters;
654 TClass *barlowBeestonLightConstraint = RooPoisson::Class();
655 Sample(const std::string &n) : name{n} {}
656};
657
658void addNormFactor(RooRealVar const *par, Sample &sample, RooWorkspace *ws)
659{
660 std::string parname = par->GetName();
661 bool isConstrained = false;
662 for (RooAbsArg const *pdf : ws->allPdfs()) {
663 if (auto gauss = dynamic_cast<RooGaussian const *>(pdf)) {
664 if (parname == gauss->getX().GetName()) {
665 sample.normfactors.emplace_back(*par, gauss);
666 isConstrained = true;
667 }
668 }
669 }
670 if (!isConstrained)
671 sample.normfactors.emplace_back(*par);
672}
673
674namespace {
675
676bool verbose = false;
677
678}
679
681 JSONNode &elem)
682{
683 RooWorkspace *ws = tool->workspace();
685
686 if (!sumpdf) {
687 if (verbose) {
688 std::cout << pdfname << " is not a sumpdf" << std::endl;
689 }
690 return false;
691 }
692
693 std::string channelName = pdfname;
694 erasePrefix(channelName, "model_");
695 eraseSuffix(channelName, "_model");
696
697 for (RooAbsArg *sample : sumpdf->funcList()) {
698 if (!dynamic_cast<RooProduct *>(sample) && !dynamic_cast<RooRealSumPdf *>(sample)) {
699 if (verbose)
700 std::cout << "sample " << sample->GetName() << " is no RooProduct or RooRealSumPdf in " << pdfname
701 << std::endl;
702 return false;
703 }
704 }
705
706 std::map<int, double> tot_yield;
707 std::map<int, double> tot_yield2;
708 std::map<int, double> rel_errors;
709 RooArgSet const *varSet = nullptr;
710 long unsigned int nBins = 0;
711
712 std::vector<Sample> samples;
713
714 for (size_t sampleidx = 0; sampleidx < sumpdf->funcList().size(); ++sampleidx) {
715 PiecewiseInterpolation *pip = nullptr;
716 std::vector<RooStats::HistFactory::FlexibleInterpVar *> fips;
717 std::vector<ParamHistFunc *> phfs;
718
719 const auto func = sumpdf->funcList().at(sampleidx);
720 Sample sample(func->GetName());
721 erasePrefix(sample.name, "L_x_");
722 eraseSuffix(sample.name, "_shapes");
723 eraseSuffix(sample.name, "_" + channelName);
724 erasePrefix(sample.name, pdfname + "_");
725 RooArgSet elems;
726 collectElements(elems, func);
727 collectElements(elems, sumpdf->coefList().at(sampleidx));
728
729 auto updateObservables = [&](RooDataHist const &dataHist) {
730 if (varSet == nullptr) {
731 varSet = dataHist.get();
732 nBins = dataHist.numEntries();
733 }
734 if (sample.hist.empty()) {
735 auto *w = dataHist.weightArray();
736 sample.hist.assign(w, w + dataHist.numEntries());
737 }
738 };
739
740 for (RooAbsArg *e : elems) {
741 if (TString(e->GetName()).Contains("binWidth")) {
742 // The bin width modifiers are handled separately. We can't just
743 // check for the RooBinWidthFunction type here, because prior to
744 // ROOT 6.26, the multiplication with the inverse bin width was
745 // done in a different way (like a normfactor with a RooRealVar,
746 // but it was stored in the dataset).
747 // Fortunately, the name was similar, so we can match the modifier
748 // name.
749 } else if (auto constVar = dynamic_cast<RooConstVar *>(e)) {
750 if (constVar->getVal() != 1.) {
751 sample.normfactors.emplace_back(*e);
752 }
753 } else if (auto par = dynamic_cast<RooRealVar *>(e)) {
754 addNormFactor(par, sample, ws);
755 } else if (auto hf = dynamic_cast<const RooHistFunc *>(e)) {
756 updateObservables(hf->dataHist());
757 } else if (auto phf = dynamic_cast<ParamHistFunc *>(e)) {
758 phfs.push_back(phf);
759 } else if (auto fip = dynamic_cast<RooStats::HistFactory::FlexibleInterpVar *>(e)) {
760 // some (modified) histfactory models have several instances of FlexibleInterpVar
761 // we collect and merge them here
762 fips.push_back(fip);
763 } else if (!pip && (pip = dynamic_cast<PiecewiseInterpolation *>(e))) {
764 } else if (auto real = dynamic_cast<RooAbsReal *>(e)) {
765 sample.otherElements.push_back(real);
766 }
767 }
768
769 // see if we can get the observables
770 if (pip) {
771 if (auto nh = dynamic_cast<RooHistFunc const *>(pip->nominalHist())) {
772 updateObservables(nh->dataHist());
773 }
774 }
775
776 // sort and configure norms
777 sortByName(sample.normfactors);
778
779 // sort and configure the normsys
780 for (auto *fip : fips) {
781 for (size_t i = 0; i < fip->variables().size(); ++i) {
782 RooAbsArg *var = fip->variables().at(i);
783 std::string sysname(var->GetName());
784 erasePrefix(sysname, "alpha_");
785 const auto *constraint = findConstraint(var);
786 if (!constraint && !var->isConstant()) {
787 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
788 } else {
789 sample.normsys.emplace_back(sysname, var, fip->high()[i], fip->low()[i],
790 constraint ? constraint->IsA() : nullptr);
791 }
792 }
793 }
794 sortByName(sample.normsys);
795
796 // sort and configure the histosys
797 if (pip) {
798 for (size_t i = 0; i < pip->paramList().size(); ++i) {
799 RooAbsArg *var = pip->paramList().at(i);
800 std::string sysname(var->GetName());
801 erasePrefix(sysname, "alpha_");
802 if (auto lo = dynamic_cast<RooHistFunc *>(pip->lowList().at(i))) {
803 if (auto hi = dynamic_cast<RooHistFunc *>(pip->highList().at(i))) {
804 const auto *constraint = findConstraint(var);
805 if (!constraint && !var->isConstant()) {
806 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
807 } else {
808 sample.histosys.emplace_back(sysname, var, lo, hi, constraint ? constraint->IsA() : nullptr);
809 }
810 }
811 }
812 }
813 sortByName(sample.histosys);
814 }
815
816 for (ParamHistFunc *phf : phfs) {
817 if (startsWith(std::string(phf->GetName()), "mc_stat_")) { // MC stat uncertainty
818 int idx = 0;
819 for (const auto &g : phf->paramList()) {
820 sample.staterrorParameters.push_back(g->GetName());
821 ++idx;
822 RooAbsPdf *constraint = findConstraint(g);
823 if (tot_yield.find(idx) == tot_yield.end()) {
824 tot_yield[idx] = 0;
825 tot_yield2[idx] = 0;
826 }
827 tot_yield[idx] += sample.hist[idx - 1];
828 tot_yield2[idx] += (sample.hist[idx - 1] * sample.hist[idx - 1]);
829 if (constraint) {
830 sample.barlowBeestonLightConstraint = constraint->IsA();
831 if (RooPoisson *constraint_p = dynamic_cast<RooPoisson *>(constraint)) {
832 double erel = 1. / std::sqrt(constraint_p->getX().getVal());
833 rel_errors[idx] = erel;
834 } else if (RooGaussian *constraint_g = dynamic_cast<RooGaussian *>(constraint)) {
835 double erel = constraint_g->getSigma().getVal() / constraint_g->getMean().getVal();
836 rel_errors[idx] = erel;
837 } else {
839 "currently, only RooPoisson and RooGaussian are supported as constraint types");
840 }
841 }
842 }
843 sample.useBarlowBeestonLight = true;
844 } else { // other ShapeSys
845 ShapeSys sys(phf->GetName());
846 erasePrefix(sys.name, channelName + "_");
847 bool isshapesys = eraseSuffix(sys.name, "_ShapeSys") || eraseSuffix(sys.name, "_shapeSys");
848 bool isshapefactor = eraseSuffix(sys.name, "_ShapeFactor") || eraseSuffix(sys.name, "_shapeFactor");
849
850 for (const auto &g : phf->paramList()) {
851 sys.parameters.push_back(g->GetName());
852 RooAbsPdf *constraint = nullptr;
853 if (isshapesys) {
854 constraint = findConstraint(g);
855 if (!constraint)
856 constraint = ws->pdf(constraintName(g->GetName()));
857 if (!constraint && !g->isConstant()) {
858 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(g->GetName()));
859 }
860 } else if (!isshapefactor) {
861 RooJSONFactoryWSTool::error("unknown type of shapesys " + std::string(phf->GetName()));
862 }
863 if (!constraint) {
864 sys.constraints.push_back(0.0);
865 } else if (auto constraint_p = dynamic_cast<RooPoisson *>(constraint)) {
866 sys.constraints.push_back(1. / std::sqrt(constraint_p->getX().getVal()));
867 if (!sys.constraint) {
868 sys.constraint = RooPoisson::Class();
869 }
870 } else if (auto constraint_g = dynamic_cast<RooGaussian *>(constraint)) {
871 sys.constraints.push_back(constraint_g->getSigma().getVal() / constraint_g->getMean().getVal());
872 if (!sys.constraint) {
873 sys.constraint = RooGaussian::Class();
874 }
875 }
876 }
877 sample.shapesys.emplace_back(std::move(sys));
878 }
879 }
880 sortByName(sample.shapesys);
881
882 // add the sample
883 samples.emplace_back(std::move(sample));
884 }
885
887
888 for (auto &sample : samples) {
889 if (sample.hist.empty()) {
890 return false;
891 }
892 if (sample.useBarlowBeestonLight) {
893 sample.histError.resize(sample.hist.size());
894 for (auto bin : rel_errors) {
895 // reverse engineering the correct partial error
896 // the (arbitrary) convention used here is that all samples should have the same relative error
897 const int i = bin.first;
898 const double relerr_tot = bin.second;
899 const double count = sample.hist[i - 1];
900 // this reconstruction is inherently imprecise, so we truncate it at some decimal places to make sure that
901 // we don't carry around too many useless digits
902 sample.histError[i - 1] = round_prec(relerr_tot * tot_yield[i] / std::sqrt(tot_yield2[i]) * count, 7);
903 }
904 }
905 }
906
907 bool observablesWritten = false;
908 for (const auto &sample : samples) {
909
910 elem["type"] << "histfactory_dist";
911
912 auto &s = RooJSONFactoryWSTool::appendNamedChild(elem["samples"], sample.name);
913
914 auto &modifiers = s["modifiers"];
915 modifiers.set_seq();
916
917 for (const auto &nf : sample.normfactors) {
918 auto &mod = modifiers.append_child();
919 mod.set_map();
920 mod["name"] << nf.name;
921 mod["parameter"] << nf.param->GetName();
922 mod["type"] << "normfactor";
923 if (nf.constraint) {
924 mod["constraint_name"] << nf.constraint->GetName();
925 tool->queueExport(*nf.constraint);
926 }
927 }
928
929 for (const auto &sys : sample.normsys) {
930 auto &mod = modifiers.append_child();
931 mod.set_map();
932 mod["name"] << sys.name;
933 mod["type"] << "normsys";
934 mod["parameter"] << sys.param->GetName();
935 mod["constraint"] << toString(sys.constraint);
936 auto &data = mod["data"].set_map();
937 data["lo"] << sys.low;
938 data["hi"] << sys.high;
939 }
940
941 for (const auto &sys : sample.histosys) {
942 auto &mod = modifiers.append_child();
943 mod.set_map();
944 mod["name"] << sys.name;
945 mod["type"] << "histosys";
946 mod["parameter"] << sys.param->GetName();
947 mod["constraint"] << toString(sys.constraint);
948 auto &data = mod["data"].set_map();
949 if (nBins != sys.low.size() || nBins != sys.high.size()) {
950 std::stringstream ss;
951 ss << "inconsistent binning: " << nBins << " bins expected, but " << sys.low.size() << "/"
952 << sys.high.size() << " found in nominal histogram errors!";
953 RooJSONFactoryWSTool::error(ss.str().c_str());
954 }
955 RooJSONFactoryWSTool::exportArray(nBins, sys.low.data(), data["lo"].set_map()["contents"]);
956 RooJSONFactoryWSTool::exportArray(nBins, sys.high.data(), data["hi"].set_map()["contents"]);
957 }
958
959 for (const auto &sys : sample.shapesys) {
960 auto &mod = modifiers.append_child();
961 mod.set_map();
962 mod["name"] << sys.name;
963 mod["type"] << "shapesys";
964 optionallyExportGammaParameters(mod, sys.name, sys.parameters);
965 mod["constraint"] << toString(sys.constraint);
966 if (sys.constraint) {
967 auto &vals = mod["data"].set_map()["vals"];
968 vals.fill_seq(sys.constraints);
969 } else {
970 auto &vals = mod["data"].set_map()["vals"];
971 vals.set_seq();
972 for (std::size_t i = 0; i < sys.parameters.size(); ++i) {
973 vals.append_child() << 0;
974 }
975 }
976 }
977
978 for (const auto &other : sample.otherElements) {
979 auto &mod = modifiers.append_child();
980 mod.set_map();
981 mod["name"] << other->GetName();
983 mod["type"] << "custom";
984 }
985
986 if (sample.useBarlowBeestonLight) {
987 auto &mod = modifiers.append_child();
988 mod.set_map();
989 mod["name"] << ::Literals::staterror;
990 mod["type"] << ::Literals::staterror;
991 optionallyExportGammaParameters(mod, "stat_" + channelName, sample.staterrorParameters);
992 mod["constraint"] << toString(sample.barlowBeestonLightConstraint);
993 }
994
995 if (!observablesWritten) {
996 auto &output = elem["axes"].set_seq();
997 for (auto *obs : static_range_cast<RooRealVar *>(*varSet)) {
998 auto &out = output.append_child().set_map();
999 std::string name = obs->GetName();
1001 out["name"] << name;
1002 writeAxis(out, *obs);
1003 }
1004 observablesWritten = true;
1005 }
1006 auto &dataNode = s["data"].set_map();
1007 if (nBins != sample.hist.size()) {
1008 std::stringstream ss;
1009 ss << "inconsistent binning: " << nBins << " bins expected, but " << sample.hist.size()
1010 << " found in nominal histogram!";
1011 RooJSONFactoryWSTool::error(ss.str().c_str());
1012 }
1013 RooJSONFactoryWSTool::exportArray(nBins, sample.hist.data(), dataNode["contents"]);
1014 if (!sample.histError.empty()) {
1015 if (nBins != sample.histError.size()) {
1016 std::stringstream ss;
1017 ss << "inconsistent binning: " << nBins << " bins expected, but " << sample.histError.size()
1018 << " found in nominal histogram errors!";
1019 RooJSONFactoryWSTool::error(ss.str().c_str());
1020 }
1021 RooJSONFactoryWSTool::exportArray(nBins, sample.histError.data(), dataNode["errors"]);
1022 }
1023 }
1024
1025 // Export all the custom modifiers
1027 tool->queueExport(*modifier);
1028 }
1029
1030 // Export all model parameters
1031 RooArgSet parameters;
1032 sumpdf->getParameters(varSet, parameters);
1033 for (RooAbsArg *param : parameters) {
1034 // This should exclude the global observables
1035 if (!startsWith(std::string{param->GetName()}, "nom_")) {
1036 tool->queueExport(*param);
1037 }
1038 }
1039
1040 return true;
1041}
1042
1043class HistFactoryStreamer_ProdPdf : public RooFit::JSONIO::Exporter {
1044public:
1045 bool autoExportDependants() const override { return false; }
1047 {
1048 RooRealSumPdf *sumpdf = nullptr;
1049 for (RooAbsArg *v : prodpdf->pdfList()) {
1050 auto thispdf = dynamic_cast<RooRealSumPdf *>(v);
1051 if (thispdf) {
1052 if (!sumpdf)
1053 sumpdf = thispdf;
1054 else
1055 return false;
1056 }
1057 }
1058 if (!sumpdf)
1059 return false;
1060
1061 return tryExportHistFactory(tool, prodpdf->GetName(), sumpdf, elem);
1062 }
1063 std::string const &key() const override
1064 {
1065 static const std::string keystring = "histfactory_dist";
1066 return keystring;
1067 }
1068 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
1069 {
1070 return tryExport(tool, static_cast<const RooProdPdf *>(p), elem);
1071 }
1072};
1073
1074class HistFactoryStreamer_SumPdf : public RooFit::JSONIO::Exporter {
1075public:
1076 bool autoExportDependants() const override { return false; }
1078 {
1079 return tryExportHistFactory(tool, sumpdf->GetName(), sumpdf, elem);
1080 }
1081 std::string const &key() const override
1082 {
1083 static const std::string keystring = "histfactory_dist";
1084 return keystring;
1085 }
1086 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
1087 {
1088 return tryExport(tool, static_cast<const RooRealSumPdf *>(p), elem);
1089 }
1090};
1091
1092STATIC_EXECUTE([]() {
1093 using namespace RooFit::JSONIO;
1094
1095 registerImporter<HistFactoryImporter>("histfactory_dist", true);
1097 registerImporter<FlexibleInterpVarFactory>("interpolation0d", true);
1102});
1103
1104} // 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)
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:110
#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:77
bool dependsOn(const RooAbsCollection &serverList, const RooAbsArg *ignoreArg=nullptr, bool valueOnly=false) const
Test whether we depend on (ie, are served by) any object in the specified collection.
bool isConstant() const
Check if the "Constant" attribute is set.
Definition RooAbsArg.h:304
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:40
TClass * IsA() const override
Definition RooAbsPdf.h:351
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:59
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
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 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:39
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
const RooAbsBinning & getBinning(const char *name=nullptr, bool verbose=true, bool createOnTheFly=false) const override
Return binning definition with name.
Configuration for a constrained, coherent shape variation of affected samples.
Configuration for an un- constrained overall systematic to scale sample normalisations.
Definition Systematics.h:62
Constrained bin-by-bin variation of affected histogram.
Persistable container for RooFit projects.
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.
RooRealVar * var(RooStringView name) const
Retrieve real-valued variable (RooRealVar) with given name. A null pointer is returned if not found.
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
TClass * IsA() const override
Definition TClass.h:627
const char * GetName() const override
Returns name of object.
Definition TNamed.h:47
Basic string class.
Definition TString.h:139
Bool_t Contains(const char *pat, ECaseCompare cmp=kExact) const
Definition TString.h:632
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)
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:2345
static void output()