diff --git a/cmake/modules/Findryml.cmake b/cmake/modules/Findryml.cmake deleted file mode 100644 index 5d4e212e25017..0000000000000 --- a/cmake/modules/Findryml.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (C) 1995-2020, Rene Brun and Fons Rademakers. -# All rights reserved. -# -# For the licensing terms see $ROOTSYS/LICENSE. -# For the list of contributors see $ROOTSYS/README/CREDITS. - -# - Locate ryml library -# -# Defines: -# -# RYML_FOUND -# RYML_LIBRARY -# RYML_LIBRARY_PATH -# RYML_INCLUDE_DIR - -find_library(RYML_LIBRARY NAMES ryml HINTS ${RYML_DIR}/lib $ENV{RYML_DIR}/lib) -find_path(RYML_INCLUDE_DIR NAMES ryml.hpp HINTS ${RYML_DIR}/include $ENV{RYML_DIR}/include) - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(ryml DEFAULT_MSG RYML_LIBRARY RYML_INCLUDE_DIR) - -mark_as_advanced(RYML_FOUND RYML_LIBRARY RYML_INCLUDE_DIR) -get_filename_component(RYML_LIBRARY_PATH ${RYML_LIBRARY} DIRECTORY) diff --git a/roofit/hs3/src/JSONFactories_HistFactory.cxx b/roofit/hs3/src/JSONFactories_HistFactory.cxx index 279dd63380ce1..d58f037ae527d 100644 --- a/roofit/hs3/src/JSONFactories_HistFactory.cxx +++ b/roofit/hs3/src/JSONFactories_HistFactory.cxx @@ -117,23 +117,6 @@ RooAbsPdf *findConstraint(RooAbsArg *g) return nullptr; } -std::string toString(TClass *c) -{ - if (!c) { - return "Const"; - } - if (c == RooPoisson::Class()) { - return "Poisson"; - } - if (c == RooGaussian::Class()) { - return "Gauss"; - } - if (c == RooLognormal::Class()) { - return "Lognormal"; - } - return "unknown"; -} - inline std::string defaultGammaName(std::string const &sysname, std::size_t i) { return "gamma_" + sysname + "_bin_" + std::to_string(i); @@ -172,10 +155,47 @@ std::string constraintName(std::string const ¶mName) return paramName + "Constraint"; } +bool isLegacyConstraintType(std::string const &value) +{ + return value == "Gauss" || value == "Poisson" || value == "Const" || value == "Lognormal"; +} + +RooAbsPdf *findNamedConstraint(RooJSONFactoryWSTool &tool, std::string const &constraintName, std::string const &sample) +{ + if (auto *constraint = tool.workspace()->pdf(constraintName)) { + return constraint; + } + + try { + return tool.request(constraintName, sample); + } catch (RooJSONFactoryWSTool::DependencyMissingError const &err) { + if (err.child() != constraintName) { + throw; + } + } + + return nullptr; +} + +RooAbsPdf &createLegacyConstraint(RooJSONFactoryWSTool &tool, const JSONNode &mod, RooRealVar ¶m, + std::string const &constraintType) +{ + if (constraintType == "Gauss") { + param.setError(1.0); + return getOrCreate(*tool.workspace(), constraintName(param.GetName()), param, + *tool.workspace()->var(std::string("nom_") + param.GetName()), 1.); + } + + RooJSONFactoryWSTool::error("legacy constraint value '" + constraintType + "' for modifier '" + + RooJSONFactoryWSTool::name(mod) + + "' is a known constraint type, but it cannot be resolved in this context"); +} + ParamHistFunc &createPHF(const std::string &phfname, std::string const &sysname, const std::vector &parnames, const std::vector &vals, RooJSONFactoryWSTool &tool, RooAbsCollection &constraints, const RooArgSet &observables, - const std::string &constraintType, double gammaMin, double gammaMax, double minSigma) + const std::string &constraintType, double gammaMin, double gammaMax, double minSigma, + bool createConstraints = true) { RooWorkspace &ws = *tool.workspace(); @@ -193,7 +213,9 @@ ParamHistFunc &createPHF(const std::string &phfname, std::string const &sysname, auto &phf = tool.wsEmplace(phfname, observables, gammas); if (vals.size() > 0) { - if (constraintType != "Const") { + if (!createConstraints) { + configureConstrainedGammas(gammas, vals, minSigma); + } else if (constraintType != "Const") { auto constraintsInfo = createGammaConstraints( gammas, vals, minSigma, constraintType == "Poisson" ? Constraint::Poisson : Constraint::Gaussian); for (auto const &term : constraintsInfo.constraints) { @@ -236,12 +258,10 @@ const JSONNode &findStaterror(const JSONNode &comp) RooAbsPdf & getOrCreateConstraint(RooJSONFactoryWSTool &tool, const JSONNode &mod, RooRealVar ¶m, const std::string &sample) { - if (auto constrName = mod.find("constraint_name")) { + JSONNode const *constrName = mod.find("constraint_name"); + if (constrName) { auto constraint_name = constrName->val(); - auto constraint = tool.workspace()->pdf(constraint_name); - if (!constraint) { - constraint = tool.request(constrName->val(), sample); - } + auto constraint = findNamedConstraint(tool, constraint_name, sample); if (!constraint) { RooJSONFactoryWSTool::error("unable to find definition of of constraint '" + constraint_name + "' for modifier '" + RooJSONFactoryWSTool::name(mod) + "'"); @@ -250,19 +270,35 @@ getOrCreateConstraint(RooJSONFactoryWSTool &tool, const JSONNode &mod, RooRealVa param.setError(gauss->getSigma().getVal()); } return *constraint; - } else { - std::string constraint_type = "Gauss"; - if (auto constrType = mod.find("constraint_type")) { - constraint_type = constrType->val(); + } + + if (auto constr = mod.find("constraint")) { + std::string constraintValue = constr->val(); + if (auto *constraint = findNamedConstraint(tool, constraintValue, sample)) { + if (auto gauss = dynamic_cast(constraint)) { + param.setError(gauss->getSigma().getVal()); + } + return *constraint; } - if (constraint_type == "Gauss") { - param.setError(1.0); - return getOrCreate(*tool.workspace(), constraintName(param.GetName()), param, - *tool.workspace()->var(std::string("nom_") + param.GetName()), 1.); + + if (isLegacyConstraintType(constraintValue)) { + return createLegacyConstraint(tool, mod, param, constraintValue); } - RooJSONFactoryWSTool::error("unknown or invalid constraint for modifier '" + RooJSONFactoryWSTool::name(mod) + - "'"); + + RooJSONFactoryWSTool::error("unable to resolve constraint value '" + constraintValue + "' for modifier '" + + RooJSONFactoryWSTool::name(mod) + + "': this looks like a legacy workspace where the 'constraint' field is neither a " + "constraint pdf name nor a supported legacy constraint type"); + } + + std::string constraint_type = "Gauss"; + if (auto constrType = mod.find("constraint_type")) { + constraint_type = constrType->val(); + } + if (isLegacyConstraintType(constraint_type)) { + return createLegacyConstraint(tool, mod, param, constraint_type); } + RooJSONFactoryWSTool::error("unknown or invalid constraint for modifier '" + RooJSONFactoryWSTool::name(mod) + "'"); } double poissonTau(RooPoisson const &constraint, RooAbsArg const &gamma) { @@ -347,7 +383,7 @@ bool importHistSample(RooJSONFactoryWSTool &tool, RooDataHist &dh, RooArgSet con RooRealVar &constrParam = getOrCreate(ws, sysname, 1., -3, 5); constrParam.setError(0.0); normElems.add(constrParam); - if (mod.has_child("constraint_name") || mod.has_child("constraint_type")) { + if (mod.has_child("constraint") || mod.has_child("constraint_name") || mod.has_child("constraint_type")) { // for norm factors, constraints are optional constraints.add(getOrCreateConstraint(tool, mod, constrParam, sampleName)); } @@ -410,11 +446,51 @@ bool importHistSample(RooJSONFactoryWSTool &tool, RooDataHist &dh, RooArgSet con RooJSONFactoryWSTool::error("unable to instantiate shapesys '" + sysname + "' with neither values nor parameters!"); } - std::string constraint(mod.has_child("constraint_type") ? mod["constraint_type"].val() - : mod.has_child("constraint") ? mod["constraint"].val() - : "unknown"); + std::string constraint = "unknown"; + std::vector constraintPdfs; + bool const hasConstraintList = mod.has_child("constraints"); + if (hasConstraintList) { + for (const auto &v : mod["constraints"].children()) { + if (v.is_null()) { + constraintPdfs.push_back(nullptr); + } else { + std::string constraintName = v.val(); + auto *constraintPdf = findNamedConstraint(tool, constraintName, sampleName); + if (!constraintPdf) { + RooJSONFactoryWSTool::error("unable to find definition of constraint '" + constraintName + + "' for modifier '" + RooJSONFactoryWSTool::name(mod) + "'"); + } + constraintPdfs.push_back(constraintPdf); + } + } + std::size_t const nGammas = std::max(vals.size(), parnames.size()); + if (constraintPdfs.size() != nGammas) { + std::stringstream ss; + ss << "modifier '" << RooJSONFactoryWSTool::name(mod) << "' has " << constraintPdfs.size() + << " constraints, but " << nGammas << " parameters"; + RooJSONFactoryWSTool::error(ss.str()); + } + } else if (mod.has_child("constraint_type")) { + constraint = mod["constraint_type"].val(); + } else if (mod.has_child("constraint")) { + std::string constraintValue = mod["constraint"].val(); + if (isLegacyConstraintType(constraintValue)) { + constraint = constraintValue; + } else { + RooJSONFactoryWSTool::error("unable to resolve constraint value '" + constraintValue + + "' for modifier '" + RooJSONFactoryWSTool::name(mod) + + "': this looks like a legacy workspace where the 'constraint' field is " + "not a supported legacy constraint type"); + } + } shapeElems.add(createPHF(funcName, sysname, parnames, vals, tool, constraints, varlist, constraint, - defaultGammaMin, defaultShapeSysGammaMax, minShapeUncertainty)); + defaultGammaMin, defaultShapeSysGammaMax, minShapeUncertainty, + /*createConstraints=*/!hasConstraintList)); + for (auto *constraintPdf : constraintPdfs) { + if (constraintPdf) { + constraints.add(*constraintPdf); + } + } } else if (modtype == "custom") { RooAbsReal *obj = ws.function(sysname); if (!obj) { @@ -668,7 +744,6 @@ struct NormFactor { std::string name; RooAbsReal const *param = nullptr; RooAbsPdf const *constraint = nullptr; - TClass *constraintType = RooGaussian::Class(); NormFactor(RooAbsReal const &par, const RooAbsPdf *constr = nullptr) : name{par.GetName()}, param{&par}, constraint{constr} { @@ -682,10 +757,9 @@ struct NormSys { double high = 1.; int interpolationCode = 4; RooAbsPdf const *constraint = nullptr; - TClass *constraintType = RooGaussian::Class(); NormSys() {}; NormSys(const std::string &n, RooAbsReal *const p, double h, double l, int i, const RooAbsPdf *c) - : name(n), param(p), low(l), high(h), interpolationCode(i), constraint(c), constraintType(c->IsA()) + : name(n), param(p), low(l), high(h), interpolationCode(i), constraint(c) { } }; @@ -696,9 +770,8 @@ struct HistoSys { std::vector low; std::vector high; RooAbsPdf const *constraint = nullptr; - TClass *constraintType = RooGaussian::Class(); HistoSys(const std::string &n, RooAbsReal *const p, RooHistFunc *l, RooHistFunc *h, const RooAbsPdf *c) - : name(n), param(p), constraint(c), constraintType(c->IsA()) + : name(n), param(p), constraint(c) { low.assign(l->dataHist().weightArray(), l->dataHist().weightArray() + l->dataHist().numEntries()); high.assign(h->dataHist().weightArray(), h->dataHist().weightArray() + h->dataHist().numEntries()); @@ -707,9 +780,8 @@ struct HistoSys { struct ShapeSys { std::string name; std::vector constraints; + std::vector constraintPdfs; std::vector parameters; - RooAbsPdf const *constraint = nullptr; - TClass *constraintType = RooGaussian::Class(); ShapeSys(const std::string &n) : name{n} {} }; @@ -865,7 +937,6 @@ struct Sample { std::vector otherElements; bool useBarlowBeestonLight = false; std::vector staterrorParameters; - TClass *barlowBeestonLightConstraintType = RooPoisson::Class(); Sample(const std::string &n) : name{n} {} }; @@ -1064,7 +1135,6 @@ Channel readChannel(RooJSONFactoryWSTool *tool, const std::string &pdfname, cons channel.tot_yield[idx] += sample.hist[idx - 1]; channel.tot_yield2[idx] += (sample.hist[idx - 1] * sample.hist[idx - 1]); if (constraint) { - sample.barlowBeestonLightConstraintType = constraint->IsA(); if (RooPoisson *constraint_p = dynamic_cast(constraint)) { double erel = 1. / std::sqrt(poissonTau(*constraint_p, *g)); channel.rel_errors[idx] = erel; @@ -1099,16 +1169,16 @@ Channel readChannel(RooJSONFactoryWSTool *tool, const std::string &pdfname, cons } if (!constraint) { sys.constraints.push_back(0.0); + sys.constraintPdfs.push_back(nullptr); } else if (auto constraint_p = dynamic_cast(constraint)) { sys.constraints.push_back(1. / std::sqrt(poissonTau(*constraint_p, *g))); - if (!sys.constraint) { - sys.constraintType = RooPoisson::Class(); - } + sys.constraintPdfs.push_back(constraint_p); } else if (auto constraint_g = dynamic_cast(constraint)) { sys.constraints.push_back(constraint_g->getSigma().getVal() / constraint_g->getMean().getVal()); - if (!sys.constraint) { - sys.constraintType = RooGaussian::Class(); - } + sys.constraintPdfs.push_back(constraint_g); + } else { + RooJSONFactoryWSTool::error( + "currently, only RooPoisson and RooGaussian are supported as constraint types"); } } sample.shapesys.emplace_back(std::move(sys)); @@ -1146,13 +1216,11 @@ void configureStatError(Channel &channel) bool exportChannel(RooJSONFactoryWSTool *tool, const Channel &channel, JSONNode &elem) { - // Write the constraint reference (either by name or by type) for any - // modifier that supports an external Gaussian/Poisson/etc. constraint. + // Write the constraint reference for any modifier that supports an + // external Gaussian/Poisson/etc. constraint. auto writeConstraint = [](JSONNode &mod, auto const &sys) { if (sys.constraint) { - mod["constraint_name"] << sys.constraint->GetName(); - } else if (sys.constraintType) { - mod["constraint_type"] << toString(sys.constraintType); + mod["constraint"] << sys.constraint->GetName(); } }; @@ -1173,7 +1241,7 @@ bool exportChannel(RooJSONFactoryWSTool *tool, const Channel &channel, JSONNode mod["parameter"] << nf.param->GetName(); mod["type"] << "normfactor"; if (nf.constraint) { - mod["constraint_name"] << nf.constraint->GetName(); + mod["constraint"] << nf.constraint->GetName(); tool->queueExport(*nf.constraint); } } @@ -1217,13 +1285,18 @@ bool exportChannel(RooJSONFactoryWSTool *tool, const Channel &channel, JSONNode mod["name"] << sys.name; mod["type"] << "shapesys"; optionallyExportGammaParameters(mod, sys.name, sys.parameters); - writeConstraint(mod, sys); - auto &vals = mod["data"].set_map()["vals"]; - if (sys.constraint || sys.constraintType) { - vals.fill_seq(sys.constraints); - } else { - vals.fill_seq(std::vector(sys.parameters.size(), 0.0)); + if (std::any_of(sys.constraintPdfs.begin(), sys.constraintPdfs.end(), + [](auto *pdf) { return pdf != nullptr; })) { + auto &constraintNames = mod["constraints"].set_seq(); + for (auto *constraint : sys.constraintPdfs) { + if (constraint) { + constraintNames.append_child() << constraint->GetName(); + } else { + constraintNames.append_child().set_null(); + } + } } + mod["data"].set_map()["vals"].fill_seq(sys.constraints); } for (const auto &other : sample.otherElements) { @@ -1245,7 +1318,6 @@ bool exportChannel(RooJSONFactoryWSTool *tool, const Channel &channel, JSONNode mod["name"] << ::Literals::staterror; mod["type"] << ::Literals::staterror; optionallyExportGammaParameters(mod, "stat_" + channel.name, sample.staterrorParameters); - mod["constraint_type"] << toString(sample.barlowBeestonLightConstraintType); } if (!observablesWritten) { @@ -1382,6 +1454,13 @@ bool tryExportHistFactory(RooJSONFactoryWSTool *tool, const std::string &pdfname tool->queueExport(*modifier.constraint); } } + for (auto &modifier : sample.shapesys) { + for (auto *constraint : modifier.constraintPdfs) { + if (constraint) { + tool->queueExport(*constraint); + } + } + } } // Export all the custom modifiers diff --git a/roofit/hs3/src/JSONFactories_RooFitCore.cxx b/roofit/hs3/src/JSONFactories_RooFitCore.cxx index 61c2558affd70..abd8d294eb5cc 100644 --- a/roofit/hs3/src/JSONFactories_RooFitCore.cxx +++ b/roofit/hs3/src/JSONFactories_RooFitCore.cxx @@ -63,6 +63,8 @@ #include #include +#include +#include using RooFit::Detail::JSONNode; @@ -71,6 +73,11 @@ using RooFit::Detail::JSONNode; /////////////////////////////////////////////////////////////////////////////////////////////////////// namespace { +bool isReservedExpressionIdentifier(const std::string &arg) +{ + return arg == "PI" || arg == "EULER" || arg == "TMath"; +} + /** * Extracts arguments from a mathematical expression. * @@ -110,34 +117,68 @@ std::set extractArguments(std::string expr) } std::string arg(expr.substr(startidx, i - startidx)); startidx = expr.size(); - arguments.insert(arg); + if (!isReservedExpressionIdentifier(arg)) { + arguments.insert(arg); + } } } } if (startidx < expr.size()) { - arguments.insert(expr.substr(startidx)); + std::string arg(expr.substr(startidx)); + if (!isReservedExpressionIdentifier(arg)) { + arguments.insert(arg); + } } return arguments; } -template -class RooFormulaArgFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - if (!p.has_child("expression")) { - RooJSONFactoryWSTool::error("no expression given for '" + name + "'"); - } - TString formula(p["expression"].val()); - RooArgList dependents; - for (const auto &d : extractArguments(formula.Data())) { - dependents.add(*tool->request(d, name)); +void replaceIdentifier(TString &expr, std::string_view identifier, std::string_view replacement) +{ + std::string in(expr.Data()); + std::string out; + out.reserve(in.size()); + + for (std::size_t pos = 0; pos < in.size();) { + const bool matches = in.compare(pos, identifier.size(), identifier) == 0; + const bool beforeIdentifier = + pos > 0 && (std::isalnum(static_cast(in[pos - 1])) || in[pos - 1] == '_'); + const std::size_t end = pos + identifier.size(); + const bool afterIdentifier = + end < in.size() && (std::isalnum(static_cast(in[end])) || in[end] == '_'); + if (matches && !beforeIdentifier && !afterIdentifier) { + out.append(replacement); + pos = end; + } else { + out.push_back(in[pos]); + ++pos; } - tool->wsImport(RooArg_t{name.c_str(), formula, dependents}); - return true; } -}; + + expr = out.c_str(); +} + +void translateImportedExpression(TString &expr) +{ + replaceIdentifier(expr, "PI", "TMath::Pi()"); + replaceIdentifier(expr, "EULER", "TMath::E()"); +} + +template +bool importFormulaArg(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + if (!p.has_child("expression")) { + RooJSONFactoryWSTool::error("no expression given for '" + name + "'"); + } + TString formula(p["expression"].val()); + translateImportedExpression(formula); + RooArgList dependents; + for (const auto &d : extractArguments(formula.Data())) { + dependents.add(*tool->request(d, name)); + } + tool->wsImport(RooArg_t{name.c_str(), formula, dependents}); + return true; +} // Fast-path importers for RooProduct, RooAddition, and RooProdPdf that // bypass the generic factory-expression mechanism. The default path @@ -146,668 +187,590 @@ class RooFormulaArgFactory : public RooFit::JSONIO::Importer { // thousands of product/sum nodes (a common shape for HistFactory models) // that JIT cost dominates JSON import time. Constructing the RooFit object // directly here keeps the work O(N) of cheap C++ calls. -class RooProductFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - tool->wsEmplace(name, tool->requestArgList(p, "factors")); - return true; - } -}; +bool importProduct(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + tool->wsEmplace(name, tool->requestArgList(p, "factors")); + return true; +} -class RooProdPdfFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - tool->wsEmplace(name, tool->requestArgList(p, "factors")); - return true; - } -}; +bool importProdPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + tool->wsEmplace(name, tool->requestArgList(p, "factors")); + return true; +} -class RooAdditionFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - tool->wsEmplace(name, tool->requestArgList(p, "summands")); - return true; - } -}; +bool importAddition(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + tool->wsEmplace(name, tool->requestArgList(p, "summands")); + return true; +} -class RooAddPdfFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - if (!tool->requestArgList(p, "coefficients").empty()) { - tool->wsEmplace(name, tool->requestArgList(p, "summands"), - tool->requestArgList(p, "coefficients")); - return true; - } - tool->wsEmplace(name, tool->requestArgList(p, "summands")); +bool importAddPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + if (!tool->requestArgList(p, "coefficients").empty()) { + tool->wsEmplace(name, tool->requestArgList(p, "summands"), + tool->requestArgList(p, "coefficients")); return true; } -}; + tool->wsEmplace(name, tool->requestArgList(p, "summands")); + return true; +} -class RooAddModelFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - tool->wsEmplace(name, tool->requestArgList(p, "summands"), - tool->requestArgList(p, "coefficients")); - return true; - } -}; +bool importAddModel(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + tool->wsEmplace(name, tool->requestArgList(p, "summands"), + tool->requestArgList(p, "coefficients")); + return true; +} -class RooBinWidthFunctionFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooHistFunc *hf = static_cast(tool->request(p["histogram"].val(), name)); - tool->wsEmplace(name, *hf, p["divideByBinWidth"].val_bool()); - return true; - } -}; +template +bool importBinWidthFunction(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooHistFunc *hf = static_cast(tool->request(p["histogram"].val(), name)); + tool->wsEmplace(name, *hf, DivideByBinWidth); + return true; +} -class RooBinSamplingPdfFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); +bool importBinSamplingPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); - RooAbsPdf *pdf = tool->requestArg(p, "pdf"); - RooRealVar *obs = tool->requestArg(p, "observable"); + RooAbsPdf *pdf = tool->requestArg(p, "pdf"); + RooRealVar *obs = tool->requestArg(p, "observable"); - if (!pdf->dependsOn(*obs)) { - RooJSONFactoryWSTool::error(std::string("pdf '") + pdf->GetName() + "' does not depend on observable '" + - obs->GetName() + "' as indicated by parent RooBinSamplingPdf '" + name + - "', please check!"); - } + if (!pdf->dependsOn(*obs)) { + RooJSONFactoryWSTool::error(std::string("pdf '") + pdf->GetName() + "' does not depend on observable '" + + obs->GetName() + "' as indicated by parent RooBinSamplingPdf '" + name + + "', please check!"); + } - if (!p.has_child("epsilon")) { - RooJSONFactoryWSTool::error("no epsilon given in '" + name + "'"); - } - double epsilon(p["epsilon"].val_double()); + if (!p.has_child("epsilon")) { + RooJSONFactoryWSTool::error("no epsilon given in '" + name + "'"); + } + double epsilon(p["epsilon"].val_double()); - tool->wsEmplace(name, *obs, *pdf, epsilon); + tool->wsEmplace(name, *obs, *pdf, epsilon); - return true; - } -}; + return true; +} -class RooRealSumPdfFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); +bool importRealSumPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); - bool extended = false; - if (p.has_child("extended") && p["extended"].val_bool()) { - extended = true; - } - tool->wsEmplace(name, tool->requestArgList(p, "samples"), - tool->requestArgList(p, "coefficients"), extended); - return true; + bool extended = false; + if (p.has_child("extended") && p["extended"].val_bool()) { + extended = true; } -}; + tool->wsEmplace(name, tool->requestArgList(p, "samples"), + tool->requestArgList(p, "coefficients"), extended); + return true; +} + +bool importRealSumFunc(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + tool->wsEmplace(name, tool->requestArgList(p, "samples"), + tool->requestArgList(p, "coefficients")); + return true; +} -class RooRealSumFuncFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - tool->wsEmplace(name, tool->requestArgList(p, "samples"), - tool->requestArgList(p, "coefficients")); - return true; - } -}; template -class RooPolynomialFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - if (!p.has_child("coefficients")) { - RooJSONFactoryWSTool::error("no coefficients given in '" + name + "'"); - } - RooAbsReal *x = tool->requestArg(p, "x"); - RooArgList coefs; - int order = 0; - int lowestOrder = 0; - for (const auto &coef : p["coefficients"].children()) { - // As long as the coefficients match the default coefficients in - // RooFit, we don't have to instantiate RooFit objects but can - // increase the lowestOrder flag. - if (order == 0 && (coef.val() == "1.0" || coef.val() == "1")) { - ++lowestOrder; - } else if (coefs.empty() && (coef.val() == "0.0" || coef.val() == "0")) { - ++lowestOrder; - } else { - coefs.add(*tool->request(coef.val(), name)); - } - ++order; +bool importPolynomial(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + if (!p.has_child("coefficients")) { + RooJSONFactoryWSTool::error("no coefficients given in '" + name + "'"); + } + RooAbsReal *x = tool->requestArg(p, "x"); + RooArgList coefs; + int order = 0; + int lowestOrder = 0; + for (const auto &coef : p["coefficients"].children()) { + // As long as the coefficients match the default coefficients in + // RooFit, we don't have to instantiate RooFit objects but can + // increase the lowestOrder flag. + if (order == 0 && (coef.val() == "1.0" || coef.val() == "1")) { + ++lowestOrder; + } else if (coefs.empty() && (coef.val() == "0.0" || coef.val() == "0")) { + ++lowestOrder; + } else { + coefs.add(*tool->request(coef.val(), name)); } - - tool->wsEmplace(name, *x, coefs, lowestOrder); - return true; + ++order; } -}; -class RooPoissonFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooAbsReal *x = tool->requestArg(p, "x"); - RooAbsReal *mean = tool->requestArg(p, "mean"); - tool->wsEmplace(name, *x, *mean, !p["integer"].val_bool()); - return true; - } -}; + tool->wsEmplace(name, *x, coefs, lowestOrder); + return true; +} -class RooDecayFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooRealVar *t = tool->requestArg(p, "t"); - RooAbsReal *tau = tool->requestArg(p, "tau"); - RooResolutionModel *model = dynamic_cast(tool->requestArg(p, "resolutionModel")); - RooDecay::DecayType decayType = static_cast(p["decayType"].val_int()); - tool->wsEmplace(name, *t, *tau, *model, decayType); - return true; - } -}; +bool importPoisson(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooAbsReal *x = tool->requestArg(p, "x"); + RooAbsReal *mean = tool->requestArg(p, "mean"); + tool->wsEmplace(name, *x, *mean, !p["integer"].val_bool()); + return true; +} -class RooTruthModelFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooRealVar *x = tool->requestArg(p, "x"); - tool->wsEmplace(name, *x); - return true; - } -}; +bool importDecay(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooRealVar *t = tool->requestArg(p, "t"); + RooAbsReal *tau = tool->requestArg(p, "tau"); + RooResolutionModel *model = dynamic_cast(tool->requestArg(p, "resolutionModel")); + RooDecay::DecayType decayType = static_cast(p["decayType"].val_int()); + tool->wsEmplace(name, *t, *tau, *model, decayType); + return true; +} -class RooGaussModelFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooRealVar *x = tool->requestArg(p, "x"); - RooRealVar *mean = tool->requestArg(p, "mean"); - RooRealVar *sigma = tool->requestArg(p, "sigma"); - tool->wsEmplace(name, *x, *mean, *sigma); - return true; - } -}; +bool importTruthModel(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooRealVar *x = tool->requestArg(p, "x"); + tool->wsEmplace(name, *x); + return true; +} -class RooRealIntegralFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooAbsReal *func = tool->requestArg(p, "integrand"); - auto vars = tool->requestArgList(p, "variables"); - RooArgSet normSet; - RooArgSet const *normSetPtr = nullptr; - if (p.has_child("normalization")) { - normSet.add(tool->requestArgSet(p, "normalization")); - normSetPtr = &normSet; - } - std::string domain; - bool hasDomain = p.has_child("domain"); - if (hasDomain) { - domain = p["domain"].val(); - } - // todo: at some point, take care of integrator configurations - tool->wsEmplace(name, *func, vars, normSetPtr, static_cast(nullptr), - hasDomain ? domain.c_str() : nullptr); - return true; +bool importGaussModel(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooRealVar *x = tool->requestArg(p, "x"); + RooRealVar *mean = tool->requestArg(p, "mean"); + RooRealVar *sigma = tool->requestArg(p, "sigma"); + tool->wsEmplace(name, *x, *mean, *sigma); + return true; +} + +bool importRealIntegral(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooAbsReal *func = tool->requestArg(p, "integrand"); + auto vars = tool->requestArgList(p, "variables"); + RooArgSet normSet; + RooArgSet const *normSetPtr = nullptr; + if (p.has_child("normalization")) { + normSet.add(tool->requestArgSet(p, "normalization")); + normSetPtr = &normSet; } -}; + std::string domain; + bool hasDomain = p.has_child("domain"); + if (hasDomain) { + domain = p["domain"].val(); + } + // todo: at some point, take care of integrator configurations + tool->wsEmplace(name, *func, vars, normSetPtr, static_cast(nullptr), + hasDomain ? domain.c_str() : nullptr); + return true; +} -class RooDerivativeFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooAbsReal *func = tool->requestArg(p, "function"); - RooRealVar *x = tool->requestArg(p, "x"); - Int_t order = p["order"].val_int(); - double eps = p["eps"].val_double(); - if (p.has_child("normalization")) { - RooArgSet normSet; - normSet.add(tool->requestArgSet(p, "normalization")); - tool->wsEmplace(name, *func, *x, normSet, order, eps); - return true; - } - tool->wsEmplace(name, *func, *x, order, eps); +bool importDerivative(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooAbsReal *func = tool->requestArg(p, "function"); + RooRealVar *x = tool->requestArg(p, "x"); + Int_t order = p["order"].val_int(); + double eps = p["eps"].val_double(); + if (p.has_child("normalization")) { + RooArgSet normSet; + normSet.add(tool->requestArgSet(p, "normalization")); + tool->wsEmplace(name, *func, *x, normSet, order, eps); return true; } -}; + tool->wsEmplace(name, *func, *x, order, eps); + return true; +} -class RooFFTConvPdfFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooRealVar *convVar = tool->requestArg(p, "conv_var"); - Int_t order = p["ipOrder"].val_int(); - RooAbsPdf *pdf1 = tool->requestArg(p, "pdf1"); - RooAbsPdf *pdf2 = tool->requestArg(p, "pdf2"); - if (p.has_child("conv_func")) { - RooAbsReal *convFunc = tool->requestArg(p, "conv_func"); - tool->wsEmplace(name, *convFunc, *convVar, *pdf1, *pdf2, order); - return true; - } - tool->wsEmplace(name, *convVar, *pdf1, *pdf2, order); +bool importFFTConvPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooRealVar *convVar = tool->requestArg(p, "conv_var"); + Int_t order = p["ipOrder"].val_int(); + RooAbsPdf *pdf1 = tool->requestArg(p, "pdf1"); + RooAbsPdf *pdf2 = tool->requestArg(p, "pdf2"); + if (p.has_child("conv_func")) { + RooAbsReal *convFunc = tool->requestArg(p, "conv_func"); + tool->wsEmplace(name, *convFunc, *convVar, *pdf1, *pdf2, order); return true; } -}; + tool->wsEmplace(name, *convVar, *pdf1, *pdf2, order); + return true; +} -class RooExtendPdfFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooAbsPdf *pdf = tool->requestArg(p, "pdf"); - RooAbsReal *norm = tool->requestArg(p, "norm"); - if (p.has_child("range")) { - std::string rangeName = p["range"].val(); - tool->wsEmplace(name, *pdf, *norm, rangeName.c_str()); - return true; - } - tool->wsEmplace(name, *pdf, *norm); +bool importExtendPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooAbsPdf *pdf = tool->requestArg(p, "pdf"); + RooAbsReal *norm = tool->requestArg(p, "norm"); + if (p.has_child("range")) { + std::string rangeName = p["range"].val(); + tool->wsEmplace(name, *pdf, *norm, rangeName.c_str()); return true; } -}; + tool->wsEmplace(name, *pdf, *norm); + return true; +} -class RooLogNormalFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooAbsReal *x = tool->requestArg(p, "x"); +bool importLogNormal(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooAbsReal *x = tool->requestArg(p, "x"); - // Same mechanism to undo the parameter transformation as in the - // RooExponentialFactory (see comments in that class for more info). - const std::string muName = p["mu"].val(); - const std::string sigmaName = p["sigma"].val(); - const bool isTransformed = endsWith(muName, "_lognormal_log"); - const std::string suffixToRemove = isTransformed ? "_lognormal_log" : ""; - RooAbsReal *mu = tool->request(removeSuffix(muName, suffixToRemove), name); - RooAbsReal *sigma = tool->request(removeSuffix(sigmaName, suffixToRemove), name); + // Same mechanism to undo the parameter transformation as in the + // importExponential() function (see comments in that function for more info). + const std::string muName = p["mu"].val(); + const std::string sigmaName = p["sigma"].val(); + const bool isTransformed = endsWith(muName, "_lognormal_log"); + const std::string suffixToRemove = isTransformed ? "_lognormal_log" : ""; + RooAbsReal *mu = tool->request(removeSuffix(muName, suffixToRemove), name); + RooAbsReal *sigma = tool->request(removeSuffix(sigmaName, suffixToRemove), name); - tool->wsEmplace(name, *x, *mu, *sigma, !isTransformed); + tool->wsEmplace(name, *x, *mu, *sigma, !isTransformed); - return true; - } -}; + return true; +} -class RooExponentialFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooAbsReal *x = tool->requestArg(p, "x"); - - // If the parameter name ends with the "_exponential_inverted" suffix, - // this means that it was exported from a RooFit object where the - // parameter first needed to be transformed on export to match the HS3 - // specification. But when re-importing such a parameter, we can simply - // skip the transformation and use the original RooFit parameter without - // the suffix. - // - // A concrete example: take the following RooFit pdf in the factory language: - // - // "Exponential::exponential_1(x[0, 10], c[-0.1])" - // - // It defines en exponential exp(c * x). However, in HS3 the exponential - // is defined as exp(-c * x), to RooFit would export these dictionaries - // to the JSON: - // - // { - // "name": "exponential_1", // HS3 exponential_dist with transformed parameter - // "type": "exponential_dist", - // "x": "x", - // "c": "c_exponential_inverted" - // }, - // { - // "name": "c_exponential_inverted", // transformation function created on-the-fly on export - // "type": "generic_function", - // "expression": "-c" - // } - // - // On import, we can directly take the non-transformed parameter, which is - // we check for the suffix and optionally remove it from the requested - // name next: - - const std::string constParamName = p["c"].val(); - const bool isInverted = endsWith(constParamName, "_exponential_inverted"); - const std::string suffixToRemove = isInverted ? "_exponential_inverted" : ""; - RooAbsReal *c = tool->request(removeSuffix(constParamName, suffixToRemove), name); - - tool->wsEmplace(name, *x, *c, !isInverted); +bool importExponential(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + RooAbsReal *x = tool->requestArg(p, "x"); + + // If the parameter name ends with the "_exponential_inverted" suffix, + // this means that it was exported from a RooFit object where the + // parameter first needed to be transformed on export to match the HS3 + // specification. But when re-importing such a parameter, we can simply + // skip the transformation and use the original RooFit parameter without + // the suffix. + // + // A concrete example: take the following RooFit pdf in the factory language: + // + // "Exponential::exponential_1(x[0, 10], c[-0.1])" + // + // It defines en exponential exp(c * x). However, in HS3 the exponential + // is defined as exp(-c * x), to RooFit would export these dictionaries + // to the JSON: + // + // { + // "name": "exponential_1", // HS3 exponential_dist with transformed parameter + // "type": "exponential_dist", + // "x": "x", + // "c": "c_exponential_inverted" + // }, + // { + // "name": "c_exponential_inverted", // transformation function created on-the-fly on export + // "type": "generic_function", + // "expression": "-c" + // } + // + // On import, we can directly take the non-transformed parameter, which is + // we check for the suffix and optionally remove it from the requested + // name next: + + const std::string constParamName = p["c"].val(); + const bool isInverted = endsWith(constParamName, "_exponential_inverted"); + const std::string suffixToRemove = isInverted ? "_exponential_inverted" : ""; + RooAbsReal *c = tool->request(removeSuffix(constParamName, suffixToRemove), name); + + tool->wsEmplace(name, *x, *c, !isInverted); + + return true; +} - return true; +bool importMultiVarGaussian(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + bool has_cov = p.has_child("covariances"); + bool has_corr = p.has_child("correlations") && p.has_child("standard_deviations"); + if (!has_cov && !has_corr) { + RooJSONFactoryWSTool::error("no covariances or correlations+standard_deviations given in '" + name + "'"); } -}; -class RooMultiVarGaussianFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - bool has_cov = p.has_child("covariances"); - bool has_corr = p.has_child("correlations") && p.has_child("standard_deviations"); - if (!has_cov && !has_corr) { - RooJSONFactoryWSTool::error("no covariances or correlations+standard_deviations given in '" + name + "'"); - } - - TMatrixDSym covmat; - - if (has_cov) { - int n = p["covariances"].num_children(); - int i = 0; - covmat.ResizeTo(n, n); - for (const auto &row : p["covariances"].children()) { - int j = 0; - for (const auto &val : row.children()) { - covmat(i, j) = val.val_double(); - ++j; - } - ++i; + TMatrixDSym covmat; + + if (has_cov) { + int n = p["covariances"].num_children(); + int i = 0; + covmat.ResizeTo(n, n); + for (const auto &row : p["covariances"].children()) { + int j = 0; + for (const auto &val : row.children()) { + covmat(i, j) = val.val_double(); + ++j; } - } else { - std::vector variances; - for (const auto &v : p["standard_deviations"].children()) { - variances.push_back(v.val_double()); - } - covmat.ResizeTo(variances.size(), variances.size()); - int i = 0; - for (const auto &row : p["correlations"].children()) { - int j = 0; - for (const auto &val : row.children()) { - covmat(i, j) = val.val_double() * variances[i] * variances[j]; - ++j; - } - ++i; + ++i; + } + } else { + std::vector variances; + for (const auto &v : p["standard_deviations"].children()) { + variances.push_back(v.val_double()); + } + covmat.ResizeTo(variances.size(), variances.size()); + int i = 0; + for (const auto &row : p["correlations"].children()) { + int j = 0; + for (const auto &val : row.children()) { + covmat(i, j) = val.val_double() * variances[i] * variances[j]; + ++j; } + ++i; } - tool->wsEmplace(name, tool->requestArgList(p, "x"), - tool->requestArgList(p, "mean"), covmat); - return true; } -}; + tool->wsEmplace(name, tool->requestArgList(p, "x"), + tool->requestArgList(p, "mean"), covmat); + return true; +} -class ParamHistFuncFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - RooArgList varList = tool->requestArgList(p, "variables"); - if (!p.has_child("axes")) { - std::stringstream ss; - ss << "No axes given in '" << name << "'" - << ". Using default binning (uniform; nbins=100). If needed, export the Workspace to JSON with a newer " - << "Root version that supports custom ParamHistFunc binnings(>=6.38.00)." << std::endl; - RooJSONFactoryWSTool::warning(ss.str()); - tool->wsEmplace(name, varList, tool->requestArgList(p, "parameters")); - return true; +RooArgList readBinning(const JSONNode &topNode, const RooArgList &varList) +{ + // Temporary map from variable name → RooRealVar + std::map> varMap; + + // Build variables from JSON + for (const JSONNode &node : topNode["axes"].children()) { + const std::string name = node["name"].val(); + std::unique_ptr obs; + + if (node.has_child("edges")) { + std::vector edges; + for (const auto &bound : node["edges"].children()) { + edges.push_back(bound.val_double()); + } + obs = std::make_unique(name.c_str(), name.c_str(), edges.front(), edges.back()); + RooBinning bins(obs->getMin(), obs->getMax()); + for (auto b : edges) + bins.addBoundary(b); + obs->setBinning(bins); + } else { + obs = std::make_unique(name.c_str(), name.c_str(), node["min"].val_double(), + node["max"].val_double()); + obs->setBins(node["nbins"].val_int()); } - tool->wsEmplace(name, readBinning(p, varList), tool->requestArgList(p, "parameters")); - return true; + + varMap[name] = std::move(obs); } -private: - RooArgList readBinning(const JSONNode &topNode, const RooArgList &varList) const - { - // Temporary map from variable name → RooRealVar - std::map> varMap; - - // Build variables from JSON - for (const JSONNode &node : topNode["axes"].children()) { - const std::string name = node["name"].val(); - std::unique_ptr obs; - - if (node.has_child("edges")) { - std::vector edges; - for (const auto &bound : node["edges"].children()) { - edges.push_back(bound.val_double()); - } - obs = std::make_unique(name.c_str(), name.c_str(), edges.front(), edges.back()); - RooBinning bins(obs->getMin(), obs->getMax()); - for (auto b : edges) - bins.addBoundary(b); - obs->setBinning(bins); - } else { - obs = std::make_unique(name.c_str(), name.c_str(), node["min"].val_double(), - node["max"].val_double()); - obs->setBins(node["nbins"].val_int()); - } + // Now build the final list following the order in varList + RooArgList vars; + for (auto *refVar : dynamic_range_cast(varList)) { + if (!refVar) + continue; - varMap[name] = std::move(obs); + auto it = varMap.find(refVar->GetName()); + if (it != varMap.end()) { + vars.addOwned(std::move(it->second)); // preserve ownership } + } + return vars; +} - // Now build the final list following the order in varList - RooArgList vars; - for (auto *refVar : dynamic_range_cast(varList)) { - if (!refVar) - continue; - - auto it = varMap.find(refVar->GetName()); - if (it != varMap.end()) { - vars.addOwned(std::move(it->second)); // preserve ownership - } - } - return vars; +bool importParamHistFunc(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + if (!p.has_child("parameters")) { + return false; } -}; + std::string name(RooJSONFactoryWSTool::name(p)); + RooArgList varList = tool->requestArgList(p, "variables"); + if (!p.has_child("axes")) { + std::stringstream ss; + ss << "No axes given in '" << name << "'" + << ". Using default binning (uniform; nbins=100). If needed, export the Workspace to JSON with a newer " + << "Root version that supports custom ParamHistFunc binnings(>=6.38.00)." << std::endl; + RooJSONFactoryWSTool::warning(ss.str()); + tool->wsEmplace(name, varList, tool->requestArgList(p, "parameters")); + return true; + } + tool->wsEmplace(name, readBinning(p, varList), tool->requestArgList(p, "parameters")); + return true; +} -class RooSplineFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - const std::string name(RooJSONFactoryWSTool::name(p)); +bool importSpline(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + const std::string name(RooJSONFactoryWSTool::name(p)); - // Mandatory fields - if (!p.has_child("x")) { - RooJSONFactoryWSTool::error("no x given in '" + name + "'"); - } - if (!p.has_child("x0") || !p.has_child("y0")) { - RooJSONFactoryWSTool::error("no x0/y0 given in '" + name + "'"); - } + // Mandatory fields + if (!p.has_child("x")) { + RooJSONFactoryWSTool::error("no x given in '" + name + "'"); + } + if (!p.has_child("x0") || !p.has_child("y0")) { + RooJSONFactoryWSTool::error("no x0/y0 given in '" + name + "'"); + } - RooAbsReal *x = tool->requestArg(p, "x"); - - // Optional fields (defaults follow RooSpline ctor defaults) - std::string algo = p.has_child("interpolation") ? p["interpolation"].val() : "poly3"; - int order = 0; - if (algo == "poly3") - order = 3; - else if (algo == "poly5") - order = 5; - else { - RooJSONFactoryWSTool::error("unsupported algo '" + algo + "' for RooSpline in '" + name + - "': allowed are 'poly3' and 'poly5'"); - } - const bool logx = p.has_child("logx") ? p["logx"].val_bool() : false; - const bool logy = p.has_child("logy") ? p["logy"].val_bool() : false; - - // Read knots - std::vector x0; - std::vector y0; - x0.reserve(p["x0"].num_children()); - y0.reserve(p["y0"].num_children()); - - for (const auto &v : p["x0"].children()) - x0.push_back(v.val_double()); - for (const auto &v : p["y0"].children()) - y0.push_back(v.val_double()); - - if (x0.size() != y0.size()) { - RooJSONFactoryWSTool::error("x0/y0 size mismatch in '" + name + "': x0 has " + std::to_string(x0.size()) + - ", y0 has " + std::to_string(y0.size())); - } - if (x0.size() < 2) { - RooJSONFactoryWSTool::error("need at least 2 knots in '" + name + "'"); - } + RooAbsReal *x = tool->requestArg(p, "x"); + + // Optional fields (defaults follow RooSpline ctor defaults) + std::string algo = p.has_child("interpolation") ? p["interpolation"].val() : "poly3"; + int order = 0; + if (algo == "poly3") + order = 3; + else if (algo == "poly5") + order = 5; + else { + RooJSONFactoryWSTool::error("unsupported algo '" + algo + "' for RooSpline in '" + name + + "': allowed are 'poly3' and 'poly5'"); + } + const bool logx = p.has_child("logx") ? p["logx"].val_bool() : false; + const bool logy = p.has_child("logy") ? p["logy"].val_bool() : false; + + // Read knots + std::vector x0; + std::vector y0; + x0.reserve(p["x0"].num_children()); + y0.reserve(p["y0"].num_children()); + + for (const auto &v : p["x0"].children()) + x0.push_back(v.val_double()); + for (const auto &v : p["y0"].children()) + y0.push_back(v.val_double()); + + if (x0.size() != y0.size()) { + RooJSONFactoryWSTool::error("x0/y0 size mismatch in '" + name + "': x0 has " + std::to_string(x0.size()) + + ", y0 has " + std::to_string(y0.size())); + } + if (x0.size() < 2) { + RooJSONFactoryWSTool::error("need at least 2 knots in '" + name + "'"); + } - // Construct RooSpline(name,title, x, x0, y0, order, logx, logy) - tool->wsEmplace<::RooSpline>(name.c_str(), *x, std::span(x0.data(), x0.size()), - std::span(y0.data(), y0.size()), order, logx, logy); + // Construct RooSpline(name,title, x, x0, y0, order, logx, logy) + tool->wsEmplace<::RooSpline>(name.c_str(), *x, std::span(x0.data(), x0.size()), + std::span(y0.data(), y0.size()), order, logx, logy); - return true; - } -}; + return true; +} /////////////////////////////////////////////////////////////////////////////////////////////////////// // specialized exporter implementations /////////////////////////////////////////////////////////////////////////////////////////////////////// template -class RooAddPdfStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - const RooArg_t *pdf = static_cast(func); - elem["type"] << key(); - RooJSONFactoryWSTool::fillSeq(elem["summands"], pdf->pdfList()); - RooJSONFactoryWSTool::fillSeq(elem["coefficients"], pdf->coefList()); - elem["extended"] << (pdf->extendMode() != RooArg_t::CanNotBeExtended); - return true; - } -}; +bool exportAddPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + const RooArg_t *pdf = static_cast(func); + elem["type"] << key; + RooJSONFactoryWSTool::fillSeq(elem["summands"], pdf->pdfList()); + RooJSONFactoryWSTool::fillSeq(elem["coefficients"], pdf->coefList()); + elem["extended"] << (pdf->extendMode() != RooArg_t::CanNotBeExtended); + return true; +} -class RooRealSumPdfStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - const RooRealSumPdf *pdf = static_cast(func); - elem["type"] << key(); - RooJSONFactoryWSTool::fillSeq(elem["samples"], pdf->funcList()); - RooJSONFactoryWSTool::fillSeq(elem["coefficients"], pdf->coefList()); - elem["extended"] << (pdf->extendMode() != RooAbsPdf::CanNotBeExtended); - return true; - } -}; +bool exportRealSumPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + const RooRealSumPdf *pdf = static_cast(func); + elem["type"] << key; + RooJSONFactoryWSTool::fillSeq(elem["samples"], pdf->funcList()); + RooJSONFactoryWSTool::fillSeq(elem["coefficients"], pdf->coefList()); + elem["extended"] << (pdf->extendMode() != RooAbsPdf::CanNotBeExtended); + return true; +} -class RooRealSumFuncStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - const RooRealSumFunc *pdf = static_cast(func); - elem["type"] << key(); - RooJSONFactoryWSTool::fillSeq(elem["samples"], pdf->funcList()); - RooJSONFactoryWSTool::fillSeq(elem["coefficients"], pdf->coefList()); - return true; - } -}; +bool exportRealSumFunc(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + const RooRealSumFunc *pdf = static_cast(func); + elem["type"] << key; + RooJSONFactoryWSTool::fillSeq(elem["samples"], pdf->funcList()); + RooJSONFactoryWSTool::fillSeq(elem["coefficients"], pdf->coefList()); + return true; +} template -class RooHistStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem) const override - { - const RooArg_t *hf = static_cast(func); - elem["type"] << key(); - RooDataHist const &dh = hf->dataHist(); - tool->exportHisto(*dh.get(), dh.numEntries(), dh.weightArray(), elem["data"].set_map()); - return true; - } -}; +bool exportHist(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + const RooArg_t *hf = static_cast(func); + elem["type"] << key; + RooDataHist const &dh = hf->dataHist(); + tool->exportHisto(*dh.get(), dh.numEntries(), dh.weightArray(), elem["data"].set_map()); + return true; +} template -class RooHistFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - std::string name(RooJSONFactoryWSTool::name(p)); - if (!p.has_child("data")) { - RooJSONFactoryWSTool::error("function '" + name + "' is of histogram type, but does not define a 'data' key"); - } - std::unique_ptr dataHist = - RooJSONFactoryWSTool::readBinnedData(p["data"], name, RooJSONFactoryWSTool::readAxes(p["data"])); - tool->wsEmplace(name, *dataHist->get(), *dataHist); - return true; +bool importHist(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + if (!p.has_child("data")) { + return false; } -}; + std::unique_ptr dataHist = + RooJSONFactoryWSTool::readBinnedData(p["data"], name, RooJSONFactoryWSTool::readAxes(p["data"])); + tool->wsEmplace(name, *dataHist->get(), *dataHist); + return true; +} -class RooBinSamplingPdfStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - const RooBinSamplingPdf *pdf = static_cast(func); - elem["type"] << key(); - elem["pdf"] << pdf->pdf().GetName(); - elem["observable"] << pdf->observable().GetName(); - elem["epsilon"] << pdf->epsilon(); - return true; - } -}; +bool exportBinSamplingPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + const RooBinSamplingPdf *pdf = static_cast(func); + elem["type"] << key; + elem["pdf"] << pdf->pdf().GetName(); + elem["observable"] << pdf->observable().GetName(); + elem["epsilon"] << pdf->epsilon(); + return true; +} -class RooBinWidthFunctionStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - const RooBinWidthFunction *pdf = static_cast(func); - elem["type"] << key(); - elem["histogram"] << pdf->histFunc().GetName(); - elem["divideByBinWidth"] << pdf->divideByBinWidth(); - return true; - } -}; +bool exportBinWidthFunction(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &) +{ + const RooBinWidthFunction *pdf = static_cast(func); + elem["type"] << (pdf->divideByBinWidth() ? "inverse_binvolume" : "binvolume"); + elem["histogram"] << pdf->histFunc().GetName(); + return true; +} + +void cleanExpression(TString &expr) +{ + // Plain substring replacement would also hit longer identifiers that + // share a prefix (e.g. "TMath::Tan" in "TMath::TanH", or "TMath::Pi" in + // "TMath::PiOver2"), corrupting the exported expression. Identifiers + // without a replacement are kept as-is. + replaceIdentifier(expr, "TMath::Exp", "exp"); + replaceIdentifier(expr, "TMath::Min", "min"); + replaceIdentifier(expr, "TMath::Max", "max"); + replaceIdentifier(expr, "TMath::Log", "log"); + replaceIdentifier(expr, "TMath::Log10", "log10"); + replaceIdentifier(expr, "TMath::Cos", "cos"); + replaceIdentifier(expr, "TMath::CosH", "cosh"); + replaceIdentifier(expr, "TMath::Sin", "sin"); + replaceIdentifier(expr, "TMath::SinH", "sinh"); + replaceIdentifier(expr, "TMath::Sqrt", "sqrt"); + replaceIdentifier(expr, "TMath::Power", "pow"); + replaceIdentifier(expr, "TMath::Erf", "erf"); + replaceIdentifier(expr, "TMath::Erfc", "erfc"); + replaceIdentifier(expr, "TMath::Floor", "floor"); + replaceIdentifier(expr, "TMath::Ceil", "ceil"); + replaceIdentifier(expr, "TMath::Abs", "abs"); + replaceIdentifier(expr, "TMath::Tan", "tan"); + replaceIdentifier(expr, "TMath::TanH", "tanh"); + replaceIdentifier(expr, "TMath::ASin", "asin"); + replaceIdentifier(expr, "TMath::ACos", "acos"); + replaceIdentifier(expr, "TMath::ATan", "atan"); + replaceIdentifier(expr, "TMath::ATan2", "atan2"); + replaceIdentifier(expr, "TMath::Pi()", "PI"); + replaceIdentifier(expr, "TMath::E()", "EULER"); +} template -class RooFormulaArgStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - const RooArg_t *pdf = static_cast(func); - elem["type"] << key(); - TString expression(pdf->expression()); - cleanExpression(expression); - // If the tokens follow the "x[#]" convention, the square braces enclosing each number - // ensures that there is a unique mapping between the token and parameter name - // If the tokens follow the "@#" convention, the numbers are not enclosed by braces. - // So there may be tokens with numbers whose lower place value forms a subset string of ones with a higher place - // value, e.g. "@1" is a subset of "@10". So the names of these parameters must be applied descending from the - // highest place value in order to ensure each parameter name is uniquely applied to its token. - for (size_t idx = pdf->nParameters(); idx--;) { - const RooAbsArg *par = pdf->getParameter(idx); - expression.ReplaceAll(("x[" + std::to_string(idx) + "]").c_str(), par->GetName()); - expression.ReplaceAll(("@" + std::to_string(idx)).c_str(), par->GetName()); - } - elem["expression"] << expression.Data(); - return true; +bool exportFormulaArg(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + const RooArg_t *pdf = static_cast(func); + elem["type"] << key; + TString expression(pdf->expression()); + cleanExpression(expression); + // If the tokens follow the "x[#]" convention, the square braces enclosing each number + // ensures that there is a unique mapping between the token and parameter name + // If the tokens follow the "@#" convention, the numbers are not enclosed by braces. + // So there may be tokens with numbers whose lower place value forms a subset string of ones with a higher place + // value, e.g. "@1" is a subset of "@10". So the names of these parameters must be applied descending from the + // highest place value in order to ensure each parameter name is uniquely applied to its token. + for (size_t idx = pdf->nParameters(); idx--;) { + const RooAbsArg *par = pdf->getParameter(idx); + expression.ReplaceAll(("x[" + std::to_string(idx) + "]").c_str(), par->GetName()); + expression.ReplaceAll(("@" + std::to_string(idx)).c_str(), par->GetName()); } + elem["expression"] << expression.Data(); + return true; +} -private: - void cleanExpression(TString &expr) const - { - expr.ReplaceAll("TMath::Exp", "exp"); - expr.ReplaceAll("TMath::Min", "min"); - expr.ReplaceAll("TMath::Max", "max"); - expr.ReplaceAll("TMath::Log", "log"); - expr.ReplaceAll("TMath::Cos", "cos"); - expr.ReplaceAll("TMath::Sin", "sin"); - expr.ReplaceAll("TMath::Sqrt", "sqrt"); - expr.ReplaceAll("TMath::Power", "pow"); - expr.ReplaceAll("TMath::Erf", "erf"); - } -}; // Write the "x" reference and the coefficient list for polynomial-like // pdfs/funcs, including the implicit defaults below "lowestOrder" so that the // output is self-documenting. @@ -825,438 +788,360 @@ void writePolynomialBody(const Pdf *pdf, JSONNode &elem) } template -class RooPolynomialStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - elem["type"] << key(); - writePolynomialBody(static_cast(func), elem); - return true; - } -}; +bool exportPolynomial(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + elem["type"] << key; + writePolynomialBody(static_cast(func), elem); + return true; +} -class RooPoissonStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - elem["x"] << pdf->getX().GetName(); - elem["mean"] << pdf->getMean().GetName(); - elem["integer"] << !pdf->getNoRounding(); - return true; - } -}; +bool exportPoisson(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + elem["x"] << pdf->getX().GetName(); + elem["mean"] << pdf->getMean().GetName(); + elem["integer"] << !pdf->getNoRounding(); + return true; +} -class RooDecayStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - elem["t"] << pdf->getT().GetName(); - elem["tau"] << pdf->getTau().GetName(); - elem["resolutionModel"] << pdf->getModel().GetName(); - elem["decayType"] << pdf->getDecayType(); +bool exportDecay(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + elem["t"] << pdf->getT().GetName(); + elem["tau"] << pdf->getTau().GetName(); + elem["resolutionModel"] << pdf->getModel().GetName(); + elem["decayType"] << pdf->getDecayType(); + + return true; +} - return true; - } -}; +bool exportTruthModel(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + elem["x"] << pdf->convVar().GetName(); -class RooTruthModelStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - elem["x"] << pdf->convVar().GetName(); + return true; +} - return true; - } -}; +bool exportGaussModel(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + elem["x"] << pdf->convVar().GetName(); + elem["mean"] << pdf->getMean().GetName(); + elem["sigma"] << pdf->getSigma().GetName(); + return true; +} -class RooGaussModelStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - elem["x"] << pdf->convVar().GetName(); - elem["mean"] << pdf->getMean().GetName(); - elem["sigma"] << pdf->getSigma().GetName(); - return true; - } -}; +bool exportLogNormal(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); -class RooLogNormalStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); + elem["type"] << key; + elem["x"] << pdf->getX().GetName(); - elem["type"] << key(); - elem["x"] << pdf->getX().GetName(); + auto &m0 = pdf->getMedian(); + auto &k = pdf->getShapeK(); - auto &m0 = pdf->getMedian(); - auto &k = pdf->getShapeK(); + if (pdf->useStandardParametrization()) { + elem["mu"] << m0.GetName(); + elem["sigma"] << k.GetName(); + } else { + elem["mu"] << tool->exportTransformed(&m0, "_lognormal_log", "log(%s)"); + elem["sigma"] << tool->exportTransformed(&k, "_lognormal_log", "log(%s)"); + } - if (pdf->useStandardParametrization()) { - elem["mu"] << m0.GetName(); - elem["sigma"] << k.GetName(); - } else { - elem["mu"] << tool->exportTransformed(&m0, "_lognormal_log", "log(%s)"); - elem["sigma"] << tool->exportTransformed(&k, "_lognormal_log", "log(%s)"); - } + return true; +} - return true; +bool exportExponential(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + elem["x"] << pdf->variable().GetName(); + auto &c = pdf->coefficient(); + if (pdf->negateCoefficient()) { + elem["c"] << c.GetName(); + } else { + elem["c"] << tool->exportTransformed(&c, "_exponential_inverted", "-%s"); } -}; -class RooExponentialStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - elem["x"] << pdf->variable().GetName(); - auto &c = pdf->coefficient(); - if (pdf->negateCoefficient()) { - elem["c"] << c.GetName(); - } else { - elem["c"] << tool->exportTransformed(&c, "_exponential_inverted", "-%s"); - } + return true; +} - return true; - } -}; +bool exportMultiVarGaussian(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + RooJSONFactoryWSTool::fillSeq(elem["x"], pdf->xVec()); + RooJSONFactoryWSTool::fillSeq(elem["mean"], pdf->muVec()); + elem["covariances"].fill_mat(pdf->covarianceMatrix()); + return true; +} -class RooMultiVarGaussianStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - RooJSONFactoryWSTool::fillSeq(elem["x"], pdf->xVec()); - RooJSONFactoryWSTool::fillSeq(elem["mean"], pdf->muVec()); - elem["covariances"].fill_mat(pdf->covarianceMatrix()); - return true; +bool exportTFnBinding(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + + TString formula(pdf->function().GetExpFormula()); + formula.ReplaceAll("x", pdf->observables()[0].GetName()); + formula.ReplaceAll("y", pdf->observables()[1].GetName()); + formula.ReplaceAll("z", pdf->observables()[2].GetName()); + for (size_t i = 0; i < pdf->parameters().size(); ++i) { + TString pname(TString::Format("[%d]", (int)i)); + formula.ReplaceAll(pname, pdf->parameters()[i].GetName()); } -}; + elem["expression"] << formula.Data(); + return true; +} -class RooTFnBindingStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - - TString formula(pdf->function().GetExpFormula()); - formula.ReplaceAll("x", pdf->observables()[0].GetName()); - formula.ReplaceAll("y", pdf->observables()[1].GetName()); - formula.ReplaceAll("z", pdf->observables()[2].GetName()); - for (size_t i = 0; i < pdf->parameters().size(); ++i) { - TString pname(TString::Format("[%d]", (int)i)); - formula.ReplaceAll(pname, pdf->parameters()[i].GetName()); - } - elem["expression"] << formula.Data(); - return true; +bool exportDerivative(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + elem["x"] << pdf->getX().GetName(); + elem["function"] << pdf->getFunc().GetName(); + if (!pdf->getNset().empty()) { + RooJSONFactoryWSTool::fillSeq(elem["normalization"], pdf->getNset()); } -}; + elem["order"] << pdf->order(); + elem["eps"] << pdf->eps(); + return true; +} -class RooDerivativeStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - elem["x"] << pdf->getX().GetName(); - elem["function"] << pdf->getFunc().GetName(); - if (!pdf->getNset().empty()) { - RooJSONFactoryWSTool::fillSeq(elem["normalization"], pdf->getNset()); - } - elem["order"] << pdf->order(); - elem["eps"] << pdf->eps(); - return true; +bool exportRealIntegral(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *integral = static_cast(func); + elem["type"] << key; + std::string integrand = integral->integrand().GetName(); + // elem["integrand"] << RooJSONFactoryWSTool::sanitizeName(integrand); + elem["integrand"] << integrand; + if (integral->intRange()) { + elem["domain"] << integral->intRange(); } -}; - -class RooRealIntegralStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *integral = static_cast(func); - elem["type"] << key(); - std::string integrand = integral->integrand().GetName(); - // elem["integrand"] << RooJSONFactoryWSTool::sanitizeName(integrand); - elem["integrand"] << integrand; - if (integral->intRange()) { - elem["domain"] << integral->intRange(); - } - RooJSONFactoryWSTool::fillSeq(elem["variables"], integral->intVars()); - if (RooArgSet const *funcNormSet = integral->funcNormSet()) { - RooJSONFactoryWSTool::fillSeq(elem["normalization"], *funcNormSet); - } - return true; + RooJSONFactoryWSTool::fillSeq(elem["variables"], integral->intVars()); + if (RooArgSet const *funcNormSet = integral->funcNormSet()) { + RooJSONFactoryWSTool::fillSeq(elem["normalization"], *funcNormSet); } -}; + return true; +} -class RooFFTConvPdfStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - if (auto convFunc = pdf->getPdfConvVar()) { - elem["conv_func"] << convFunc->GetName(); - } - elem["conv_var"] << pdf->getConvVar().GetName(); - elem["pdf1"] << pdf->getPdf1().GetName(); - elem["pdf2"] << pdf->getPdf2().GetName(); - elem["ipOrder"] << pdf->getInterpolationOrder(); - return true; +bool exportFFTConvPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + if (auto convFunc = pdf->getPdfConvVar()) { + elem["conv_func"] << convFunc->GetName(); } -}; + elem["conv_var"] << pdf->getConvVar().GetName(); + elem["pdf1"] << pdf->getPdf1().GetName(); + elem["pdf2"] << pdf->getPdf2().GetName(); + elem["ipOrder"] << pdf->getInterpolationOrder(); + return true; +} -class RooExtendPdfStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - if (auto rangeName = pdf->getRangeName()) { - elem["range"] << rangeName->GetName(); - } - elem["pdf"] << pdf->pdf().GetName(); - elem["norm"] << pdf->getN().GetName(); - return true; +bool exportExtendPdf(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + if (auto rangeName = pdf->getRangeName()) { + elem["range"] << rangeName->GetName(); } -}; + elem["pdf"] << pdf->pdf().GetName(); + elem["norm"] << pdf->getN().GetName(); + return true; +} -class ParamHistFuncStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override - { - auto *pdf = static_cast(func); - elem["type"] << key(); - RooJSONFactoryWSTool::fillSeq(elem["variables"], pdf->dataVars()); - RooJSONFactoryWSTool::fillSeq(elem["parameters"], pdf->paramList()); - auto &observablesNode = elem["axes"].set_seq(); - // axes have to be ordered to get consistent bin indices - for (auto *var : static_range_cast(pdf->dataVars())) { - RooJSONFactoryWSTool::exportAxis(observablesNode.append_child().set_map(), *var); - } - return true; +bool exportParamHistFunc(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto *pdf = static_cast(func); + elem["type"] << key; + RooJSONFactoryWSTool::fillSeq(elem["variables"], pdf->dataVars()); + RooJSONFactoryWSTool::fillSeq(elem["parameters"], pdf->paramList()); + auto &observablesNode = elem["axes"].set_seq(); + // axes have to be ordered to get consistent bin indices + for (auto *var : static_range_cast(pdf->dataVars())) { + RooJSONFactoryWSTool::exportAxis(observablesNode.append_child().set_map(), *var); } -}; + return true; +} -class RooSplineStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; +bool exportSpline(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem, std::string const &key) +{ + auto const *rs = static_cast(func); + + elem["type"] << key; + + // Independent variable + elem["x"] << rs->x().GetName(); + + // Spline configuration + // Canonical algo for RooSpline + elem["interpolation"] << (rs->order() == 5 ? "poly5" : "poly3"); + elem["logx"] << rs->logx(); + elem["logy"] << rs->logy(); + + // Serialize knots as primitive arrays + TSpline const &sp = rs->spline(); + auto &x0 = elem["x0"].set_seq(); + auto &y0 = elem["y0"].set_seq(); + + const int np = sp.GetNp(); + for (int i = 0; i < np; ++i) { + double xk = 0.0, yk = 0.0; + sp.GetKnot(i, xk, yk); + x0.append_child() << xk; + y0.append_child() << yk; + } - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, RooFit::Detail::JSONNode &elem) const override - { - auto const *rs = static_cast(func); - - elem["type"] << key(); - - // Independent variable - elem["x"] << rs->x().GetName(); - - // Spline configuration - // Canonical algo for RooSpline - elem["interpolation"] << (rs->order() == 5 ? "poly5" : "poly3"); - elem["logx"] << rs->logx(); - elem["logy"] << rs->logy(); - - // Serialize knots as primitive arrays - TSpline const &sp = rs->spline(); - auto &x0 = elem["x0"].set_seq(); - auto &y0 = elem["y0"].set_seq(); - - const int np = sp.GetNp(); - for (int i = 0; i < np; ++i) { - double xk = 0.0, yk = 0.0; - sp.GetKnot(i, xk, yk); - x0.append_child() << xk; - y0.append_child() << yk; - } + return true; +} - return true; - } -}; +bool importWrapperPdf(RooJSONFactoryWSTool *tool, const JSONNode &node) +{ + if (node["type"].val() != "density_function_dist") + return false; -class RooWrapperPdfImporter : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const RooFit::Detail::JSONNode &node) const override - { - if (node["type"].val() != "density_function_dist") - return false; + auto name = RooJSONFactoryWSTool::name(node); + auto *func = tool->requestArg(node, "function"); - auto name = RooJSONFactoryWSTool::name(node); - auto *func = tool->requestArg(node, "function"); + bool selfNormalized = false; - bool selfNormalized = false; + if (auto sn = node.find("selfNormalized")) + selfNormalized = sn->val_bool(); - if (auto sn = node.find("selfNormalized")) - selfNormalized = sn->val_bool(); + tool->wsEmplace(name, *func, selfNormalized); + return true; +} - tool->wsEmplace(name, *func, selfNormalized); - return true; - } -}; +bool exportWrapperPdf(RooJSONFactoryWSTool *, const RooAbsArg *arg, JSONNode &node, std::string const &key) +{ + auto const *pdf = dynamic_cast(arg); + if (!pdf) + return false; -class RooWrapperPdfStreamer : public RooFit::JSONIO::Exporter { -public: - std::string const &key() const override; + node["type"] << key; - bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *arg, RooFit::Detail::JSONNode &node) const override - { - auto const *pdf = dynamic_cast(arg); - if (!pdf) - return false; + // Proxy name in RooWrapperPdf is "_func" / "func" depending on accessor/proxy export. + // Prefer a public accessor if one exists; otherwise inspect proxies as below. + auto const *funcProxy = dynamic_cast(pdf->getProxy(0)); + if (!funcProxy || !funcProxy->absArg()) + return false; - node["type"] << "density_function_dist"; + node["function"] << funcProxy->absArg()->GetName(); + if (pdf->selfNormalized()) + node["selfnormalized"] << true; - // Proxy name in RooWrapperPdf is "_func" / "func" depending on accessor/proxy export. - // Prefer a public accessor if one exists; otherwise inspect proxies as below. - auto const *funcProxy = dynamic_cast(pdf->getProxy(0)); - if (!funcProxy || !funcProxy->absArg()) - return false; + return true; +} - node["function"] << funcProxy->absArg()->GetName(); - if (pdf->selfNormalized()) - node["selfnormalized"] << true; +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// instantiate all importers and exporters +/////////////////////////////////////////////////////////////////////////////////////////////////////// - return true; - } +// Adapters that wrap the plain import/export functions above into the +// RooFit::JSONIO::Importer/Exporter interface. The exporter also owns the HS3 +// type key, which is passed at registration time. +template +class FuncImporter : public RooFit::JSONIO::Importer { +public: + bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override { return Func(tool, p); } }; -#define DEFINE_EXPORTER_KEY(class_name, name) \ - std::string const &class_name::key() const \ - { \ - const static std::string keystring = name; \ - return keystring; \ +template +class FuncExporter : public RooFit::JSONIO::Exporter { +public: + FuncExporter(std::string key) : _key{std::move(key)} {} + std::string const &key() const override { return _key; } + bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem) const override + { + return Func(tool, func, elem, _key); } -template <> -DEFINE_EXPORTER_KEY(RooAddPdfStreamer, "mixture_dist"); -template <> -DEFINE_EXPORTER_KEY(RooAddPdfStreamer, "mixture_model"); -DEFINE_EXPORTER_KEY(RooBinSamplingPdfStreamer, "binsampling"); -DEFINE_EXPORTER_KEY(RooWrapperPdfStreamer, "density_function_dist"); -DEFINE_EXPORTER_KEY(RooBinWidthFunctionStreamer, "binwidth"); -template <> -DEFINE_EXPORTER_KEY(RooPolynomialStreamer, "legacy_exp_poly_dist"); -DEFINE_EXPORTER_KEY(RooExponentialStreamer, "exponential_dist"); -template <> -DEFINE_EXPORTER_KEY(RooFormulaArgStreamer, "generic_function"); -template <> -DEFINE_EXPORTER_KEY(RooFormulaArgStreamer, "generic_dist"); -template <> -DEFINE_EXPORTER_KEY(RooHistStreamer, "histogram"); -template <> -DEFINE_EXPORTER_KEY(RooHistStreamer, "histogram_dist"); -DEFINE_EXPORTER_KEY(RooLogNormalStreamer, "lognormal_dist"); -DEFINE_EXPORTER_KEY(RooMultiVarGaussianStreamer, "multivariate_normal_dist"); -DEFINE_EXPORTER_KEY(RooPoissonStreamer, "poisson_dist"); -DEFINE_EXPORTER_KEY(RooDecayStreamer, "decay_dist"); -DEFINE_EXPORTER_KEY(RooTruthModelStreamer, "truth_model_function"); -DEFINE_EXPORTER_KEY(RooGaussModelStreamer, "gauss_model_function"); -template <> -DEFINE_EXPORTER_KEY(RooPolynomialStreamer, "polynomial_dist"); -template <> -DEFINE_EXPORTER_KEY(RooPolynomialStreamer, "polynomial"); -DEFINE_EXPORTER_KEY(RooRealSumFuncStreamer, "weighted_sum"); -DEFINE_EXPORTER_KEY(RooRealSumPdfStreamer, "weighted_sum_dist"); -DEFINE_EXPORTER_KEY(RooTFnBindingStreamer, "generic_function"); -DEFINE_EXPORTER_KEY(RooRealIntegralStreamer, "integral"); -DEFINE_EXPORTER_KEY(RooDerivativeStreamer, "derivative"); -DEFINE_EXPORTER_KEY(RooFFTConvPdfStreamer, "fft_conv_pdf"); -DEFINE_EXPORTER_KEY(RooExtendPdfStreamer, "rate_extended_dist"); -DEFINE_EXPORTER_KEY(ParamHistFuncStreamer, "step"); -DEFINE_EXPORTER_KEY(RooSplineStreamer, "spline"); +private: + const std::string _key; +}; -/////////////////////////////////////////////////////////////////////////////////////////////////////// -// instantiate all importers and exporters -/////////////////////////////////////////////////////////////////////////////////////////////////////// +template +void registerImporter(const std::string &key, bool topPriority = true) +{ + RooFit::JSONIO::registerImporter(key, std::make_unique>(), topPriority); +} + +template +void registerExporter(TClass const *cl, std::string key, bool topPriority = true) +{ + RooFit::JSONIO::registerExporter(cl, std::make_unique>(std::move(key)), topPriority); +} STATIC_EXECUTE([]() { - using namespace RooFit::JSONIO; - - registerImporter("density_function_dist"); - registerImporter("rate_extended_dist"); - registerImporter("product", false); - registerImporter("product_dist", false); - registerImporter("sum", false); - registerImporter("mixture_dist", false); - registerImporter("mixture_model", false); - registerImporter("binsampling_dist", false); - registerImporter("binwidth", false); - registerImporter>("legacy_exp_poly_dist", false); - registerImporter("exponential_dist", false); - registerImporter>("generic_function", false); - registerImporter>("generic_dist", false); - registerImporter>("histogram", false); - registerImporter>("histogram_dist", false); - registerImporter("lognormal_dist", false); - registerImporter("multivariate_normal_dist", false); - registerImporter("poisson_dist", false); - registerImporter("decay_dist", false); - registerImporter("truth_model_function", false); - registerImporter("gauss_model_function", false); - registerImporter>("polynomial_dist", false); - registerImporter>("polynomial", false); - registerImporter("weighted_sum_dist", false); - registerImporter("weighted_sum", false); - registerImporter("integral", false); - registerImporter("derivative", false); - registerImporter("fft_conv_pdf", false); - registerImporter("extend_pdf", false); - registerImporter("step", false); - registerImporter("spline", false); - - registerExporter(RooWrapperPdf::Class()); - registerExporter>(RooAddPdf::Class(), false); - registerExporter>(RooAddModel::Class(), false); - registerExporter(RooBinSamplingPdf::Class(), false); - registerExporter(RooBinWidthFunction::Class(), false); - registerExporter>(RooLegacyExpPoly::Class(), false); - registerExporter(RooExponential::Class(), false); - registerExporter>(RooFormulaVar::Class(), false); - registerExporter>(RooGenericPdf::Class(), false); - registerExporter>(RooHistFunc::Class(), false); - registerExporter>(RooHistPdf::Class(), false); - registerExporter(RooLognormal::Class(), false); - registerExporter(RooMultiVarGaussian::Class(), false); - registerExporter(RooPoisson::Class(), false); - registerExporter(RooDecay::Class(), false); - registerExporter(RooTruthModel::Class(), false); - registerExporter(RooGaussModel::Class(), false); - registerExporter>(RooPolynomial::Class(), false); - registerExporter>(RooPolyVar::Class(), false); - registerExporter(RooRealSumFunc::Class(), false); - registerExporter(RooRealSumPdf::Class(), false); - registerExporter(RooTFnBinding::Class(), false); - registerExporter(RooRealIntegral::Class(), false); - registerExporter(RooDerivative::Class(), false); - registerExporter(RooFFTConvPdf::Class(), false); - registerExporter(RooExtendPdf::Class(), false); - registerExporter(ParamHistFunc::Class(), false); - registerExporter(RooSpline::Class(), false); + registerImporter("density_function_dist"); + registerImporter("rate_extended_dist"); + registerImporter("product", false); + registerImporter("product_dist", false); + registerImporter("sum", false); + registerImporter("mixture_dist", false); + registerImporter("mixture_resolution_model", false); + registerImporter("binsampling_dist", false); + registerImporter>("binvolume", false); + registerImporter>("inverse_binvolume", false); + registerImporter>("legacy_exp_poly_dist", false); + registerImporter("exponential_dist", false); + registerImporter>("generic", false); + registerImporter>("generic_function", false); + registerImporter>("generic_dist", false); + registerImporter>("histogram", false); + registerImporter>("step", false); + registerImporter>("histogram_dist", false); + registerImporter("lognormal_dist", false); + registerImporter("multivariate_normal_dist", false); + registerImporter("poisson_dist", false); + registerImporter("decay_dist", false); + registerImporter("delta_resolution_model", false); + registerImporter("gauss_resolution_model", false); + registerImporter>("polynomial_dist", false); + registerImporter>("polynomial", false); + registerImporter("weighted_sum_dist", false); + registerImporter("weighted_sum", false); + registerImporter("integral", false); + registerImporter("derivative", false); + registerImporter("fft_convolution_dist", false); + registerImporter("extend_pdf", false); + registerImporter("step", false); + registerImporter("spline", false); + + registerExporter(RooWrapperPdf::Class(), "density_function_dist"); + registerExporter>(RooAddPdf::Class(), "mixture_dist", false); + registerExporter>(RooAddModel::Class(), "mixture_resolution_model", false); + registerExporter(RooBinSamplingPdf::Class(), "binsampling", false); + registerExporter(RooBinWidthFunction::Class(), "binvolume", false); + registerExporter>(RooLegacyExpPoly::Class(), "legacy_exp_poly_dist", false); + registerExporter(RooExponential::Class(), "exponential_dist", false); + registerExporter>(RooFormulaVar::Class(), "generic", false); + registerExporter>(RooGenericPdf::Class(), "generic_dist", false); + registerExporter>(RooHistFunc::Class(), "step", false); + registerExporter>(RooHistPdf::Class(), "histogram_dist", false); + registerExporter(RooLognormal::Class(), "lognormal_dist", false); + registerExporter(RooMultiVarGaussian::Class(), "multivariate_normal_dist", false); + registerExporter(RooPoisson::Class(), "poisson_dist", false); + registerExporter(RooDecay::Class(), "decay_dist", false); + registerExporter(RooTruthModel::Class(), "delta_resolution_model", false); + registerExporter(RooGaussModel::Class(), "gauss_resolution_model", false); + registerExporter>(RooPolynomial::Class(), "polynomial_dist", false); + registerExporter>(RooPolyVar::Class(), "polynomial", false); + registerExporter(RooRealSumFunc::Class(), "weighted_sum", false); + registerExporter(RooRealSumPdf::Class(), "weighted_sum_dist", false); + registerExporter(RooTFnBinding::Class(), "generic_function", false); + registerExporter(RooRealIntegral::Class(), "integral", false); + registerExporter(RooDerivative::Class(), "derivative", false); + registerExporter(RooFFTConvPdf::Class(), "fft_convolution_dist", false); + registerExporter(RooExtendPdf::Class(), "rate_extended_dist", false); + registerExporter(ParamHistFunc::Class(), "step", false); + registerExporter(RooSpline::Class(), "spline", false); }); } // namespace diff --git a/roofit/hs3/src/RooJSONFactoryWSTool.cxx b/roofit/hs3/src/RooJSONFactoryWSTool.cxx index 5deabfd3bbfad..d44a40fc6db94 100644 --- a/roofit/hs3/src/RooJSONFactoryWSTool.cxx +++ b/roofit/hs3/src/RooJSONFactoryWSTool.cxx @@ -1047,8 +1047,8 @@ void RooJSONFactoryWSTool::exportVariable(const RooAbsArg *v, JSONNode &node, bo var["const"] << true; } else if (rrv) { var["value"] << rrv->getVal(); - if (rrv->isConstant() && storeConstant) { - var["const"] << rrv->isConstant(); + if (storeConstant && (rrv->isConstant() || rrv->getMin() >= rrv->getMax())) { + var["const"] << true; } else if (storeBins) { var["min"] << rrv->getMin(); var["max"] << rrv->getMax(); diff --git a/roofit/hs3/test/CMakeLists.txt b/roofit/hs3/test/CMakeLists.txt index d152337c8a7a8..6f8a9019fba0c 100644 --- a/roofit/hs3/test/CMakeLists.txt +++ b/roofit/hs3/test/CMakeLists.txt @@ -1,2 +1 @@ -ROOT_ADD_GTEST(testRooFitHS3 testRooFitHS3.cxx LIBRARIES RooFitCore RooFit RooFitHS3) -ROOT_ADD_GTEST(testHS3SimultaneousFit testHS3SimultaneousFit.cxx LIBRARIES RooFitCore RooFit RooFitHS3 RooStats) +ROOT_ADD_GTEST(testRooFitHS3 testRooFitHS3.cxx LIBRARIES RooFitCore RooFit RooFitHS3 RooStats) diff --git a/roofit/hs3/test/testHS3SimultaneousFit.cxx b/roofit/hs3/test/testHS3SimultaneousFit.cxx deleted file mode 100644 index 346d0728bdfb1..0000000000000 --- a/roofit/hs3/test/testHS3SimultaneousFit.cxx +++ /dev/null @@ -1,120 +0,0 @@ -// Test for the JSON IO of a full workspace with a multi-channel model and -// data. -// Author: Jonas Rembser, CERN 02/2023 - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -namespace { - -std::unique_ptr writeJSONAndFitModel(std::string &jsonStr) -{ - using namespace RooFit; - - RooWorkspace ws{"workspace"}; - - // Build two channels for different observables where the distributions - // share one parameter: the mean for the signal. - - // Channel 1: Gaussian signal and exponential background - ws.factory("Gaussian::sig_1(x_1[0, 10], mean[5.0, 0, 10], sigma_1[0.5, 0.1, 10.0])"); - ws.factory("Exponential::bkg_1(x_1, c_1[-0.2, -100, -0.001])"); - ws.factory("SUM::model_1(n_sig_1[10000, 0, 10000000] * sig_1, nbkg_2[100000, 0, 10000000] * bkg_1)"); - - // Channel 2: Crystal ball signal and polynomial background - ws.factory("CBShape::sig_2(x_2[0, 10], mean[5.0, 0, 10], sigma_2[0.8, 0.1, 10.0], alpha[0.9, 0.1, 10.0], " - "ncb[1.0, 0.1, 10.0])"); - ws.factory("Polynomial::bkg_2(x_2, {3.0, a_1[-0.3, -10, 10], a_2[0.01, -10, 10]}, 0)"); - ws.factory("SUM::model_2(n_sig_2[30000, 0, 10000000] * sig_2, nbkg_2[100000, 0, 10000000] * bkg_2)"); - - // Simultaneous PDF and model config - ws.factory("SIMUL::simPdf(channelCat[channel_1=0, channel_2=1], channel_1=model_1, channel_2=model_2)"); - - RooFit::ModelConfig modelConfig{"ModelConfig"}; - - modelConfig.SetWS(ws); - modelConfig.SetPdf("simPdf"); - modelConfig.SetParametersOfInterest("mean"); - modelConfig.SetObservables("x_1,x_2"); - - ws.import(modelConfig); - - RooRealVar &x1 = *ws.var("x_1"); - RooRealVar &x2 = *ws.var("x_2"); - x1.setBins(20); - x2.setBins(20); - - std::map> datasets; - datasets["channel_1"] = std::unique_ptr{ws.pdf("model_1")->generateBinned(x1)}; - datasets["channel_2"] = std::unique_ptr{ws.pdf("model_2")->generateBinned(x2)}; - - datasets["channel_1"]->SetName("obsData_channel_1"); - datasets["channel_2"]->SetName("obsData_channel_2"); - - RooDataSet obsData{"obsData", "obsData", {x1, x2}, Index(*ws.cat("channelCat")), Import(datasets)}; - ws.import(obsData); - - auto &pdf = *ws.pdf("simPdf"); - auto &data = *ws.data("obsData"); - - // Export before fitting to keep the prefit values - jsonStr = RooJSONFactoryWSTool{ws}.exportJSONtoString(); - - return std::unique_ptr{ - pdf.fitTo(data, Save(), PrintLevel(-1), PrintEvalErrors(-1), Minimizer("Minuit2"))}; -} - -std::unique_ptr readJSONAndFitModel(std::string const &jsonStr) -{ - using namespace RooFit; - - RooWorkspace ws{"workspace"}; - RooJSONFactoryWSTool tool{ws}; - - tool.importJSONfromString(jsonStr); - - // Make sure that there is exactly one dataset in the new workspace, and - // that there are no spurious datasets left over from first importing the - // channel datasets that later get merged to the combined dataset - EXPECT_EQ(ws.allData().size(), 1) << "Unexpected number of datasets in the new workspace"; - - auto &pdf = *ws.pdf("simPdf"); - auto &data = *ws.data("obsData"); - - return std::unique_ptr{ - pdf.fitTo(data, Save(), PrintLevel(-1), PrintEvalErrors(-1), Minimizer("Minuit2"))}; -} - -} // namespace - -TEST(RooFitHS3, SimultaneousFit) -{ - RooHelpers::LocalChangeMsgLevel changeMsgLvl(RooFit::WARNING); - - using namespace RooFit; - - std::string jsonStr; - - std::unique_ptr res1 = writeJSONAndFitModel(jsonStr); - std::unique_ptr res2 = readJSONAndFitModel(jsonStr); - - // todo: also check the modelconfig for equality - - EXPECT_TRUE(res2->isIdentical(*res1)); -} diff --git a/roofit/hs3/test/testRooFitHS3.cxx b/roofit/hs3/test/testRooFitHS3.cxx index 5026e0699e9c0..fbd3df6d33f20 100644 --- a/roofit/hs3/test/testRooFitHS3.cxx +++ b/roofit/hs3/test/testRooFitHS3.cxx @@ -7,18 +7,21 @@ #include #include +#include #include #include #include #include #include -#include +#include +#include +#include #include +#include #include #include #include #include -#include #include #include #include @@ -26,9 +29,9 @@ #include #include #include +#include +#include #include -#include -#include #include @@ -39,7 +42,7 @@ namespace { // If the JSON files should be written out for debugging purpose. -const bool writeJsonFiles = true; +const bool writeJsonFiles = false; // Validate the JSON IO for a given RooAbsReal in a RooWorkspace. The workspace // will be written out and read back, and then the values of the old and new @@ -420,6 +423,30 @@ TEST(RooFitHS3, ParameterStepWidthsFallbackExcludesDataAxes) EXPECT_FALSE(ws2.var("x")->hasError()); } +TEST(RooFitHS3, FixedRangeParameterExportsConst) +{ + RooWorkspace ws{"ws_fixed_range"}; + RooRealVar x{"x", "x", 0.0, -5.0, 5.0}; + RooRealVar fixed{"fixed", "fixed", 1.0, 1.0, 1.0}; + RooRealVar sigma{"sigma", "sigma", 1.0, 0.1, 10.0}; + RooGaussian gauss{"gauss", "gauss", x, fixed, sigma}; + fixed.setConstant(false); + ws.import(gauss, RooFit::Silence()); + + const std::string json = RooJSONFactoryWSTool{ws}.exportJSONtoString(); + const auto fixedPos = json.find("\"const\":true,\"name\":\"fixed\""); + ASSERT_NE(fixedPos, std::string::npos) << json; + const auto fixedBegin = json.rfind("{", fixedPos); + ASSERT_NE(fixedBegin, std::string::npos) << json; + const auto fixedEnd = json.find("}", fixedPos); + ASSERT_NE(fixedEnd, std::string::npos) << json; + const std::string fixedNode = json.substr(fixedBegin, fixedEnd - fixedBegin); + + EXPECT_NE(fixedNode.find("\"const\":true"), std::string::npos) << fixedNode; + EXPECT_EQ(fixedNode.find("\"min\""), std::string::npos) << fixedNode; + EXPECT_EQ(fixedNode.find("\"max\""), std::string::npos) << fixedNode; +} + TEST(RooFitHS3, ParameterStepWidthsImportAfterDefaultSnapshot) { const std::string json = R"({ @@ -684,6 +711,58 @@ TEST(RooFitHS3, RooGenericPdf) EXPECT_EQ(status, 0); } +TEST(RooFitHS3, GenericExpressionCleanup) +{ + RooRealVar x{"x", "x", 0.5, -1.0, 1.0}; + RooFormulaVar formula{"formula", + "formula", + "TMath::Floor(x) + TMath::Ceil(x) + TMath::Abs(x) + TMath::Tan(x) + " + "TMath::ASin(x / 2.) + TMath::ACos(x / 2.) + TMath::ATan(x) + TMath::Pi() + TMath::E()", + RooArgList{x}}; + + RooWorkspace ws1{"ws_expr_cleanup"}; + ws1.import(formula, RooFit::Silence()); + const std::string json = RooJSONFactoryWSTool{ws1}.exportJSONtoString(); + + for (const char *token : {"floor", "ceil", "abs", "tan", "asin", "acos", "atan", "PI", "EULER"}) { + EXPECT_NE(json.find(token), std::string::npos) << json; + } + EXPECT_EQ(json.find("TMath::Pi"), std::string::npos) << json; + EXPECT_EQ(json.find("TMath::E"), std::string::npos) << json; + + RooWorkspace ws2{"ws_expr_cleanup_2"}; + ASSERT_TRUE(RooJSONFactoryWSTool{ws2}.importJSONfromString(json)); + auto *imported = ws2.function("formula"); + ASSERT_NE(imported, nullptr); + ws2.var("x")->setVal(0.5); + EXPECT_DOUBLE_EQ(imported->getVal(), formula.getVal()); +} + +// The expression cleanup must not corrupt identifiers that merely share a +// prefix with a replaced one, like TMath::PiOver2 ("TMath::Pi") or +// TMath::TanH ("TMath::Tan"). +TEST(RooFitHS3, GenericExpressionCleanupKeepsLongerIdentifiers) +{ + RooRealVar x{"x", "x", 0.5, -1.0, 1.0}; + RooFormulaVar formula{"formula", "formula", + "TMath::PiOver2() + TMath::TanH(x) + TMath::SinH(x) + TMath::Log10(2. + x)", RooArgList{x}}; + + RooWorkspace ws1{"ws_expr_cleanup_prefixes"}; + ws1.import(formula, RooFit::Silence()); + const std::string json = RooJSONFactoryWSTool{ws1}.exportJSONtoString(); + + for (const char *token : {"PIOver2", "tanH", "sinH"}) { + EXPECT_EQ(json.find(token), std::string::npos) << json; + } + + RooWorkspace ws2{"ws_expr_cleanup_prefixes_2"}; + ASSERT_TRUE(RooJSONFactoryWSTool{ws2}.importJSONfromString(json)); + auto *imported = ws2.function("formula"); + ASSERT_NE(imported, nullptr); + ws2.var("x")->setVal(0.5); + EXPECT_DOUBLE_EQ(imported->getVal(), formula.getVal()); +} + TEST(RooFitHS3, RooHistPdf) { RooHelpers::LocalChangeMsgLevel changeMsgLvl(RooFit::WARNING); @@ -699,6 +778,73 @@ TEST(RooFitHS3, RooHistPdf) EXPECT_EQ(status, 0); } +TEST(RooFitHS3, RooBinWidthFunctionUsesBinVolumeKeys) +{ + RooRealVar x{"x", "x", 0.0, 2.0}; + x.setBins(2); + + RooDataHist dataHist{"dataHist", "dataHist", x}; + dataHist.set(0, 2.0, -1); + dataHist.set(1, 4.0, -1); + + RooHistFunc histFunc{"histFunc", "histFunc", x, dataHist}; + RooBinWidthFunction binVolume{"binVolume", "binVolume", histFunc, false}; + RooBinWidthFunction inverseBinVolume{"inverseBinVolume", "inverseBinVolume", histFunc, true}; + + RooWorkspace ws1{"ws_binvolume"}; + ws1.import(binVolume, RooFit::Silence()); + ws1.import(inverseBinVolume, RooFit::Silence(), RooFit::RecycleConflictNodes()); + + const std::string json = RooJSONFactoryWSTool{ws1}.exportJSONtoString(); + EXPECT_NE(json.find("\"type\":\"binvolume\""), std::string::npos) << json; + EXPECT_NE(json.find("\"type\":\"inverse_binvolume\""), std::string::npos) << json; + EXPECT_EQ(json.find("divideByBinWidth"), std::string::npos) << json; + EXPECT_EQ(json.find("\"type\":\"binwidth\""), std::string::npos) << json; + + RooWorkspace ws2{"ws_binvolume_2"}; + ASSERT_TRUE(RooJSONFactoryWSTool{ws2}.importJSONfromString(json)); + auto *importedBinVolume = dynamic_cast(ws2.function("binVolume")); + auto *importedInverseBinVolume = dynamic_cast(ws2.function("inverseBinVolume")); + ASSERT_NE(importedBinVolume, nullptr); + ASSERT_NE(importedInverseBinVolume, nullptr); + EXPECT_FALSE(importedBinVolume->divideByBinWidth()); + EXPECT_TRUE(importedInverseBinVolume->divideByBinWidth()); +} + +TEST(RooFitHS3, StepDispatchesToRooHistFuncAndParamHistFunc) +{ + RooRealVar x{"x", "x", 0.0, 2.0}; + x.setBins(2); + + RooDataHist dataHist{"dataHist", "dataHist", x}; + dataHist.set(0, 3.0, -1); + dataHist.set(1, 5.0, -1); + + RooHistFunc histFunc{"histFunc", "histFunc", x, dataHist}; + RooRealVar p0{"p0", "p0", 1.0}; + RooRealVar p1{"p1", "p1", 2.0}; + ParamHistFunc paramHistFunc{"paramHistFunc", "paramHistFunc", RooArgList{x}, RooArgList{p0, p1}}; + + RooWorkspace ws1{"ws_step"}; + ws1.import(histFunc, RooFit::Silence()); + ws1.import(paramHistFunc, RooFit::Silence()); + + const std::string json = RooJSONFactoryWSTool{ws1}.exportJSONtoString(); + EXPECT_NE(json.find("\"name\":\"histFunc\",\"type\":\"step\""), std::string::npos) << json; + EXPECT_EQ(json.find("\"name\":\"histFunc\",\"type\":\"histogram\""), std::string::npos) << json; + const auto paramHistFuncPos = json.find("\"name\":\"paramHistFunc\""); + ASSERT_NE(paramHistFuncPos, std::string::npos) << json; + const auto paramHistFuncEnd = json.find("}", paramHistFuncPos); + ASSERT_NE(paramHistFuncEnd, std::string::npos) << json; + const std::string paramHistFuncNode = json.substr(paramHistFuncPos, paramHistFuncEnd - paramHistFuncPos); + EXPECT_NE(paramHistFuncNode.find("\"type\":\"step\""), std::string::npos) << paramHistFuncNode; + + RooWorkspace ws2{"ws_step_2"}; + ASSERT_TRUE(RooJSONFactoryWSTool{ws2}.importJSONfromString(json)); + EXPECT_NE(dynamic_cast(ws2.function("histFunc")), nullptr); + EXPECT_NE(dynamic_cast(ws2.function("paramHistFunc")), nullptr); +} + TEST(RooFitHS3, RooLandau) { int status = validate({"Landau::landau(x[0, 10], mean[5], sigma[1.0, 0.1, 10])"}); @@ -1162,6 +1308,149 @@ TEST(RooFitHS3, HistFactoryZeroYieldBin) } } +TEST(RooFitHS3, HistFactoryConstraintKeyMigration) +{ + const std::string jsonStr = R"({ + "metadata": {"hs3_version": "0.1.90"}, + "domains": [ + { + "name": "default_domain", + "type": "product_domain", + "axes": [ + {"name": "obs_channel0", "min": 0.0, "max": 2.0, "nbins": 2}, + {"name": "nom_mu", "min": 1.0, "max": 1.0}, + {"name": "sigma_mu", "min": 1.0, "max": 1.0} + ] + } + ], + "parameter_points": [ + { + "name": "default_values", + "parameters": [ + {"name": "nom_mu", "value": 1.0, "const": true}, + {"name": "sigma_mu", "value": 1.0, "const": true} + ] + } + ], + "distributions": [ + { + "name": "model_channel0", + "type": "histfactory_dist", + "axes": [ + {"name": "obs_channel0", "min": 0.0, "max": 2.0, "nbins": 2} + ], + "samples": [ + { + "name": "sig", + "data": {"contents": [10.0, 20.0]}, + "modifiers": [ + { + "name": "mu", + "parameter": "mu", + "type": "normfactor", + "constraint_name": "muConstraint" + } + ] + } + ] + }, + { + "name": "muConstraint", + "type": "gaussian_dist", + "x": "mu", + "mean": "nom_mu", + "sigma": "sigma_mu" + } + ], + "data": [ + { + "name": "obsData_channel0", + "type": "binned", + "axes": [ + {"name": "obs_channel0", "min": 0.0, "max": 2.0, "nbins": 2} + ], + "contents": [10.0, 20.0] + } + ] + })"; + + RooHelpers::LocalChangeMsgLevel changeMsgLvl(RooFit::WARNING); + + RooWorkspace ws{"ws_hf_constraint"}; + ASSERT_TRUE(RooJSONFactoryWSTool{ws}.importJSONfromString(jsonStr)); + + const std::string exported = RooJSONFactoryWSTool{ws}.exportJSONtoString(); + EXPECT_NE(exported.find("\"constraint\":\"muConstraint\""), std::string::npos) << exported; + EXPECT_EQ(exported.find("constraint_name"), std::string::npos) << exported; + EXPECT_EQ(exported.find("constraint_type"), std::string::npos) << exported; +} + +TEST(RooFitHS3, HistFactoryLegacyConstraintTypeInConstraintKey) +{ + const std::string jsonStr = R"({ + "metadata": {"hs3_version": "0.1.90"}, + "domains": [ + { + "name": "default_domain", + "type": "product_domain", + "axes": [ + {"name": "obs_channel0", "min": 0.0, "max": 2.0, "nbins": 2} + ] + } + ], + "distributions": [ + { + "name": "model_channel0", + "type": "histfactory_dist", + "axes": [ + {"name": "obs_channel0", "min": 0.0, "max": 2.0, "nbins": 2} + ], + "samples": [ + { + "name": "sig", + "data": {"contents": [10.0, 20.0]}, + "modifiers": [ + { + "name": "lumi", + "type": "normsys", + "constraint": "Gauss", + "data": {"lo": 0.95, "hi": 1.05} + } + ] + } + ] + } + ], + "data": [ + { + "name": "obsData_channel0", + "type": "binned", + "axes": [ + {"name": "obs_channel0", "min": 0.0, "max": 2.0, "nbins": 2} + ], + "contents": [10.0, 20.0] + } + ] + })"; + + RooHelpers::LocalChangeMsgLevel changeMsgLvl(RooFit::WARNING); + + RooWorkspace ws{"ws_hf_legacy_constraint_type"}; + ASSERT_TRUE(RooJSONFactoryWSTool{ws}.importJSONfromString(jsonStr)); + + auto *constraint = dynamic_cast(ws.pdf("alpha_lumiConstraint")); + ASSERT_NE(constraint, nullptr); + auto *alpha = ws.var("alpha_lumi"); + ASSERT_NE(alpha, nullptr); + EXPECT_DOUBLE_EQ(alpha->getError(), 1.0); + + const std::string exported = RooJSONFactoryWSTool{ws}.exportJSONtoString(); + EXPECT_NE(exported.find("\"constraint\":\"alpha_lumiConstraint\""), std::string::npos) << exported; + EXPECT_EQ(exported.find("\"constraint\":\"Gauss\""), std::string::npos) << exported; + EXPECT_EQ(exported.find("constraint_name"), std::string::npos) << exported; + EXPECT_EQ(exported.find("constraint_type"), std::string::npos) << exported; +} + // Snapshot export must keep all variables that any pdf depends on, even when // the variable is not in the set of separately exported objects. Global // observables of HistFactory constraint pdfs (the nominal "nom_*" parameters) @@ -1244,3 +1533,101 @@ TEST(RooFitHS3, SnapshotKeepsGlobalObservables) EXPECT_NE(snap->find(arg->GetName()), nullptr) << "Snapshot is missing pdf-dependent variable " << arg->GetName(); } } + +namespace { + +std::unique_ptr writeJSONAndFitModel(std::string &jsonStr) +{ + using namespace RooFit; + + RooWorkspace ws{"workspace"}; + + // Build two channels for different observables where the distributions + // share one parameter: the mean for the signal. + + // Channel 1: Gaussian signal and exponential background + ws.factory("Gaussian::sig_1(x_1[0, 10], mean[5.0, 0, 10], sigma_1[0.5, 0.1, 10.0])"); + ws.factory("Exponential::bkg_1(x_1, c_1[-0.2, -100, -0.001])"); + ws.factory("SUM::model_1(n_sig_1[10000, 0, 10000000] * sig_1, nbkg_2[100000, 0, 10000000] * bkg_1)"); + + // Channel 2: Crystal ball signal and polynomial background + ws.factory("CBShape::sig_2(x_2[0, 10], mean[5.0, 0, 10], sigma_2[0.8, 0.1, 10.0], alpha[0.9, 0.1, 10.0], " + "ncb[1.0, 0.1, 10.0])"); + ws.factory("Polynomial::bkg_2(x_2, {3.0, a_1[-0.3, -10, 10], a_2[0.01, -10, 10]}, 0)"); + ws.factory("SUM::model_2(n_sig_2[30000, 0, 10000000] * sig_2, nbkg_2[100000, 0, 10000000] * bkg_2)"); + + // Simultaneous PDF and model config + ws.factory("SIMUL::simPdf(channelCat[channel_1=0, channel_2=1], channel_1=model_1, channel_2=model_2)"); + + RooFit::ModelConfig modelConfig{"ModelConfig"}; + + modelConfig.SetWS(ws); + modelConfig.SetPdf("simPdf"); + modelConfig.SetParametersOfInterest("mean"); + modelConfig.SetObservables("x_1,x_2"); + + ws.import(modelConfig); + + RooRealVar &x1 = *ws.var("x_1"); + RooRealVar &x2 = *ws.var("x_2"); + x1.setBins(20); + x2.setBins(20); + + std::map> datasets; + datasets["channel_1"] = std::unique_ptr{ws.pdf("model_1")->generateBinned(x1)}; + datasets["channel_2"] = std::unique_ptr{ws.pdf("model_2")->generateBinned(x2)}; + + datasets["channel_1"]->SetName("obsData_channel_1"); + datasets["channel_2"]->SetName("obsData_channel_2"); + + RooDataSet obsData{"obsData", "obsData", {x1, x2}, Index(*ws.cat("channelCat")), Import(datasets)}; + ws.import(obsData); + + auto &pdf = *ws.pdf("simPdf"); + auto &data = *ws.data("obsData"); + + // Export before fitting to keep the prefit values + jsonStr = RooJSONFactoryWSTool{ws}.exportJSONtoString(); + + return std::unique_ptr{ + pdf.fitTo(data, Save(), PrintLevel(-1), PrintEvalErrors(-1), Minimizer("Minuit2"))}; +} + +std::unique_ptr readJSONAndFitModel(std::string const &jsonStr) +{ + using namespace RooFit; + + RooWorkspace ws{"workspace"}; + RooJSONFactoryWSTool tool{ws}; + + tool.importJSONfromString(jsonStr); + + // Make sure that there is exactly one dataset in the new workspace, and + // that there are no spurious datasets left over from first importing the + // channel datasets that later get merged to the combined dataset + EXPECT_EQ(ws.allData().size(), 1) << "Unexpected number of datasets in the new workspace"; + + auto &pdf = *ws.pdf("simPdf"); + auto &data = *ws.data("obsData"); + + return std::unique_ptr{ + pdf.fitTo(data, Save(), PrintLevel(-1), PrintEvalErrors(-1), Minimizer("Minuit2"))}; +} + +} // namespace + +TEST(RooFitHS3, SimultaneousFit) +{ + RooHelpers::LocalChangeMsgLevel changeMsgLvl(RooFit::WARNING); + + using namespace RooFit; + + std::string jsonStr; + + std::unique_ptr res1 = writeJSONAndFitModel(jsonStr); + std::unique_ptr res2 = readJSONAndFitModel(jsonStr); + + // todo: also check the modelconfig for equality + + EXPECT_TRUE(res2->isIdentical(*res1)); +} diff --git a/roofit/jsoninterface/src/RYMLParser.cxx b/roofit/jsoninterface/src/RYMLParser.cxx deleted file mode 100644 index ea15f589fb8b5..0000000000000 --- a/roofit/jsoninterface/src/RYMLParser.cxx +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Project: RooFit - * Authors: - * Carsten D. Burgard, DESY/ATLAS, Dec 2021 - * - * Copyright (c) 2022, CERN - * - * Redistribution and use in source and binary forms, - * with or without modification, are permitted according to the terms - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) - */ - -#include -#include - -#include -#include -#include -#include - -#include "RYMLParser.h" - -namespace { -size_t count_nlines(c4::csubstr src) -{ - // helper function to count the lines - size_t n = (src.len > 0); - while (src.len > 0) { - n += (src.begins_with('\n') || src.begins_with('\r')); - src = src.sub(1); - } - return n; -} - -c4::yml::Tree makeTree(std::istream &is) -{ - if (!is.good()) - throw std::runtime_error("invalid input!"); - std::string s(std::istreambuf_iterator(is), {}); - - auto src = c4::to_csubstr(s.c_str()); - size_t nlines = count_nlines(src); - c4::yml::Tree tree(nlines, s.size()); - c4::yml::Parser np; - np.parse_in_place({}, tree.copy_to_arena(src), &tree); - return tree; -} -} // namespace - -// TRYMLTree Implementation -class TRYMLTree::Impl { - ryml::Tree tree; - -public: - Impl(){}; - Impl(std::istream &is) : tree(makeTree(is)) - { - // constructor parsing the RYML tree - } - inline ryml::Tree &get() - { - // getter for the RYML tree reference - return this->tree; - } - inline const ryml::Tree &get() const - { - // const getter for the RYML tree reference - return this->tree; - } -}; - -// TRYMLNode Implementation -class TRYMLTree::Node::Impl { - c4::yml::NodeRef node; - -public: - Impl(const c4::yml::NodeRef &n) - : node(n){ - // constructor taking the RYML node reference - }; - inline static TRYMLTree::Node &mkNode(TRYMLTree *t, c4::yml::NodeRef node) - { - // function for creating a new node based on a RYML node reference - return t->incache(TRYMLTree::Node(t, TRYMLTree::Node::Impl(node))); - } - inline c4::yml::NodeRef &get() - { - // getter for the RYML node reference - return node; - } - inline const c4::yml::NodeRef &get() const - { - // getter for the RYML node reference - return node; - } -}; - -// JSONTree interface implementation - -void TRYMLTree::Node::writeJSON(std::ostream &os) const -{ - // write the tree as JSON to an ostream - os << c4::yml::as_json(node->get()); -} - -void TRYMLTree::Node::writeYML(std::ostream &os) const -{ - // write the tree as YML to an ostream - os << node->get(); -} - -TRYMLTree::Node &TRYMLTree::Node::set_map() -{ - // assign this node to be a map (JSON object) - node->get() |= c4::yml::MAP; - return *this; -} - -TRYMLTree::Node &TRYMLTree::Node::set_seq() -{ - // assign this node to be a sequence (JSON array) - node->get() |= c4::yml::SEQ; - return *this; -} - -TRYMLTree::Node &TRYMLTree::Node::set_null() -{ - throw std::logic_error("Function not yet implemented"); -} - -void TRYMLTree::Node::clear() -{ - throw std::logic_error("Function not yet implemented"); -} - -TRYMLTree::TRYMLTree(std::istream &is) - : tree(std::make_unique(is)){ - // constructor taking an istream (for reading) - }; - -TRYMLTree::TRYMLTree() - : tree(std::make_unique()){ - // default constructor (for writing) - }; - -TRYMLTree::~TRYMLTree() -{ - // destructor. clears the cache. - clearcache(); -}; - -// JSONNode interface implementation - -TRYMLTree::Node::Node(TRYMLTree *t, const TRYMLTree::Node::Impl &imp) : tree(t), node(std::make_unique(imp)) -{ - // construct a new node from scratch -} - -TRYMLTree::Node::Node(const Node &other) : Node(other.tree, other.node->get()) -{ - // copy constructor -} - -TRYMLTree::Node &TRYMLTree::rootnode() -{ - // obtain the root node of a tree - return Node::Impl::mkNode(this, tree->get().rootref()); -} - -TRYMLTree::Node &TRYMLTree::Node::operator<<(std::string const &s) -{ - // write a string to this node - node->get() << s; - return *this; -} - -TRYMLTree::Node &TRYMLTree::Node::operator<<(int i) -{ - // write an int to this node - node->get() << i; - return *this; -} - -TRYMLTree::Node &TRYMLTree::Node::operator<<(double d) -{ - // write a double to this node - node->get() << d; - return *this; -} - -TRYMLTree::Node &TRYMLTree::Node::operator<<(bool b) -{ - // write a bool to this node - node->get() << b; - return *this; -} - -const TRYMLTree::Node &TRYMLTree::Node::operator>>(std::string &v) const -{ - // read a string from this node - node->get() >> v; - return *this; -} - -TRYMLTree::Node &TRYMLTree::Node::operator[](std::string const &k) -{ - // get a child node with the given key - return Impl::mkNode(tree, node->get()[c4::to_csubstr(tree->incache(k))]); -} - -const TRYMLTree::Node &TRYMLTree::Node::operator[](std::string const &k) const -{ - // get a child node with the given key (const version) - return Impl::mkNode(tree, node->get()[c4::to_csubstr(tree->incache(k))]); -} - -bool TRYMLTree::Node::is_container() const -{ - // return true if this node can have child nodes - return node->get().is_container(); -} - -bool TRYMLTree::Node::is_map() const -{ - // return true if this node is a map (JSON object) - return node->get().is_map(); -} - -bool TRYMLTree::Node::is_seq() const -{ - // return true if this node is a sequence (JSON array) - return node->get().is_seq(); -} - -bool TRYMLTree::Node::is_null() const -{ - return !node->get().has_val() && node->get().num_children() == 0; -} - -std::string TRYMLTree::Node::key() const -{ - // obtain the key of this node - std::stringstream ss; - ss << node->get().key(); - return ss.str(); -} - -std::string TRYMLTree::Node::val() const -{ - ; - // obtain the value of this node (as a string) - std::stringstream ss; - ss << node->get().val(); - return ss.str(); -} - -TRYMLTree::Node &TRYMLTree::Node::append_child() -{ - // append a new child to this node - return Impl::mkNode(tree, node->get().append_child()); -} - -bool TRYMLTree::Node::has_key() const -{ - // return true if this node has a key - return node->get().has_key(); -} - -bool TRYMLTree::Node::has_val() const -{ - // return true if this node has a value - return node->get().has_val(); -} - -bool TRYMLTree::Node::has_child(std::string const &s) const -{ - // return true if this node has a child with the given key - return node->get().has_child(c4::to_csubstr(s.c_str())); -} - -size_t TRYMLTree::Node::num_children() const -{ - // return the number of child nodes of this node - return node->get().num_children(); -} - -TRYMLTree::Node &TRYMLTree::Node::child(size_t pos) -{ - // return the child with the given index - return Impl::mkNode(tree, node->get().child(pos)); -} - -const TRYMLTree::Node &TRYMLTree::Node::child(size_t pos) const -{ - // return the child with the given index (const version) - return Impl::mkNode(tree, node->get().child(pos)); -} - -// specific functions - -namespace { -void error_cb(const char *msg, size_t msg_len, c4::yml::Location /*not used*/, void * /*user_data*/) -{ - // error callback using std::runtime_error - if (msg && msg_len > 0) { - throw std::runtime_error(msg); - } else { - throw std::runtime_error("error handler invoked without error message"); - } -} - -bool setcallbacks() -{ - // set the custom callback functions - c4::yml::set_callbacks(c4::yml::Callbacks(c4::yml::get_callbacks().m_user_data, c4::yml::get_callbacks().m_allocate, - c4::yml::get_callbacks().m_free, &::error_cb)); - return true; -} -bool ok = setcallbacks(); -} // namespace - -const char *TRYMLTree::incache(const std::string &str) -{ - // obtain a string from the string cache - _strcache.push_back(str); - return _strcache.back().c_str(); -} - -TRYMLTree::Node &TRYMLTree::incache(const TRYMLTree::Node &n) -{ - // obtain a node from the node cache - _nodecache.push_back(n); - return _nodecache.back(); -} - -void TRYMLTree::clearcache() -{ - // clear all caches - TRYMLTree::_strcache.clear(); - TRYMLTree::_nodecache.clear(); -} diff --git a/roofit/jsoninterface/src/RYMLParser.h b/roofit/jsoninterface/src/RYMLParser.h deleted file mode 100644 index 7be8bda5c4291..0000000000000 --- a/roofit/jsoninterface/src/RYMLParser.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Project: RooFit - * Authors: - * Carsten D. Burgard, DESY/ATLAS, Dec 2021 - * - * Copyright (c) 2022, CERN - * - * Redistribution and use in source and binary forms, - * with or without modification, are permitted according to the terms - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) - */ - -#ifndef RYML_PARSER_H -#define RYML_PARSER_H - -#include - -#include - -#include -#include -#include - -class TRYMLTree : public RooFit::Detail::JSONTree { -protected: - class Impl; - std::unique_ptr tree; - -public: - class Node : public RooFit::Detail::JSONNode { - protected: - TRYMLTree *tree; - class Impl; - friend TRYMLTree; - std::unique_ptr node; - - public: - void writeJSON(std::ostream &os) const override; - void writeYML(std::ostream &) const override; - - Node(TRYMLTree *t, const Impl &other); - Node(const Node &other); - Node &operator<<(std::string const &s) override; - Node &operator<<(int i) override; - Node &operator<<(double d) override; - Node &operator<<(bool b) override; - const Node &operator>>(std::string &v) const override; - Node &operator[](std::string const &k) override; - const Node &operator[](std::string const &k) const override; - bool is_container() const override; - bool is_map() const override; - bool is_seq() const override; - bool is_null() const override; - Node &set_map() override; - Node &set_seq() override; - Node &set_null() override; - void clear() override; - std::string key() const override; - std::string val() const override; - bool has_key() const override; - bool has_val() const override; - bool has_child(std::string const &) const override; - Node &append_child() override; - size_t num_children() const override; - Node &child(size_t pos) override; - const Node &child(size_t pos) const override; - }; - -protected: - std::list _strcache; - std::list _nodecache; - -public: - Node &incache(const Node &n); - const char *incache(const std::string &str); - void clearcache(); - -public: - TRYMLTree(); - ~TRYMLTree() override; - TRYMLTree(std::istream &is); - Node &rootnode() override; -}; - -#endif