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