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