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
768#include <regex>
769#include <string>
770#include <cctype>
771#include <cstdlib>
772#include <iostream>
773
774NormSys parseOverallModifierFormula(const std::string &s, RooFormulaVar *formula)
775{
776 static const std::regex pattern(
777 R"(^\s*1(?:\.0)?\s*([\+\-])\s*([a-zA-Z_][a-zA-Z0-9_]*|[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*\*\s*([a-zA-Z_][a-zA-Z0-9_]*|[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*$)");
778
779 NormSys sys;
780 double sign = 1.0;
781
782 std::smatch match;
783 if (std::regex_match(s, match, pattern)) {
784 if (match[1].str() == "-") {
785 sign = -1.0;
786 }
787
788 std::string token2 = match[2].str();
789 std::string token3 = match[4].str();
790
791 RooAbsReal *p2 = static_cast<RooAbsReal *>(formula->getParameter(token2.c_str()));
792 RooAbsReal *p3 = static_cast<RooAbsReal *>(formula->getParameter(token3.c_str()));
793 RooRealVar *v2 = dynamic_cast<RooRealVar *>(p2);
794 RooRealVar *v3 = dynamic_cast<RooRealVar *>(p3);
795
796 auto *constr2 = findConstraint(v2);
797 auto *constr3 = findConstraint(v3);
798
799 if (constr2 && !p3) {
800 sys.name = p2->GetName();
801 sys.param = p2;
802 sys.high = sign * toDouble(token3);
803 sys.low = -sign * toDouble(token3);
804 } else if (!p2 && constr3) {
805 sys.name = p3->GetName();
806 sys.param = p3;
807 sys.high = sign * toDouble(token2);
808 sys.low = -sign * toDouble(token2);
809 } else if (constr2 && p3 && !constr3) {
810 sys.name = v2->GetName();
811 sys.param = v2;
812 sys.high = sign * p3->getVal();
813 sys.low = -sign * p3->getVal();
814 } else if (p2 && !constr2 && constr3) {
815 sys.name = v3->GetName();
816 sys.param = v3;
817 sys.high = sign * p2->getVal();
818 sys.low = -sign * p2->getVal();
819 }
820
821 // interpolation code 1 means linear, which is what we have here
822 sys.interpolationCode = 1;
823
824 erasePrefix(sys.name, "alpha_");
825 }
826 return sys;
827}
828
829void collectElements(RooArgSet &elems, RooAbsArg *arg)
830{
831 if (auto prod = dynamic_cast<RooProduct *>(arg)) {
832 for (const auto &e : prod->components()) {
833 collectElements(elems, e);
834 }
835 } else {
836 elems.add(*arg);
837 }
838}
839
840bool allRooRealVar(const RooAbsCollection &list)
841{
842 for (auto *var : list) {
843 if (!dynamic_cast<RooRealVar *>(var)) {
844 return false;
845 }
846 }
847 return true;
848}
849
850struct Sample {
851 std::string name;
852 std::vector<double> hist;
853 std::vector<double> histError;
854 std::vector<NormFactor> normfactors;
855 std::vector<NormSys> normsys;
856 std::vector<HistoSys> histosys;
857 std::vector<ShapeSys> shapesys;
858 std::vector<GenericElement> tmpElements;
859 std::vector<GenericElement> otherElements;
860 bool useBarlowBeestonLight = false;
861 std::vector<RooAbsReal *> staterrorParameters;
862 TClass *barlowBeestonLightConstraintType = RooPoisson::Class();
863 Sample(const std::string &n) : name{n} {}
864};
865
866void addNormFactor(RooRealVar const *par, Sample &sample, RooWorkspace *ws)
867{
868 std::string parname = par->GetName();
869 bool isConstrained = false;
870 for (RooAbsArg const *pdf : ws->allPdfs()) {
871 if (auto gauss = dynamic_cast<RooGaussian const *>(pdf)) {
872 if (parname == gauss->getX().GetName()) {
873 sample.normfactors.emplace_back(*par, gauss);
874 isConstrained = true;
875 }
876 }
877 }
878 if (!isConstrained)
879 sample.normfactors.emplace_back(*par);
880}
881
882namespace {
883
884bool verbose = false;
885
886}
887
888struct Channel {
889 std::string name;
890 std::vector<Sample> samples;
891 std::map<int, double> tot_yield;
892 std::map<int, double> tot_yield2;
893 std::map<int, double> rel_errors;
894 RooArgSet const *varSet = nullptr;
895 long unsigned int nBins = 0;
896};
897
898Channel readChannel(RooJSONFactoryWSTool *tool, const std::string &pdfname, const RooRealSumPdf *sumpdf)
899{
900 Channel channel;
901
902 RooWorkspace *ws = tool->workspace();
903
904 channel.name = pdfname;
905 erasePrefix(channel.name, "model_");
906 eraseSuffix(channel.name, "_model");
907
908 for (size_t sampleidx = 0; sampleidx < sumpdf->funcList().size(); ++sampleidx) {
909 PiecewiseInterpolation *pip = nullptr;
910 std::vector<ParamHistFunc *> phfs;
911
912 const auto func = sumpdf->funcList().at(sampleidx);
913 Sample sample(func->GetName());
914 erasePrefix(sample.name, "L_x_");
915 eraseSuffix(sample.name, "_shapes");
916 eraseSuffix(sample.name, "_" + channel.name);
917 erasePrefix(sample.name, pdfname + "_");
918
919 auto updateObservables = [&](RooDataHist const &dataHist) {
920 if (channel.varSet == nullptr) {
921 channel.varSet = dataHist.get();
922 channel.nBins = dataHist.numEntries();
923 }
924 if (sample.hist.empty()) {
925 auto *w = dataHist.weightArray();
926 sample.hist.assign(w, w + dataHist.numEntries());
927 }
928 };
929 auto processElements = [&](const auto &elements, auto &&self) -> void {
930 for (RooAbsArg *e : elements) {
931 if (TString(e->GetName()).Contains("binWidth")) {
932 // The bin width modifiers are handled separately. We can't just
933 // check for the RooBinWidthFunction type here, because prior to
934 // ROOT 6.26, the multiplication with the inverse bin width was
935 // done in a different way (like a normfactor with a RooRealVar,
936 // but it was stored in the dataset).
937 // Fortunately, the name was similar, so we can match the modifier
938 // name.
939 } else if (auto constVar = dynamic_cast<RooConstVar *>(e)) {
940 if (constVar->getVal() != 1.) {
941 sample.normfactors.emplace_back(*constVar);
942 }
943 } else if (auto par = dynamic_cast<RooRealVar *>(e)) {
944 addNormFactor(par, sample, ws);
945 } else if (auto hf = dynamic_cast<const RooHistFunc *>(e)) {
946 updateObservables(hf->dataHist());
947 } else if (ParamHistFunc *phf = dynamic_cast<ParamHistFunc *>(e); phf && allRooRealVar(phf->paramList())) {
948 phfs.push_back(phf);
949 } else if (auto fip = dynamic_cast<RooStats::HistFactory::FlexibleInterpVar *>(e)) {
950 // some (modified) histfactory models have several instances of FlexibleInterpVar
951 // we collect and merge them
952 for (size_t i = 0; i < fip->variables().size(); ++i) {
953 RooAbsReal *var = static_cast<RooAbsReal *>(fip->variables().at(i));
954 std::string sysname(var->GetName());
955 erasePrefix(sysname, "alpha_");
956 const auto *constraint = findConstraint(var);
957 if (!constraint && !var->isConstant()) {
958 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
959 } else {
960 sample.normsys.emplace_back(sysname, var, fip->high()[i], fip->low()[i],
961 fip->interpolationCodes()[i], constraint);
962 }
963 }
964 } else if (!pip && (pip = dynamic_cast<PiecewiseInterpolation *>(e))) {
965 // nothing to do here, already assigned
966 } else if (RooFormulaVar *formula = dynamic_cast<RooFormulaVar *>(e)) {
967 // people do a lot of fancy stuff with RooFormulaVar, like including NormSys via explicit formulae.
968 // let's try to decompose it into building blocks
969 TString expression(formula->expression());
970 for (size_t i = formula->nParameters(); i--;) {
971 const RooAbsArg *p = formula->getParameter(i);
972 expression.ReplaceAll(("x[" + std::to_string(i) + "]").c_str(), p->GetName());
973 expression.ReplaceAll(("@" + std::to_string(i)).c_str(), p->GetName());
974 }
975 auto components = splitTopLevelProduct(expression.Data());
976 if (components.size() == 0) {
977 // it's not a product, let's just treat it as an unknown element
978 sample.otherElements.push_back(formula);
979 } else {
980 // it is a prododuct, we can try to handle the elements separately
981 std::vector<RooAbsArg *> realComponents;
982 int idx = 0;
983 for (auto &comp : components) {
984 // check if this is a trivial element of a product, we can treat it as its own modifier
985 auto *part = formula->getParameter(comp.c_str());
986 if (part) {
987 realComponents.push_back(part);
988 continue;
989 }
990 // check if this is an attempt at explicitly encoding an overallSys
991 auto normsys = parseOverallModifierFormula(comp, formula);
992 if (normsys.param) {
993 sample.normsys.emplace_back(std::move(normsys));
994 continue;
995 }
996
997 // this is something non-trivial, let's deal with it separately
998 std::string name = std::string(formula->GetName()) + "_part" + std::to_string(idx);
999 ++idx;
1000 auto *var = new RooFormulaVar(name.c_str(), name.c_str(), comp.c_str(), formula->dependents());
1001 sample.tmpElements.push_back({var});
1002 }
1003 self(realComponents, self);
1004 }
1005 } else if (auto real = dynamic_cast<RooAbsReal *>(e)) {
1006 sample.otherElements.push_back(real);
1007 }
1008 }
1009 };
1010
1011 RooArgSet elems;
1012 collectElements(elems, func);
1013 collectElements(elems, sumpdf->coefList().at(sampleidx));
1014 processElements(elems, processElements);
1015
1016 // see if we can get the observables
1017 if (pip) {
1018 if (auto nh = dynamic_cast<RooHistFunc const *>(pip->nominalHist())) {
1019 updateObservables(nh->dataHist());
1020 }
1021 }
1022
1023 // sort and configure norms
1024 sortByName(sample.normfactors);
1025 sortByName(sample.normsys);
1026
1027 // sort and configure the histosys
1028 if (pip) {
1029 for (size_t i = 0; i < pip->paramList().size(); ++i) {
1030 RooAbsReal *var = static_cast<RooAbsReal *>(pip->paramList().at(i));
1031 std::string sysname(var->GetName());
1032 erasePrefix(sysname, "alpha_");
1033 if (auto lo = dynamic_cast<RooHistFunc *>(pip->lowList().at(i))) {
1034 if (auto hi = dynamic_cast<RooHistFunc *>(pip->highList().at(i))) {
1035 const auto *constraint = findConstraint(var);
1036 if (!constraint && !var->isConstant()) {
1037 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(var->GetName()));
1038 } else {
1039 sample.histosys.emplace_back(sysname, var, lo, hi, constraint);
1040 }
1041 }
1042 }
1043 }
1044 sortByName(sample.histosys);
1045 }
1046
1047 for (ParamHistFunc *phf : phfs) {
1048 if (startsWith(std::string(phf->GetName()), "mc_stat_")) { // MC stat uncertainty
1049 int idx = 0;
1050 for (const auto &g : phf->paramList()) {
1051 sample.staterrorParameters.push_back(static_cast<RooRealVar *>(g));
1052 ++idx;
1053 RooAbsPdf *constraint = findConstraint(g);
1054 if (channel.tot_yield.find(idx) == channel.tot_yield.end()) {
1055 channel.tot_yield[idx] = 0;
1056 channel.tot_yield2[idx] = 0;
1057 }
1058 channel.tot_yield[idx] += sample.hist[idx - 1];
1059 channel.tot_yield2[idx] += (sample.hist[idx - 1] * sample.hist[idx - 1]);
1060 if (constraint) {
1061 sample.barlowBeestonLightConstraintType = constraint->IsA();
1062 if (RooPoisson *constraint_p = dynamic_cast<RooPoisson *>(constraint)) {
1063 double erel = 1. / std::sqrt(constraint_p->getX().getVal());
1064 channel.rel_errors[idx] = erel;
1065 } else if (RooGaussian *constraint_g = dynamic_cast<RooGaussian *>(constraint)) {
1066 double erel = constraint_g->getSigma().getVal() / constraint_g->getMean().getVal();
1067 channel.rel_errors[idx] = erel;
1068 } else {
1070 "currently, only RooPoisson and RooGaussian are supported as constraint types");
1071 }
1072 }
1073 }
1074 sample.useBarlowBeestonLight = true;
1075 } else { // other ShapeSys
1076 ShapeSys sys(phf->GetName());
1077 erasePrefix(sys.name, channel.name + "_");
1078 bool isshapesys = eraseSuffix(sys.name, "_ShapeSys") || eraseSuffix(sys.name, "_shapeSys");
1079 bool isshapefactor = eraseSuffix(sys.name, "_ShapeFactor") || eraseSuffix(sys.name, "_shapeFactor");
1080
1081 for (const auto &g : phf->paramList()) {
1082 sys.parameters.push_back(static_cast<RooRealVar *>(g));
1083 RooAbsPdf *constraint = nullptr;
1084 if (isshapesys) {
1085 constraint = findConstraint(g);
1086 if (!constraint)
1087 constraint = ws->pdf(constraintName(g->GetName()));
1088 if (!constraint && !g->isConstant()) {
1089 RooJSONFactoryWSTool::error("cannot find constraint for " + std::string(g->GetName()));
1090 }
1091 } else if (!isshapefactor) {
1092 RooJSONFactoryWSTool::error("unknown type of shapesys " + std::string(phf->GetName()));
1093 }
1094 if (!constraint) {
1095 sys.constraints.push_back(0.0);
1096 } else if (auto constraint_p = dynamic_cast<RooPoisson *>(constraint)) {
1097 sys.constraints.push_back(1. / std::sqrt(constraint_p->getX().getVal()));
1098 if (!sys.constraint) {
1099 sys.constraintType = RooPoisson::Class();
1100 }
1101 } else if (auto constraint_g = dynamic_cast<RooGaussian *>(constraint)) {
1102 sys.constraints.push_back(constraint_g->getSigma().getVal() / constraint_g->getMean().getVal());
1103 if (!sys.constraint) {
1104 sys.constraintType = RooGaussian::Class();
1105 }
1106 }
1107 }
1108 sample.shapesys.emplace_back(std::move(sys));
1109 }
1110 }
1111 sortByName(sample.shapesys);
1112
1113 // add the sample
1114 channel.samples.emplace_back(std::move(sample));
1115 }
1116
1117 sortByName(channel.samples);
1118 return channel;
1119}
1120
1121void configureStatError(Channel &channel)
1122{
1123 for (auto &sample : channel.samples) {
1124 if (sample.useBarlowBeestonLight) {
1125 sample.histError.resize(sample.hist.size());
1126 for (auto bin : channel.rel_errors) {
1127 // reverse engineering the correct partial error
1128 // the (arbitrary) convention used here is that all samples should have the same relative error
1129 const int i = bin.first;
1130 const double relerr_tot = bin.second;
1131 const double count = sample.hist[i - 1];
1132 // this reconstruction is inherently imprecise, so we truncate it at some decimal places to make sure that
1133 // we don't carry around too many useless digits
1134 sample.histError[i - 1] =
1135 round_prec(relerr_tot * channel.tot_yield[i] / std::sqrt(channel.tot_yield2[i]) * count, 7);
1136 }
1137 }
1138 }
1139}
1140
1141bool exportChannel(RooJSONFactoryWSTool *tool, const Channel &channel, JSONNode &elem)
1142{
1143 // Write the constraint reference (either by name or by type) for any
1144 // modifier that supports an external Gaussian/Poisson/etc. constraint.
1145 auto writeConstraint = [](JSONNode &mod, auto const &sys) {
1146 if (sys.constraint) {
1147 mod["constraint_name"] << sys.constraint->GetName();
1148 } else if (sys.constraintType) {
1149 mod["constraint_type"] << toString(sys.constraintType);
1150 }
1151 };
1152
1153 bool observablesWritten = false;
1154 for (const auto &sample : channel.samples) {
1155
1156 elem["type"] << "histfactory_dist";
1157
1158 auto &s = RooJSONFactoryWSTool::appendNamedChild(elem["samples"], sample.name);
1159
1160 auto &modifiers = s["modifiers"];
1161 modifiers.set_seq();
1162
1163 for (const auto &nf : sample.normfactors) {
1164 auto &mod = modifiers.append_child();
1165 mod.set_map();
1166 mod["name"] << nf.name;
1167 mod["parameter"] << nf.param->GetName();
1168 mod["type"] << "normfactor";
1169 if (nf.constraint) {
1170 mod["constraint_name"] << nf.constraint->GetName();
1171 tool->queueExport(*nf.constraint);
1172 }
1173 }
1174
1175 for (const auto &sys : sample.normsys) {
1176 auto &mod = modifiers.append_child();
1177 mod.set_map();
1178 mod["name"] << sys.name;
1179 mod["type"] << "normsys";
1180 mod["parameter"] << sys.param->GetName();
1181 if (sys.interpolationCode != 4) {
1182 mod["interpolation"] << sys.interpolationCode;
1183 }
1184 writeConstraint(mod, sys);
1185 auto &data = mod["data"].set_map();
1186 data["lo"] << sys.low;
1187 data["hi"] << sys.high;
1188 }
1189
1190 for (const auto &sys : sample.histosys) {
1191 auto &mod = modifiers.append_child();
1192 mod.set_map();
1193 mod["name"] << sys.name;
1194 mod["type"] << "histosys";
1195 mod["parameter"] << sys.param->GetName();
1196 writeConstraint(mod, sys);
1197 auto &data = mod["data"].set_map();
1198 if (channel.nBins != sys.low.size() || channel.nBins != sys.high.size()) {
1199 std::stringstream ss;
1200 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sys.low.size() << "/"
1201 << sys.high.size() << " found in nominal histogram errors!";
1202 RooJSONFactoryWSTool::error(ss.str().c_str());
1203 }
1204 RooJSONFactoryWSTool::exportArray(channel.nBins, sys.low.data(), data["lo"].set_map()["contents"]);
1205 RooJSONFactoryWSTool::exportArray(channel.nBins, sys.high.data(), data["hi"].set_map()["contents"]);
1206 }
1207
1208 for (const auto &sys : sample.shapesys) {
1209 auto &mod = modifiers.append_child();
1210 mod.set_map();
1211 mod["name"] << sys.name;
1212 mod["type"] << "shapesys";
1213 optionallyExportGammaParameters(mod, sys.name, sys.parameters);
1214 writeConstraint(mod, sys);
1215 auto &vals = mod["data"].set_map()["vals"];
1216 if (sys.constraint || sys.constraintType) {
1217 vals.fill_seq(sys.constraints);
1218 } else {
1219 vals.fill_seq(std::vector<double>(sys.parameters.size(), 0.0));
1220 }
1221 }
1222
1223 for (const auto &other : sample.otherElements) {
1224 auto &mod = modifiers.append_child();
1225 mod.set_map();
1226 mod["name"] << other.name;
1227 mod["type"] << "custom";
1228 }
1229 for (const auto &other : sample.tmpElements) {
1230 auto &mod = modifiers.append_child();
1231 mod.set_map();
1232 mod["name"] << other.name;
1233 mod["type"] << "custom";
1234 }
1235
1236 if (sample.useBarlowBeestonLight) {
1237 auto &mod = modifiers.append_child();
1238 mod.set_map();
1239 mod["name"] << ::Literals::staterror;
1240 mod["type"] << ::Literals::staterror;
1241 optionallyExportGammaParameters(mod, "stat_" + channel.name, sample.staterrorParameters);
1242 mod["constraint_type"] << toString(sample.barlowBeestonLightConstraintType);
1243 }
1244
1245 if (!observablesWritten) {
1246 auto &output = elem["axes"].set_seq();
1247 for (auto *obs : static_range_cast<RooRealVar *>(*channel.varSet)) {
1248 auto &out = output.append_child().set_map();
1249 std::string name = obs->GetName();
1251 out["name"] << name;
1252 writeAxis(out, *obs);
1253 }
1254 observablesWritten = true;
1255 }
1256 auto &dataNode = s["data"].set_map();
1257 if (channel.nBins != sample.hist.size()) {
1258 std::stringstream ss;
1259 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sample.hist.size()
1260 << " found in nominal histogram!";
1261 RooJSONFactoryWSTool::error(ss.str().c_str());
1262 }
1263 RooJSONFactoryWSTool::exportArray(channel.nBins, sample.hist.data(), dataNode["contents"]);
1264 if (!sample.histError.empty()) {
1265 if (channel.nBins != sample.histError.size()) {
1266 std::stringstream ss;
1267 ss << "inconsistent binning: " << channel.nBins << " bins expected, but " << sample.histError.size()
1268 << " found in nominal histogram errors!";
1269 RooJSONFactoryWSTool::error(ss.str().c_str());
1270 }
1271 RooJSONFactoryWSTool::exportArray(channel.nBins, sample.histError.data(), dataNode["errors"]);
1272 }
1273 }
1274
1275 return true;
1276}
1277
1278std::vector<RooAbsPdf *> findLostConstraints(const Channel &channel, const std::vector<RooAbsPdf *> &constraints)
1279{
1280 // collect all the vars that are used by the model
1281 std::set<const RooAbsReal *> vars;
1282 for (const auto &sample : channel.samples) {
1283 for (const auto &nf : sample.normfactors) {
1284 vars.insert(nf.param);
1285 }
1286 for (const auto &sys : sample.normsys) {
1287 vars.insert(sys.param);
1288 }
1289
1290 for (const auto &sys : sample.histosys) {
1291 vars.insert(sys.param);
1292 }
1293 for (const auto &sys : sample.shapesys) {
1294 for (const auto &par : sys.parameters) {
1295 vars.insert(par);
1296 }
1297 }
1298 if (sample.useBarlowBeestonLight) {
1299 for (const auto &par : sample.staterrorParameters) {
1300 vars.insert(par);
1301 }
1302 }
1303 }
1304
1305 // check if there is any constraint present that is unrelated to these vars
1306 std::vector<RooAbsPdf *> lostConstraints;
1307 for (auto *pdf : constraints) {
1308 bool related = false;
1309 for (const auto *var : vars) {
1310 if (pdf->dependsOn(*var)) {
1311 related = true;
1312 }
1313 }
1314 if (!related) {
1315 lostConstraints.push_back(pdf);
1316 }
1317 }
1318 // return the constraints that would be "lost" when exporting the model
1319 return lostConstraints;
1320}
1321
1322bool tryExportHistFactory(RooJSONFactoryWSTool *tool, const std::string &pdfname, const RooRealSumPdf *sumpdf,
1323 std::vector<RooAbsPdf *> constraints, JSONNode &elem)
1324{
1325 // some preliminary checks
1326 if (!sumpdf) {
1327 if (verbose) {
1328 std::cout << pdfname << " is not a sumpdf" << std::endl;
1329 }
1330 return false;
1331 }
1332
1333 for (RooAbsArg *sample : sumpdf->funcList()) {
1334 if (!dynamic_cast<RooProduct *>(sample) && !dynamic_cast<RooRealSumPdf *>(sample)) {
1335 if (verbose)
1336 std::cout << "sample " << sample->GetName() << " is no RooProduct or RooRealSumPdf in " << pdfname
1337 << std::endl;
1338 return false;
1339 }
1340 }
1341
1342 auto channel = readChannel(tool, pdfname, sumpdf);
1343
1344 // sanity checks
1345 if (channel.samples.size() == 0)
1346 return false;
1347 for (auto &sample : channel.samples) {
1348 if (sample.hist.empty()) {
1349 return false;
1350 }
1351 }
1352
1353 // stat error handling
1354 configureStatError(channel);
1355
1356 auto lostConstraints = findLostConstraints(channel, constraints);
1357 // Export all the lost constraints
1358 for (const auto *constraint : lostConstraints) {
1360 "losing constraint term '" + std::string(constraint->GetName()) +
1361 "', implicit constraints are not supported by HS3 yet! The term will appear in the HS3 file, but will not be "
1362 "picked up when creating a likelihood from it! You will have to add it manually as an external constraint.");
1363 tool->queueExport(*constraint);
1364 }
1365
1366 // Export all the regular modifiers
1367 for (const auto &sample : channel.samples) {
1368 for (auto &modifier : sample.normfactors) {
1369 if (modifier.constraint) {
1370 tool->queueExport(*modifier.constraint);
1371 }
1372 }
1373 for (auto &modifier : sample.normsys) {
1374 if (modifier.constraint) {
1375 tool->queueExport(*modifier.constraint);
1376 }
1377 }
1378 for (auto &modifier : sample.histosys) {
1379 if (modifier.constraint) {
1380 tool->queueExport(*modifier.constraint);
1381 }
1382 }
1383 }
1384
1385 // Export all the custom modifiers
1386 for (const auto &sample : channel.samples) {
1387 for (auto &modifier : sample.otherElements) {
1388 tool->queueExport(*modifier.function);
1389 }
1390 for (auto &modifier : sample.tmpElements) {
1391 tool->queueExportTemporary(modifier.function);
1392 }
1393 }
1394
1395 // Export all model parameters
1396 RooArgSet parameters;
1397 sumpdf->getParameters(channel.varSet, parameters);
1398 for (RooAbsArg *param : parameters) {
1399 // This should exclude the global observables
1400 if (!startsWith(std::string{param->GetName()}, "nom_")) {
1401 tool->queueExport(*param);
1402 }
1403 }
1404
1405 return exportChannel(tool, channel, elem);
1406}
1407
1408class HistFactoryStreamer_ProdPdf : public RooFit::JSONIO::Exporter {
1409public:
1410 bool autoExportDependants() const override { return false; }
1411 bool tryExport(RooJSONFactoryWSTool *tool, const RooProdPdf *prodpdf, JSONNode &elem) const
1412 {
1413 std::vector<RooAbsPdf *> constraints;
1414 RooRealSumPdf *sumpdf = nullptr;
1415 for (RooAbsArg *v : prodpdf->pdfList()) {
1416 RooAbsPdf *pdf = static_cast<RooAbsPdf *>(v);
1417 auto thispdf = dynamic_cast<RooRealSumPdf *>(pdf);
1418 if (thispdf) {
1419 if (!sumpdf)
1420 sumpdf = thispdf;
1421 else
1422 return false;
1423 } else {
1424 constraints.push_back(pdf);
1425 }
1426 }
1427 if (!sumpdf)
1428 return false;
1429
1430 bool ok = tryExportHistFactory(tool, prodpdf->GetName(), sumpdf, constraints, elem);
1431 return ok;
1432 }
1433 std::string const &key() const override
1434 {
1435 static const std::string keystring = "histfactory_dist";
1436 return keystring;
1437 }
1438 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
1439 {
1440 return tryExport(tool, static_cast<const RooProdPdf *>(p), elem);
1441 }
1442};
1443
1444class HistFactoryStreamer_SumPdf : public RooFit::JSONIO::Exporter {
1445public:
1446 bool autoExportDependants() const override { return false; }
1447 bool tryExport(RooJSONFactoryWSTool *tool, const RooRealSumPdf *sumpdf, JSONNode &elem) const
1448 {
1449 std::vector<RooAbsPdf *> constraints;
1450 return tryExportHistFactory(tool, sumpdf->GetName(), sumpdf, constraints, elem);
1451 }
1452 std::string const &key() const override
1453 {
1454 static const std::string keystring = "histfactory_dist";
1455 return keystring;
1456 }
1457 bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *p, JSONNode &elem) const override
1458 {
1459 return tryExport(tool, static_cast<const RooRealSumPdf *>(p), elem);
1460 }
1461};
1462
1463STATIC_EXECUTE([]() {
1464 using namespace RooFit::JSONIO;
1465
1466 registerImporter<HistFactoryImporter>("histfactory_dist", true);
1468 registerImporter<FlexibleInterpVarFactory>("interpolation0d", true);
1473});
1474
1475} // namespace
bool startsWith(std::string_view str, std::string_view prefix)
bool endsWith(std::string_view str, std::string_view suffix)
ROOT::R::TRInterface & r
Definition Object.C:4
#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
start
Definition Rotated.cxx:223
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:69
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: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)
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