From d53e08d433a76198c21aa7f0aa5dfac3acbc54e5 Mon Sep 17 00:00:00 2001 From: Phmonski Date: Wed, 1 Jul 2026 09:35:43 +0200 Subject: [PATCH 1/6] [RF][HS3] Align RooFit HS3 JSON I/O with the current HS3 Align RooFit HS3 JSON I/O with the current HS3 naming and HistFactory conventions. **Summary** Align RooFit HS3 JSON I/O with the current HS3 naming and HistFactory conventions. - Replace `RooBinWidthFunction` HS3 type `binwidth` with: - `binvolume` for `divideByBinWidth == false` - `inverse_binvolume` for `divideByBinWidth == true` - Stop exporting `divideByBinWidth` and remove legacy `binwidth` import support. - Extend generic expression cleanup for `floor`, `ceil`, `abs`, `tan`, `asin`, `acos`, `atan`, `PI`, and `EULER`. - Treat `PI` and `EULER` as reserved expression constants on import. - Migrate HistFactory modifier export from `constraint_name` to `constraint`, and omit `constraint_type`. - Instead of exporting `constraint_type`, `shapesys` modifiers now export the actual per-bin constraint pdf names via a new `constraints` list. This list is parallel to the `parameters` list, so each gamma parameter has a corresponding constraint entry. Entries can also be `null` for parameters without a constraint. - Keep HistFactory import compatibility for `constraint`, `constraint_name`, and legacy `constraint_type`. - Export `const: true` for parameters whose `min >= max`. - Export both `RooHistFunc` and `ParamHistFunc` as `step`. - Add a `step` importer dispatcher that selects `RooHistFunc` for `data` and `ParamHistFunc` for `parameters`. - Keep legacy `histogram` import support for old `RooHistFunc` HS3 files. - Rename the following exporter keys: - generic_function -> generic - gauss_model_function -> gauss_resolution_model - truth_model_function -> delta_resolution_model - mixture_model -> mixture_resolution_model - fft_conv_pdf -> fft_convolution_dist **Tests** Added focused coverage in `testRooFitHS3.cxx` for: - `binvolume` / `inverse_binvolume` export and import - fixed-range parameter export as `const: true` - `step` export/import dispatch for `RooHistFunc` and `ParamHistFunc` - HistFactory `constraint` export with legacy `constraint_name` import - generic expression cleanup for new function names and constants (cherry picked from commit e6d966aa9eb7e5c3b8bdb33509ea61aeaddff430) --- roofit/hs3/src/JSONFactories_HistFactory.cxx | 190 ++++++++++---- roofit/hs3/src/JSONFactories_RooFitCore.cxx | 98 +++++-- roofit/hs3/src/RooJSONFactoryWSTool.cxx | 4 +- roofit/hs3/test/testRooFitHS3.cxx | 263 +++++++++++++++++++ 4 files changed, 489 insertions(+), 66 deletions(-) diff --git a/roofit/hs3/src/JSONFactories_HistFactory.cxx b/roofit/hs3/src/JSONFactories_HistFactory.cxx index 279dd63380ce1..cda071011819a 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) { @@ -707,6 +783,7 @@ struct HistoSys { struct ShapeSys { std::string name; std::vector constraints; + std::vector constraintPdfs; std::vector parameters; RooAbsPdf const *constraint = nullptr; TClass *constraintType = RooGaussian::Class(); @@ -1099,16 +1176,22 @@ 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))); + sys.constraintPdfs.push_back(constraint_p); if (!sys.constraint) { sys.constraintType = RooPoisson::Class(); } } else if (auto constraint_g = dynamic_cast(constraint)) { sys.constraints.push_back(constraint_g->getSigma().getVal() / constraint_g->getMean().getVal()); + sys.constraintPdfs.push_back(constraint_g); if (!sys.constraint) { sys.constraintType = RooGaussian::Class(); } + } else { + RooJSONFactoryWSTool::error( + "currently, only RooPoisson and RooGaussian are supported as constraint types"); } } sample.shapesys.emplace_back(std::move(sys)); @@ -1146,13 +1229,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 +1254,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); } } @@ -1218,6 +1299,17 @@ bool exportChannel(RooJSONFactoryWSTool *tool, const Channel &channel, JSONNode mod["type"] << "shapesys"; optionallyExportGammaParameters(mod, sys.name, sys.parameters); writeConstraint(mod, sys); + 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(); + } + } + } auto &vals = mod["data"].set_map()["vals"]; if (sys.constraint || sys.constraintType) { vals.fill_seq(sys.constraints); @@ -1245,7 +1337,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 +1473,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..2e6389d02a629 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,16 +117,52 @@ 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; } +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; + } + } + + expr = out.c_str(); +} + +void translateImportedExpression(TString &expr) +{ + replaceIdentifier(expr, "PI", "TMath::Pi()"); + replaceIdentifier(expr, "EULER", "TMath::E()"); +} + template class RooFormulaArgFactory : public RooFit::JSONIO::Importer { public: @@ -130,6 +173,7 @@ class RooFormulaArgFactory : public RooFit::JSONIO::Importer { 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)); @@ -202,13 +246,14 @@ class RooAddModelFactory : public RooFit::JSONIO::Importer { } }; +template 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()); + tool->wsEmplace(name, *hf, DivideByBinWidth); return true; } }; @@ -265,6 +310,7 @@ class RooRealSumFuncFactory : public RooFit::JSONIO::Importer { return true; } }; + template class RooPolynomialFactory : public RooFit::JSONIO::Importer { public: @@ -550,6 +596,9 @@ class ParamHistFuncFactory : public RooFit::JSONIO::Importer { public: bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override { + if (!p.has_child("parameters")) { + return false; + } std::string name(RooJSONFactoryWSTool::name(p)); RooArgList varList = tool->requestArgList(p, "variables"); if (!p.has_child("axes")) { @@ -733,7 +782,7 @@ class RooHistFactory : public RooFit::JSONIO::Importer { { 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"); + return false; } std::unique_ptr dataHist = RooJSONFactoryWSTool::readBinnedData(p["data"], name, RooJSONFactoryWSTool::readAxes(p["data"])); @@ -762,9 +811,8 @@ class RooBinWidthFunctionStreamer : public RooFit::JSONIO::Exporter { bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override { const RooBinWidthFunction *pdf = static_cast(func); - elem["type"] << key(); + elem["type"] << (pdf->divideByBinWidth() ? "inverse_binvolume" : "binvolume"); elem["histogram"] << pdf->histFunc().GetName(); - elem["divideByBinWidth"] << pdf->divideByBinWidth(); return true; } }; @@ -806,6 +854,17 @@ class RooFormulaArgStreamer : public RooFit::JSONIO::Exporter { expr.ReplaceAll("TMath::Sqrt", "sqrt"); expr.ReplaceAll("TMath::Power", "pow"); expr.ReplaceAll("TMath::Erf", "erf"); + expr.ReplaceAll("TMath::Floor", "floor"); + expr.ReplaceAll("TMath::Ceil", "ceil"); + expr.ReplaceAll("TMath::Abs", "abs"); + expr.ReplaceAll("TMath::Tan", "tan"); + expr.ReplaceAll("TMath::ASin", "asin"); + expr.ReplaceAll("TMath::ACos", "acos"); + expr.ReplaceAll("TMath::ATan", "atan"); + expr.ReplaceAll("TMath::Pi()", "PI"); + expr.ReplaceAll("TMath::Pi", "PI"); + expr.ReplaceAll("TMath::E()", "EULER"); + expr.ReplaceAll("TMath::E", "EULER"); } }; // Write the "x" reference and the coefficient list for polynomial-like @@ -1155,27 +1214,27 @@ class RooWrapperPdfStreamer : public RooFit::JSONIO::Exporter { template <> DEFINE_EXPORTER_KEY(RooAddPdfStreamer, "mixture_dist"); template <> -DEFINE_EXPORTER_KEY(RooAddPdfStreamer, "mixture_model"); +DEFINE_EXPORTER_KEY(RooAddPdfStreamer, "mixture_resolution_model"); DEFINE_EXPORTER_KEY(RooBinSamplingPdfStreamer, "binsampling"); DEFINE_EXPORTER_KEY(RooWrapperPdfStreamer, "density_function_dist"); -DEFINE_EXPORTER_KEY(RooBinWidthFunctionStreamer, "binwidth"); +DEFINE_EXPORTER_KEY(RooBinWidthFunctionStreamer, "binvolume"); template <> DEFINE_EXPORTER_KEY(RooPolynomialStreamer, "legacy_exp_poly_dist"); DEFINE_EXPORTER_KEY(RooExponentialStreamer, "exponential_dist"); template <> -DEFINE_EXPORTER_KEY(RooFormulaArgStreamer, "generic_function"); +DEFINE_EXPORTER_KEY(RooFormulaArgStreamer, "generic"); template <> DEFINE_EXPORTER_KEY(RooFormulaArgStreamer, "generic_dist"); template <> -DEFINE_EXPORTER_KEY(RooHistStreamer, "histogram"); +DEFINE_EXPORTER_KEY(RooHistStreamer, "step"); 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"); +DEFINE_EXPORTER_KEY(RooTruthModelStreamer, "delta_resolution_model"); +DEFINE_EXPORTER_KEY(RooGaussModelStreamer, "gauss_resolution_model"); template <> DEFINE_EXPORTER_KEY(RooPolynomialStreamer, "polynomial_dist"); template <> @@ -1185,7 +1244,7 @@ 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(RooFFTConvPdfStreamer, "fft_convolution_dist"); DEFINE_EXPORTER_KEY(RooExtendPdfStreamer, "rate_extended_dist"); DEFINE_EXPORTER_KEY(ParamHistFuncStreamer, "step"); DEFINE_EXPORTER_KEY(RooSplineStreamer, "spline"); @@ -1203,28 +1262,31 @@ STATIC_EXECUTE([]() { registerImporter("product_dist", false); registerImporter("sum", false); registerImporter("mixture_dist", false); - registerImporter("mixture_model", false); + registerImporter("mixture_resolution_model", false); registerImporter("binsampling_dist", false); - registerImporter("binwidth", 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("truth_model_function", false); - registerImporter("gauss_model_function", 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_conv_pdf", false); + registerImporter("fft_convolution_dist", false); registerImporter("extend_pdf", false); registerImporter("step", false); registerImporter("spline", false); 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/testRooFitHS3.cxx b/roofit/hs3/test/testRooFitHS3.cxx index 5026e0699e9c0..21025fc039a5d 100644 --- a/roofit/hs3/test/testRooFitHS3.cxx +++ b/roofit/hs3/test/testRooFitHS3.cxx @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include @@ -420,6 +422,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 +710,33 @@ 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()); +} + TEST(RooFitHS3, RooHistPdf) { RooHelpers::LocalChangeMsgLevel changeMsgLvl(RooFit::WARNING); @@ -699,6 +752,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 +1282,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) From 72738b335131823ad5ce7e69906621b433791265 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 13 Jul 2026 18:26:05 +0200 Subject: [PATCH 2/6] [RF][HS3] Merge HS3 into one executable The `testHS3SimultaneousFit` was pretty small anyway, and having only a single executable saves linking time. (cherry picked from commit 9a111850b8ca838c68f70a42b536bc787264aa90) --- roofit/hs3/test/CMakeLists.txt | 3 +- roofit/hs3/test/testHS3SimultaneousFit.cxx | 120 --------------------- roofit/hs3/test/testRooFitHS3.cxx | 113 +++++++++++++++++-- 3 files changed, 107 insertions(+), 129 deletions(-) delete mode 100644 roofit/hs3/test/testHS3SimultaneousFit.cxx 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 21025fc039a5d..5c015ee4bb09c 100644 --- a/roofit/hs3/test/testRooFitHS3.cxx +++ b/roofit/hs3/test/testRooFitHS3.cxx @@ -7,19 +7,21 @@ #include #include -#include #include +#include #include #include #include #include -#include +#include +#include +#include #include +#include #include #include #include #include -#include #include #include #include @@ -27,10 +29,9 @@ #include #include #include -#include -#include -#include +#include #include +#include #include @@ -41,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 @@ -1507,3 +1508,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)); +} From a91ef61f3fd3570cf5e11eb94c20a9d550763ead Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 13 Jul 2026 18:28:16 +0200 Subject: [PATCH 3/6] [RF][HS3] Fix math expression replacements Fix math expression replacements to not corrup expressions that are containing other replaced substriongs as replaced tokens, like `TMath::Log10` that contains `TMath::Log`. (cherry picked from commit 51dfa586ed6f29e04220517d7e6b9f715082b081) --- roofit/hs3/src/JSONFactories_RooFitCore.cxx | 48 ++++++++++++--------- roofit/hs3/test/testRooFitHS3.cxx | 25 +++++++++++ 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/roofit/hs3/src/JSONFactories_RooFitCore.cxx b/roofit/hs3/src/JSONFactories_RooFitCore.cxx index 2e6389d02a629..7de1de85397f9 100644 --- a/roofit/hs3/src/JSONFactories_RooFitCore.cxx +++ b/roofit/hs3/src/JSONFactories_RooFitCore.cxx @@ -845,26 +845,34 @@ class RooFormulaArgStreamer : public RooFit::JSONIO::Exporter { 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"); - expr.ReplaceAll("TMath::Floor", "floor"); - expr.ReplaceAll("TMath::Ceil", "ceil"); - expr.ReplaceAll("TMath::Abs", "abs"); - expr.ReplaceAll("TMath::Tan", "tan"); - expr.ReplaceAll("TMath::ASin", "asin"); - expr.ReplaceAll("TMath::ACos", "acos"); - expr.ReplaceAll("TMath::ATan", "atan"); - expr.ReplaceAll("TMath::Pi()", "PI"); - expr.ReplaceAll("TMath::Pi", "PI"); - expr.ReplaceAll("TMath::E()", "EULER"); - expr.ReplaceAll("TMath::E", "EULER"); + // 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"); } }; // Write the "x" reference and the coefficient list for polynomial-like diff --git a/roofit/hs3/test/testRooFitHS3.cxx b/roofit/hs3/test/testRooFitHS3.cxx index 5c015ee4bb09c..fbd3df6d33f20 100644 --- a/roofit/hs3/test/testRooFitHS3.cxx +++ b/roofit/hs3/test/testRooFitHS3.cxx @@ -738,6 +738,31 @@ TEST(RooFitHS3, GenericExpressionCleanup) 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); From 7023e922797125996aa0ae489d8051b5bea3d56b Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 13 Jul 2026 18:32:49 +0200 Subject: [PATCH 4/6] [RF][HS3] Remove some dead code about constraint handling (cherry picked from commit 040d5790b933e68ced85157a0cc85cc2a90af03a) --- roofit/hs3/src/JSONFactories_HistFactory.cxx | 25 +++----------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/roofit/hs3/src/JSONFactories_HistFactory.cxx b/roofit/hs3/src/JSONFactories_HistFactory.cxx index cda071011819a..d58f037ae527d 100644 --- a/roofit/hs3/src/JSONFactories_HistFactory.cxx +++ b/roofit/hs3/src/JSONFactories_HistFactory.cxx @@ -744,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} { @@ -758,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) { } }; @@ -772,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()); @@ -785,8 +782,6 @@ struct ShapeSys { std::vector constraints; std::vector constraintPdfs; std::vector parameters; - RooAbsPdf const *constraint = nullptr; - TClass *constraintType = RooGaussian::Class(); ShapeSys(const std::string &n) : name{n} {} }; @@ -942,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} {} }; @@ -1141,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; @@ -1180,15 +1173,9 @@ Channel readChannel(RooJSONFactoryWSTool *tool, const std::string &pdfname, cons } else if (auto constraint_p = dynamic_cast(constraint)) { sys.constraints.push_back(1. / std::sqrt(poissonTau(*constraint_p, *g))); sys.constraintPdfs.push_back(constraint_p); - if (!sys.constraint) { - sys.constraintType = RooPoisson::Class(); - } } else if (auto constraint_g = dynamic_cast(constraint)) { sys.constraints.push_back(constraint_g->getSigma().getVal() / constraint_g->getMean().getVal()); sys.constraintPdfs.push_back(constraint_g); - if (!sys.constraint) { - sys.constraintType = RooGaussian::Class(); - } } else { RooJSONFactoryWSTool::error( "currently, only RooPoisson and RooGaussian are supported as constraint types"); @@ -1298,7 +1285,6 @@ bool exportChannel(RooJSONFactoryWSTool *tool, const Channel &channel, JSONNode mod["name"] << sys.name; mod["type"] << "shapesys"; optionallyExportGammaParameters(mod, sys.name, sys.parameters); - writeConstraint(mod, sys); if (std::any_of(sys.constraintPdfs.begin(), sys.constraintPdfs.end(), [](auto *pdf) { return pdf != nullptr; })) { auto &constraintNames = mod["constraints"].set_seq(); @@ -1310,12 +1296,7 @@ bool exportChannel(RooJSONFactoryWSTool *tool, const Channel &channel, JSONNode } } } - 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)); - } + mod["data"].set_map()["vals"].fill_seq(sys.constraints); } for (const auto &other : sample.otherElements) { From a88deba18f30dce966335e9e714939a75e889165 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 13 Jul 2026 18:45:15 +0200 Subject: [PATCH 5/6] [RF] Remove RYMLParser class and CMake code to find Rapid YAML The YAML parser (for the `ryml` backend for RooFit JSON interface) is dead code for over two years, as it was commented out from the build in commit 643a73d8039. It's better to remove the dead code at this point, and it can always be brought back or re-implemented if really needed. (cherry picked from commit ddc6c6fb41a650e7f5ec3ccbb02857bcb5c9aad2) --- cmake/modules/Findryml.cmake | 23 -- roofit/jsoninterface/src/RYMLParser.cxx | 341 ------------------------ roofit/jsoninterface/src/RYMLParser.h | 85 ------ 3 files changed, 449 deletions(-) delete mode 100644 cmake/modules/Findryml.cmake delete mode 100644 roofit/jsoninterface/src/RYMLParser.cxx delete mode 100644 roofit/jsoninterface/src/RYMLParser.h 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/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 From e985da3a2615d6ea01412cd1bf9c3204d6d91f50 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 13 Jul 2026 18:59:11 +0200 Subject: [PATCH 6/6] [RF][HS3] Reduce class defintion boilerplate code by using functions This is just a refactor to reduce the code footprint. (cherry picked from commit 6732eba223dff42b2a36ea4685012c3ca3ae9923) --- roofit/hs3/src/JSONFactories_RooFitCore.cxx | 1833 +++++++++---------- 1 file changed, 824 insertions(+), 1009 deletions(-) diff --git a/roofit/hs3/src/JSONFactories_RooFitCore.cxx b/roofit/hs3/src/JSONFactories_RooFitCore.cxx index 7de1de85397f9..abd8d294eb5cc 100644 --- a/roofit/hs3/src/JSONFactories_RooFitCore.cxx +++ b/roofit/hs3/src/JSONFactories_RooFitCore.cxx @@ -164,24 +164,21 @@ void translateImportedExpression(TString &expr) } 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()); - 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; +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 @@ -190,691 +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; +} template -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, DivideByBinWidth); - return true; - } -}; +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; +} -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; - } -}; +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; +} 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; - } - } 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; + 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; } - 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 - { - if (!p.has_child("parameters")) { - return false; + } else { + std::vector variances; + for (const auto &v : p["standard_deviations"].children()) { + variances.push_back(v.val_double()); } - 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; + 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, readBinning(p, varList), tool->requestArgList(p, "parameters")); - return true; } + tool->wsEmplace(name, tool->requestArgList(p, "x"), + tool->requestArgList(p, "mean"), covmat); + return true; +} -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()); +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()); } - - varMap[name] = std::move(obs); + 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; - - auto it = varMap.find(refVar->GetName()); - if (it != varMap.end()) { - vars.addOwned(std::move(it->second)); // preserve ownership - } - } - return vars; + varMap[name] = std::move(obs); } -}; -class RooSplineFactory : public RooFit::JSONIO::Importer { -public: - bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override - { - const std::string name(RooJSONFactoryWSTool::name(p)); + // Now build the final list following the order in varList + RooArgList vars; + for (auto *refVar : dynamic_range_cast(varList)) { + if (!refVar) + continue; - // 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 + "'"); + auto it = varMap.find(refVar->GetName()); + if (it != varMap.end()) { + vars.addOwned(std::move(it->second)); // preserve ownership } + } + return vars; +} - 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 + "'"); - } +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; +} - // 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); +bool importSpline(RooJSONFactoryWSTool *tool, const JSONNode &p) +{ + const std::string name(RooJSONFactoryWSTool::name(p)); - return true; + // 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 + "'"); + } + + // 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; +} /////////////////////////////////////////////////////////////////////////////////////////////////////// // 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")) { - return false; - } - 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"] << (pdf->divideByBinWidth() ? "inverse_binvolume" : "binvolume"); - elem["histogram"] << pdf->histFunc().GetName(); - 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 - { - // 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"); - } -}; // 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. @@ -892,441 +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_resolution_model"); -DEFINE_EXPORTER_KEY(RooBinSamplingPdfStreamer, "binsampling"); -DEFINE_EXPORTER_KEY(RooWrapperPdfStreamer, "density_function_dist"); -DEFINE_EXPORTER_KEY(RooBinWidthFunctionStreamer, "binvolume"); -template <> -DEFINE_EXPORTER_KEY(RooPolynomialStreamer, "legacy_exp_poly_dist"); -DEFINE_EXPORTER_KEY(RooExponentialStreamer, "exponential_dist"); -template <> -DEFINE_EXPORTER_KEY(RooFormulaArgStreamer, "generic"); -template <> -DEFINE_EXPORTER_KEY(RooFormulaArgStreamer, "generic_dist"); -template <> -DEFINE_EXPORTER_KEY(RooHistStreamer, "step"); -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, "delta_resolution_model"); -DEFINE_EXPORTER_KEY(RooGaussModelStreamer, "gauss_resolution_model"); -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_convolution_dist"); -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_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()); - 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