From 6ca225935e77062d0560ca29935556e4760f3650 Mon Sep 17 00:00:00 2001 From: Zeff020 <35220042+Zeff020@users.noreply.github.com> Date: Thu, 20 Jan 2022 14:07:38 +0100 Subject: [PATCH 1/9] [RF] Fixes evaluate() function in RooRealL (#9456) --- .../inc/RooFit/TestStatistics/RooRealL.h | 5 +- .../src/TestStatistics/RooRealL.cxx | 17 +++- roofit/roofitcore/test/CMakeLists.txt | 2 + .../TestStatistics/TestStatistics_ref.root | Bin 0 -> 12076 bytes .../test/TestStatistics/testPlot.cpp | 96 ++++++++++++++++++ test/stressRooFit_tests.h | 2 +- 6 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 roofit/roofitcore/test/TestStatistics/TestStatistics_ref.root create mode 100644 roofit/roofitcore/test/TestStatistics/testPlot.cpp diff --git a/roofit/roofitcore/inc/RooFit/TestStatistics/RooRealL.h b/roofit/roofitcore/inc/RooFit/TestStatistics/RooRealL.h index 617b1a55e15d8..a7b57b99b2b95 100644 --- a/roofit/roofitcore/inc/RooFit/TestStatistics/RooRealL.h +++ b/roofit/roofitcore/inc/RooFit/TestStatistics/RooRealL.h @@ -30,7 +30,6 @@ class RooRealL : public RooAbsReal { RooRealL(const char *name, const char *title, std::shared_ptr likelihood); RooRealL(const RooRealL &other, const char *name = 0); - Double_t evaluate() const override; inline TObject *clone(const char *newname) const override { return new RooRealL(*this, newname); } inline double globalNormalization() const @@ -42,10 +41,14 @@ class RooRealL : public RooAbsReal { inline double getCarry() const { return eval_carry; } inline Double_t defaultErrorLevel() const override { return 0.5; } +protected: + Double_t evaluate() const override; + private: std::shared_ptr likelihood_; mutable double eval_carry = 0; RooSetProxy vars_proxy_; // sets up client-server connections + RooArgSet vars_obs_; // list of observables ClassDefOverride(RooRealL, 0); }; diff --git a/roofit/roofitcore/src/TestStatistics/RooRealL.cxx b/roofit/roofitcore/src/TestStatistics/RooRealL.cxx index 353219016608f..e60319c4c663f 100644 --- a/roofit/roofitcore/src/TestStatistics/RooRealL.cxx +++ b/roofit/roofitcore/src/TestStatistics/RooRealL.cxx @@ -12,6 +12,7 @@ #include #include +#include namespace RooFit { namespace TestStatistics { @@ -30,17 +31,29 @@ RooRealL::RooRealL(const char *name, const char *title, std::shared_ptr : RooAbsReal(name, title), likelihood_(std::move(likelihood)), vars_proxy_("varsProxy", "proxy set of parameters", this) { + vars_obs_.add(*likelihood_->getParameters()); vars_proxy_.add(*likelihood_->getParameters()); } RooRealL::RooRealL(const RooRealL &other, const char *name) - : RooAbsReal(other, name), likelihood_(other.likelihood_), vars_proxy_("varsProxy", "proxy set of parameters", this) + : RooAbsReal(other, name), likelihood_(other.likelihood_), vars_proxy_("varsProxy", this, other.vars_proxy_) { - vars_proxy_.add(*likelihood_->getParameters()); + vars_obs_.add(other.vars_obs_) ; } Double_t RooRealL::evaluate() const { + // Transfer values from proxy variables to internal variables of likelihood + if (!vars_proxy_.empty()) { + for (auto i = 0u; i < vars_obs_.size(); ++i) { + auto harg = vars_obs_[i]; + const auto parg = vars_proxy_[i]; + + if (harg != parg) { + ((RooAbsRealLValue*)harg)->setVal(((RooAbsReal*)parg)->getVal()); + } + } + } // Evaluate as straight FUNC std::size_t last_component = likelihood_->getNComponents(); diff --git a/roofit/roofitcore/test/CMakeLists.txt b/roofit/roofitcore/test/CMakeLists.txt index 3f54ad7aaf1ba..99ae78d62fa94 100644 --- a/roofit/roofitcore/test/CMakeLists.txt +++ b/roofit/roofitcore/test/CMakeLists.txt @@ -51,6 +51,8 @@ ROOT_ADD_GTEST(testRooRealL TestStatistics/RooRealL.cpp LIBRARIES RooFitCore Roo ROOT_ADD_GTEST(testGlobalObservables testGlobalObservables.cxx LIBRARIES RooFit) ROOT_ADD_GTEST(testRooPolyFunc testRooPolyFunc.cxx LIBRARIES Gpad RooFit) if (roofit_multiprocess) + ROOT_ADD_GTEST(testTestStatisticsPlot TestStatistics/testPlot.cpp LIBRARIES RooFitMultiProcess RooFitCore RooFit + COPY_TO_BUILDDIR ${CMAKE_CURRENT_SOURCE_DIR}/TestStatistics/TestStatistics_ref.root) ROOT_ADD_GTEST(testLikelihoodGradientJob TestStatistics/testLikelihoodGradientJob.cpp LIBRARIES RooFitMultiProcess RooFitCore RooFit RooStats m) target_include_directories(testLikelihoodGradientJob PRIVATE ${RooFitCore_MultiProcess_TestStatistics_INCLUDE_DIR}) endif() diff --git a/roofit/roofitcore/test/TestStatistics/TestStatistics_ref.root b/roofit/roofitcore/test/TestStatistics/TestStatistics_ref.root new file mode 100644 index 0000000000000000000000000000000000000000..6b8c89029b9eacf7a3b97c54f31bc83c52970c01 GIT binary patch literal 12076 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 literal 0 HcmV?d00001 diff --git a/roofit/roofitcore/test/TestStatistics/testPlot.cpp b/roofit/roofitcore/test/TestStatistics/testPlot.cpp new file mode 100644 index 0000000000000..a027425d553c7 --- /dev/null +++ b/roofit/roofitcore/test/TestStatistics/testPlot.cpp @@ -0,0 +1,96 @@ +/* + * Project: RooFit + * Authors: + * ZW, Zef Wolffs, NIKHEF, zefwolffs@gmail.com + * + * Copyright (c) 2021, 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 +#include +#include +#include +#include + +#include + +#include + +using namespace RooFit; + +class TestRooRealLPlot : public RooUnitTest { +public: + TestRooRealLPlot(TFile &refFile, bool writeRef, int verbose, std::string const &batchMode) + : RooUnitTest("Plotting and minimization with RooFit::TestStatistics", &refFile, writeRef, verbose, batchMode){}; + Bool_t testCode() + { + + // C r e a t e m o d e l a n d d a t a + // --------------------------------------- + // Constructing a workspace with pdf and dataset + RooWorkspace w("w"); + w.factory("expr::Nexp('mu*S+B',mu[1,-1,10],S[10],B[20])"); + w.factory("Poisson::model(Nobs[0,100],Nexp)"); + w.var("Nobs")->setBins(4); + RooDataSet d("d", "d", *w.var("Nobs")); + w.var("Nobs")->setVal(25); + d.add(*w.var("Nobs")); + + // P e r f o r m a p a r a l l e l l i k e l i h o o d m i n i m i z a t i o n + // -------------------------------------------------------------------------------- + + // Creating a RooAbsL likelihood + std::shared_ptr likelihood = + RooFit::TestStatistics::buildLikelihood(w.pdf("model"), &d); + + // Creating a minimizer and explicitly setting type of parallelization + std::size_t nWorkers = 1; + RooFit::MultiProcess::Config::setDefaultNWorkers(nWorkers); + RooMinimizer m(likelihood, RooFit::TestStatistics::MinuitFcnGrad::LikelihoodMode::serial, + RooFit::TestStatistics::MinuitFcnGrad::LikelihoodGradientMode::multiprocess); + m.setMinimizerType("Minuit2"); + + // Minimize + m.migrad(); + + // C o n v e r t t o R o o R e a l L a n d p l o t + // --------------------------------------------------- + + // Create a RooRealL which has plotting functionality + std::shared_ptr likelihood_real( + new RooFit::TestStatistics::RooRealL("likelihood", "", likelihood)); + RooPlot *xframe = w.var("mu")->frame(-1, 10); + likelihood_real->plotOn(xframe, RooFit::Precision(1)); + + // Clean up the minimizer + m.cleanup(); + + // --- Post processing for RooUnitTest --- + regPlot(xframe, "TestRooRealLPlot_plot"); + + return true; + } +}; + +TEST(TestStatisticsPlot, RooRealL) +{ + // Run the RooUnitTest and assert that it succeeds with gtest + + RooUnitTest::setMemDir(gDirectory); + + TFile fref("TestStatistics_ref.root"); + + TestRooRealLPlot plotTest{fref, false, 0, "off"}; + bool result = plotTest.runTest(); + ASSERT_TRUE(result); +} \ No newline at end of file diff --git a/test/stressRooFit_tests.h b/test/stressRooFit_tests.h index e0ba1d459a8bc..5b86b0ac429a0 100644 --- a/test/stressRooFit_tests.h +++ b/test/stressRooFit_tests.h @@ -6456,4 +6456,4 @@ class TestBasic804 : public RooUnitTest return kTRUE ; } -} ; +} ; \ No newline at end of file From c04006414644a1acf703626d27327c83d21099fa Mon Sep 17 00:00:00 2001 From: Bertrand Bellenot Date: Thu, 20 Jan 2022 17:15:01 +0100 Subject: [PATCH 2/9] Fix testHistFactory compilation error on Windows Fix the following compilation error on Windows: ``` testHistFactory.obj : error LNK2019: unresolved external symbol "class std::basic_string,class std::allocator > const & __cdecl RooFit::tmpPath(void)" (?tmpPath@RooFit@@YAAEBV?$basic_string@DU?$char_traits@D@std@@V$allocator@D@2@@std@@XZ) referenced in function "public: virtual void __cdecl HFFixture_ModelProperties_Test::TestBody(void)" (?TestBody@HFFixture_ModelProperties_Test@@UEAAXXZ) testHistFactory.exe : fatal error LNK1120: 1 unresolved externals ``` --- roofit/histfactory/test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roofit/histfactory/test/CMakeLists.txt b/roofit/histfactory/test/CMakeLists.txt index f797b08d33a66..f7c7e5fda6c31 100644 --- a/roofit/histfactory/test/CMakeLists.txt +++ b/roofit/histfactory/test/CMakeLists.txt @@ -7,5 +7,5 @@ # @author Stephan Hageboeck CERN, 2019 ROOT_ADD_GTEST(testHistFactory testHistFactory.cxx - LIBRARIES RooFitCore RooFit RooStats HistFactory RooBatchCompute + LIBRARIES RooFitCommon RooFitCore RooFit RooStats HistFactory RooBatchCompute COPY_TO_BUILDDIR ${CMAKE_CURRENT_SOURCE_DIR}/ref_6.16_example_UsingC_channel1_meas_model.root ${CMAKE_CURRENT_SOURCE_DIR}/ref_6.16_example_UsingC_combined_meas_model.root) From def3448dbe3ada2bf0aa2553e9395b7334ce0213 Mon Sep 17 00:00:00 2001 From: Carsten Burgard Date: Tue, 25 Jan 2022 00:23:36 +0100 Subject: [PATCH 3/9] [RF] Refactor RooFitHS3 to solve various bugs and improve usability The first implementation of RooFitH3 had a number of shortcomings, which this PR addresses. In detail: * It is now possible to read JSON files independent of the ordering * A priority mechanism has been implemented for importers and exporters * Duplicate and dead code has been removed * Many small bugs have been fixed * The JSONInterface has been made public and moved from Detail to Experimental, so users can write their own importers & exporters * The two unit tests have been fixed Co-authored-by: Nicolas Morange Co-authored-by: Jonas Rembser --- etc/RooFitHS3_wsexportkeys.json | 22 +- etc/RooFitHS3_wsfactoryexpressions.json | 18 +- .../HistFactory/PiecewiseInterpolation.h | 5 +- .../src/PiecewiseInterpolation.cxx | 8 +- roofit/hs3/CMakeLists.txt | 1 + .../hs3/inc/RooFitHS3/HistFactoryJSONTool.h | 8 +- .../{src => inc/RooFitHS3}/JSONInterface.h | 21 +- .../hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h | 148 ++- roofit/hs3/src/HistFactoryJSONTool.cxx | 7 +- roofit/hs3/src/JSONFactories_HistFactory.cxx | 345 +++++-- roofit/hs3/src/JSONFactories_RooFitCore.cxx | 437 +++++++- roofit/hs3/src/JSONInterface.cxx | 34 +- roofit/hs3/src/JSONParser.cxx | 4 +- roofit/hs3/src/JSONParser.h | 5 +- roofit/hs3/src/RYMLParser.h | 2 +- roofit/hs3/src/RooJSONFactoryWSTool.cxx | 976 +++++++++++------- roofit/hs3/test/testRooFitHS3.cxx | 73 +- ...actory.py => test_hs3_histfactory_json.py} | 38 +- roofit/roofit/inc/RooGaussian.h | 9 + roofit/roofitcore/inc/RooBinSamplingPdf.h | 4 + roofit/roofitcore/inc/RooBinWidthFunction.h | 2 + roofit/roofitcore/inc/RooFormulaVar.h | 4 +- roofit/roofitcore/inc/RooGenericPdf.h | 3 + roofit/roofitcore/inc/RooGlobalFunc.h | 2 +- roofit/roofitcore/src/RooWorkspace.cxx | 4 +- tutorials/roofit/rf515_hfJSON.json | 10 +- tutorials/roofit/rf515_hfJSON.py | 18 +- 27 files changed, 1597 insertions(+), 611 deletions(-) rename roofit/hs3/{src => inc/RooFitHS3}/JSONInterface.h (85%) rename roofit/hs3/test/{histfactory.py => test_hs3_histfactory_json.py} (86%) diff --git a/etc/RooFitHS3_wsexportkeys.json b/etc/RooFitHS3_wsexportkeys.json index bc4c1025d039b..b2850905e5e0c 100644 --- a/etc/RooFitHS3_wsexportkeys.json +++ b/etc/RooFitHS3_wsexportkeys.json @@ -14,6 +14,13 @@ "mean": "mean" } }, + "RooExponential": { + "type": "Exponential", + "proxies": { + "x": "x", + "c": "c" + } + }, "RooProduct": { "type": "prod", "proxies": { @@ -34,13 +41,6 @@ "paramSet": "parameters" } }, - "RooRealSumPdf": { - "type": "sumpdf", - "proxies": { - "funcList": "samples", - "coefList": "coefficients" - } - }, "RooAddPdf": { "type": "pdfsum", "proxies": { @@ -49,6 +49,12 @@ "coefficients": "coefficients" } }, + "RooAddition": { + "type": "sum", + "proxies": { + "set": "summands" + } + }, "RooArgusBG": { "type": "ARGUS", "proxies": { @@ -58,4 +64,4 @@ "p": "power" } } -} \ No newline at end of file +} diff --git a/etc/RooFitHS3_wsfactoryexpressions.json b/etc/RooFitHS3_wsfactoryexpressions.json index b0438de78e932..309b340a3f1f0 100644 --- a/etc/RooFitHS3_wsfactoryexpressions.json +++ b/etc/RooFitHS3_wsfactoryexpressions.json @@ -7,6 +7,13 @@ "sigma" ] }, + "Exponential": { + "class": "RooExponential", + "arguments": [ + "x", + "c" + ] + }, "Poisson": { "class": "RooPoisson", "arguments": [ @@ -29,13 +36,12 @@ "high" ] }, - "sumpdf": { - "class": "RooRealSumPdf", + "sum": { + "class": "RooAddition", "arguments": [ - "samples", - "coefficients" + "summands" ] - }, + }, "paramhist": { "class": "ParamHistFunc", "arguments": [ @@ -52,4 +58,4 @@ "power" ] } -} \ No newline at end of file +} diff --git a/roofit/histfactory/inc/RooStats/HistFactory/PiecewiseInterpolation.h b/roofit/histfactory/inc/RooStats/HistFactory/PiecewiseInterpolation.h index dd458c0f14b44..ea6246ae30b3e 100644 --- a/roofit/histfactory/inc/RooStats/HistFactory/PiecewiseInterpolation.h +++ b/roofit/histfactory/inc/RooStats/HistFactory/PiecewiseInterpolation.h @@ -49,7 +49,8 @@ class PiecewiseInterpolation : public RooAbsReal { const RooArgList& lowList() const { return _lowSet ; } const RooArgList& highList() const { return _highSet ; } const RooArgList& paramList() const { return _paramSet ; } - + const std::vector& interpolationCodes() const { return _interpCode; } + //virtual Bool_t forceAnalyticalInt(const RooAbsArg&) const { return kTRUE ; } Bool_t setBinIntegrator(RooArgSet& allVars) ; @@ -58,7 +59,7 @@ class PiecewiseInterpolation : public RooAbsReal { void setPositiveDefinite(bool flag=true){_positiveDefinite=flag;} - void setInterpCode(RooAbsReal& param, int code); + void setInterpCode(RooAbsReal& param, int code, bool silent=false); void setAllInterpCodes(int code); void printAllInterpCodes(); diff --git a/roofit/histfactory/src/PiecewiseInterpolation.cxx b/roofit/histfactory/src/PiecewiseInterpolation.cxx index f64fe537b3216..27bf877e8ed59 100644 --- a/roofit/histfactory/src/PiecewiseInterpolation.cxx +++ b/roofit/histfactory/src/PiecewiseInterpolation.cxx @@ -742,14 +742,16 @@ Double_t PiecewiseInterpolation::analyticalIntegralWN(Int_t code, const RooArgSe //////////////////////////////////////////////////////////////////////////////// -void PiecewiseInterpolation::setInterpCode(RooAbsReal& param, int code){ +void PiecewiseInterpolation::setInterpCode(RooAbsReal& param, int code, bool silent){ int index = _paramSet.index(¶m); if(index<0){ coutE(InputArguments) << "PiecewiseInterpolation::setInterpCode ERROR: " << param.GetName() << " is not in list" << endl ; } else { - coutW(InputArguments) << "PiecewiseInterpolation::setInterpCode : " << param.GetName() - << " is now " << code << endl ; + if(!silent){ + coutW(InputArguments) << "PiecewiseInterpolation::setInterpCode : " << param.GetName() + << " is now " << code << endl ; + } _interpCode.at(index) = code; } } diff --git a/roofit/hs3/CMakeLists.txt b/roofit/hs3/CMakeLists.txt index 8e3feccd684a3..bf62c51b60902 100644 --- a/roofit/hs3/CMakeLists.txt +++ b/roofit/hs3/CMakeLists.txt @@ -28,6 +28,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitHS3 HEADERS RooFitHS3/RooJSONFactoryWSTool.h RooFitHS3/HistFactoryJSONTool.h + RooFitHS3/JSONInterface.h SOURCES src/RooJSONFactoryWSTool.cxx src/HistFactoryJSONTool.cxx diff --git a/roofit/hs3/inc/RooFitHS3/HistFactoryJSONTool.h b/roofit/hs3/inc/RooFitHS3/HistFactoryJSONTool.h index c7223ae3c9890..c1144bc642a13 100644 --- a/roofit/hs3/inc/RooFitHS3/HistFactoryJSONTool.h +++ b/roofit/hs3/inc/RooFitHS3/HistFactoryJSONTool.h @@ -5,7 +5,7 @@ #include namespace RooFit { -namespace Detail { +namespace Experimental { class JSONNode; } } // namespace RooFit @@ -21,8 +21,8 @@ class JSONTool { protected: RooStats::HistFactory::Measurement *_measurement; - void Export(const RooStats::HistFactory::Channel &c, RooFit::Detail::JSONNode &t) const; - void Export(const RooStats::HistFactory::Sample &s, RooFit::Detail::JSONNode &t) const; + void Export(const RooStats::HistFactory::Channel &c, RooFit::Experimental::JSONNode &t) const; + void Export(const RooStats::HistFactory::Sample &s, RooFit::Experimental::JSONNode &t) const; public: JSONTool(RooStats::HistFactory::Measurement *); @@ -31,7 +31,7 @@ class JSONTool { void PrintJSON(std::string const &filename); void PrintYAML(std::ostream &os = std::cout); void PrintYAML(std::string const &filename); - void Export(RooFit::Detail::JSONNode &t) const; + void Export(RooFit::Experimental::JSONNode &t) const; }; } // namespace HistFactory diff --git a/roofit/hs3/src/JSONInterface.h b/roofit/hs3/inc/RooFitHS3/JSONInterface.h similarity index 85% rename from roofit/hs3/src/JSONInterface.h rename to roofit/hs3/inc/RooFitHS3/JSONInterface.h index 82be301eb69c9..7f09e584f4f7d 100644 --- a/roofit/hs3/src/JSONInterface.h +++ b/roofit/hs3/inc/RooFitHS3/JSONInterface.h @@ -7,7 +7,7 @@ #include namespace RooFit { -namespace Detail { +namespace Experimental { class JSONNode { protected: @@ -80,6 +80,8 @@ class JSONNode { virtual int val_int() const { return atoi(this->val().c_str()); } virtual float val_float() const { return atof(this->val().c_str()); } virtual bool val_bool() const { return atoi(this->val().c_str()); } + template + T val_t() const; virtual bool has_key() const = 0; virtual bool has_val() const = 0; virtual bool has_child(std::string const &) const = 0; @@ -103,13 +105,24 @@ class JSONNode { virtual const JSONNode &child(size_t pos) const = 0; }; -} // namespace Detail +} // namespace Experimental } // namespace RooFit class JSONTree { - virtual RooFit::Detail::JSONNode &rootnode() = 0; + virtual RooFit::Experimental::JSONNode &rootnode() = 0; }; -std::ostream &operator<<(std::ostream &os, RooFit::Detail::JSONNode const &s); +std::ostream &operator<<(std::ostream &os, RooFit::Experimental::JSONNode const &s); +template +std::vector &operator<<(std::vector &v, RooFit::Experimental::JSONNode const &n) +{ + if (!n.is_seq()) { + throw std::runtime_error("node " + n.key() + " is not of sequence type!"); + } + for (const auto &e : n.children()) { + v.push_back(e.val_t()); + } + return v; +} #endif diff --git a/roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h b/roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h index ad0edf153a5fd..231a4a42badb0 100644 --- a/roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h +++ b/roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h @@ -2,12 +2,14 @@ #define RooFitHS3_RooJSONFactoryWSTool_h #include +#include #include #include class RooAbsArg; class RooAbsReal; +class RooAbsPdf; class RooDataHist; class RooDataSet; class RooRealVar; @@ -17,31 +19,49 @@ class RooWorkspace; class TH1; namespace RooFit { -namespace Detail { +namespace Experimental { class JSONNode; } } // namespace RooFit class RooJSONFactoryWSTool { public: + struct Config { + static bool stripObservables; + }; + class Importer { public: - virtual bool importPdf(RooJSONFactoryWSTool *, const RooFit::Detail::JSONNode &) const { return false; } - virtual bool importFunction(RooJSONFactoryWSTool *, const RooFit::Detail::JSONNode &) const { return false; } + virtual bool importPdf(RooJSONFactoryWSTool *, const RooFit::Experimental::JSONNode &) const { return false; } + virtual bool importFunction(RooJSONFactoryWSTool *, const RooFit::Experimental::JSONNode &) const + { + return false; + } virtual ~Importer(){}; }; class Exporter { public: + virtual std::string const &key() const = 0; virtual bool autoExportDependants() const { return true; } - virtual bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *, RooFit::Detail::JSONNode &) const + virtual bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *, RooFit::Experimental::JSONNode &) const { return false; } virtual ~Exporter(){}; }; + struct ExportKeys { + std::string type; + std::map proxies; + }; + struct ImportExpression { + TClass *tclass = nullptr; + std::vector arguments; + }; - typedef std::map ImportMap; - typedef std::map ExportMap; + typedef std::map> ImportMap; + typedef std::map> ExportMap; + typedef std::map ExportKeysMap; + typedef std::map ImportExpressionMap; struct Var { int nbins; @@ -50,36 +70,80 @@ class RooJSONFactoryWSTool { std::vector bounds; Var(int n) : nbins(n), min(0), max(n) {} - Var(const RooFit::Detail::JSONNode &val); + Var(const RooFit::Experimental::JSONNode &val); }; + std::ostream &log(RooFit::MsgLevel level) const; + protected: struct Scope { RooArgSet observables; std::map objects; }; mutable Scope _scope; + const RooFit::Experimental::JSONNode *_rootnode_input = nullptr; + RooFit::Experimental::JSONNode *_rootnode_output = nullptr; + + RooFit::Experimental::JSONNode &orootnode(); + const RooFit::Experimental::JSONNode &irootnode() const; RooWorkspace *_workspace; + static ImportMap _importers; static ExportMap _exporters; - void prepare(); - std::map loadData(const RooFit::Detail::JSONNode &n); + static ExportKeysMap _exportKeys; + static ImportExpressionMap _pdfFactoryExpressions; + static ImportExpressionMap _funcFactoryExpressions; + + std::map loadData(const RooFit::Experimental::JSONNode &n); RooDataSet *unbinned(RooDataHist *hist); RooRealVar *getWeightVar(const char *name); RooRealVar *createObservable(const std::string &name, const RooJSONFactoryWSTool::Var &var); public: - static std::string name(const RooFit::Detail::JSONNode &n); + class MissingRootnodeError : public std::exception { + public: + virtual const char *what() const noexcept override { return "no rootnode set"; } + }; + + class DependencyMissingError : public std::exception { + std::string _parent, _child, _class, _message; + + public: + DependencyMissingError(const std::string &p, const std::string &c, const std::string &classname) + : _parent(p), _child(c), _class(classname) + { + _message = "object '" + _parent + "' is missing dependency '" + _child + "' of type '" + _class + "'"; + }; + const std::string &parent() const { return _parent; } + const std::string &child() const { return _child; } + const std::string &classname() const { return _class; } + virtual const char *what() const noexcept override { return _message.c_str(); } + }; + friend DependencyMissingError; + + static std::string name(const RooFit::Experimental::JSONNode &n); + + template + T *request(const std::string &objname, const std::string &requestAuthor); RooJSONFactoryWSTool(RooWorkspace &ws) : _workspace{&ws} {} RooWorkspace *workspace() { return this->_workspace; } - static bool registerImporter(const std::string &key, const RooJSONFactoryWSTool::Importer *f); - static bool registerExporter(const TClass *key, const RooJSONFactoryWSTool::Exporter *f); + static bool + registerImporter(const std::string &key, const RooJSONFactoryWSTool::Importer *f, bool topPriority = true); + static int removeImporters(const std::string &needle); + static bool registerExporter(const TClass *key, const RooJSONFactoryWSTool::Exporter *f, bool topPriority = true); + static int removeExporters(const std::string &needle); static void printImporters(); static void printExporters(); + static const ImportMap &importers() { return _importers; } + static const ExportMap &exporters() { return _exporters; } + static const ImportExpressionMap &pdfImportExpressions() { return _pdfFactoryExpressions; } + static const ImportExpressionMap &functionImportExpressions() { return _funcFactoryExpressions; } + static const ExportKeysMap &exportKeys() { return _exportKeys; } + // error handling helpers static void error(const char *s) { throw std::runtime_error(s); } static void error(const std::string &s) { throw std::runtime_error(s); } @@ -122,28 +186,30 @@ class RooJSONFactoryWSTool { return names; } - static std::string genPrefix(const RooFit::Detail::JSONNode &p, bool trailing_underscore); - static void exportHistogram(const TH1 &h, RooFit::Detail::JSONNode &n, const std::vector &obsnames, - const TH1 *errH = 0, bool writeObservables = true, bool writeErrors = true); - void exportData(RooAbsData *data, RooFit::Detail::JSONNode &n); - static void writeObservables(const TH1 &h, RooFit::Detail::JSONNode &n, const std::vector &varnames); + static std::string genPrefix(const RooFit::Experimental::JSONNode &p, bool trailing_underscore); + static void exportHistogram(const TH1 &h, RooFit::Experimental::JSONNode &n, + const std::vector &obsnames, const TH1 *errH = 0, + bool writeObservables = true, bool writeErrors = true); + void exportData(RooAbsData *data, RooFit::Experimental::JSONNode &n); + static void + writeObservables(const TH1 &h, RooFit::Experimental::JSONNode &n, const std::vector &varnames); static std::vector> generateBinIndices(const RooArgList &vars); RooDataHist * - readBinnedData(const RooFit::Detail::JSONNode &n, const std::string &namecomp, const RooArgList &observables); + readBinnedData(const RooFit::Experimental::JSONNode &n, const std::string &namecomp, RooArgList observables); static std::map - readObservables(const RooFit::Detail::JSONNode &n, const std::string &obsnamecomp); - RooArgSet getObservables(const RooFit::Detail::JSONNode &n, const std::string &obsnamecomp); + readObservables(const RooFit::Experimental::JSONNode &n, const std::string &obsnamecomp); + RooArgSet getObservables(const RooFit::Experimental::JSONNode &n, const std::string &obsnamecomp); void setScopeObservables(const RooArgList &args); RooAbsArg *getScopeObject(const std::string &name); void setScopeObject(const std::string &key, RooAbsArg *obj); void clearScope(); - bool importJSON(std::string const& filename); - bool importYML(std::string const& filename); + bool importJSON(std::string const &filename); + bool importYML(std::string const &filename); bool importJSON(std::istream &os); bool importYML(std::istream &os); - bool exportJSON(std::string const& fileName); - bool exportYML(std::string const& fileName); + bool exportJSON(std::string const &fileName); + bool exportYML(std::string const &fileName); bool exportJSON(std::ostream &os); bool exportYML(std::ostream &os); @@ -159,21 +225,29 @@ class RooJSONFactoryWSTool { static void clearExportKeys(); static void printExportKeys(); - void importFunctions(const RooFit::Detail::JSONNode &n); - void importPdfs(const RooFit::Detail::JSONNode &n); - void importVariables(const RooFit::Detail::JSONNode &n); - void importDependants(const RooFit::Detail::JSONNode &n); + void importAllNodes(const RooFit::Experimental::JSONNode &n); + + void importFunctions(const RooFit::Experimental::JSONNode &n); + void importPdfs(const RooFit::Experimental::JSONNode &n); + void importVariables(const RooFit::Experimental::JSONNode &n); + void importFunction(const RooFit::Experimental::JSONNode &n, bool isPdf); + void importVariable(const RooFit::Experimental::JSONNode &n); + void configureVariable(const RooFit::Experimental::JSONNode &p, RooRealVar &v); + void importDependants(const RooFit::Experimental::JSONNode &n); + + void configureToplevelPdf(const RooFit::Experimental::JSONNode &n, RooAbsPdf &pdf); - bool find(const RooFit::Detail::JSONNode &n, const std::string &elem); - void append(RooFit::Detail::JSONNode &n, const std::string &elem); + bool find(const RooFit::Experimental::JSONNode &n, const std::string &elem); + void append(RooFit::Experimental::JSONNode &n, const std::string &elem); - void exportAttributes(const RooAbsArg *arg, RooFit::Detail::JSONNode &n); - void exportVariable(const RooAbsReal *v, RooFit::Detail::JSONNode &n); - void exportVariables(const RooArgSet &allElems, RooFit::Detail::JSONNode &n); - void exportObject(const RooAbsArg *func, RooFit::Detail::JSONNode &n); - void exportFunctions(const RooArgSet &allElems, RooFit::Detail::JSONNode &n); + void exportAttributes(const RooAbsArg *arg, RooFit::Experimental::JSONNode &n); + void exportVariable(const RooAbsReal *v, RooFit::Experimental::JSONNode &n); + void exportVariables(const RooArgSet &allElems, RooFit::Experimental::JSONNode &n); + void exportObject(const RooAbsArg *func, RooFit::Experimental::JSONNode &n); + void exportFunctions(const RooArgSet &allElems, RooFit::Experimental::JSONNode &n); - void exportAll(RooFit::Detail::JSONNode &n); - void exportDependants(const RooAbsArg *source, RooFit::Detail::JSONNode &n); + void exportAllObjects(RooFit::Experimental::JSONNode &n); + void exportDependants(const RooAbsArg *source, RooFit::Experimental::JSONNode &n); + void exportDependants(const RooAbsArg *source, RooFit::Experimental::JSONNode *n); }; #endif diff --git a/roofit/hs3/src/HistFactoryJSONTool.cxx b/roofit/hs3/src/HistFactoryJSONTool.cxx index f7e646f474e75..f91483915c408 100644 --- a/roofit/hs3/src/HistFactoryJSONTool.cxx +++ b/roofit/hs3/src/HistFactoryJSONTool.cxx @@ -5,8 +5,6 @@ #include "RooStats/HistFactory/Channel.h" #include "RooStats/HistFactory/Sample.h" -#include "JSONInterface.h" - #ifdef ROOFIT_HS3_WITH_RYML #include "RYMLParser.h" typedef TRYMLTree tree_t; @@ -15,7 +13,7 @@ typedef TRYMLTree tree_t; typedef TJSONTree tree_t; #endif -using RooFit::Detail::JSONNode; +using RooFit::Experimental::JSONNode; RooStats::HistFactory::JSONTool::JSONTool(RooStats::HistFactory::Measurement *m) : _measurement(m){}; @@ -27,7 +25,7 @@ void RooStats::HistFactory::JSONTool::Export(const RooStats::HistFactory::Sample obsnames.push_back("obs_z_" + sample.GetChannelName()); s.set_map(); - s["type"] << "histogram"; + s["type"] << "hist-sample"; if (sample.GetOverallSysList().size() > 0) { auto &overallSys = s["overallSystematics"]; @@ -147,6 +145,7 @@ void RooStats::HistFactory::JSONTool::Export(JSONNode &n) const auto &sim = pdflist[this->_measurement->GetName()]; sim.set_map(); sim["type"] << "simultaneous"; + sim["index"] << "channelCat"; auto &simdict = sim["dict"]; simdict.set_map(); simdict["InterpolationScheme"] << this->_measurement->GetInterpolationScheme(); diff --git a/roofit/hs3/src/JSONFactories_HistFactory.cxx b/roofit/hs3/src/JSONFactories_HistFactory.cxx index 1647846bc4b5f..1ae40e5663221 100644 --- a/roofit/hs3/src/JSONFactories_HistFactory.cxx +++ b/roofit/hs3/src/JSONFactories_HistFactory.cxx @@ -1,4 +1,5 @@ #include +#include #include #include @@ -12,15 +13,15 @@ #include #include #include +#include #include #include #include -#include "JSONInterface.h" #include "static_execute.h" -using RooFit::Detail::JSONNode; +using RooFit::Experimental::JSONNode; namespace { inline void collectNames(const JSONNode &n, std::vector &names) @@ -82,15 +83,16 @@ TH1 *histFunc2TH1(const RooHistFunc *hf) return hist; } -RooPoisson *findPoissonClient(RooAbsArg *gamma) +template +T *findClient(RooAbsArg *gamma) { for (const auto &client : gamma->clients()) { - if (client->InheritsFrom(RooPoisson::Class())) { - return (RooPoisson *)client; + if (client->InheritsFrom(T::Class())) { + return static_cast(client); } else { - RooPoisson *p = findPoissonClient(client); - if (p) - return p; + T *c = findClient(client); + if (c) + return c; } } return NULL; @@ -130,7 +132,7 @@ RooRealVar *getNP(RooJSONFactoryWSTool *tool, const char *parname) } RooAbsPdf *getConstraint(RooJSONFactoryWSTool *tool, const std::string &sysname) { - RooAbsPdf *pdf = tool->workspace()->pdf(sysname.c_str()); + RooAbsPdf *pdf = tool->workspace()->pdf((sysname + "_constraint").c_str()); if (!pdf) { pdf = (RooAbsPdf *)(tool->workspace()->factory( TString::Format("RooGaussian::%s_constraint(alpha_%s,nom_alpha_%s,sigma_alpha_%s)", sysname.c_str(), @@ -145,7 +147,7 @@ RooAbsPdf *getConstraint(RooJSONFactoryWSTool *tool, const std::string &sysname) class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { public: - virtual bool importFunction(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + bool importFunction(RooJSONFactoryWSTool *tool, const JSONNode &p) const override { std::string name(RooJSONFactoryWSTool::name(p)); std::string prefix = RooJSONFactoryWSTool::genPrefix(p, true); @@ -159,16 +161,20 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { RooArgSet shapeElems; RooArgSet normElems; auto varlist = tool->getObservables(p["data"], prefix); - RooDataHist *dh = tool->readBinnedData(p["data"], name, varlist); + RooDataHist *dh = dynamic_cast(tool->workspace()->embeddedData(name.c_str())); + if (!dh) { + dh = tool->readBinnedData(p["data"], name, varlist); + tool->workspace()->import(*dh, RooFit::Silence(true), RooFit::Embedded()); + } RooHistFunc *hf = new RooHistFunc(("hist_" + name).c_str(), RooJSONFactoryWSTool::name(p).c_str(), *(dh->get()), *dh); - RooBinWidthFunction *binning = - new RooBinWidthFunction(TString::Format("%s_binWidth", name.c_str()).Data(), - TString::Format("%s_binWidth", name.c_str()).Data(), *hf, true); + RooBinWidthFunction *binning = new RooBinWidthFunction( + TString::Format("%s_binWidth", (prefix.size() > 0 ? prefix : name).c_str()).Data(), + TString::Format("%s_binWidth", (prefix.size() > 0 ? prefix : name).c_str()).Data(), *hf, true); shapeElems.add(*binning); tmp.push_back(binning); - if (p["statError"].val_bool()) { + if (p.has_child("statError") && p["statError"].val_bool()) { RooAbsArg *phf = tool->getScopeObject("mcstat"); if (phf) { shapeElems.add(*phf); @@ -209,6 +215,7 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { } RooStats::HistFactory::FlexibleInterpVar *v = new RooStats::HistFactory::FlexibleInterpVar( ("overallSys_" + name).c_str(), ("overallSys_" + name).c_str(), nps, 1., low, high); + v->setAllInterpCodes(4); // default HistFactory interpCode normElems.add(*v); tmp.push_back(v); } @@ -222,12 +229,22 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { : "alpha_" + sysname); RooAbsReal *par = ::getNP(tool, parname.c_str()); nps.add(*par); - RooDataHist *dh_low = tool->readBinnedData(sys["dataLow"], sysname + "Low_" + name, varlist); + RooDataHist *dh_low = + dynamic_cast(tool->workspace()->embeddedData((sysname + "Low_" + name).c_str())); + if (!dh_low) { + dh_low = tool->readBinnedData(sys["dataLow"], sysname + "Low_" + name, varlist); + tool->workspace()->import(*dh_low, RooFit::Silence(true), RooFit::Embedded()); + } RooHistFunc *hf_low = new RooHistFunc((sysname + "Low_" + name).c_str(), RooJSONFactoryWSTool::name(p).c_str(), *(dh_low->get()), *dh_low); low.add(*hf_low); tmp.push_back(hf_low); - RooDataHist *dh_high = tool->readBinnedData(sys["dataHigh"], sysname + "High_" + name, varlist); + RooDataHist *dh_high = + dynamic_cast(tool->workspace()->embeddedData((sysname + "High_" + name).c_str())); + if (!dh_high) { + dh_high = tool->readBinnedData(sys["dataHigh"], sysname + "High_" + name, varlist); + tool->workspace()->import(*dh_high, RooFit::Silence(true), RooFit::Embedded()); + } RooHistFunc *hf_high = new RooHistFunc((sysname + "High_" + name).c_str(), RooJSONFactoryWSTool::name(p).c_str(), *(dh_high->get()), *dh_high); @@ -235,7 +252,8 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { tmp.push_back(hf_high); } PiecewiseInterpolation *v = new PiecewiseInterpolation( - ("histoSys_" + name).c_str(), ("histoSys_" + name).c_str(), *hf, nps, low, high, false); + ("histoSys_" + name).c_str(), ("histoSys_" + name).c_str(), *hf, low, high, nps, false); + v->setAllInterpCodes(4); // default interpCode for HistFactory shapeElems.add(*v); tmp.push_back(v); } else { @@ -243,10 +261,10 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { tmp.push_back(hf); } RooProduct shape(name.c_str(), (name + "_shape").c_str(), shapeElems); - tool->workspace()->import(shape, RooFit::RecycleConflictNodes(true)); + tool->workspace()->import(shape, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); if (normElems.size() > 0) { RooProduct norm((name + "_norm").c_str(), (name + "_norm").c_str(), normElems); - tool->workspace()->import(norm, RooFit::RecycleConflictNodes(true)); + tool->workspace()->import(norm, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); } else { tool->workspace()->factory(("RooConstVar::" + name + "_norm(1.)").c_str()); } @@ -263,42 +281,54 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { public: - ParamHistFunc *createPHF(const std::string &name, const std::vector sumW, const std::vector &sumW2, + ParamHistFunc *createPHF(const std::string &name, const std::vector &sumW, const std::vector &sumW2, RooArgList &nps, RooArgList &constraints, const RooArgSet &observables, - double statErrorThreshold) const + double statErrorThreshold, const std::string &statErrorType) const { RooArgList gammas; for (size_t i = 0; i < sumW.size(); ++i) { TString gname = TString::Format("gamma_stat_%s_bin_%d", name.c_str(), (int)i); - TString tname = TString::Format("tau_stat_%s_bin_%d", name.c_str(), (int)i); - TString prodname = TString::Format("nExp_stat_%s_bin_%d", name.c_str(), (int)i); - TString poisname = TString::Format("Constraint_stat_%s_bin_%d", name.c_str(), (int)i); double err = sqrt(sumW2[i]) / sumW[i]; if (err > 0) { - double tauCV = 1. / (err * err); RooRealVar *g = new RooRealVar(gname.Data(), gname.Data(), 1.); g->setAttribute("np"); - if (err < statErrorThreshold) { - g->setConstant(true); - } else { - g->setConstant(false); - } - RooRealVar *tau = new RooRealVar(tname.Data(), tname.Data(), tauCV); - // tau->setAttribute("glob"); - tau->setConstant(true); - tau->setRange(tau->getVal(), tau->getVal()); - RooArgSet elems; - elems.add(*g); - elems.add(*tau); + g->setConstant(err < statErrorThreshold); g->setError(err); g->setMin(1. - 10 * err); g->setMax(1. + 10 * err); - RooProduct *prod = new RooProduct(prodname.Data(), prodname.Data(), elems); - RooPoisson *pois = new RooPoisson(poisname.Data(), poisname.Data(), *tau, *prod); - pois->setNoRounding(true); gammas.add(*g, true); nps.add(*g); - constraints.add(*pois, true); + + if (statErrorType == "Gauss") { + TString tname = TString::Format("nom_gamma_stat_%s_bin_%d", name.c_str(), (int)i); + TString poisname = TString::Format("gamma_stat_%s_bin_%d_constraint", name.c_str(), (int)i); + TString sname = TString::Format("gamma_stat_%s_bin_%d_sigma", name.c_str(), (int)i); + RooRealVar *tau = new RooRealVar(tname.Data(), tname.Data(), 1); + tau->setAttribute("glob"); + tau->setConstant(true); + tau->setRange(0, 10); + RooConstVar *sigma = new RooConstVar(sname.Data(), sname.Data(), err); + RooGaussian *gaus = new RooGaussian(poisname.Data(), poisname.Data(), *tau, *g, *sigma); + constraints.add(*gaus, true); + } else if (statErrorType == "Poisson") { + TString tname = TString::Format("tau_stat_%s_bin_%d", name.c_str(), (int)i); + TString prodname = TString::Format("nExp_stat_%s_bin_%d", name.c_str(), (int)i); + TString poisname = TString::Format("Constraint_stat_%s_bin_%d", name.c_str(), (int)i); + double tauCV = 1. / (err * err); + RooRealVar *tau = new RooRealVar(tname.Data(), tname.Data(), tauCV); + tau->setAttribute("glob"); + tau->setConstant(true); + tau->setRange(tauCV - 10. / err, tauCV + 10. / err); + RooArgSet elems; + elems.add(*g); + elems.add(*tau); + RooProduct *prod = new RooProduct(prodname.Data(), prodname.Data(), elems); + RooPoisson *pois = new RooPoisson(poisname.Data(), poisname.Data(), *tau, *prod); + pois->setNoRounding(true); + constraints.add(*pois, true); + } else { + RooJSONFactoryWSTool::error("unknown constraint type " + statErrorType); + } } else { RooRealVar *g = new RooRealVar(gname.Data(), gname.Data(), 1.); g->setConstant(true); @@ -315,7 +345,7 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { } } - virtual bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override { std::string name(RooJSONFactoryWSTool::name(p)); RooArgList funcs; @@ -327,10 +357,13 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { RooArgList nps; std::vector usesStatError; double statErrorThreshold = 0; + std::string statErrorType = "Poisson"; if (p.has_child("statError")) { auto &staterr = p["statError"]; if (staterr.has_child("relThreshold")) statErrorThreshold = staterr["relThreshold"].val_float(); + if (staterr.has_child("constraint")) + statErrorType = staterr["constraint"].val(); } std::vector sumW; std::vector sumW2; @@ -347,7 +380,7 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { std::string fname(RooJSONFactoryWSTool::name(comp)); auto &def = comp.is_container() ? comp : p["functions"][fname.c_str()]; std::string fprefix = RooJSONFactoryWSTool::genPrefix(def, true); - if (def["type"].val() == "histogram") { + if (def["type"].val() == "hist-sample") { try { if (observables.size() == 0) { observables.add(tool->getObservables(comp["data"], fprefix)); @@ -373,29 +406,23 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { coefnames.push_back(fprefix + fname + "_norm"); } - ParamHistFunc *phf = createPHF(name, sumW, sumW2, nps, constraints, observables, statErrorThreshold); + ParamHistFunc *phf = + createPHF(name, sumW, sumW2, nps, constraints, observables, statErrorThreshold, statErrorType); if (phf) { - tool->workspace()->import(*phf, RooFit::RecycleConflictNodes()); + tool->workspace()->import(*phf, RooFit::RecycleConflictNodes(), RooFit::Silence(true)); tool->setScopeObject("mcstat", tool->workspace()->function(phf->GetName())); delete phf; } tool->importFunctions(p["samples"]); for (const auto &fname : funcnames) { - RooAbsReal *func = tool->workspace()->function(fname.c_str()); - if (!func) { - RooJSONFactoryWSTool::error("unable to obtain component '" + fname + "' of '" + name + "'"); - } + RooAbsReal *func = tool->request(fname.c_str(), name); funcs.add(*func); } for (const auto &coefname : coefnames) { - RooAbsReal *coef = tool->workspace()->function(coefname.c_str()); - if (!coef) { - RooJSONFactoryWSTool::error("unable to obtain component '" + coefname + "' of '" + name + "'"); - } + RooAbsReal *coef = tool->request(coefname.c_str(), name); coefs.add(*coef); } - for (auto &np : nps) { for (auto client : np->clients()) { if (client->InheritsFrom(RooAbsPdf::Class()) && !constraints.find(*client)) { @@ -409,14 +436,16 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { } if (constraints.getSize() == 0) { RooRealSumPdf sum(name.c_str(), name.c_str(), funcs, coefs, true); - tool->workspace()->import(sum, RooFit::RecycleConflictNodes(true)); + sum.setAttribute("BinnedLikelihood"); + tool->workspace()->import(sum, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); } else { RooRealSumPdf sum((name + "_model").c_str(), name.c_str(), funcs, coefs, true); - tool->workspace()->import(sum, RooFit::RecycleConflictNodes(true)); + sum.setAttribute("BinnedLikelihood"); + tool->workspace()->import(sum, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); RooArgList lhelems; lhelems.add(sum); RooProdPdf prod(name.c_str(), name.c_str(), constraints, RooFit::Conditional(lhelems, observables)); - tool->workspace()->import(prod, RooFit::RecycleConflictNodes(true)); + tool->workspace()->import(prod, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); } tool->clearScope(); @@ -430,11 +459,16 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { namespace { class FlexibleInterpVarStreamer : public RooJSONFactoryWSTool::Exporter { public: - virtual bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override + std::string const &key() const override + { + static const std::string keystring = "interpolation0d"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override { const RooStats::HistFactory::FlexibleInterpVar *fip = static_cast(func); - elem["type"] << "interpolation0d"; + elem["type"] << key(); auto &vars = elem["vars"]; vars.set_seq(); for (const auto &v : fip->variables()) { @@ -447,6 +481,98 @@ class FlexibleInterpVarStreamer : public RooJSONFactoryWSTool::Exporter { } }; +class PiecewiseInterpolationStreamer : public RooJSONFactoryWSTool::Exporter { +public: + std::string const &key() const override + { + static const std::string keystring = "interpolation"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override + { + const PiecewiseInterpolation *pip = static_cast(func); + elem["type"] << key(); + elem["interpolationCodes"] << pip->interpolationCodes(); + auto &vars = elem["vars"]; + vars.set_seq(); + for (const auto &v : pip->paramList()) { + vars.append_child() << v->GetName(); + } + + auto &nom = elem["nom"]; + nom << pip->nominalHist()->GetName(); + + auto &high = elem["high"]; + high.set_seq(); + for (const auto &v : pip->highList()) { + high.append_child() << v->GetName(); + } + + auto &low = elem["low"]; + low.set_seq(); + for (const auto &v : pip->lowList()) { + low.append_child() << v->GetName(); + } + return true; + } +}; +} // namespace + +namespace { +class PiecewiseInterpolationFactory : public RooJSONFactoryWSTool::Importer { +public: + bool importFunction(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + { + std::string name(RooJSONFactoryWSTool::name(p)); + if (!p.has_child("vars")) { + RooJSONFactoryWSTool::error("no vars of '" + name + "'"); + } + if (!p.has_child("high")) { + RooJSONFactoryWSTool::error("no high variations of '" + name + "'"); + } + if (!p.has_child("low")) { + RooJSONFactoryWSTool::error("no low variations of '" + name + "'"); + } + if (!p.has_child("nom")) { + RooJSONFactoryWSTool::error("no nominal variation of '" + name + "'"); + } + + std::string nomname(p["nom"].val()); + RooAbsReal *nominal = tool->request(nomname, name); + + RooArgList vars; + for (const auto &d : p["vars"].children()) { + std::string objname(RooJSONFactoryWSTool::name(d)); + RooRealVar *obj = tool->request(objname, name); + vars.add(*obj); + } + + RooArgList high; + for (const auto &d : p["high"].children()) { + std::string objname(RooJSONFactoryWSTool::name(d)); + RooAbsReal *obj = tool->request(objname, name); + high.add(*obj); + } + + RooArgList low; + for (const auto &d : p["low"].children()) { + std::string objname(RooJSONFactoryWSTool::name(d)); + RooAbsReal *obj = tool->request(objname, name); + low.add(*obj); + } + + PiecewiseInterpolation pip(name.c_str(), name.c_str(), *nominal, low, high, vars); + + if (p.has_child("interpolationCodes")) { + for (size_t i = 0; i < vars.size(); ++i) { + pip.setInterpCode(*static_cast(vars.at(i)), p["interpolationCodes"][i].val_int(), true); + } + } + + tool->workspace()->import(pip, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); + return true; + } +}; } // namespace namespace { @@ -483,10 +609,19 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { return false; } // this seems to be ok - elem["type"] << "histfactory"; + elem["type"] << key(); auto &samples = elem["samples"]; samples.set_map(); + bool has_poisson_constraints = false; + bool has_gauss_constraints = false; + std::map tot_yield; + std::map tot_yield2; + std::map rel_errors; + std::map bb_histograms; + std::map nonbb_histograms; + std::vector varnames; + for (size_t sampleidx = 0; sampleidx < sumpdf->funcList().size(); ++sampleidx) { const auto func = sumpdf->funcList().at(sampleidx); const auto coef = sumpdf->coefList().at(sampleidx); @@ -498,7 +633,7 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { samplename = samplename.substr(0, end); auto &s = samples[samplename]; s.set_map(); - s["type"] << "histogram"; + s["type"] << "hist-sample"; RooArgSet elems; if (func->InheritsFrom(RooProduct::Class())) { collectElements(elems, (RooProduct *)func); @@ -514,7 +649,6 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { ParamHistFunc *phf = NULL; PiecewiseInterpolation *pip = NULL; RooStats::HistFactory::FlexibleInterpVar *fip = NULL; - std::vector varnames; for (const auto &e : elems) { if (e->InheritsFrom(RooConstVar::Class())) { if (((RooConstVar *)e)->getVal() == 1.) @@ -592,50 +726,89 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { int idx = 0; for (const auto &g : phf->paramList()) { ++idx; - RooPoisson *constraint = findPoissonClient(g); - if (constraint) { - double erel = 1. / sqrt(constraint->getX().getVal()); - hist->SetBinError(idx, erel * hist->GetBinContent(idx)); - } else { - hist->SetBinError(idx, 0); + RooPoisson *constraint_p = findClient(g); + RooGaussian *constraint_g = findClient(g); + if (tot_yield.find(idx) == tot_yield.end()) { + tot_yield[idx] = 0; + tot_yield2[idx] = 0; + } + tot_yield[idx] += hist->GetBinContent(idx); + tot_yield2[idx] += (hist->GetBinContent(idx) * hist->GetBinContent(idx)); + if (constraint_p) { + double erel = 1. / std::sqrt(constraint_p->getX().getVal()); + rel_errors[idx] = erel; + has_poisson_constraints = true; + } else if (constraint_g) { + double erel = constraint_g->getSigma().getVal() / constraint_g->getMean().getVal(); + rel_errors[idx] = erel; + has_gauss_constraints = true; } } + bb_histograms[samplename] = hist; } else { + nonbb_histograms[samplename] = hist; s["statError"] << 0; } - if (hist) { - auto &data = s["data"]; - RooJSONFactoryWSTool::writeObservables(*hist, elem, varnames); - RooJSONFactoryWSTool::exportHistogram(*hist, data, varnames, 0, false, phf); - delete hist; + auto &ns = s["namespaces"]; + ns.set_seq(); + ns.append_child() << chname; + } + for (const auto &hist : nonbb_histograms) { + auto &s = samples[hist.first]; + auto &data = s["data"]; + RooJSONFactoryWSTool::writeObservables(*hist.second, elem, varnames); + RooJSONFactoryWSTool::exportHistogram(*hist.second, data, varnames, 0, false, false); + delete hist.second; + } + for (const auto &hist : bb_histograms) { + auto &s = samples[hist.first]; + for (auto bin : rel_errors) { + // reverse engineering the correct partial error + // the (arbitrary) convention used here is that all samples should have the same relative error + const int i = bin.first; + const double relerr_tot = bin.second; + const double count = hist.second->GetBinContent(i); + hist.second->SetBinError(i, relerr_tot * tot_yield[i] / sqrt(tot_yield2[i]) * count); } + auto &data = s["data"]; + RooJSONFactoryWSTool::writeObservables(*hist.second, elem, varnames); + RooJSONFactoryWSTool::exportHistogram(*hist.second, data, varnames, 0, false, true); + delete hist.second; + } + auto &statError = elem["statError"]; + statError.set_map(); + if (has_poisson_constraints) { + statError["constraint"] << "Poisson"; + } else if (has_gauss_constraints) { + statError["constraint"] << "Gauss"; } return true; } - virtual bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *p, JSONNode &elem) const override + std::string const &key() const override + { + static const std::string keystring = "histfactory"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *p, JSONNode &elem) const override { const RooProdPdf *prodpdf = static_cast(p); if (tryExport(prodpdf, elem)) { return true; } - elem["type"] << "pdfprod"; - auto &factors = elem["factors"]; - factors.set_seq(); - for (const auto &v : prodpdf->pdfList()) { - factors.append_child() << v->GetName(); - } - return true; + return false; } }; STATIC_EXECUTE( - RooJSONFactoryWSTool::registerImporter("histfactory", new RooRealSumPdfFactory()); - RooJSONFactoryWSTool::registerImporter("histogram", new RooHistogramFactory()); + RooJSONFactoryWSTool::registerImporter("histfactory", new RooRealSumPdfFactory(), true); + RooJSONFactoryWSTool::registerImporter("hist-sample", new RooHistogramFactory(), true); + RooJSONFactoryWSTool::registerImporter("interpolation", new PiecewiseInterpolationFactory(), true); RooJSONFactoryWSTool::registerExporter(RooStats::HistFactory::FlexibleInterpVar::Class(), - new FlexibleInterpVarStreamer()); - RooJSONFactoryWSTool::registerExporter(RooProdPdf::Class(), new HistFactoryStreamer()); + new FlexibleInterpVarStreamer(), true); + RooJSONFactoryWSTool::registerExporter(PiecewiseInterpolation::Class(), new PiecewiseInterpolationStreamer(), true); + RooJSONFactoryWSTool::registerExporter(RooProdPdf::Class(), new HistFactoryStreamer(), true); ) diff --git a/roofit/hs3/src/JSONFactories_RooFitCore.cxx b/roofit/hs3/src/JSONFactories_RooFitCore.cxx index 9dbba5d57edbf..7b830145e382a 100644 --- a/roofit/hs3/src/JSONFactories_RooFitCore.cxx +++ b/roofit/hs3/src/JSONFactories_RooFitCore.cxx @@ -1,55 +1,110 @@ #include +#include #include -#include -#include -#include -#include -#include #include -#include - -#include "JSONInterface.h" #include "static_execute.h" -using RooFit::Detail::JSONNode; +using RooFit::Experimental::JSONNode; /////////////////////////////////////////////////////////////////////////////////////////////////////// // individually implemented importers /////////////////////////////////////////////////////////////////////////////////////////////////////// +#include + namespace { +class RooGenericPdfFactory : public RooJSONFactoryWSTool::Importer { +public: + bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + { + std::string name(RooJSONFactoryWSTool::name(p)); + if (!p.has_child("dependents")) { + RooJSONFactoryWSTool::error("no dependents of '" + name + "'"); + } + if (!p.has_child("formula")) { + RooJSONFactoryWSTool::error("no formula given for '" + name + "'"); + } + RooArgList dependents; + for (const auto &d : p["dependents"].children()) { + std::string objname(RooJSONFactoryWSTool::name(d)); + TObject *obj = tool->workspace()->obj(objname.c_str()); + if (obj->InheritsFrom(RooAbsArg::Class())) { + dependents.add(*static_cast(obj)); + } + } + TString formula(p["formula"].val()); + RooGenericPdf thepdf(name.c_str(), formula.Data(), dependents); + tool->workspace()->import(thepdf, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); + return true; + } +}; +} // namespace +#include + +namespace { +class RooFormulaVarFactory : public RooJSONFactoryWSTool::Importer { +public: + bool importFunction(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + { + std::string name(RooJSONFactoryWSTool::name(p)); + if (!p.has_child("dependents")) { + RooJSONFactoryWSTool::error("no dependents of '" + name + "'"); + } + if (!p.has_child("formula")) { + RooJSONFactoryWSTool::error("no formula given for '" + name + "'"); + } + RooArgList dependents; + for (const auto &d : p["dependents"].children()) { + std::string objname(RooJSONFactoryWSTool::name(d)); + TObject *obj = tool->workspace()->obj(objname.c_str()); + if (obj->InheritsFrom(RooAbsArg::Class())) { + dependents.add(*static_cast(obj)); + } + } + TString formula(p["formula"].val()); + RooFormulaVar thevar(name.c_str(), formula.Data(), dependents); + tool->workspace()->import(thevar, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); + return true; + } +}; +} // namespace + +#include + +namespace { class RooProdPdfFactory : public RooJSONFactoryWSTool::Importer { public: - virtual bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override { std::string name(RooJSONFactoryWSTool::name(p)); RooArgSet factors; - if (!p.has_child("factors")) { + if (!p.has_child("pdfs")) { RooJSONFactoryWSTool::error("no pdfs of '" + name + "'"); } - if (!p["factors"].is_seq()) { + if (!p["pdfs"].is_seq()) { RooJSONFactoryWSTool::error("pdfs '" + name + "' are not a list."); } - for (const auto &comp : p["factors"].children()) { + for (const auto &comp : p["pdfs"].children()) { std::string pdfname(comp.val()); - RooAbsPdf *pdf = tool->workspace()->pdf(pdfname.c_str()); - if (!pdf) { - RooJSONFactoryWSTool::error("unable to obtain component '" + pdfname + "' of '" + name + "'."); - } + RooAbsPdf *pdf = tool->request(pdfname, name); factors.add(*pdf); } RooProdPdf prod(name.c_str(), name.c_str(), factors); - tool->workspace()->import(prod); + tool->workspace()->import(prod, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); return true; } }; +} // namespace + +#include +namespace { class RooAddPdfFactory : public RooJSONFactoryWSTool::Importer { public: - virtual bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override { std::string name(RooJSONFactoryWSTool::name(p)); RooArgList pdfs; @@ -68,67 +123,194 @@ class RooAddPdfFactory : public RooJSONFactoryWSTool::Importer { } for (const auto &comp : p["summands"].children()) { std::string pdfname(comp.val()); - RooAbsPdf *pdf = tool->workspace()->pdf(pdfname.c_str()); - if (!pdf) { - RooJSONFactoryWSTool::error("unable to obtain component '" + pdfname + "' of '" + name + "'."); - } + RooAbsPdf *pdf = tool->request(pdfname, name); pdfs.add(*pdf); } for (const auto &comp : p["coefficients"].children()) { std::string coefname(comp.val()); - RooAbsArg *coef = tool->workspace()->arg(coefname.c_str()); - if (!coef) { - RooJSONFactoryWSTool::error("unable to obtain component '" + coefname + "' of '" + name + "'."); - } + RooAbsReal *coef = tool->request(coefname, name); coefs.add(*coef); } RooAddPdf add(name.c_str(), name.c_str(), pdfs, coefs); - tool->workspace()->import(add); + tool->workspace()->import(add, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); return true; } }; +} // namespace + +#include +namespace { +class RooBinWidthFunctionFactory : public RooJSONFactoryWSTool::Importer { +public: + bool importFunction(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + { + std::string name(RooJSONFactoryWSTool::name(p)); + bool divideByBinWidth = p["divideByBinWidth"].val_bool(); + RooHistFunc *hf = dynamic_cast(tool->request(p["histogram"].val(), name)); + RooBinWidthFunction func(name.c_str(), name.c_str(), *hf, divideByBinWidth); + tool->workspace()->import(func, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); + return true; + } +}; +} // namespace + +#include +#include + +namespace { class RooSimultaneousFactory : public RooJSONFactoryWSTool::Importer { public: - virtual bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override { std::string name(RooJSONFactoryWSTool::name(p)); if (!p.has_child("channels")) { RooJSONFactoryWSTool::error("no channel components of '" + name + "'"); } - tool->importPdfs(p["channels"]); std::map components; - RooCategory cat("channelCat", "channelCat"); + std::string indexname(p["index"].val()); + RooCategory cat(indexname.c_str(), indexname.c_str()); for (const auto &comp : p["channels"].children()) { std::string catname(RooJSONFactoryWSTool::name(comp)); + tool->log(RooFit::INFO) << "importing category " << catname << std::endl; + tool->importFunction(comp, true); std::string pdfname(comp.has_val() ? comp.val() : RooJSONFactoryWSTool::name(comp)); - RooAbsPdf *pdf = tool->workspace()->pdf(pdfname.c_str()); - if (!pdf) { - RooJSONFactoryWSTool::error("unable to obtain component '" + pdfname + "' of '" + name + "'"); - } + RooAbsPdf *pdf = tool->request(pdfname, name); components[catname] = pdf; cat.defineType(catname.c_str()); } RooSimultaneous simpdf(name.c_str(), name.c_str(), components, cat); - tool->workspace()->import(simpdf); + tool->workspace()->import(simpdf, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); + return true; + } +}; +} // namespace + +#include +#include + +namespace { +class RooBinSamplingPdfFactory : public RooJSONFactoryWSTool::Importer { +public: + bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + { + std::string name(RooJSONFactoryWSTool::name(p)); + + if (!p.has_child("pdf")) { + RooJSONFactoryWSTool::error("no pdf given in '" + name + "'"); + } + std::string pdfname(p["pdf"].val()); + RooAbsPdf *pdf = tool->request(pdfname, name); + + if (!p.has_child("observable")) { + RooJSONFactoryWSTool::error("no observable given in '" + name + "'"); + } + std::string obsname(p["observable"].val()); + RooRealVar *obs = tool->request(obsname, name); + + if (!pdf->dependsOn(*obs)) { + pdf->Print("t"); + RooJSONFactoryWSTool::error("pdf '" + pdfname + "' does not depend on observable '" + obsname + + "' 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_float()); + + RooBinSamplingPdf thepdf(name.c_str(), name.c_str(), *obs, *pdf, epsilon); + tool->workspace()->import(thepdf, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); + return true; } }; +} // namespace + +#include + +namespace { +class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { +public: + bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + { + std::string name(RooJSONFactoryWSTool::name(p)); + if (!p.has_child("samples")) { + RooJSONFactoryWSTool::error("no samples given in '" + name + "'"); + } + if (!p.has_child("coefficients")) { + RooJSONFactoryWSTool::error("no coefficients given in '" + name + "'"); + } + RooArgList samples; + for (const auto &sample : p["samples"].children()) { + RooAbsReal *s = tool->request(sample.val(), name); + samples.add(*s); + } + RooArgList coefficients; + for (const auto &coef : p["coefficients"].children()) { + RooAbsReal *c = tool->request(coef.val(), name); + coefficients.add(*c); + } + bool extended = false; + if (p.has_child("extended") && p["extended"].val_bool()) { + extended = true; + } + RooRealSumPdf thepdf(name.c_str(), name.c_str(), samples, coefficients, extended); + tool->workspace()->import(thepdf, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); + return true; + } +}; } // namespace /////////////////////////////////////////////////////////////////////////////////////////////////////// // specialized exporter implementations /////////////////////////////////////////////////////////////////////////////////////////////////////// +#include + +namespace { +class RooRealSumPdfStreamer : public RooJSONFactoryWSTool::Exporter { +public: + std::string const &key() const override + { + const static std::string keystring = "sumpdf"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override + { + const RooRealSumPdf *pdf = static_cast(func); + elem["type"] << key(); + auto &samples = elem["samples"]; + samples.set_seq(); + auto &coefs = elem["coefficients"]; + coefs.set_seq(); + for (const auto &s : pdf->funcList()) { + samples.append_child() << s->GetName(); + } + for (const auto &c : pdf->coefList()) { + coefs.append_child() << c->GetName(); + } + elem["extended"] << (pdf->extendMode() == RooAbsPdf::CanBeExtended); + return true; + } +}; +} // namespace + namespace { class RooSimultaneousStreamer : public RooJSONFactoryWSTool::Exporter { public: + std::string const &key() const override + { + const static std::string keystring = "simultaneous"; + return keystring; + } bool autoExportDependants() const override { return false; } - virtual bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem) const override + bool exportObject(RooJSONFactoryWSTool *tool, const RooAbsArg *func, JSONNode &elem) const override { const RooSimultaneous *sim = static_cast(func); - elem["type"] << "simultaneous"; + elem["type"] << key(); + elem["index"] << sim->indexCat().GetName(); auto &channels = elem["channels"]; channels.set_map(); const auto &indexCat = sim->indexCat(); @@ -142,14 +324,24 @@ class RooSimultaneousStreamer : public RooJSONFactoryWSTool::Exporter { return true; } }; +} // namespace + +#include +#include +namespace { class RooHistFuncStreamer : public RooJSONFactoryWSTool::Exporter { public: - virtual bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override + std::string const &key() const override + { + static const std::string keystring = "histogram"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override { const RooHistFunc *hf = static_cast(func); const RooDataHist &dh = hf->dataHist(); - elem["type"] << "histogram"; + elem["type"] << key(); RooArgList vars(*dh.get()); TH1 *hist = hf->createHistogram(RooJSONFactoryWSTool::concat(&vars).c_str()); auto &data = elem["data"]; @@ -158,16 +350,169 @@ class RooHistFuncStreamer : public RooJSONFactoryWSTool::Exporter { return true; } }; +} // namespace + +namespace { +class RooHistFuncFactory : public RooJSONFactoryWSTool::Importer { +public: + bool importFunction(RooJSONFactoryWSTool *tool, const JSONNode &p) const override + { + std::string name(RooJSONFactoryWSTool::name(p)); + if (!p.has_child("data")) { + RooJSONFactoryWSTool::error("function '" + name + "' is of histogram type, but does not define a 'data' key"); + } + auto varlist = tool->getObservables(p["data"], name); + RooDataHist *dh = dynamic_cast(tool->workspace()->embeddedData(name.c_str())); + if (!dh) { + dh = tool->readBinnedData(p["data"], name, varlist); + tool->workspace()->import(*dh, RooFit::Silence(true), RooFit::Embedded()); + } + RooHistFunc hf(name.c_str(), name.c_str(), *(dh->get()), *dh); + tool->workspace()->import(hf, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); + return true; + } +}; +} // namespace + +#include + +namespace { +class RooBinSamplingPdfStreamer : public RooJSONFactoryWSTool::Exporter { +public: + std::string const &key() const override + { + static const std::string keystring = "binsampling"; + return keystring; + } + 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; + } +}; +} // namespace + +#include + +namespace { +class RooProdPdfStreamer : public RooJSONFactoryWSTool::Exporter { +public: + std::string const &key() const override + { + static const std::string keystring = "pdfprod"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override + { + const RooProdPdf *pdf = static_cast(func); + elem["type"] << key(); + auto &factors = elem["pdfs"]; + for (const auto &f : pdf->pdfList()) { + factors.append_child() << f->GetName(); + } + return true; + } +}; +} // namespace + +#include + +namespace { +class RooGenericPdfStreamer : public RooJSONFactoryWSTool::Exporter { +public: + std::string const &key() const override + { + static const std::string keystring = "genericpdf"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override + { + const RooGenericPdf *pdf = static_cast(func); + elem["type"] << key(); + elem["formula"] << pdf->expression(); + auto &factors = elem["dependents"]; + for (const auto &f : pdf->dependents()) { + factors.append_child() << f->GetName(); + } + return true; + } +}; +} // namespace + +#include + +namespace { +class RooBinWidthFunctionStreamer : public RooJSONFactoryWSTool::Exporter { +public: + std::string const &key() const override + { + static const std::string keystring = "binwidth"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override + { + const RooBinWidthFunction *pdf = static_cast(func); + elem["type"] << key(); + elem["histogram"] << pdf->histFunc().GetName(); + elem["divideByBinWidth"] << pdf->divideByBinWidth(); + return true; + } +}; +} // namespace + +#include + +namespace { +class RooFormulaVarStreamer : public RooJSONFactoryWSTool::Exporter { +public: + std::string const &key() const override + { + static const std::string keystring = "formulavar"; + return keystring; + } + bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *func, JSONNode &elem) const override + { + const RooFormulaVar *var = static_cast(func); + elem["type"] << key(); + elem["formula"] << var->expression(); + auto &factors = elem["dependents"]; + for (const auto &f : var->dependents()) { + factors.append_child() << f->GetName(); + } + return true; + } +}; +} // namespace + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// instantiate all importers and exporters +/////////////////////////////////////////////////////////////////////////////////////////////////////// +namespace { STATIC_EXECUTE( - RooJSONFactoryWSTool::registerImporter("pdfprod", new RooProdPdfFactory()); - RooJSONFactoryWSTool::registerImporter("pdfsum", new RooAddPdfFactory()); - RooJSONFactoryWSTool::registerImporter("simultaneous", new RooSimultaneousFactory()); + RooJSONFactoryWSTool::registerImporter("pdfprod", new RooProdPdfFactory(), false); + RooJSONFactoryWSTool::registerImporter("genericpdf", new RooGenericPdfFactory(), false); + RooJSONFactoryWSTool::registerImporter("formulavar", new RooFormulaVarFactory(), false); + RooJSONFactoryWSTool::registerImporter("binsampling", new RooBinSamplingPdfFactory(), false); + RooJSONFactoryWSTool::registerImporter("pdfsum", new RooAddPdfFactory(), false); + RooJSONFactoryWSTool::registerImporter("histogram", new RooHistFuncFactory(), false); + RooJSONFactoryWSTool::registerImporter("simultaneous", new RooSimultaneousFactory(), false); + RooJSONFactoryWSTool::registerImporter("binwidth", new RooBinWidthFunctionFactory(), false); + RooJSONFactoryWSTool::registerImporter("sumpdf", new RooRealSumPdfFactory(), false); - RooJSONFactoryWSTool::registerExporter(RooSimultaneous::Class(), new RooSimultaneousStreamer()); - RooJSONFactoryWSTool::registerExporter(RooHistFunc::Class(), new RooHistFuncStreamer()); + RooJSONFactoryWSTool::registerExporter(RooBinWidthFunction::Class(), new RooBinWidthFunctionStreamer(), false); + RooJSONFactoryWSTool::registerExporter(RooProdPdf::Class(), new RooProdPdfStreamer(), false); + RooJSONFactoryWSTool::registerExporter(RooSimultaneous::Class(), new RooSimultaneousStreamer(), false); + RooJSONFactoryWSTool::registerExporter(RooBinSamplingPdf::Class(), new RooBinSamplingPdfStreamer(), false); + RooJSONFactoryWSTool::registerExporter(RooHistFunc::Class(), new RooHistFuncStreamer(), false); + RooJSONFactoryWSTool::registerExporter(RooGenericPdf::Class(), new RooGenericPdfStreamer(), false); + RooJSONFactoryWSTool::registerExporter(RooFormulaVar::Class(), new RooFormulaVarStreamer(), false); + RooJSONFactoryWSTool::registerExporter(RooRealSumPdf::Class(), new RooRealSumPdfStreamer(), false); ) - } // namespace diff --git a/roofit/hs3/src/JSONInterface.cxx b/roofit/hs3/src/JSONInterface.cxx index f693c08a973dc..27baf885dfce7 100644 --- a/roofit/hs3/src/JSONInterface.cxx +++ b/roofit/hs3/src/JSONInterface.cxx @@ -1,9 +1,39 @@ -#include "JSONInterface.h" +#include -using RooFit::Detail::JSONNode; +namespace RooFit { +namespace Experimental { std::ostream &operator<<(std::ostream &os, JSONNode const &s) { s.writeJSON(os); return os; } + +template <> +int JSONNode::val_t() const +{ + return val_int(); +} +template <> +float JSONNode::val_t() const +{ + return val_float(); +} +template <> +double JSONNode::val_t() const +{ + return val_float(); +} +template <> +bool JSONNode::val_t() const +{ + return val_bool(); +} +template <> +std::string JSONNode::val_t() const +{ + return val(); +} + +} +} diff --git a/roofit/hs3/src/JSONParser.cxx b/roofit/hs3/src/JSONParser.cxx index 36a40b57e5a06..59793a394657a 100644 --- a/roofit/hs3/src/JSONParser.cxx +++ b/roofit/hs3/src/JSONParser.cxx @@ -225,8 +225,8 @@ std::string TJSONTree::Node::val() const case nlohmann::json::value_t::number_unsigned: return ::itoa(node->get().get()); case nlohmann::json::value_t::number_float: return ::ftoa(node->get().get()); default: - throw std::runtime_error(std::string("implicit string conversion for type ") + node->get().type_name() + - std::string(" not supported!")); + throw std::runtime_error(std::string("node " + node->key() + ": implicit string conversion for type " + + node->get().type_name() + " not supported!")); } } diff --git a/roofit/hs3/src/JSONParser.h b/roofit/hs3/src/JSONParser.h index 276af3d74e048..7881e112d7197 100644 --- a/roofit/hs3/src/JSONParser.h +++ b/roofit/hs3/src/JSONParser.h @@ -1,6 +1,7 @@ #ifndef JSON_PARSER_H #define JSON_PARSER_H -#include "JSONInterface.h" + +#include #include #include @@ -8,7 +9,7 @@ class TJSONTree : public JSONTree { public: - class Node : public RooFit::Detail::JSONNode { + class Node : public RooFit::Experimental::JSONNode { protected: TJSONTree *tree; class Impl; diff --git a/roofit/hs3/src/RYMLParser.h b/roofit/hs3/src/RYMLParser.h index 301b60e305024..e5dd762268c5a 100644 --- a/roofit/hs3/src/RYMLParser.h +++ b/roofit/hs3/src/RYMLParser.h @@ -14,7 +14,7 @@ class TRYMLTree : public JSONTree { std::unique_ptr tree; public: - class Node : public RooFit::Detail::JSONNode { + class Node : public RooFit::Experimental::JSONNode { protected: TRYMLTree *tree; class Impl; diff --git a/roofit/hs3/src/RooJSONFactoryWSTool.cxx b/roofit/hs3/src/RooJSONFactoryWSTool.cxx index 2c7fbf338b5ce..5b013ee693208 100644 --- a/roofit/hs3/src/RooJSONFactoryWSTool.cxx +++ b/roofit/hs3/src/RooJSONFactoryWSTool.cxx @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -28,7 +29,114 @@ typedef TRYMLTree tree_t; typedef TJSONTree tree_t; #endif -using RooFit::Detail::JSONNode; +using RooFit::Experimental::JSONNode; + +namespace { +bool isNumber(const std::string &str) +{ + bool first = true; + for (char const &c : str) { + if (std::isdigit(c) == 0 && c != '.' && !(first && (c == '-' || c == '+'))) + return false; + first = false; + } + return true; +} +} // namespace + +RooFit::Experimental::JSONNode &RooJSONFactoryWSTool::orootnode() +{ + if (_rootnode_output) + return *_rootnode_output; + throw MissingRootnodeError(); +} +const RooFit::Experimental::JSONNode &RooJSONFactoryWSTool::irootnode() const +{ + if (_rootnode_input) + return *_rootnode_input; + throw MissingRootnodeError(); +} + +template <> +RooRealVar *RooJSONFactoryWSTool::request(const std::string &objname, const std::string &requestAuthor) +{ + RooRealVar *retval = this->workspace()->var(objname.c_str()); + if (retval) + return retval; + if (irootnode().has_child("variables")) { + const JSONNode &vars = irootnode()["variables"]; + if (vars.has_child(objname)) { + this->importVariable(vars[objname]); + retval = this->workspace()->var(objname.c_str()); + if (retval) + return retval; + } + } + throw DependencyMissingError(requestAuthor, objname, "RooRealVar"); +} + +template <> +RooAbsPdf *RooJSONFactoryWSTool::request(const std::string &objname, const std::string &requestAuthor) +{ + RooAbsPdf *retval = this->workspace()->pdf(objname.c_str()); + if (retval) + return retval; + if (irootnode().has_child("pdfs")) { + const JSONNode &pdfs = irootnode()["pdfs"]; + if (pdfs.has_child(objname)) { + this->importFunction(pdfs[objname], true); + retval = this->workspace()->pdf(objname.c_str()); + if (retval) + return retval; + } + } + throw DependencyMissingError(requestAuthor, objname, "RooAbsPdf"); +} + +template <> +RooAbsReal *RooJSONFactoryWSTool::request(const std::string &objname, const std::string &requestAuthor) +{ + RooAbsReal *retval = NULL; + retval = this->workspace()->pdf(objname.c_str()); + if (retval) + return retval; + retval = this->workspace()->function(objname.c_str()); + if (retval) + return retval; + retval = this->workspace()->var(objname.c_str()); + if (retval) + return retval; + if (isNumber(objname)) + return dynamic_cast(this->workspace()->factory(objname.c_str())); + if (irootnode().has_child("pdfs")) { + const JSONNode &pdfs = irootnode()["pdfs"]; + if (pdfs.has_child(objname)) { + this->importFunction(pdfs[objname], true); + retval = this->workspace()->pdf(objname.c_str()); + if (retval) + return retval; + } + } + if (irootnode().has_child("variables")) { + const JSONNode &vars = irootnode()["variables"]; + if (vars.has_child(objname)) { + this->importVariable(vars[objname]); + retval = this->workspace()->var(objname.c_str()); + if (retval) + return retval; + } + } + if (irootnode().has_child("functions")) { + const JSONNode &funcs = irootnode()["functions"]; + if (funcs.has_child(objname)) { + this->importFunction(funcs[objname], false); + retval = this->workspace()->function(objname.c_str()); + if (retval) + return retval; + } + } + throw DependencyMissingError(requestAuthor, objname, "RooAbsReal"); +} namespace { @@ -43,14 +151,17 @@ RooJSONFactoryWSTool::Var::Var(const JSONNode &val) { if (val.is_map()) { if (!val.has_child("nbins")) - RooJSONFactoryWSTool::error("no nbins given"); + this->nbins = 1; + else + this->nbins = val["nbins"].val_int(); if (!val.has_child("min")) - RooJSONFactoryWSTool::error("no min given"); + this->min = 0; + else + this->min = val["min"].val_float(); if (!val.has_child("max")) - RooJSONFactoryWSTool::error("no max given"); - this->nbins = val["nbins"].val_int(); - this->min = val["min"].val_float(); - this->max = val["max"].val_float(); + this->max = 1; + else + this->max = val["max"].val_float(); } else if (val.is_seq()) { for (size_t i = 0; i < val.num_children(); ++i) { this->bounds.push_back(val[i].val_float()); @@ -80,20 +191,16 @@ std::string RooJSONFactoryWSTool::genPrefix(const JSONNode &p, bool trailing_und namespace { // helpers for serializing / deserializing binned datasets -inline void genIndicesHelper(std::vector> &combinations, const RooArgList &vars, size_t curridx) +inline void genIndicesHelper(std::vector> &combinations, std::vector &curr_comb, + const std::vector &vars_numbins, size_t curridx) { - if (curridx == vars.size()) { - std::vector indices(curridx); - for (size_t i = 0; i < curridx; ++i) { - RooRealVar *v = (RooRealVar *)(vars.at(i)); - indices[i] = v->getBinning().binNumber(v->getVal()); - } - combinations.push_back(indices); + if (curridx == vars_numbins.size()) { + // we have filled a combination. Copy it. + combinations.push_back(std::vector(curr_comb)); } else { - RooRealVar *v = (RooRealVar *)(vars.at(curridx)); - for (int i = 0; i < v->numBins(); ++i) { - v->setVal(v->getBinning().binCenter(i)); - ::genIndicesHelper(combinations, vars, curridx + 1); + for (int i = 0; i < vars_numbins[curridx]; ++i) { + curr_comb[curridx] = i; + ::genIndicesHelper(combinations, curr_comb, vars_numbins, curridx + 1); } } } @@ -109,35 +216,95 @@ std::string containerName(RooAbsArg *elem) } } // namespace +bool RooJSONFactoryWSTool::Config::stripObservables = true; + // maps to hold the importers and exporters for runtime lookup RooJSONFactoryWSTool::ImportMap RooJSONFactoryWSTool::_importers = RooJSONFactoryWSTool::ImportMap(); RooJSONFactoryWSTool::ExportMap RooJSONFactoryWSTool::_exporters = RooJSONFactoryWSTool::ExportMap(); - -bool RooJSONFactoryWSTool::registerImporter(const std::string &key, const RooJSONFactoryWSTool::Importer *f) +RooJSONFactoryWSTool::ExportKeysMap RooJSONFactoryWSTool::_exportKeys = RooJSONFactoryWSTool::ExportKeysMap(); +RooJSONFactoryWSTool::ImportExpressionMap RooJSONFactoryWSTool::_pdfFactoryExpressions = + RooJSONFactoryWSTool::ImportExpressionMap(); +RooJSONFactoryWSTool::ImportExpressionMap RooJSONFactoryWSTool::_funcFactoryExpressions = + RooJSONFactoryWSTool::ImportExpressionMap(); + +bool RooJSONFactoryWSTool::registerImporter(const std::string &key, const RooJSONFactoryWSTool::Importer *f, + bool topPriority) { - if (RooJSONFactoryWSTool::_importers.find(key) != RooJSONFactoryWSTool::_importers.end()) - return false; - RooJSONFactoryWSTool::_importers.insert(std::make_pair(key, f)); + auto it = RooJSONFactoryWSTool::_importers.find(key); + if (it == RooJSONFactoryWSTool::_importers.end()) + RooJSONFactoryWSTool::_importers.insert( + std::make_pair(key, std::vector())); + it = RooJSONFactoryWSTool::_importers.find(key); + if (topPriority) { + it->second.insert(it->second.begin(), f); + } else { + it->second.push_back(f); + } return true; } -bool RooJSONFactoryWSTool::registerExporter(const TClass *key, const RooJSONFactoryWSTool::Exporter *f) + +bool RooJSONFactoryWSTool::registerExporter(const TClass *key, const RooJSONFactoryWSTool::Exporter *f, + bool topPriority) { - if (RooJSONFactoryWSTool::_exporters.find(key) != RooJSONFactoryWSTool::_exporters.end()) - return false; - RooJSONFactoryWSTool::_exporters.insert(std::make_pair(key, f)); + auto it = RooJSONFactoryWSTool::_exporters.find(key); + if (it == RooJSONFactoryWSTool::_exporters.end()) + RooJSONFactoryWSTool::_exporters.insert( + std::make_pair(key, std::vector())); + it = RooJSONFactoryWSTool::_exporters.find(key); + if (topPriority) { + it->second.insert(it->second.begin(), f); + } else { + it->second.push_back(f); + } return true; } +int RooJSONFactoryWSTool::removeImporters(const std::string &needle) +{ + int n = 0; + for (auto &element : RooJSONFactoryWSTool::_importers) { + for (size_t i = element.second.size(); i > 0; --i) { + auto imp = element.second[i - 1]; + std::string name(typeid(*imp).name()); + if (name.find(needle) != std::string::npos) { + element.second.erase(element.second.begin() + i - 1); + ++n; + } + } + } + return n; +} + +int RooJSONFactoryWSTool::removeExporters(const std::string &needle) +{ + int n = 0; + for (auto &element : RooJSONFactoryWSTool::_exporters) { + for (size_t i = element.second.size(); i > 0; --i) { + auto imp = element.second[i - 1]; + std::string name(typeid(*imp).name()); + if (name.find(needle) != std::string::npos) { + element.second.erase(element.second.begin() + i - 1); + ++n; + } + } + } + return n; +} + void RooJSONFactoryWSTool::printImporters() { for (const auto &x : RooJSONFactoryWSTool::_importers) { - std::cout << x.first << "\t" << typeid(*x.second).name() << std::endl; + for (const auto &e : x.second) { + std::cout << x.first << "\t" << typeid(*e).name() << std::endl; + } } } void RooJSONFactoryWSTool::printExporters() { for (const auto &x : RooJSONFactoryWSTool::_exporters) { - std::cout << x.first << "\t" << typeid(*x.second).name() << std::endl; + for (const auto &e : x.second) { + std::cout << x.first->GetName() << "\t" << typeid(*e).name() << std::endl; + } } } @@ -194,59 +361,57 @@ inline void writeAxis(JSONNode &bounds, const TAxis &ax) /////////////////////////////////////////////////////////////////////////////////////////////////////// namespace { -struct JSON_Factory_Expression { - TClass *tclass; - std::vector arguments; - std::string generate(const JSONNode &p) - { - std::string name(RooJSONFactoryWSTool::name(p)); - std::stringstream expression; - std::string classname(this->tclass->GetName()); - size_t colon = classname.find_last_of(":"); - if (colon < classname.size()) { - expression << classname.substr(colon + 1); - } else { - expression << classname; +std::string generate(const RooJSONFactoryWSTool::ImportExpression &ex, const JSONNode &p, RooJSONFactoryWSTool *tool) +{ + std::string name(RooJSONFactoryWSTool::name(p)); + std::stringstream expression; + std::string classname(ex.tclass->GetName()); + size_t colon = classname.find_last_of(":"); + if (colon < classname.size()) { + expression << classname.substr(colon + 1); + } else { + expression << classname; + } + expression << "::" << name << "("; + bool first = true; + for (auto k : ex.arguments) { + if (!first) + expression << ","; + first = false; + if (k == "true") { + expression << "1"; + continue; + } else if (k == "false") { + expression << "0"; + continue; + } else if (!p.has_child(k)) { + std::stringstream err; + err << "factory expression for class '" << ex.tclass->GetName() << "', which expects key '" << k + << "' missing from input for object '" << name << "', skipping."; + RooJSONFactoryWSTool::error(err.str().c_str()); } - expression << "::" << name << "("; - bool first = true; - for (auto k : this->arguments) { - if (!first) - expression << ","; - first = false; - if (k == "true") { - expression << "1"; - continue; - } else if (k == "false") { - expression << "0"; - continue; - } else if (!p.has_child(k)) { - std::stringstream err; - err << "factory expression for class '" << this->tclass->GetName() << "', which expects key '" << k - << "' missing from input for object '" << name << "', skipping."; - RooJSONFactoryWSTool::error(err.str().c_str()); - } - if (p[k].is_seq()) { - expression << "{"; - bool f = true; - for (const auto &x : p[k].children()) { - if (!f) - expression << ","; - f = false; - expression << x.val(); - } - expression << "}"; - } else { - expression << p[k].val(); + if (p[k].is_seq()) { + expression << "{"; + bool f = true; + for (const auto &x : p[k].children()) { + if (!f) + expression << ","; + f = false; + std::string obj(x.val()); + tool->request(obj, name); + expression << obj; } + expression << "}"; + } else { + std::string obj(p[k].val()); + tool->request(obj, name); + expression << obj; } - expression << ")"; - return expression.str(); } -}; -std::map _pdfFactoryExpressions; -std::map _funcFactoryExpressions; -} // namespace + expression << ")"; + return expression.str(); +} +}; // namespace void RooJSONFactoryWSTool::loadFactoryExpressions(const std::string &fname) { @@ -271,7 +436,7 @@ void RooJSONFactoryWSTool::loadFactoryExpressions(const std::string &fname) if (!c) { std::cerr << "unable to find class " << classname << ", skipping." << std::endl; } else { - JSON_Factory_Expression ex; + ImportExpression ex; ex.tclass = c; if (!cl.has_child("arguments")) { std::cerr << "class " << classname << " seems to have no arguments attached, skipping" << std::endl; @@ -325,7 +490,13 @@ void RooJSONFactoryWSTool::printFactoryExpressions() std::vector> RooJSONFactoryWSTool::generateBinIndices(const RooArgList &vars) { std::vector> combinations; - ::genIndicesHelper(combinations, vars, 0); + std::vector vars_numbins; + vars_numbins.reserve(vars.size()); + for (auto &absv : vars) { + vars_numbins.push_back(((RooRealVar *)absv)->numBins()); + } + std::vector curr_comb(vars.size()); + ::genIndicesHelper(combinations, curr_comb, vars_numbins, 0); return combinations; } @@ -389,13 +560,6 @@ void RooJSONFactoryWSTool::exportHistogram(const TH1 &h, JSONNode &n, const std: // RooProxy-based export handling /////////////////////////////////////////////////////////////////////////////////////////////////////// -namespace { -struct JSON_Export_Keys { - std::string type; - std::map proxies; -}; -std::map _exportKeys; -} // namespace void RooJSONFactoryWSTool::loadExportKeys(const std::string &fname) { // load a yml file defining the export keys @@ -413,7 +577,7 @@ void RooJSONFactoryWSTool::loadExportKeys(const std::string &fname) if (!c) { std::cerr << "unable to find class " << classname << ", skipping." << std::endl; } else { - JSON_Export_Keys ex; + ExportKeys ex; if (!cl.has_child("type")) { std::cerr << "class " << classname << "has not type key set, skipping" << std::endl; continue; @@ -563,21 +727,30 @@ void RooJSONFactoryWSTool::exportObject(const RooAbsArg *func, JSONNode &n) TClass *cl = TClass::GetClass(func->ClassName()); auto it = _exporters.find(cl); + bool ok = false; if (it != _exporters.end()) { // check if we have a specific exporter available - if (it->second->autoExportDependants()) - RooJSONFactoryWSTool::exportDependants(func, n); - auto &elem = n[func->GetName()]; - elem.set_map(); - try { - if (!it->second->exportObject(this, func, elem)) { - std::cerr << "exporter for type " << cl->GetName() << " does not export objects!" << std::endl; + for (auto &exp : it->second) { + try { + auto &elem = n[func->GetName()]; + elem.set_map(); + if (!exp->exportObject(this, func, elem)) { + continue; + } + if (exp->autoExportDependants()) { + RooJSONFactoryWSTool::exportDependants(func, &orootnode()); + } + RooJSONFactoryWSTool::exportAttributes(func, elem); + ok = true; + } catch (const std::exception &ex) { + std::cerr << "error exporting " << func->Class()->GetName() << " " << func->GetName() << ": " << ex.what() + << ". skipping." << std::endl; + return; } - RooJSONFactoryWSTool::exportAttributes(func, elem); - } catch (const std::exception &ex) { - std::cerr << ex.what() << ". skipping." << std::endl; - return; + if (ok) + break; } - } else { // generic export using the factory expressions + } + if (!ok) { // generic export using the factory expressions const auto &dict = _exportKeys.find(cl); if (dict == _exportKeys.end()) { std::cerr << "unable to export class '" << cl->GetName() << "' - no export keys available!" << std::endl; @@ -601,6 +774,8 @@ void RooJSONFactoryWSTool::exportObject(const RooAbsArg *func, JSONNode &n) return; } + RooJSONFactoryWSTool::exportDependants(func, &orootnode()); + auto &elem = n[func->GetName()]; elem.set_map(); elem["type"] << dict->second.type; @@ -657,44 +832,67 @@ void RooJSONFactoryWSTool::importFunctions(const JSONNode &n) if (!n.is_map()) return; for (const auto &p : n.children()) { - // some preparations: what type of function are we dealing with here? - std::string name(RooJSONFactoryWSTool::name(p)); - if (name.empty()) - continue; + importFunction(p, false); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// importing functions +void RooJSONFactoryWSTool::importFunction(const JSONNode &p, bool isPdf) +{ + // some preparations: what type of function are we dealing with here? + std::string name(RooJSONFactoryWSTool::name(p)); + // if it's an empty name, it's a lost cause, let's just skip it + if (name.empty()) + return; + // if the function already exists, we don't need to do anything + if (isPdf) { if (this->_workspace->pdf(name.c_str())) - continue; - if (!p.is_map()) - continue; - std::string prefix = RooJSONFactoryWSTool::genPrefix(p, true); - if (prefix.size() > 0) - name = prefix + name; - if (!p.has_child("type")) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() no type given for '" << name << "', skipping." << std::endl; - logInputArgumentsError(std::move(ss)); - continue; - } - std::string functype(p["type"].val()); - this->importDependants(p); + return; + } else { + if (this->_workspace->function(name.c_str())) + return; + } + // if the key we found is not a map, it's an error + if (!p.is_map()) { + std::stringstream ss; + ss << "RooJSONFactoryWSTool() function node " + name + " is not a map!"; + logInputArgumentsError(std::move(ss)); + return; + } + std::string prefix = RooJSONFactoryWSTool::genPrefix(p, true); + if (prefix.size() > 0) + name = prefix + name; + if (!p.has_child("type")) { + std::stringstream ss; + ss << "RooJSONFactoryWSTool() no type given for function '" << name << "', skipping." << std::endl; + logInputArgumentsError(std::move(ss)); + return; + } + + bool toplevel = false; + if (isPdf && p.has_child("tags")) { + toplevel = RooJSONFactoryWSTool::find(p["tags"], "toplevel"); + } + + std::string functype(p["type"].val()); + this->importDependants(p); + + try { // check for specific implementations auto it = _importers.find(functype); + bool ok = false; if (it != _importers.end()) { - try { - if (!it->second->importFunction(this, p)) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() importer for type " << functype << " does not import functions!" - << std::endl; - logInputArgumentsError(std::move(ss)); - } - } catch (const std::exception &ex) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() " << ex.what() << ". skipping." << std::endl; - logInputArgumentsError(std::move(ss)); + for (auto &imp : it->second) { + ok = isPdf ? imp->importPdf(this, p) : imp->importFunction(this, p); + if (ok) + break; } - } else { // generic import using the factory expressions - auto expr = _funcFactoryExpressions.find(functype); - if (expr != _funcFactoryExpressions.end()) { - std::string expression = expr->second.generate(p); + } + if (!ok) { // generic import using the factory expressions + auto expr = isPdf ? _pdfFactoryExpressions.find(functype) : _funcFactoryExpressions.find(functype); + if (expr != (isPdf ? _pdfFactoryExpressions.end() : _funcFactoryExpressions.end())) { + std::string expression = ::generate(expr->second, p, this); if (!this->_workspace->factory(expression.c_str())) { std::stringstream ss; ss << "RooJSONFactoryWSTool() failed to create " << expr->second.tclass->GetName() << " '" << name @@ -720,17 +918,29 @@ void RooJSONFactoryWSTool::importFunctions(const JSONNode &n) "how to do this!" << std::endl; logInputArgumentsError(std::move(ss)); - continue; + return; } } RooAbsReal *func = this->_workspace->function(name.c_str()); if (!func) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() something went wrong importing function '" << name << "'." << std::endl; - logInputArgumentsError(std::move(ss)); + std::stringstream err; + err << "something went wrong importing function '" << name << "'."; + RooJSONFactoryWSTool::error(err.str().c_str()); } else { ::importAttributes(func, p); + + if (isPdf && toplevel) { + configureToplevelPdf(p, *static_cast(func)); + } } + } catch (const RooJSONFactoryWSTool::DependencyMissingError &ex) { + throw; + } catch (const RooJSONFactoryWSTool::MissingRootnodeError &ex) { + throw; + } catch (const std::exception &ex) { + std::stringstream ss; + ss << "RooJSONFactoryWSTool(): error importing " << name << ": " << ex.what() << ". skipping." << std::endl; + logInputArgumentsError(std::move(ss)); } } @@ -741,9 +951,9 @@ RooDataSet *RooJSONFactoryWSTool::unbinned(RooDataHist *hist) { RooArgSet obs(*hist->get()); RooRealVar *weight = this->getWeightVar("weight"); - obs.add(*weight); + obs.add(*weight, true); RooDataSet *data = new RooDataSet(hist->GetName(), hist->GetTitle(), obs, RooFit::WeightVar(*weight)); - for (Int_t i = 0; i < hist->numEntries(); ++i) { + for (int i = 0; i < hist->numEntries(); ++i) { data->add(*hist->get(i), hist->weight(i)); } return data; @@ -787,7 +997,7 @@ std::map RooJSONFactoryWSTool::loadData(const JSONNod auto vars = this->getObservables(p, name); RooArgList varlist(vars); RooRealVar *weightVar = this->getWeightVar("weight"); - vars.add(*weightVar); + vars.add(*weightVar, true); RooDataSet *data = new RooDataSet(name, name, vars, RooFit::WeightVar(*weightVar)); auto &coords = p["coordinates"]; auto &weights = p["weights"]; @@ -823,10 +1033,10 @@ std::map RooJSONFactoryWSTool::loadData(const JSONNod logInputArgumentsError(std::move(ss)); } else { RooArgSet allVars; - allVars.add(*channelCat); + allVars.add(*channelCat, true); std::map datasets; for (const auto &subd : subMap) { - allVars.add(*subd.second->get()); + allVars.add(*subd.second->get(), true); if (subd.second->InheritsFrom(RooDataHist::Class())) { datasets[subd.first] = unbinned(((RooDataHist *)subd.second)); } else { @@ -834,14 +1044,14 @@ std::map RooJSONFactoryWSTool::loadData(const JSONNod } } RooRealVar *weightVar = this->getWeightVar("weight"); - allVars.add(*weightVar); + allVars.add(*weightVar, true); RooDataSet *data = new RooDataSet(name.c_str(), name.c_str(), allVars, RooFit::Index(*channelCat), RooFit::Import(datasets), RooFit::WeightVar(*weightVar)); dataMap[name] = data; } } else { std::stringstream ss; - ss << "RooJSONFactoryWSTool() failed to create dataset" << name << std::endl; + ss << "RooJSONFactoryWSTool() failed to create dataset " << name << std::endl; logInputArgumentsError(std::move(ss)); } } @@ -890,36 +1100,55 @@ void RooJSONFactoryWSTool::exportData(RooAbsData *data, JSONNode &n) exportVariables(observables, obs); auto &weights = output["counts"]; weights.set_seq(); - for (Int_t i = 0; i < dh->numEntries(); ++i) { + for (int i = 0; i < dh->numEntries(); ++i) { dh->get(i); weights.append_child() << dh->weight(); } } else { // this is a regular unbinned dataset RooDataSet *ds = (RooDataSet *)(data); + + bool singlePoint = (ds->numEntries() <= 1); RooArgSet reduced_obs; - for (Int_t i = 0; i < ds->numEntries(); ++i) { - ds->get(i); - for (const auto &obs : observables) { - RooRealVar *rv = (RooRealVar *)(obs); - if (rv->getVal() != 0) - reduced_obs.add(*rv); + if (Config::stripObservables) { + if (!singlePoint) { + std::map> obs_values; + for (int i = 0; i < ds->numEntries(); ++i) { + ds->get(i); + for (const auto &obs : observables) { + RooRealVar *rv = (RooRealVar *)(obs); + obs_values[rv].push_back(rv->getVal()); + } + } + for (auto &obs_it : obs_values) { + auto &vals = obs_it.second; + double v0 = vals[0]; + bool is_const_val = std::all_of(vals.begin(), vals.end(), [v0](double v) { return v == v0; }); + if (!is_const_val) + reduced_obs.add(*(obs_it.first), true); + } } + } else { + reduced_obs.add(observables); + } + if (reduced_obs.size() > 0) { + auto &obsset = output["observables"]; + obsset.set_map(); + exportVariables(reduced_obs, obsset); } - auto &obsset = output["observables"]; - obsset.set_map(); - exportVariables(reduced_obs, obsset); - auto &coordinates = output["coordinates"]; - coordinates.set_seq(); - auto &weights = output["weights"]; + auto &weights = singlePoint && Config::stripObservables ? output["counts"] : output["weights"]; weights.set_seq(); - for (Int_t i = 0; i < ds->numEntries(); ++i) { - auto &point = coordinates.append_child(); + for (int i = 0; i < ds->numEntries(); ++i) { ds->get(i); - point.set_seq(); - for (const auto &obs : reduced_obs) { - RooRealVar *rv = (RooRealVar *)(obs); - point.append_child() << rv->getVal(); + if (!(Config::stripObservables && singlePoint)) { + auto &coordinates = output["coordinates"]; + coordinates.set_seq(); + auto &point = coordinates.append_child(); + point.set_seq(); + for (const auto &obs : reduced_obs) { + RooRealVar *rv = (RooRealVar *)(obs); + point.append_child() << rv->getVal(); + } } weights.append_child() << ds->weight(); } @@ -948,7 +1177,7 @@ RooArgSet RooJSONFactoryWSTool::getObservables(const JSONNode &n, const std::str void RooJSONFactoryWSTool::setScopeObservables(const RooArgList &args) { - this->_scope.observables.add(args); + this->_scope.observables.add(args, true); } void RooJSONFactoryWSTool::setScopeObject(const std::string &name, RooAbsArg *obj) { @@ -960,7 +1189,8 @@ RooAbsArg *RooJSONFactoryWSTool::getScopeObject(const std::string &name) } void RooJSONFactoryWSTool::clearScope() { - this->_scope = Scope(); + this->_scope.objects.clear(); + this->_scope.observables.clear(); } /////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -979,11 +1209,14 @@ RooRealVar *RooJSONFactoryWSTool::createObservable(const std::string &name, cons /////////////////////////////////////////////////////////////////////////////////////////////////////// // reading binned data -RooDataHist * -RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std::string &namecomp, const RooArgList &varlist) +RooDataHist *RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std::string &namecomp, RooArgList varlist) { if (!n.is_map()) - throw "data is not a map"; + RooJSONFactoryWSTool::error("data is not a map"); + if (varlist.size() == 0) { + std::string obsname = "obs_x_" + namecomp; + varlist.add(*(this->_workspace->factory((obsname + "[0.]").c_str()))); + } auto bins = RooJSONFactoryWSTool::generateBinIndices(varlist); if (!n.has_child("counts")) RooJSONFactoryWSTool::error("no counts given"); @@ -994,13 +1227,25 @@ RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std::string &namec RooJSONFactoryWSTool::error(TString::Format("inconsistent bin numbers: counts=%d, bins=%d", (int)counts.num_children(), (int)(bins.size()))); RooDataHist *dh = new RooDataHist(("dataHist_" + namecomp).c_str(), namecomp.c_str(), varlist); + // temporarily disable dirty flag propagation when filling the RDH + std::vector initVals; + for (auto &v : varlist) { + v->setDirtyInhibit(true); + initVals.push_back(((RooRealVar *)v)->getVal()); + } for (size_t ibin = 0; ibin < bins.size(); ++ibin) { for (size_t i = 0; i < bins[ibin].size(); ++i) { RooRealVar *v = (RooRealVar *)(varlist.at(i)); - v->setVal(v->getBinning().binCenter(bins[ibin][i])); + v->setBin(bins[ibin][i]); } dh->add(varlist, counts[ibin].val_float()); } + // re-enable dirty flag propagation + for (size_t i = 0; i < varlist.size(); ++i) { + RooRealVar *v = (RooRealVar *)(varlist.at(i)); + v->setVal(initVals[i]); + v->setDirtyInhibit(false); + } return dh; } @@ -1037,127 +1282,58 @@ void RooJSONFactoryWSTool::importPdfs(const JSONNode &n) if (!n.is_map()) return; for (const auto &p : n.children()) { - // general preparations: what type of pdf should we build? - std::string name(RooJSONFactoryWSTool::name(p)); - if (name.empty()) - continue; - if (this->_workspace->pdf(name.c_str())) - continue; - if (!p.is_map()) - continue; - std::string prefix = RooJSONFactoryWSTool::genPrefix(p, true); - if (prefix.size() > 0) - name = prefix + name; - if (!p.has_child("type")) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() no type given for '" << name << "', skipping." << std::endl; - logInputArgumentsError(std::move(ss)); - continue; - } - bool toplevel = false; - if (p.has_child("tags")) { - toplevel = RooJSONFactoryWSTool::find(p["tags"], "toplevel"); - } - std::string pdftype(p["type"].val()); - this->importDependants(p); + this->importFunction(p, true); + } +} - // check for specific implementations - auto it = _importers.find(pdftype); - if (it != _importers.end()) { - try { - if (!it->second->importPdf(this, p)) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() importer for type " << pdftype << " does not import pdfs!" << std::endl; - logInputArgumentsError(std::move(ss)); - } - } catch (const std::exception &ex) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() " << ex.what() << ". skipping." << std::endl; - logInputArgumentsError(std::move(ss)); - } - } else { // default implementation using the factory expressions - auto expr = _pdfFactoryExpressions.find(pdftype); - if (expr != _pdfFactoryExpressions.end()) { - std::string expression = expr->second.generate(p); - if (!this->_workspace->factory(expression.c_str())) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() failed to create " << expr->second.tclass->GetName() << " '" << name - << "', skipping. expression was\n" - << expression << std::endl; - logInputArgumentsError(std::move(ss)); - } - } else { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() no handling for pdftype '" << pdftype << "' implemented, skipping." - << "\n" - << "there are several possible reasons for this:\n" - << " 1. " << pdftype << " is a custom type that is not available in RooFit.\n" - << " 2. " << pdftype - << " is a ROOT class that nobody ever bothered to write a deserialization definition for.\n" - << " 3. something is wrong with your setup, e.g. you might have called " - "RooJSONFactoryWSTool::clearFactoryExpressions() and/or never successfully read a file defining " - "these expressions with RooJSONFactoryWSTool::loadFactoryExpressions(filename)\n" - << "either way, please make sure that:\n" - << " 3: you are reading a file with export keys - call RooJSONFactoryWSTool::printFactoryExpressions() " - "to see what is available\n" - << " 2 & 1: you might need to write a serialization definition yourself. check INSERTLINKHERE to see " - "how to do this!" - << std::endl; - logInputArgumentsError(std::move(ss)); +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// configure a pdf as "toplevel" by creating a modelconfig for it +void RooJSONFactoryWSTool::configureToplevelPdf(const JSONNode &p, RooAbsPdf &pdf) +{ + // if this is a toplevel pdf, also create a modelConfig for it + std::string mcname = "ModelConfig"; + if (p.has_child("dict")) { + if (p["dict"].has_child("ModelConfig")) { + mcname = p["dict"]["ModelConfig"].val(); + } + } + RooStats::ModelConfig *mc = new RooStats::ModelConfig(mcname.c_str(), pdf.GetName()); + this->_workspace->import(*mc); + RooStats::ModelConfig *inwsmc = dynamic_cast(this->_workspace->obj(mcname.c_str())); + if (inwsmc) { + inwsmc->SetWS(*(this->_workspace)); + inwsmc->SetPdf(pdf); + RooArgSet observables; + RooArgSet nps; + RooArgSet pois; + RooArgSet globs; + RooArgSet *pdfVars = pdf.getVariables(); + for (auto &var : this->_workspace->allVars()) { + if (!pdfVars->find(*var)) continue; + if (var->getAttribute("observable")) { + observables.add(*var, true); } - } - // post-processing: make sure that the pdf has been created, and attach needed attributes - RooAbsPdf *pdf = this->_workspace->pdf(name.c_str()); - if (!pdf) { - - std::stringstream ss; - ss << "RooJSONFactoryWSTool() something went wrong importing pdf '" << name << "'." << std::endl; - logInputArgumentsError(std::move(ss)); - } else { - ::importAttributes(pdf, p); - if (toplevel) { - // if this is a toplevel pdf, also cereate a modelConfig for it - std::string mcname = name + "_modelConfig"; - RooStats::ModelConfig *mc = new RooStats::ModelConfig(mcname.c_str(), name.c_str()); - this->_workspace->import(*mc); - RooStats::ModelConfig *inwsmc = - dynamic_cast(this->_workspace->obj(mcname.c_str())); - if (inwsmc) { - inwsmc->SetWS(*(this->_workspace)); - inwsmc->SetPdf(*pdf); - RooArgSet observables; - RooArgSet nps; - RooArgSet pois; - RooArgSet globs; - for (auto var : this->_workspace->allVars()) { - if (!pdf->dependsOn(*var)) - continue; - if (var->getAttribute("observable")) { - observables.add(*var); - } - if (var->getAttribute("np")) { - nps.add(*var); - } - if (var->getAttribute("poi")) { - pois.add(*var); - } - if (var->getAttribute("glob")) { - globs.add(*var); - } - } - inwsmc->SetObservables(observables); - inwsmc->SetParametersOfInterest(pois); - inwsmc->SetNuisanceParameters(nps); - inwsmc->SetGlobalObservables(globs); - } else { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() object '" << mcname - << "' in workspace is not of type RooStats::ModelConfig!" << std::endl; - logInputArgumentsError(std::move(ss)); - } + if (var->getAttribute("np")) { + nps.add(*var, true); + } + if (var->getAttribute("poi")) { + pois.add(*var, true); + } + if (var->getAttribute("glob")) { + globs.add(*var, true); } } + inwsmc->SetObservables(observables); + inwsmc->SetParametersOfInterest(pois); + inwsmc->SetNuisanceParameters(nps); + inwsmc->SetGlobalObservables(globs); + delete pdfVars; + } else { + std::stringstream ss; + ss << "RooJSONFactoryWSTool() object '" << mcname << "' in workspace is not of type RooStats::ModelConfig!" + << std::endl; + logInputArgumentsError(std::move(ss)); } } @@ -1169,38 +1345,64 @@ void RooJSONFactoryWSTool::importVariables(const JSONNode &n) if (!n.is_map()) return; for (const auto &p : n.children()) { - std::string name(RooJSONFactoryWSTool::name(p)); - if (this->_workspace->var(name.c_str())) - continue; - if (!p.is_map()) { - std::stringstream ss; - ss << "RooJSONFactoryWSTool() node '" << name << "' is not a map, skipping." << std::endl; - logInputArgumentsError(std::move(ss)); - continue; - } - double val(p.has_child("value") ? p["value"].val_float() : 1.); - RooRealVar v(name.c_str(), name.c_str(), val); - if (p.has_child("min")) - v.setMin(p["min"].val_float()); - if (p.has_child("max")) - v.setMax(p["max"].val_float()); - if (p.has_child("nbins")) - v.setBins(p["nbins"].val_int()); - if (p.has_child("relErr")) - v.setError(v.getVal() * p["relErr"].val_float()); - if (p.has_child("err")) - v.setError(p["err"].val_float()); - if (p.has_child("const")) - v.setConstant(p["const"].val_bool()); - else - v.setConstant(false); - ::importAttributes(&v, p); - this->_workspace->import(v); + importVariable(p); } } +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// importing variable +void RooJSONFactoryWSTool::importVariable(const JSONNode &p) +{ + // import a RooRealVar object + std::string name(RooJSONFactoryWSTool::name(p)); + if (this->_workspace->var(name.c_str())) + return; + if (!p.is_map()) { + std::stringstream ss; + ss << "RooJSONFactoryWSTool() node '" << name << "' is not a map, skipping." << std::endl; + logInputArgumentsError(std::move(ss)); + return; + } + RooRealVar v(name.c_str(), name.c_str(), 1.); + configureVariable(p, v); + ::importAttributes(&v, p); + this->_workspace->import(v, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// configuring variable +void RooJSONFactoryWSTool::configureVariable(const JSONNode &p, RooRealVar &v) +{ + if (p.has_child("value")) + v.setVal(p["value"].val_float()); + if (p.has_child("min")) + v.setMin(p["min"].val_float()); + if (p.has_child("max")) + v.setMax(p["max"].val_float()); + if (p.has_child("nbins")) + v.setBins(p["nbins"].val_int()); + if (p.has_child("relErr")) + v.setError(v.getVal() * p["relErr"].val_float()); + if (p.has_child("err")) + v.setError(p["err"].val_float()); + if (p.has_child("const")) + v.setConstant(p["const"].val_bool()); + else + v.setConstant(false); +} + /////////////////////////////////////////////////////////////////////////////////////////////////////// // export all dependants (servers) of a RooAbsArg +void RooJSONFactoryWSTool::exportDependants(const RooAbsArg *source, JSONNode *n) +{ + if (n) { + this->exportDependants(source, *n); + } else { + RooJSONFactoryWSTool::error( + "cannot export dependents without a valid root node, only call within the context of 'exportAllObjects'"); + } +} + void RooJSONFactoryWSTool::exportDependants(const RooAbsArg *source, JSONNode &n) { // export all the servers of a given RooAbsArg @@ -1241,14 +1443,15 @@ std::string RooJSONFactoryWSTool::name(const JSONNode &n) return ss.str(); } -void RooJSONFactoryWSTool::exportAll(JSONNode &n) +void RooJSONFactoryWSTool::exportAllObjects(JSONNode &n) { // export all ModelConfig objects and attached Pdfs - RooArgSet main; + std::vector mcs; + std::vector toplevel; + this->_rootnode_output = &n; for (auto obj : this->_workspace->allGenericObjects()) { if (obj->InheritsFrom(RooStats::ModelConfig::Class())) { RooStats::ModelConfig *mc = static_cast(obj); - RooAbsPdf *pdf = mc->GetPdf(); auto &vars = n["variables"]; vars.set_map(); if (mc->GetObservables()) { @@ -1284,16 +1487,18 @@ void RooJSONFactoryWSTool::exportAll(JSONNode &n) } } } - main.add(*pdf); + mcs.push_back(mc); } } - for (auto obj : this->_workspace->allPdfs()) { - RooAbsPdf *pdf = dynamic_cast(obj); - if (!pdf) - continue; - if ((pdf->getAttribute("toplevel") || pdf->clients().size() == 0) && !main.find(*pdf)) { - this->exportDependants(pdf, n); - main.add(*pdf); + for (auto pdf : this->_workspace->allPdfs()) { + if (!pdf->hasClients() || pdf->getAttribute("toplevel")) { + bool hasMC = false; + for (const auto &mc : mcs) { + if (mc->GetPdf() == pdf) + hasMC = true; + } + if (!hasMC) + toplevel.push_back(static_cast(pdf)); } } for (auto d : this->_workspace->allData()) { @@ -1301,31 +1506,58 @@ void RooJSONFactoryWSTool::exportAll(JSONNode &n) data.set_map(); this->exportData(d, data); } - if (main.size() > 0) { + for (const auto *snsh_obj : this->_workspace->getSnapshots()) { + const RooArgSet *snsh = static_cast(snsh_obj); + auto &snapshots = n["snapshots"]; + snapshots.set_map(); + auto &coll = snapshots[snsh->GetName()]; + coll.set_map(); + this->exportVariables(*snsh, coll); + } + for (const auto &mc : mcs) { auto &pdfs = n["pdfs"]; pdfs.set_map(); - RooJSONFactoryWSTool::RooJSONFactoryWSTool::exportFunctions(main, pdfs); - for (auto &pdf : main) { - auto &node = pdfs[pdf->GetName()]; - node.set_map(); - auto &tags = node["tags"]; + RooAbsPdf *pdf = mc->GetPdf(); + RooJSONFactoryWSTool::exportObject(pdf, pdfs); + auto &node = pdfs[pdf->GetName()]; + node.set_map(); + auto &tags = node["tags"]; + tags.set_seq(); + if (!pdf->getAttribute("toplevel")) RooJSONFactoryWSTool::append(tags, "toplevel"); + auto &dict = node["dict"]; + dict.set_map(); + dict["ModelConfig"] << mc->GetName(); + } + for (const auto &pdf : toplevel) { + auto &pdfs = n["pdfs"]; + pdfs.set_map(); + RooJSONFactoryWSTool::exportObject(pdf, pdfs); + auto &node = pdfs[pdf->GetName()]; + node.set_map(); + auto &tags = node["tags"]; + tags.set_seq(); + if (!pdf->getAttribute("toplevel")) + RooJSONFactoryWSTool::append(tags, "toplevel"); + auto &dict = node["dict"]; + dict.set_map(); + if (toplevel.size() + mcs.size() == 1) { + dict["ModelConfig"] << "ModelConfig"; + } else { + dict["ModelConfig"] << std::string(pdf->GetName()) + "_modelConfig"; } - } else { - std::cerr << "no ModelConfig found in workspace and no pdf identified as toplevel by 'toplevel' attribute or an " - "empty client list. nothing exported!" - << std::endl; } + this->_rootnode_output = nullptr; } -Bool_t RooJSONFactoryWSTool::importJSONfromString(const std::string &s) +bool RooJSONFactoryWSTool::importJSONfromString(const std::string &s) { // import the workspace from JSON std::stringstream ss(s); return importJSON(ss); } -Bool_t RooJSONFactoryWSTool::importYMLfromString(const std::string &s) +bool RooJSONFactoryWSTool::importYMLfromString(const std::string &s) { // import the workspace from YML std::stringstream ss(s); @@ -1348,17 +1580,17 @@ std::string RooJSONFactoryWSTool::exportYMLtoString() return ss.str(); } -Bool_t RooJSONFactoryWSTool::exportJSON(std::ostream &os) +bool RooJSONFactoryWSTool::exportJSON(std::ostream &os) { // export the workspace in JSON tree_t p; JSONNode &n = p.rootnode(); n.set_map(); - this->exportAll(n); + this->exportAllObjects(n); n.writeJSON(os); return true; } -Bool_t RooJSONFactoryWSTool::exportJSON(std::string const& filename) +bool RooJSONFactoryWSTool::exportJSON(std::string const &filename) { // export the workspace in JSON std::ofstream out(filename.c_str()); @@ -1371,17 +1603,17 @@ Bool_t RooJSONFactoryWSTool::exportJSON(std::string const& filename) return this->exportJSON(out); } -Bool_t RooJSONFactoryWSTool::exportYML(std::ostream &os) +bool RooJSONFactoryWSTool::exportYML(std::ostream &os) { // export the workspace in YML tree_t p; JSONNode &n = p.rootnode(); n.set_map(); - this->exportAll(n); + this->exportAllObjects(n); n.writeYML(os); return true; } -Bool_t RooJSONFactoryWSTool::exportYML(std::string const& filename) +bool RooJSONFactoryWSTool::exportYML(std::string const &filename) { // export the workspace in YML std::ofstream out(filename.c_str()); @@ -1394,32 +1626,59 @@ Bool_t RooJSONFactoryWSTool::exportYML(std::string const& filename) return this->exportYML(out); } -void RooJSONFactoryWSTool::prepare() -{ - gROOT->ProcessLine("using namespace RooStats::HistFactory;"); -} - -Bool_t RooJSONFactoryWSTool::importJSON(std::istream &is) +void RooJSONFactoryWSTool::importAllNodes(const RooFit::Experimental::JSONNode &n) { // import a JSON file to the workspace try { - tree_t p(is); - JSONNode &n = p.rootnode(); - this->prepare(); + this->_rootnode_input = &n; + gROOT->ProcessLine("using namespace RooStats::HistFactory;"); this->importDependants(n); + if (n.has_child("data")) { auto data = this->loadData(n["data"]); for (const auto &d : data) { this->_workspace->import(*d.second); } } + + this->_workspace->saveSnapshot("fromJSON", this->_workspace->allVars()); + if (n.has_child("snapshots")) { + for (const auto &snsh : n["snapshots"].children()) { + std::string name = RooJSONFactoryWSTool::name(snsh); + if (name == "fromJSON") + continue; + for (const auto &var : snsh.children()) { + std::string vname = RooJSONFactoryWSTool::name(var); + RooRealVar *rrv = this->_workspace->var(vname.c_str()); + if (!rrv) + continue; + this->configureVariable(var, *rrv); + } + } + } + this->_workspace->loadSnapshot("fromJSON"); + + } catch (const std::exception &ex) { + this->_rootnode_input = nullptr; + throw; + } + this->_rootnode_input = nullptr; +} + +bool RooJSONFactoryWSTool::importJSON(std::istream &is) +{ + // import a JSON file to the workspace + try { + tree_t p(is); + this->importAllNodes(p.rootnode()); } catch (const std::exception &ex) { std::cerr << "unable to import JSON: " << ex.what() << std::endl; return false; } return true; } -Bool_t RooJSONFactoryWSTool::importJSON(std::string const& filename) + +bool RooJSONFactoryWSTool::importJSON(std::string const &filename) { // import a JSON file to the workspace std::ifstream infile(filename.c_str()); @@ -1432,21 +1691,19 @@ Bool_t RooJSONFactoryWSTool::importJSON(std::string const& filename) return this->importJSON(infile); } -Bool_t RooJSONFactoryWSTool::importYML(std::istream &is) +bool RooJSONFactoryWSTool::importYML(std::istream &is) { // import a YML file to the workspace try { tree_t p(is); - JSONNode &n = p.rootnode(); - this->prepare(); - this->importDependants(n); + this->importAllNodes(p.rootnode()); } catch (const std::exception &ex) { std::cerr << "unable to import JSON: " << ex.what() << std::endl; return false; } return true; } -Bool_t RooJSONFactoryWSTool::importYML(std::string const& filename) +bool RooJSONFactoryWSTool::importYML(std::string const &filename) { // import a YML file to the workspace std::ifstream infile(filename.c_str()); @@ -1458,3 +1715,8 @@ Bool_t RooJSONFactoryWSTool::importYML(std::string const& filename) } return this->importYML(infile); } + +std::ostream &RooJSONFactoryWSTool::log(RooFit::MsgLevel level) const +{ + return RooMsgService::instance().log(static_cast(nullptr), level, RooFit::IO); +} diff --git a/roofit/hs3/test/testRooFitHS3.cxx b/roofit/hs3/test/testRooFitHS3.cxx index 1c913ab7daf26..ef715ce7f397a 100644 --- a/roofit/hs3/test/testRooFitHS3.cxx +++ b/roofit/hs3/test/testRooFitHS3.cxx @@ -1,21 +1,26 @@ #include "RooRealVar.h" #include "RooConstVar.h" -#include "RooArgusBG.h" -#include "RooGaussian.h" -#include "RooAddPdf.h" -#include "RooDataSet.h" -#include "RooPlot.h" #include "RooWorkspace.h" -#include "TSystem.h" +#include "TROOT.h" #include #include "gtest/gtest.h" -#ifndef __CINT__ #include "RooGlobalFunc.h" -#endif + +// includes for RooArgusBG +#include "RooArgusBG.h" +#include "RooGaussian.h" +#include "RooAddPdf.h" + +// includes for SimultaneousGaussians +#include "RooRealVar.h" +#include "RooSimultaneous.h" +#include "RooProdPdf.h" +#include "RooCategory.h" + using namespace RooFit; @@ -43,10 +48,58 @@ TEST(RooFitHS3, RooArgusBG) RooRealVar nbkg("nbkg", "#background events", 800, 0., 10000); RooAddPdf model("model", "g+a", RooArgList(signalModel, background), RooArgList(nsig, nbkg)); - std::string rootetcPath = gSystem->Getenv("ROOTSYS"); - RooJSONFactoryWSTool::loadExportKeys(rootetcPath + "/etc/root/RooFitHS3_wsexportkeys.json"); + auto etcDir = std::string(TROOT::GetEtcDir()); + RooJSONFactoryWSTool::loadExportKeys(etcDir + "/RooFitHS3_wsexportkeys.json"); + RooJSONFactoryWSTool::loadFactoryExpressions(etcDir + "/RooFitHS3_wsfactoryexpressions.json"); + RooWorkspace work; work.import(model); RooJSONFactoryWSTool tool(work); tool.exportJSON("argus.json"); }; + +TEST(RooFitHS3, SimultaneousGaussians) +{ + using namespace RooFit; + + // Import keys and factory expressions files for the RooJSONFactoryWSTool. + auto etcDir = std::string(TROOT::GetEtcDir()); + RooJSONFactoryWSTool::loadExportKeys(etcDir + "/RooFitHS3_wsexportkeys.json"); + RooJSONFactoryWSTool::loadFactoryExpressions(etcDir + "/RooFitHS3_wsfactoryexpressions.json"); + + // Create a test model: RooSimultaneous with Gaussian in one component, and + // product of two Gaussians in the other. + RooRealVar x("x", "x", -8, 8); + RooRealVar mean("mean", "mean", 0, -8, 8); + RooRealVar sigma("sigma", "sigma", 0.3, 0.1, 10); + RooGaussian g1("g1", "g1", x, mean, sigma); + RooGaussian g2("g2", "g2", x, mean, RooConst(0.3)); + RooProdPdf model("model", "model", RooArgList{g1, g2}); + RooGaussian model_ctl("model_ctl", "model_ctl", x, mean, sigma); + RooCategory sample("sample", "sample", {{"physics", 0}, {"control", 1}}); + RooSimultaneous simPdf("simPdf", "simultaneous pdf", sample); + simPdf.addPdf(model, "physics"); + simPdf.addPdf(model_ctl, "control"); + + // this is a handy way of triggering the creation of a ModelConfig upon re-import + simPdf.setAttribute("toplevel"); + + // Export to JSON + { + RooWorkspace ws{"workspace"}; + ws.import(simPdf); + RooJSONFactoryWSTool tool{ws}; + tool.exportJSON("simPdf.json"); + // Output can be pretty-printed with `python -m json.tool simPdf.json` + } + + // Import JSON + { + RooWorkspace ws{"workspace"}; + RooJSONFactoryWSTool tool{ws}; + tool.importJSON("simPdf.json"); + + ASSERT_TRUE(ws.pdf("g1")); + ASSERT_TRUE(ws.pdf("g2")); + } +} diff --git a/roofit/hs3/test/histfactory.py b/roofit/hs3/test/test_hs3_histfactory_json.py similarity index 86% rename from roofit/hs3/test/histfactory.py rename to roofit/hs3/test/test_hs3_histfactory_json.py index 03a3f7a34547a..4e55f261128d8 100644 --- a/roofit/hs3/test/histfactory.py +++ b/roofit/hs3/test/test_hs3_histfactory_json.py @@ -4,8 +4,9 @@ msg = ROOT.RooMsgService.instance() msg.setGlobalKillBelow(ROOT.RooFit.WARNING) + class TestHS3HistFactoryJSON(unittest.TestCase): - def measurement(self, inputFileName="histfactory-input.root"): + def measurement(self, inputFileName="test_hs3_histfactory_json_input.root"): ROOT.gROOT.SetBatch(True) meas = ROOT.RooStats.HistFactory.Measurement("meas", "meas") meas.SetOutputFilePrefix("./results/example_") @@ -64,7 +65,7 @@ def test_closure(self): mc = ws["ModelConfig"] assert mc - mc_from_js = ws_from_js["meas_modelConfig"] + mc_from_js = ws_from_js["ModelConfig"] assert mc_from_js pdf = mc.GetPdf() @@ -79,18 +80,13 @@ def test_closure(self): data_from_js = ws_from_js["obsData"] assert data_from_js - pdf.fitTo( - data, - Strategy=1, - Minos=mc.GetParametersOfInterest(), - GlobalObservables = mc.GetGlobalObservables() - ) + pdf.fitTo(data, Strategy=1, Minos=mc.GetParametersOfInterest(), GlobalObservables=mc.GetGlobalObservables()) pdf_from_js.fitTo( data_from_js, Strategy=1, Minos=mc_from_js.GetParametersOfInterest(), - GlobalObservables=mc_from_js.GetGlobalObservables() + GlobalObservables=mc_from_js.GetGlobalObservables(), ) from math import isclose @@ -112,23 +108,23 @@ def test_closure_loop(self): meas = self.measurement() ws = self.toWS(meas) - tool = ROOT.RooJSONFactoryWSTool(ws) + tool = ROOT.RooJSONFactoryWSTool(ws) js = tool.exportJSONtoString() - - newws = ROOT.RooWorkspace("new"); + + newws = ROOT.RooWorkspace("new") newtool = ROOT.RooJSONFactoryWSTool(newws) newtool.importJSONfromString(js) - - mc = ws.obj("ModelConfig") + + mc = ws["ModelConfig"] assert mc - newmc = newws["simPdf_modelConfig"] + newmc = newws["ModelConfig"] assert newmc pdf = mc.GetPdf() assert pdf - newpdf = newws["simPdf"] + newpdf = newmc.GetPdf() assert newpdf data = ws["obsData"] @@ -136,13 +132,13 @@ def test_closure_loop(self): newdata = newws["obsData"] assert newdata - - pdf.fitTo( + + pdf.fitTo( data, Strategy=1, Minos=mc.GetParametersOfInterest(), GlobalObservables=mc.GetGlobalObservables(), - PrintLevel=-1, + PrintLevel=-1, ) newpdf.fitTo( @@ -150,7 +146,7 @@ def test_closure_loop(self): Strategy=1, Minos=newmc.GetParametersOfInterest(), GlobalObservables=newmc.GetGlobalObservables(), - PrintLevel=-1 + PrintLevel=-1, ) from math import isclose @@ -166,7 +162,7 @@ def test_closure_loop(self): newws.var("mu").getError(), rel_tol=1e-4, abs_tol=1e-4, - ) + ) if __name__ == "__main__": diff --git a/roofit/roofit/inc/RooGaussian.h b/roofit/roofit/inc/RooGaussian.h index 03113cdabeb53..a7a35fdc981c2 100644 --- a/roofit/roofit/inc/RooGaussian.h +++ b/roofit/roofit/inc/RooGaussian.h @@ -38,6 +38,15 @@ class RooGaussian : public RooAbsPdf { Int_t getGenerator(const RooArgSet& directVars, RooArgSet &generateVars, Bool_t staticInitOK=kTRUE) const override; void generateEvent(Int_t code) override; + /// Get the x variable. + RooAbsReal const& getX() const { return x.arg(); } + + /// Get the mean parameter. + RooAbsReal const& getMean() const { return mean.arg(); } + + /// Get the sigma parameter. + RooAbsReal const& getSigma() const { return sigma.arg(); } + protected: RooRealProxy x ; diff --git a/roofit/roofitcore/inc/RooBinSamplingPdf.h b/roofit/roofitcore/inc/RooBinSamplingPdf.h index 7a6c19e6e301f..a0c5927b1135e 100644 --- a/roofit/roofitcore/inc/RooBinSamplingPdf.h +++ b/roofit/roofitcore/inc/RooBinSamplingPdf.h @@ -95,6 +95,10 @@ class RooBinSamplingPdf : public RooAbsPdf { static std::unique_ptr create(RooAbsPdf& pdf, RooAbsData const &data, double precision); + double epsilon() const { return _relEpsilon; } + const RooAbsPdf& pdf() const { return _pdf.arg(); } + const RooAbsReal& observable() const { return _observable.arg(); } + protected: double evaluate() const override; RooSpan evaluateSpan(RooBatchCompute::RunContext& evalData, const RooArgSet* normSet) const override; diff --git a/roofit/roofitcore/inc/RooBinWidthFunction.h b/roofit/roofitcore/inc/RooBinWidthFunction.h index c9ad9fafe8fd7..c5a2c9a316a83 100644 --- a/roofit/roofitcore/inc/RooBinWidthFunction.h +++ b/roofit/roofitcore/inc/RooBinWidthFunction.h @@ -68,6 +68,8 @@ class RooBinWidthFunction : public RooAbsReal { return _histFunc->plotSamplingHint(obs, xlo, xhi); } + bool divideByBinWidth() const { return _divideByBinWidth; } + const RooHistFunc& histFunc() const { return (*_histFunc); } double evaluate() const override; RooSpan evaluateSpan(RooBatchCompute::RunContext& evalData, const RooArgSet* normSet) const override; diff --git a/roofit/roofitcore/inc/RooFormulaVar.h b/roofit/roofitcore/inc/RooFormulaVar.h index a44babbbd5aff..9c68dbff3f65c 100644 --- a/roofit/roofitcore/inc/RooFormulaVar.h +++ b/roofit/roofitcore/inc/RooFormulaVar.h @@ -37,7 +37,9 @@ class RooFormulaVar : public RooAbsReal { virtual TObject* clone(const char* newname) const { return new RooFormulaVar(*this,newname); } inline Bool_t ok() const { return getFormula().ok() ; } - + const char* expression() const { return _formExpr.Data(); } + const RooArgList& dependents() const { return _actualVars; } + /// Return pointer to parameter with given name. inline RooAbsArg* getParameter(const char* name) const { return _actualVars.find(name) ; diff --git a/roofit/roofitcore/inc/RooGenericPdf.h b/roofit/roofitcore/inc/RooGenericPdf.h index d5f999eaa8e08..f801af0668c5b 100644 --- a/roofit/roofitcore/inc/RooGenericPdf.h +++ b/roofit/roofitcore/inc/RooGenericPdf.h @@ -42,6 +42,9 @@ class RooGenericPdf : public RooAbsPdf { // Debugging void dumpFormula() { formula().dump() ; } + const char* expression() const { return _formExpr.Data(); } + const RooArgList& dependents() const { return _actualVars; } + protected: RooFormula& formula() const ; diff --git a/roofit/roofitcore/inc/RooGlobalFunc.h b/roofit/roofitcore/inc/RooGlobalFunc.h index 119a9b5baa7cd..127358380d9dd 100644 --- a/roofit/roofitcore/inc/RooGlobalFunc.h +++ b/roofit/roofitcore/inc/RooGlobalFunc.h @@ -59,7 +59,7 @@ enum MsgLevel { DEBUG=0, INFO=1, PROGRESS=2, WARNING=3, ERROR=4, FATAL=5 } ; /// Topics for a RooMsgService::StreamConfig in RooMsgService enum MsgTopic { Generation=1, Minimization=2, Plotting=4, Fitting=8, Integration=16, LinkStateMgmt=32, Eval=64, Caching=128, Optimization=256, ObjectHandling=512, InputArguments=1024, Tracing=2048, - Contents=4096, DataHandling=8192, NumIntegration=16384, FastEvaluations=1<<15, HistFactory=1<<16 }; + Contents=4096, DataHandling=8192, NumIntegration=16384, FastEvaluations=1<<15, HistFactory=1<<16, IO=1<<17 }; enum MPSplit { BulkPartition=0, Interleave=1, SimComponents=2, Hybrid=3 } ; /// For setting the batch mode flag with the BatchMode() command argument to diff --git a/roofit/roofitcore/src/RooWorkspace.cxx b/roofit/roofitcore/src/RooWorkspace.cxx index 7d6b68cd93d1d..9004bc387e15f 100644 --- a/roofit/roofitcore/src/RooWorkspace.cxx +++ b/roofit/roofitcore/src/RooWorkspace.cxx @@ -473,7 +473,9 @@ Bool_t RooWorkspace::import(const RooAbsArg& inArg, return kTRUE ; } } else { - coutI(ObjectHandling) << "RooWorkSpace::import(" << GetName() << ") Recycling existing object " << inArg.GetName() << " created with identical factory specification" << endl ; + if(!silence) { + coutI(ObjectHandling) << "RooWorkSpace::import(" << GetName() << ") Recycling existing object " << inArg.GetName() << " created with identical factory specification" << endl ; + } } } diff --git a/tutorials/roofit/rf515_hfJSON.json b/tutorials/roofit/rf515_hfJSON.json index 431933fd0306b..52315aa243c90 100644 --- a/tutorials/roofit/rf515_hfJSON.json +++ b/tutorials/roofit/rf515_hfJSON.json @@ -1,6 +1,10 @@ { "pdfs": { "main": { + "dict": { + "InterpolationScheme": "" + }, + "index": "channelCat", "type": "simultaneous", "tags": [ "toplevel" @@ -17,7 +21,7 @@ }, "samples": { "signal": { - "type": "histogram", + "type": "hist-sample", "overallSystematics": { "syst1": { "low": 0.95, @@ -36,7 +40,7 @@ } }, "background1": { - "type": "histogram", + "type": "hist-sample", "overallSystematics": { "syst2": { "low": 0.95, @@ -56,7 +60,7 @@ } }, "background2": { - "type": "histogram", + "type": "hist-sample", "overallSystematics": { "syst3": { "low": 0.95, diff --git a/tutorials/roofit/rf515_hfJSON.py b/tutorials/roofit/rf515_hfJSON.py index c86c7e48aca53..ac62009fc4102 100644 --- a/tutorials/roofit/rf515_hfJSON.py +++ b/tutorials/roofit/rf515_hfJSON.py @@ -21,15 +21,16 @@ # use it to import the information from your JSON file tool.importJSON(ROOT.gROOT.GetTutorialDir().Data() + "/roofit/rf515_hfJSON.json") +ws.Print() # now, you can easily use your workspace to run your fit (as you usually would) # the model config is named after your pdf, i.e. _modelConfig -model = ws["main_modelConfig"] -pdf = model.GetPdf() -result = pdf.fitTo(ws["observed"], - Save=True, - GlobalObservables=model.GetGlobalObservables(), - PrintLevel=-1) +model = ws["ModelConfig"] +pdf = model.GetPdf() + +# we are fitting a clone of the model now, such that we are not double-fitting +# the model in the closure check +result = pdf.cloneTree().fitTo(ws["observed"], Save=True, GlobalObservables=model.GetGlobalObservables(), PrintLevel=-1) result.Print() # in the end, you can again write to json @@ -41,8 +42,5 @@ tool2 = ROOT.RooJSONFactoryWSTool(ws2) tool2.importJSON("myWorkspace.json") model2 = ws2["main_modelConfig"] -result = model.GetPdf().fitTo(ws2["observed"], - Save=True, - GlobalObservables=model.GetGlobalObservables(), - PrintLevel=-1) +result = model.GetPdf().fitTo(ws2["observed"], Save=True, GlobalObservables=model.GetGlobalObservables(), PrintLevel=-1) result.Print() From a36d98f4842984b57d5a9e26c7f314a27bf9bcc6 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Tue, 25 Jan 2022 18:48:15 +0100 Subject: [PATCH 4/9] [RF] Overload `RooAbsArg::addOwnedComponents` to accept smart pointers The usecase for this overload is to transfer the ownership of RooAbsArgs from `std::unique_ptr` to another RooAbsArg, without having to call `release()` on the smart pointer, which is a code smell because ownership should always be clear without the smart pointer having to release it. --- roofit/roofitcore/inc/RooAbsArg.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/roofit/roofitcore/inc/RooAbsArg.h b/roofit/roofitcore/inc/RooAbsArg.h index b33c360ac2710..409297900ea58 100644 --- a/roofit/roofitcore/inc/RooAbsArg.h +++ b/roofit/roofitcore/inc/RooAbsArg.h @@ -545,6 +545,13 @@ class RooAbsArg : public TNamed, public RooPrintable { virtual void printCompactTreeHook(std::ostream& os, const char *ind="") ; Bool_t addOwnedComponents(const RooArgSet& comps) ; + + // Transfer the ownership of one or more other RooAbsArgs to this RooAbsArg + // via a `std::unique_ptr`. + template + bool addOwnedComponents(std::unique_ptr... comps) { + return addOwnedComponents({*comps.release() ...}); + } const RooArgSet* ownedComponents() const { return _ownedComponents ; } void setProhibitServerRedirect(Bool_t flag) { _prohibitServerRedirect = flag ; } From a94a69db6d3bfdbaff301f7f6907fc6cf3d6d8fe Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Tue, 25 Jan 2022 13:39:24 +0100 Subject: [PATCH 5/9] [RF] Fix memory leaks in RooFitHS3 by avoiding manual memory management Calling `new` and `delete` is now completely avoided in RooFitHS3, and `std::unique_ptr` is always used instead. --- .../hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h | 29 ++- roofit/hs3/src/JSONFactories_HistFactory.cxx | 202 +++++++++--------- roofit/hs3/src/JSONFactories_RooFitCore.cxx | 46 ++-- roofit/hs3/src/JSONParser.cxx | 9 +- roofit/hs3/src/RYMLParser.cxx | 6 +- roofit/hs3/src/RooJSONFactoryWSTool.cxx | 150 ++++++------- roofit/roofitcore/inc/RooAbsArg.h | 5 +- 7 files changed, 234 insertions(+), 213 deletions(-) diff --git a/roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h b/roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h index 231a4a42badb0..ea95f7c93b125 100644 --- a/roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h +++ b/roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h @@ -5,6 +5,7 @@ #include #include +#include #include class RooAbsArg; @@ -58,8 +59,8 @@ class RooJSONFactoryWSTool { std::vector arguments; }; - typedef std::map> ImportMap; - typedef std::map> ExportMap; + typedef std::map>> ImportMap; + typedef std::map>> ExportMap; typedef std::map ExportKeysMap; typedef std::map ImportExpressionMap; @@ -95,8 +96,8 @@ class RooJSONFactoryWSTool { static ImportExpressionMap _pdfFactoryExpressions; static ImportExpressionMap _funcFactoryExpressions; - std::map loadData(const RooFit::Experimental::JSONNode &n); - RooDataSet *unbinned(RooDataHist *hist); + std::map> loadData(const RooFit::Experimental::JSONNode &n); + std::unique_ptr unbinned(RooDataHist const &hist); RooRealVar *getWeightVar(const char *name); RooRealVar *createObservable(const std::string &name, const RooJSONFactoryWSTool::Var &var); @@ -130,10 +131,22 @@ class RooJSONFactoryWSTool { RooJSONFactoryWSTool(RooWorkspace &ws) : _workspace{&ws} {} RooWorkspace *workspace() { return this->_workspace; } - static bool - registerImporter(const std::string &key, const RooJSONFactoryWSTool::Importer *f, bool topPriority = true); + template + static bool registerImporter(const std::string &key, bool topPriority = true) + { + return registerImporter(key, std::make_unique(), topPriority); + } + template + static bool registerExporter(const TClass *key, bool topPriority = true) + { + return registerExporter(key, std::make_unique(), topPriority); + } + + static bool registerImporter(const std::string &key, std::unique_ptr f, + bool topPriority = true); + static bool registerExporter(const TClass *key, std::unique_ptr f, + bool topPriority = true); static int removeImporters(const std::string &needle); - static bool registerExporter(const TClass *key, const RooJSONFactoryWSTool::Exporter *f, bool topPriority = true); static int removeExporters(const std::string &needle); static void printImporters(); static void printExporters(); @@ -194,7 +207,7 @@ class RooJSONFactoryWSTool { static void writeObservables(const TH1 &h, RooFit::Experimental::JSONNode &n, const std::vector &varnames); static std::vector> generateBinIndices(const RooArgList &vars); - RooDataHist * + std::unique_ptr readBinnedData(const RooFit::Experimental::JSONNode &n, const std::string &namecomp, RooArgList observables); static std::map readObservables(const RooFit::Experimental::JSONNode &n, const std::string &obsnamecomp); diff --git a/roofit/hs3/src/JSONFactories_HistFactory.cxx b/roofit/hs3/src/JSONFactories_HistFactory.cxx index 1ae40e5663221..637c81c0d55db 100644 --- a/roofit/hs3/src/JSONFactories_HistFactory.cxx +++ b/roofit/hs3/src/JSONFactories_HistFactory.cxx @@ -19,6 +19,8 @@ #include +#include + #include "static_execute.h" using RooFit::Experimental::JSONNode; @@ -68,13 +70,13 @@ std::vector getVarnames(const RooHistFunc *hf) return RooJSONFactoryWSTool::names(&vars); } -TH1 *histFunc2TH1(const RooHistFunc *hf) +std::unique_ptr histFunc2TH1(const RooHistFunc *hf) { const RooDataHist &dh = hf->dataHist(); RooArgList vars(*dh.get()); auto varnames = RooJSONFactoryWSTool::names(&vars); - TH1 *hist = hf->createHistogram(RooJSONFactoryWSTool::concat(&vars).c_str()); - hist->SetDirectory(NULL); + std::unique_ptr hist{hf->createHistogram(RooJSONFactoryWSTool::concat(&vars).c_str())}; + hist->SetDirectory(nullptr); auto volumes = dh.binVolumes(0, dh.numEntries()); for (size_t i = 0; i < volumes.size(); ++i) { hist->SetBinContent(i + 1, hist->GetBinContent(i + 1) / volumes[i]); @@ -95,7 +97,7 @@ T *findClient(RooAbsArg *gamma) return c; } } - return NULL; + return nullptr; } RooRealVar *getNP(RooJSONFactoryWSTool *tool, const char *parname) @@ -157,22 +159,28 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { RooJSONFactoryWSTool::error("function '" + name + "' is of histogram type, but does not define a 'data' key"); } try { - std::vector tmp; + std::stack> ownedArgsStack; RooArgSet shapeElems; RooArgSet normElems; auto varlist = tool->getObservables(p["data"], prefix); - RooDataHist *dh = dynamic_cast(tool->workspace()->embeddedData(name.c_str())); - if (!dh) { - dh = tool->readBinnedData(p["data"], name, varlist); - tool->workspace()->import(*dh, RooFit::Silence(true), RooFit::Embedded()); - } - RooHistFunc *hf = - new RooHistFunc(("hist_" + name).c_str(), RooJSONFactoryWSTool::name(p).c_str(), *(dh->get()), *dh); - RooBinWidthFunction *binning = new RooBinWidthFunction( - TString::Format("%s_binWidth", (prefix.size() > 0 ? prefix : name).c_str()).Data(), - TString::Format("%s_binWidth", (prefix.size() > 0 ? prefix : name).c_str()).Data(), *hf, true); - shapeElems.add(*binning); - tmp.push_back(binning); + + auto getBinnedData = [&tool, &p, &varlist](std::string const &binnedDataName) -> RooDataHist & { + auto *dh = dynamic_cast(tool->workspace()->embeddedData(binnedDataName.c_str())); + if (!dh) { + auto dhForImport = tool->readBinnedData(p["data"], binnedDataName, varlist); + tool->workspace()->import(*dhForImport, RooFit::Silence(true), RooFit::Embedded()); + dh = static_cast(tool->workspace()->embeddedData(dhForImport->GetName())); + } + return *dh; + }; + + RooDataHist &dh = getBinnedData(name); + auto hf = std::make_unique(("hist_" + name).c_str(), RooJSONFactoryWSTool::name(p).c_str(), + *(dh.get()), dh); + ownedArgsStack.push(std::make_unique( + TString::Format("%s_binWidth", (!prefix.empty() ? prefix : name).c_str()).Data(), + TString::Format("%s_binWidth", (!prefix.empty() ? prefix : name).c_str()).Data(), *hf, true)); + shapeElems.add(*ownedArgsStack.top()); if (p.has_child("statError") && p["statError"].val_bool()) { RooAbsArg *phf = tool->getScopeObject("mcstat"); @@ -213,11 +221,11 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { RooJSONFactoryWSTool::error("overall systematic '" + sysname + "' doesn't have a valid parameter!"); } } - RooStats::HistFactory::FlexibleInterpVar *v = new RooStats::HistFactory::FlexibleInterpVar( + auto v = std::make_unique( ("overallSys_" + name).c_str(), ("overallSys_" + name).c_str(), nps, 1., low, high); v->setAllInterpCodes(4); // default HistFactory interpCode normElems.add(*v); - tmp.push_back(v); + ownedArgsStack.push(std::move(v)); } if (p.has_child("histogramSystematics")) { RooArgList nps; @@ -229,36 +237,24 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { : "alpha_" + sysname); RooAbsReal *par = ::getNP(tool, parname.c_str()); nps.add(*par); - RooDataHist *dh_low = - dynamic_cast(tool->workspace()->embeddedData((sysname + "Low_" + name).c_str())); - if (!dh_low) { - dh_low = tool->readBinnedData(sys["dataLow"], sysname + "Low_" + name, varlist); - tool->workspace()->import(*dh_low, RooFit::Silence(true), RooFit::Embedded()); - } - RooHistFunc *hf_low = new RooHistFunc((sysname + "Low_" + name).c_str(), - RooJSONFactoryWSTool::name(p).c_str(), *(dh_low->get()), *dh_low); - low.add(*hf_low); - tmp.push_back(hf_low); - RooDataHist *dh_high = - dynamic_cast(tool->workspace()->embeddedData((sysname + "High_" + name).c_str())); - if (!dh_high) { - dh_high = tool->readBinnedData(sys["dataHigh"], sysname + "High_" + name, varlist); - tool->workspace()->import(*dh_high, RooFit::Silence(true), RooFit::Embedded()); - } - RooHistFunc *hf_high = - new RooHistFunc((sysname + "High_" + name).c_str(), RooJSONFactoryWSTool::name(p).c_str(), - *(dh_high->get()), *dh_high); - high.add(*hf_high); - tmp.push_back(hf_high); + RooDataHist &dh_low = getBinnedData(sysname + "Low_" + name); + ownedArgsStack.push(std::make_unique( + (sysname + "Low_" + name).c_str(), RooJSONFactoryWSTool::name(p).c_str(), *(dh_low.get()), dh_low)); + low.add(*ownedArgsStack.top()); + RooDataHist &dh_high = getBinnedData(sysname + "High_" + name); + ownedArgsStack.push(std::make_unique((sysname + "High_" + name).c_str(), + RooJSONFactoryWSTool::name(p).c_str(), + *(dh_high.get()), dh_high)); + high.add(*ownedArgsStack.top()); } - PiecewiseInterpolation *v = new PiecewiseInterpolation( - ("histoSys_" + name).c_str(), ("histoSys_" + name).c_str(), *hf, low, high, nps, false); + auto v = std::make_unique(("histoSys_" + name).c_str(), + ("histoSys_" + name).c_str(), *hf, low, high, nps, false); v->setAllInterpCodes(4); // default interpCode for HistFactory shapeElems.add(*v); - tmp.push_back(v); + ownedArgsStack.push(std::move(v)); } else { shapeElems.add(*hf); - tmp.push_back(hf); + ownedArgsStack.push(std::move(hf)); } RooProduct shape(name.c_str(), (name + "_shape").c_str(), shapeElems); tool->workspace()->import(shape, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); @@ -268,9 +264,6 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { } else { tool->workspace()->factory(("RooConstVar::" + name + "_norm(1.)").c_str()); } - for (auto e : tmp) { - delete e; - } } catch (const std::runtime_error &e) { RooJSONFactoryWSTool::error("function '" + name + "' is of histogram type, but 'data' is not a valid definition. " + e.what() + "."); @@ -281,16 +274,27 @@ class RooHistogramFactory : public RooJSONFactoryWSTool::Importer { class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { public: - ParamHistFunc *createPHF(const std::string &name, const std::vector &sumW, const std::vector &sumW2, - RooArgList &nps, RooArgList &constraints, const RooArgSet &observables, - double statErrorThreshold, const std::string &statErrorType) const + struct CreatePHFAndConstraintsOutput { + std::unique_ptr phf; + RooArgList constraints; + std::stack> keepAlive; + }; + + CreatePHFAndConstraintsOutput createPHFAndConstraints(const std::string &name, const std::vector &sumW, + const std::vector &sumW2, const RooArgSet &observables, + double statErrorThreshold, + const std::string &statErrorType) const { RooArgList gammas; + RooArgList nps; + RooArgList constraints; + std::stack> keepAlive; for (size_t i = 0; i < sumW.size(); ++i) { TString gname = TString::Format("gamma_stat_%s_bin_%d", name.c_str(), (int)i); double err = sqrt(sumW2[i]) / sumW[i]; if (err > 0) { - RooRealVar *g = new RooRealVar(gname.Data(), gname.Data(), 1.); + keepAlive.push(std::make_unique(gname.Data(), gname.Data(), 1.)); + RooRealVar *g = static_cast(keepAlive.top().get()); g->setAttribute("np"); g->setConstant(err < statErrorThreshold); g->setError(err); @@ -303,46 +307,57 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { TString tname = TString::Format("nom_gamma_stat_%s_bin_%d", name.c_str(), (int)i); TString poisname = TString::Format("gamma_stat_%s_bin_%d_constraint", name.c_str(), (int)i); TString sname = TString::Format("gamma_stat_%s_bin_%d_sigma", name.c_str(), (int)i); - RooRealVar *tau = new RooRealVar(tname.Data(), tname.Data(), 1); + auto tau = std::make_unique(tname.Data(), tname.Data(), 1); tau->setAttribute("glob"); tau->setConstant(true); tau->setRange(0, 10); - RooConstVar *sigma = new RooConstVar(sname.Data(), sname.Data(), err); - RooGaussian *gaus = new RooGaussian(poisname.Data(), poisname.Data(), *tau, *g, *sigma); + auto sigma = std::make_unique(sname.Data(), sname.Data(), err); + auto gaus = std::make_unique(poisname.Data(), poisname.Data(), *tau, *g, *sigma); + gaus->addOwnedComponents(std::move(tau), std::move(sigma)); constraints.add(*gaus, true); + keepAlive.push(std::move(gaus)); } else if (statErrorType == "Poisson") { TString tname = TString::Format("tau_stat_%s_bin_%d", name.c_str(), (int)i); TString prodname = TString::Format("nExp_stat_%s_bin_%d", name.c_str(), (int)i); TString poisname = TString::Format("Constraint_stat_%s_bin_%d", name.c_str(), (int)i); double tauCV = 1. / (err * err); - RooRealVar *tau = new RooRealVar(tname.Data(), tname.Data(), tauCV); + auto tau = std::make_unique(tname.Data(), tname.Data(), tauCV); tau->setAttribute("glob"); tau->setConstant(true); tau->setRange(tauCV - 10. / err, tauCV + 10. / err); RooArgSet elems; elems.add(*g); elems.add(*tau); - RooProduct *prod = new RooProduct(prodname.Data(), prodname.Data(), elems); - RooPoisson *pois = new RooPoisson(poisname.Data(), poisname.Data(), *tau, *prod); + auto prod = std::make_unique(prodname.Data(), prodname.Data(), elems); + auto pois = std::make_unique(poisname.Data(), poisname.Data(), *tau, *prod); + pois->addOwnedComponents(std::move(tau), std::move(prod)); pois->setNoRounding(true); constraints.add(*pois, true); + keepAlive.push(std::move(pois)); } else { RooJSONFactoryWSTool::error("unknown constraint type " + statErrorType); } } else { - RooRealVar *g = new RooRealVar(gname.Data(), gname.Data(), 1.); + auto g = std::make_unique(gname.Data(), gname.Data(), 1.); g->setConstant(true); gammas.add(*g, true); + keepAlive.push(std::move(g)); + } + } + for (auto &np : nps) { + for (auto client : np->clients()) { + if (client->InheritsFrom(RooAbsPdf::Class()) && !constraints.find(*client)) { + constraints.add(*client); + } } } - if (gammas.size() > 0) { - ParamHistFunc *phf = - new ParamHistFunc(TString::Format("%s_mcstat", name.c_str()), "staterror", observables, gammas); + std::unique_ptr phf; + if (!gammas.empty()) { + phf = std::make_unique(TString::Format("%s_mcstat", name.c_str()), "staterror", observables, + gammas); phf->recursiveRedirectServers(observables); - return phf; - } else { - return NULL; } + return {std::move(phf), std::move(constraints), std::move(keepAlive)}; } bool importPdf(RooJSONFactoryWSTool *tool, const JSONNode &p) const override @@ -353,8 +368,6 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { if (!p.has_child("samples")) { RooJSONFactoryWSTool::error("no samples in '" + name + "', skipping."); } - RooArgList constraints; - RooArgList nps; std::vector usesStatError; double statErrorThreshold = 0; std::string statErrorType = "Poisson"; @@ -406,12 +419,11 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { coefnames.push_back(fprefix + fname + "_norm"); } - ParamHistFunc *phf = - createPHF(name, sumW, sumW2, nps, constraints, observables, statErrorThreshold, statErrorType); - if (phf) { + auto out = createPHFAndConstraints(name, sumW, sumW2, observables, statErrorThreshold, statErrorType); + RooArgList constraints = std::move(out.constraints); + if (ParamHistFunc *phf = out.phf.get()) { tool->workspace()->import(*phf, RooFit::RecycleConflictNodes(), RooFit::Silence(true)); tool->setScopeObject("mcstat", tool->workspace()->function(phf->GetName())); - delete phf; } tool->importFunctions(p["samples"]); @@ -423,18 +435,11 @@ class RooRealSumPdfFactory : public RooJSONFactoryWSTool::Importer { RooAbsReal *coef = tool->request(coefname.c_str(), name); coefs.add(*coef); } - for (auto &np : nps) { - for (auto client : np->clients()) { - if (client->InheritsFrom(RooAbsPdf::Class()) && !constraints.find(*client)) { - constraints.add(*client); - } - } - } for (auto sysname : sysnames) { RooAbsPdf *pdf = ::getConstraint(tool, sysname.c_str()); constraints.add(*pdf); } - if (constraints.getSize() == 0) { + if (constraints.empty()) { RooRealSumPdf sum(name.c_str(), name.c_str(), funcs, coefs, true); sum.setAttribute("BinnedLikelihood"); tool->workspace()->import(sum, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); @@ -596,7 +601,7 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { chname = chname.substr(6); } elem["name"] << chname; - RooRealSumPdf *sumpdf = NULL; + RooRealSumPdf *sumpdf = nullptr; for (const auto &v : prodpdf->pdfList()) { if (v->InheritsFrom(RooRealSumPdf::Class())) { sumpdf = static_cast(v); @@ -618,8 +623,8 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { std::map tot_yield; std::map tot_yield2; std::map rel_errors; - std::map bb_histograms; - std::map nonbb_histograms; + std::map> bb_histograms; + std::map> nonbb_histograms; std::vector varnames; for (size_t sampleidx = 0; sampleidx < sumpdf->funcList().size(); ++sampleidx) { @@ -645,10 +650,10 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { } else { elems.add(*coef); } - TH1 *hist = NULL; - ParamHistFunc *phf = NULL; - PiecewiseInterpolation *pip = NULL; - RooStats::HistFactory::FlexibleInterpVar *fip = NULL; + std::unique_ptr hist; + ParamHistFunc *phf = nullptr; + PiecewiseInterpolation *pip = nullptr; + RooStats::HistFactory::FlexibleInterpVar *fip = nullptr; for (const auto &e : elems) { if (e->InheritsFrom(RooConstVar::Class())) { if (((RooConstVar *)e)->getVal() == 1.) @@ -693,13 +698,11 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { auto &sys = systs[sysname]; sys.set_map(); auto &dataLow = sys["dataLow"]; - TH1 *histLow = histFunc2TH1(dynamic_cast(pip->lowList().at(i))); + auto histLow = histFunc2TH1(dynamic_cast(pip->lowList().at(i))); RooJSONFactoryWSTool::exportHistogram(*histLow, dataLow, varnames, 0, false, false); - delete histLow; auto &dataHigh = sys["dataHigh"]; - TH1 *histHigh = histFunc2TH1(dynamic_cast(pip->highList().at(i))); + auto histHigh = histFunc2TH1(dynamic_cast(pip->highList().at(i))); RooJSONFactoryWSTool::exportHistogram(*histHigh, dataHigh, varnames, 0, false, false); - delete histHigh; } } if (!hist) { @@ -744,9 +747,9 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { has_gauss_constraints = true; } } - bb_histograms[samplename] = hist; + bb_histograms[samplename] = std::move(hist); } else { - nonbb_histograms[samplename] = hist; + nonbb_histograms[samplename] = std::move(hist); s["statError"] << 0; } auto &ns = s["namespaces"]; @@ -758,7 +761,6 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { auto &data = s["data"]; RooJSONFactoryWSTool::writeObservables(*hist.second, elem, varnames); RooJSONFactoryWSTool::exportHistogram(*hist.second, data, varnames, 0, false, false); - delete hist.second; } for (const auto &hist : bb_histograms) { auto &s = samples[hist.first]; @@ -773,7 +775,6 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { auto &data = s["data"]; RooJSONFactoryWSTool::writeObservables(*hist.second, elem, varnames); RooJSONFactoryWSTool::exportHistogram(*hist.second, data, varnames, 0, false, true); - delete hist.second; } auto &statError = elem["statError"]; statError.set_map(); @@ -802,13 +803,14 @@ class HistFactoryStreamer : public RooJSONFactoryWSTool::Exporter { STATIC_EXECUTE( - RooJSONFactoryWSTool::registerImporter("histfactory", new RooRealSumPdfFactory(), true); - RooJSONFactoryWSTool::registerImporter("hist-sample", new RooHistogramFactory(), true); - RooJSONFactoryWSTool::registerImporter("interpolation", new PiecewiseInterpolationFactory(), true); - RooJSONFactoryWSTool::registerExporter(RooStats::HistFactory::FlexibleInterpVar::Class(), - new FlexibleInterpVarStreamer(), true); - RooJSONFactoryWSTool::registerExporter(PiecewiseInterpolation::Class(), new PiecewiseInterpolationStreamer(), true); - RooJSONFactoryWSTool::registerExporter(RooProdPdf::Class(), new HistFactoryStreamer(), true); + using Tool = RooJSONFactoryWSTool; + + Tool::registerImporter("histfactory", true); + Tool::registerImporter("hist-sample", true); + Tool::registerImporter("interpolation", true); + Tool::registerExporter(RooStats::HistFactory::FlexibleInterpVar::Class(), true); + Tool::registerExporter(PiecewiseInterpolation::Class(), true); + Tool::registerExporter(RooProdPdf::Class(), true); ) diff --git a/roofit/hs3/src/JSONFactories_RooFitCore.cxx b/roofit/hs3/src/JSONFactories_RooFitCore.cxx index 7b830145e382a..c9579ff32277e 100644 --- a/roofit/hs3/src/JSONFactories_RooFitCore.cxx +++ b/roofit/hs3/src/JSONFactories_RooFitCore.cxx @@ -343,10 +343,9 @@ class RooHistFuncStreamer : public RooJSONFactoryWSTool::Exporter { const RooDataHist &dh = hf->dataHist(); elem["type"] << key(); RooArgList vars(*dh.get()); - TH1 *hist = hf->createHistogram(RooJSONFactoryWSTool::concat(&vars).c_str()); + std::unique_ptr hist{hf->createHistogram(RooJSONFactoryWSTool::concat(&vars).c_str())}; auto &data = elem["data"]; RooJSONFactoryWSTool::exportHistogram(*hist, data, RooJSONFactoryWSTool::names(&vars)); - delete hist; return true; } }; @@ -364,8 +363,9 @@ class RooHistFuncFactory : public RooJSONFactoryWSTool::Importer { auto varlist = tool->getObservables(p["data"], name); RooDataHist *dh = dynamic_cast(tool->workspace()->embeddedData(name.c_str())); if (!dh) { - dh = tool->readBinnedData(p["data"], name, varlist); - tool->workspace()->import(*dh, RooFit::Silence(true), RooFit::Embedded()); + auto dhForImport = tool->readBinnedData(p["data"], name, varlist); + tool->workspace()->import(*dhForImport, RooFit::Silence(true), RooFit::Embedded()); + dh = static_cast(tool->workspace()->embeddedData(dhForImport->GetName())); } RooHistFunc hf(name.c_str(), name.c_str(), *(dh->get()), *dh); tool->workspace()->import(hf, RooFit::RecycleConflictNodes(true), RooFit::Silence(true)); @@ -495,24 +495,26 @@ class RooFormulaVarStreamer : public RooJSONFactoryWSTool::Exporter { namespace { STATIC_EXECUTE( - RooJSONFactoryWSTool::registerImporter("pdfprod", new RooProdPdfFactory(), false); - RooJSONFactoryWSTool::registerImporter("genericpdf", new RooGenericPdfFactory(), false); - RooJSONFactoryWSTool::registerImporter("formulavar", new RooFormulaVarFactory(), false); - RooJSONFactoryWSTool::registerImporter("binsampling", new RooBinSamplingPdfFactory(), false); - RooJSONFactoryWSTool::registerImporter("pdfsum", new RooAddPdfFactory(), false); - RooJSONFactoryWSTool::registerImporter("histogram", new RooHistFuncFactory(), false); - RooJSONFactoryWSTool::registerImporter("simultaneous", new RooSimultaneousFactory(), false); - RooJSONFactoryWSTool::registerImporter("binwidth", new RooBinWidthFunctionFactory(), false); - RooJSONFactoryWSTool::registerImporter("sumpdf", new RooRealSumPdfFactory(), false); - - RooJSONFactoryWSTool::registerExporter(RooBinWidthFunction::Class(), new RooBinWidthFunctionStreamer(), false); - RooJSONFactoryWSTool::registerExporter(RooProdPdf::Class(), new RooProdPdfStreamer(), false); - RooJSONFactoryWSTool::registerExporter(RooSimultaneous::Class(), new RooSimultaneousStreamer(), false); - RooJSONFactoryWSTool::registerExporter(RooBinSamplingPdf::Class(), new RooBinSamplingPdfStreamer(), false); - RooJSONFactoryWSTool::registerExporter(RooHistFunc::Class(), new RooHistFuncStreamer(), false); - RooJSONFactoryWSTool::registerExporter(RooGenericPdf::Class(), new RooGenericPdfStreamer(), false); - RooJSONFactoryWSTool::registerExporter(RooFormulaVar::Class(), new RooFormulaVarStreamer(), false); - RooJSONFactoryWSTool::registerExporter(RooRealSumPdf::Class(), new RooRealSumPdfStreamer(), false); + using Tool = RooJSONFactoryWSTool; + + Tool::registerImporter("pdfprod", false); + Tool::registerImporter("genericpdf", false); + Tool::registerImporter("formulavar", false); + Tool::registerImporter("binsampling", false); + Tool::registerImporter("pdfsum", false); + Tool::registerImporter("histogram", false); + Tool::registerImporter("simultaneous", false); + Tool::registerImporter("binwidth", false); + Tool::registerImporter("sumpdf", false); + + Tool::registerExporter(RooBinWidthFunction::Class(), false); + Tool::registerExporter(RooProdPdf::Class(), false); + Tool::registerExporter(RooSimultaneous::Class(), false); + Tool::registerExporter(RooBinSamplingPdf::Class(), false); + Tool::registerExporter(RooHistFunc::Class(), false); + Tool::registerExporter(RooGenericPdf::Class(), false); + Tool::registerExporter(RooFormulaVar::Class(), false); + Tool::registerExporter(RooRealSumPdf::Class(), false); ) } // namespace diff --git a/roofit/hs3/src/JSONParser.cxx b/roofit/hs3/src/JSONParser.cxx index 59793a394657a..cf1aacbf98298 100644 --- a/roofit/hs3/src/JSONParser.cxx +++ b/roofit/hs3/src/JSONParser.cxx @@ -74,11 +74,14 @@ TJSONTree::Node &TJSONTree::Node::Impl::mkNode(TJSONTree *t, const std::string & return t->incache(Node(t, ref)); } -TJSONTree::Node::Node(TJSONTree *t, std::istream &is) : tree(t), node(new Impl::BaseNode(is)) {} +TJSONTree::Node::Node(TJSONTree *t, std::istream &is) : tree(t), node(std::make_unique(is)) {} -TJSONTree::Node::Node(TJSONTree *t) : tree(t), node(new Impl::BaseNode()) {} +TJSONTree::Node::Node(TJSONTree *t) : tree(t), node(std::make_unique()) {} -TJSONTree::Node::Node(TJSONTree *t, Impl &other) : tree(t), node(new Impl::NodeRef(other.key(), other.get())) {} +TJSONTree::Node::Node(TJSONTree *t, Impl &other) + : tree(t), node(std::make_unique(other.key(), other.get())) +{ +} TJSONTree::Node::Node(const Node &other) : Node(other.tree, *other.node) {} diff --git a/roofit/hs3/src/RYMLParser.cxx b/roofit/hs3/src/RYMLParser.cxx index b89d0234e6c94..14ad1f1e1eb55 100644 --- a/roofit/hs3/src/RYMLParser.cxx +++ b/roofit/hs3/src/RYMLParser.cxx @@ -110,12 +110,12 @@ void TRYMLTree::Node::set_seq() } TRYMLTree::TRYMLTree(std::istream &is) - : tree(new Impl(is)){ + : tree(std::make_unique(is)){ // constructor taking an istream (for reading) }; TRYMLTree::TRYMLTree() - : tree(new Impl()){ + : tree(std::make_unique()){ // default constructor (for writing) }; @@ -127,7 +127,7 @@ TRYMLTree::~TRYMLTree() // JSONNode interface implementation -TRYMLTree::Node::Node(TRYMLTree *t, const TRYMLTree::Node::Impl &imp) : tree(t), node(new Impl(imp)) +TRYMLTree::Node::Node(TRYMLTree *t, const TRYMLTree::Node::Impl &imp) : tree(t), node(std::make_unique(imp)) { // construct a new node from scratch } diff --git a/roofit/hs3/src/RooJSONFactoryWSTool.cxx b/roofit/hs3/src/RooJSONFactoryWSTool.cxx index 5b013ee693208..179d6003ff6db 100644 --- a/roofit/hs3/src/RooJSONFactoryWSTool.cxx +++ b/roofit/hs3/src/RooJSONFactoryWSTool.cxx @@ -1,10 +1,5 @@ #include -#include -#include -#include -#include - #include #include #include @@ -29,6 +24,12 @@ typedef TRYMLTree tree_t; typedef TJSONTree tree_t; #endif +#include +#include +#include +#include +#include + using RooFit::Experimental::JSONNode; namespace { @@ -96,7 +97,7 @@ RooAbsPdf *RooJSONFactoryWSTool::request(const std::string &objname, template <> RooAbsReal *RooJSONFactoryWSTool::request(const std::string &objname, const std::string &requestAuthor) { - RooAbsReal *retval = NULL; + RooAbsReal *retval = nullptr; retval = this->workspace()->pdf(objname.c_str()); if (retval) return retval; @@ -227,34 +228,32 @@ RooJSONFactoryWSTool::ImportExpressionMap RooJSONFactoryWSTool::_pdfFactoryExpre RooJSONFactoryWSTool::ImportExpressionMap RooJSONFactoryWSTool::_funcFactoryExpressions = RooJSONFactoryWSTool::ImportExpressionMap(); -bool RooJSONFactoryWSTool::registerImporter(const std::string &key, const RooJSONFactoryWSTool::Importer *f, - bool topPriority) +bool RooJSONFactoryWSTool::registerImporter(const std::string &key, + std::unique_ptr f, bool topPriority) { auto it = RooJSONFactoryWSTool::_importers.find(key); if (it == RooJSONFactoryWSTool::_importers.end()) - RooJSONFactoryWSTool::_importers.insert( - std::make_pair(key, std::vector())); + RooJSONFactoryWSTool::_importers[key]; it = RooJSONFactoryWSTool::_importers.find(key); if (topPriority) { - it->second.insert(it->second.begin(), f); + it->second.insert(it->second.begin(), std::move(f)); } else { - it->second.push_back(f); + it->second.push_back(std::move(f)); } return true; } -bool RooJSONFactoryWSTool::registerExporter(const TClass *key, const RooJSONFactoryWSTool::Exporter *f, +bool RooJSONFactoryWSTool::registerExporter(const TClass *key, std::unique_ptr f, bool topPriority) { auto it = RooJSONFactoryWSTool::_exporters.find(key); if (it == RooJSONFactoryWSTool::_exporters.end()) - RooJSONFactoryWSTool::_exporters.insert( - std::make_pair(key, std::vector())); + RooJSONFactoryWSTool::_exporters[key]; it = RooJSONFactoryWSTool::_exporters.find(key); if (topPriority) { - it->second.insert(it->second.begin(), f); + it->second.insert(it->second.begin(), std::move(f)); } else { - it->second.push_back(f); + it->second.push_back(std::move(f)); } return true; } @@ -264,7 +263,7 @@ int RooJSONFactoryWSTool::removeImporters(const std::string &needle) int n = 0; for (auto &element : RooJSONFactoryWSTool::_importers) { for (size_t i = element.second.size(); i > 0; --i) { - auto imp = element.second[i - 1]; + auto *imp = element.second[i - 1].get(); std::string name(typeid(*imp).name()); if (name.find(needle) != std::string::npos) { element.second.erase(element.second.begin() + i - 1); @@ -280,7 +279,7 @@ int RooJSONFactoryWSTool::removeExporters(const std::string &needle) int n = 0; for (auto &element : RooJSONFactoryWSTool::_exporters) { for (size_t i = element.second.size(); i > 0; --i) { - auto imp = element.second[i - 1]; + auto *imp = element.second[i - 1].get(); std::string name(typeid(*imp).name()); if (name.find(needle) != std::string::npos) { element.second.erase(element.second.begin() + i - 1); @@ -294,16 +293,20 @@ int RooJSONFactoryWSTool::removeExporters(const std::string &needle) void RooJSONFactoryWSTool::printImporters() { for (const auto &x : RooJSONFactoryWSTool::_importers) { - for (const auto &e : x.second) { - std::cout << x.first << "\t" << typeid(*e).name() << std::endl; + for (const auto &ePtr : x.second) { + // Passing *e directory to typeid results in clang warnings. + auto const& e = *ePtr; + std::cout << x.first << "\t" << typeid(e).name() << std::endl; } } } void RooJSONFactoryWSTool::printExporters() { for (const auto &x : RooJSONFactoryWSTool::_exporters) { - for (const auto &e : x.second) { - std::cout << x.first->GetName() << "\t" << typeid(*e).name() << std::endl; + for (const auto &ePtr : x.second) { + // Passing *e directory to typeid results in clang warnings. + auto const& e = *ePtr; + std::cout << x.first->GetName() << "\t" << typeid(e).name() << std::endl; } } } @@ -947,14 +950,14 @@ void RooJSONFactoryWSTool::importFunction(const JSONNode &p, bool isPdf) /////////////////////////////////////////////////////////////////////////////////////////////////////// // generating an unbinned dataset from a binned one -RooDataSet *RooJSONFactoryWSTool::unbinned(RooDataHist *hist) +std::unique_ptr RooJSONFactoryWSTool::unbinned(RooDataHist const &hist) { - RooArgSet obs(*hist->get()); + RooArgSet obs(*hist.get()); RooRealVar *weight = this->getWeightVar("weight"); obs.add(*weight, true); - RooDataSet *data = new RooDataSet(hist->GetName(), hist->GetTitle(), obs, RooFit::WeightVar(*weight)); - for (int i = 0; i < hist->numEntries(); ++i) { - data->add(*hist->get(i), hist->weight(i)); + auto data = std::make_unique(hist.GetName(), hist.GetTitle(), obs, RooFit::WeightVar(*weight)); + for (int i = 0; i < hist.numEntries(); ++i) { + data->add(*hist.get(i), hist.weight(i)); } return data; } @@ -974,9 +977,9 @@ RooRealVar *RooJSONFactoryWSTool::getWeightVar(const char *weightName) /////////////////////////////////////////////////////////////////////////////////////////////////////// // importing data -std::map RooJSONFactoryWSTool::loadData(const JSONNode &n) +std::map> RooJSONFactoryWSTool::loadData(const JSONNode &n) { - std::map dataMap; + std::map> dataMap; if (!n.is_map()) { return dataMap; } @@ -998,7 +1001,7 @@ std::map RooJSONFactoryWSTool::loadData(const JSONNod RooArgList varlist(vars); RooRealVar *weightVar = this->getWeightVar("weight"); vars.add(*weightVar, true); - RooDataSet *data = new RooDataSet(name, name, vars, RooFit::WeightVar(*weightVar)); + auto data = std::make_unique(name, name, vars, RooFit::WeightVar(*weightVar)); auto &coords = p["coordinates"]; auto &weights = p["weights"]; if (coords.num_children() != weights.num_children()) { @@ -1016,12 +1019,12 @@ std::map RooJSONFactoryWSTool::loadData(const JSONNod RooJSONFactoryWSTool::error("inconsistent number of coordinates and observables!"); } for (size_t j = 0; j < point.num_children(); ++j) { - RooRealVar *v = (RooRealVar *)(varlist.at(j)); + auto *v = static_cast(varlist.at(j)); v->setVal(point[j].val_float()); } data->add(vars, weights[i].val_float()); } - dataMap[name] = data; + dataMap[name] = std::move(data); } else if (p.has_child("index")) { // combined measurement auto subMap = loadData(p); @@ -1034,20 +1037,22 @@ std::map RooJSONFactoryWSTool::loadData(const JSONNod } else { RooArgSet allVars; allVars.add(*channelCat, true); + std::stack> ownedDataSets; std::map datasets; for (const auto &subd : subMap) { allVars.add(*subd.second->get(), true); if (subd.second->InheritsFrom(RooDataHist::Class())) { - datasets[subd.first] = unbinned(((RooDataHist *)subd.second)); + ownedDataSets.push(unbinned(static_cast(*subd.second))); + datasets[subd.first] = ownedDataSets.top().get(); } else { - datasets[subd.first] = ((RooDataSet *)subd.second); + datasets[subd.first] = static_cast(subd.second.get()); } } RooRealVar *weightVar = this->getWeightVar("weight"); allVars.add(*weightVar, true); - RooDataSet *data = new RooDataSet(name.c_str(), name.c_str(), allVars, RooFit::Index(*channelCat), - RooFit::Import(datasets), RooFit::WeightVar(*weightVar)); - dataMap[name] = data; + dataMap[name] = + std::make_unique(name.c_str(), name.c_str(), allVars, RooFit::Index(*channelCat), + RooFit::Import(datasets), RooFit::WeightVar(*weightVar)); } } else { std::stringstream ss; @@ -1067,7 +1072,7 @@ void RooJSONFactoryWSTool::exportData(RooAbsData *data, JSONNode &n) output.set_map(); // find category observables - RooAbsCategory *cat = NULL; + RooAbsCategory *cat = nullptr; for (const auto &obs : observables) { if (obs->InheritsFrom(RooAbsCategory::Class())) { if (cat) { @@ -1083,7 +1088,7 @@ void RooJSONFactoryWSTool::exportData(RooAbsData *data, JSONNode &n) // this is a composite dataset RooDataSet *ds = (RooDataSet *)(data); output["index"] << cat->GetName(); - TList *dataList = ds->split(*(cat), true); + std::unique_ptr dataList{ds->split(*(cat), true)}; if (!dataList) { RooJSONFactoryWSTool::error("unable to split dataset '" + std::string(ds->GetName()) + "' at '" + std::string(cat->GetName()) + "'"); @@ -1091,7 +1096,6 @@ void RooJSONFactoryWSTool::exportData(RooAbsData *data, JSONNode &n) for (RooAbsData *absData : static_range_cast(*dataList)) { this->exportData(absData, output); } - delete dataList; } else if (data->InheritsFrom(RooDataHist::Class())) { // this is a binned dataset RooDataHist *dh = (RooDataHist *)(data); @@ -1209,7 +1213,8 @@ RooRealVar *RooJSONFactoryWSTool::createObservable(const std::string &name, cons /////////////////////////////////////////////////////////////////////////////////////////////////////// // reading binned data -RooDataHist *RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std::string &namecomp, RooArgList varlist) +std::unique_ptr +RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std::string &namecomp, RooArgList varlist) { if (!n.is_map()) RooJSONFactoryWSTool::error("data is not a map"); @@ -1226,7 +1231,7 @@ RooDataHist *RooJSONFactoryWSTool::readBinnedData(const JSONNode &n, const std:: if (counts.num_children() != bins.size()) RooJSONFactoryWSTool::error(TString::Format("inconsistent bin numbers: counts=%d, bins=%d", (int)counts.num_children(), (int)(bins.size()))); - RooDataHist *dh = new RooDataHist(("dataHist_" + namecomp).c_str(), namecomp.c_str(), varlist); + auto dh = std::make_unique(("dataHist_" + namecomp).c_str(), namecomp.c_str(), varlist); // temporarily disable dirty flag propagation when filling the RDH std::vector initVals; for (auto &v : varlist) { @@ -1297,8 +1302,10 @@ void RooJSONFactoryWSTool::configureToplevelPdf(const JSONNode &p, RooAbsPdf &pd mcname = p["dict"]["ModelConfig"].val(); } } - RooStats::ModelConfig *mc = new RooStats::ModelConfig(mcname.c_str(), pdf.GetName()); - this->_workspace->import(*mc); + { + RooStats::ModelConfig mc{mcname.c_str(), pdf.GetName()}; + this->_workspace->import(mc); + } RooStats::ModelConfig *inwsmc = dynamic_cast(this->_workspace->obj(mcname.c_str())); if (inwsmc) { inwsmc->SetWS(*(this->_workspace)); @@ -1307,7 +1314,7 @@ void RooJSONFactoryWSTool::configureToplevelPdf(const JSONNode &p, RooAbsPdf &pd RooArgSet nps; RooArgSet pois; RooArgSet globs; - RooArgSet *pdfVars = pdf.getVariables(); + std::unique_ptr pdfVars{pdf.getVariables()}; for (auto &var : this->_workspace->allVars()) { if (!pdfVars->find(*var)) continue; @@ -1328,7 +1335,6 @@ void RooJSONFactoryWSTool::configureToplevelPdf(const JSONNode &p, RooAbsPdf &pd inwsmc->SetParametersOfInterest(pois); inwsmc->SetNuisanceParameters(nps); inwsmc->SetGlobalObservables(globs); - delete pdfVars; } else { std::stringstream ss; ss << "RooJSONFactoryWSTool() object '" << mcname << "' in workspace is not of type RooStats::ModelConfig!" @@ -1628,40 +1634,34 @@ bool RooJSONFactoryWSTool::exportYML(std::string const &filename) void RooJSONFactoryWSTool::importAllNodes(const RooFit::Experimental::JSONNode &n) { - // import a JSON file to the workspace - try { - this->_rootnode_input = &n; - gROOT->ProcessLine("using namespace RooStats::HistFactory;"); - this->importDependants(n); - - if (n.has_child("data")) { - auto data = this->loadData(n["data"]); - for (const auto &d : data) { - this->_workspace->import(*d.second); - } + this->_rootnode_input = &n; + gROOT->ProcessLine("using namespace RooStats::HistFactory;"); + this->importDependants(n); + + if (n.has_child("data")) { + auto data = this->loadData(n["data"]); + for (const auto &d : data) { + this->_workspace->import(*d.second); } + } - this->_workspace->saveSnapshot("fromJSON", this->_workspace->allVars()); - if (n.has_child("snapshots")) { - for (const auto &snsh : n["snapshots"].children()) { - std::string name = RooJSONFactoryWSTool::name(snsh); - if (name == "fromJSON") + this->_workspace->saveSnapshot("fromJSON", this->_workspace->allVars()); + if (n.has_child("snapshots")) { + for (const auto &snsh : n["snapshots"].children()) { + std::string name = RooJSONFactoryWSTool::name(snsh); + if (name == "fromJSON") + continue; + for (const auto &var : snsh.children()) { + std::string vname = RooJSONFactoryWSTool::name(var); + RooRealVar *rrv = this->_workspace->var(vname.c_str()); + if (!rrv) continue; - for (const auto &var : snsh.children()) { - std::string vname = RooJSONFactoryWSTool::name(var); - RooRealVar *rrv = this->_workspace->var(vname.c_str()); - if (!rrv) - continue; - this->configureVariable(var, *rrv); - } + this->configureVariable(var, *rrv); } } - this->_workspace->loadSnapshot("fromJSON"); - - } catch (const std::exception &ex) { - this->_rootnode_input = nullptr; - throw; } + this->_workspace->loadSnapshot("fromJSON"); + this->_rootnode_input = nullptr; } diff --git a/roofit/roofitcore/inc/RooAbsArg.h b/roofit/roofitcore/inc/RooAbsArg.h index 409297900ea58..c22f7242d1278 100644 --- a/roofit/roofitcore/inc/RooAbsArg.h +++ b/roofit/roofitcore/inc/RooAbsArg.h @@ -26,12 +26,13 @@ #include "RooLinkedListIter.h" #include +#include +#include #include +#include #include -#include #include #include -#include #ifndef R__LESS_INCLUDES #include "TClass.h" From 4fd7709f093a438b28a5b59086a86fed7a134f1e Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Sun, 23 Jan 2022 21:57:31 +0100 Subject: [PATCH 6/9] [RF][PyROOT] Correctly transfer properties from python mirror classes Unlike instance methods, a `property` object in Python 2 has no `__func__` attribute and can be reassigned to a new class directly. --- .../ROOT/_pythonization/_roofit/__init__.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_roofit/__init__.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_roofit/__init__.py index 6266714ed521a..89c84e4fd3aa0 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_roofit/__init__.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_roofit/__init__.py @@ -144,20 +144,13 @@ def is_defined(funcname): return sorted([attr for attr in dir(klass) if is_defined(attr)]) -def is_staticmethod(klass, func_name): - """Check if the function with name `func_name` of a class is a static method.""" +def is_staticmethod_py2(klass, func_name): + """Check if the function with name `func_name` of a class is a static method in Python 2.""" - import sys - - if sys.version_info >= (3, 0): - func = getattr(klass(), func_name) - else: - func = getattr(klass, func_name) - - return type(func).__name__ == "function" + return type(getattr(klass, func_name)).__name__ == "function" -def rebind_instancemethod(to_class, from_class, func_name): +def rebind_attribute(to_class, from_class, func_name): """ Bind the instance method `from_class.func_name` also to class `to_class`. """ @@ -168,7 +161,9 @@ def rebind_instancemethod(to_class, from_class, func_name): if sys.version_info >= (3, 0): to_method = from_method - elif is_staticmethod(from_class, func_name): + elif isinstance(from_method, property): + to_method = from_method + elif is_staticmethod_py2(from_class, func_name): to_method = staticmethod(from_method) else: import new @@ -231,7 +226,7 @@ def pythonize_roofit_class(klass, name): setattr(klass, func_name_orig, func_orig) - rebind_instancemethod(klass, python_klass, func_name) + rebind_attribute(klass, python_klass, func_name) return From dc19faa0513e21387792138a02adf99fd89648e2 Mon Sep 17 00:00:00 2001 From: Rahul Balasubramanian Date: Wed, 26 Jan 2022 17:12:57 +0530 Subject: [PATCH 7/9] [RF] Add tutorials for RooLagrangianMorphFunc and fix factory interface * Add two tutorials `rf711_lagrangianmorph` and `rf712_lagrangianmorphfit` to demonstrate the usage of the `RooLagrangianMorphFunc` class * The commit also includes an update to `rf512_wsfactory_oper` showing an example for the new options of `taylorexpand` and `lagrangianmorph` * formatting of `tutorials/roofit/rf710_roopoly.py` * The attribute for new physics couplings in the `RooLagrangianMorphFunc` class is changed from `NP` to `NewPhysics` to avoid confusion with other abbreviations * The `lagrangianmorph` factory interface is update to accept arguments in any order --- roofit/roofit/src/RooLagrangianMorphFunc.cxx | 16 +- roofit/roofitcore/src/RooFactoryWSTool.cxx | 19 +- .../input_histos_rf_lagrangianmorph.root | Bin 0 -> 103902 bytes tutorials/roofit/rf512_wsfactory_oper.C | 9 + tutorials/roofit/rf512_wsfactory_oper.py | 8 + tutorials/roofit/rf710_roopoly.C | 2 +- tutorials/roofit/rf710_roopoly.py | 13 +- tutorials/roofit/rf711_lagrangianmorph.C | 182 ++++++++++++++++++ tutorials/roofit/rf711_lagrangianmorph.py | 176 +++++++++++++++++ tutorials/roofit/rf712_lagrangianmorphfit.C | 137 +++++++++++++ tutorials/roofit/rf712_lagrangianmorphfit.py | 147 ++++++++++++++ 11 files changed, 691 insertions(+), 18 deletions(-) create mode 100644 tutorials/roofit/input_histos_rf_lagrangianmorph.root create mode 100644 tutorials/roofit/rf711_lagrangianmorph.C create mode 100644 tutorials/roofit/rf711_lagrangianmorph.py create mode 100644 tutorials/roofit/rf712_lagrangianmorphfit.C create mode 100644 tutorials/roofit/rf712_lagrangianmorphfit.py diff --git a/roofit/roofit/src/RooLagrangianMorphFunc.cxx b/roofit/roofit/src/RooLagrangianMorphFunc.cxx index 1b9bccce3112c..68bbb67c750ad 100644 --- a/roofit/roofit/src/RooLagrangianMorphFunc.cxx +++ b/roofit/roofit/src/RooLagrangianMorphFunc.cxx @@ -619,7 +619,7 @@ inline void addCoupling(T &set, const TString &name, const TString &formula, con { if (!set.find(name)) { RooFormulaVar *c = new RooFormulaVar(name, formula, components); - c->setAttribute("NP", isNP); + c->setAttribute("NewPhysics", isNP); set.add(*c); } } @@ -1205,7 +1205,7 @@ buildFormulas(const char *mfname, const RooLagrangianMorphFunc::ParamMap &inputP RooAbsReal *coupling = dynamic_cast(couplings.at(j)); for (int k = 0; k < exponent; ++k) { ss.add(*coupling); - if (coupling->getAttribute("NP")) { + if (coupling->getAttribute("NewPhysics")) { nNP++; } } @@ -1228,7 +1228,7 @@ buildFormulas(const char *mfname, const RooLagrangianMorphFunc::ParamMap &inputP auto obj = dynamic_cast(itr); if (!obj) continue; - TString sval(obj->getStringAttribute("NP")); + TString sval(obj->getStringAttribute("NewPhysics")); int val = atoi(sval); if (val == nNP) { if (flagsZero.find(obj->GetName()) != flagsZero.end() && flagsZero.at(obj->GetName())) { @@ -1984,23 +1984,23 @@ void RooLagrangianMorphFunc::init() closeFile(file); this->addServerList(this->_physics); RooRealVar *nNP0 = new RooRealVar("nNP0", "nNP0", 1., 0, 1.); - nNP0->setStringAttribute("NP", "0"); + nNP0->setStringAttribute("NewPhysics", "0"); nNP0->setConstant(true); this->_flags.add(*nNP0); RooRealVar *nNP1 = new RooRealVar("nNP1", "nNP1", 1., 0, 1.); - nNP1->setStringAttribute("NP", "1"); + nNP1->setStringAttribute("NewPhysics", "1"); nNP1->setConstant(true); this->_flags.add(*nNP1); RooRealVar *nNP2 = new RooRealVar("nNP2", "nNP2", 1., 0, 1.); - nNP2->setStringAttribute("NP", "2"); + nNP2->setStringAttribute("NewPhysics", "2"); nNP2->setConstant(true); this->_flags.add(*nNP2); RooRealVar *nNP3 = new RooRealVar("nNP3", "nNP3", 1., 0, 1.); - nNP3->setStringAttribute("NP", "3"); + nNP3->setStringAttribute("NewPhysics", "3"); nNP3->setConstant(true); this->_flags.add(*nNP3); RooRealVar *nNP4 = new RooRealVar("nNP4", "nNP4", 1., 0, 1.); - nNP4->setStringAttribute("NP", "4"); + nNP4->setStringAttribute("NewPhysics", "4"); nNP4->setConstant(true); this->_flags.add(*nNP4); } diff --git a/roofit/roofitcore/src/RooFactoryWSTool.cxx b/roofit/roofitcore/src/RooFactoryWSTool.cxx index 2243b28e500ff..fd7e99db7b244 100644 --- a/roofit/roofitcore/src/RooFactoryWSTool.cxx +++ b/roofit/roofitcore/src/RooFactoryWSTool.cxx @@ -41,6 +41,7 @@ It interprets all expressions for RooWorkspace::factory(const char*). #include "TInterpreter.h" #include "TEnum.h" #include "RooAbsPdf.h" +#include #include #include "strtok.h" #include "strlcpy.h" @@ -2038,6 +2039,9 @@ std::string RooFactoryWSTool::SpecialsIFace::create(RooFactoryWSTool& ft, const } else if (cl == "lagrangianmorph") { // Perform syntax check. Warn about any meta parameters other than the ones needed + const std::array funcArgs{{"fileName","observableName","couplings","folders"}}; + map mapped_inputs; + for (unsigned int i=1 ; i 0 && pargv[i].find("$NewPhysics(")!=0) - strlcat(pargsmorph, ",", BUFFER_SIZE); - - else if (pargv[i].find("$NewPhysics(")==0) { + if (pargv[i].find("$NewPhysics(")==0) { vector subargs = ft.splitFunctionArgs(pargv[i].c_str()) ; for(const auto& subarg: subargs) { char buf[BUFFER_SIZE]; @@ -2068,7 +2069,7 @@ std::string RooFactoryWSTool::SpecialsIFace::create(RooFactoryWSTool& ft, const tok = R__STRTOK_R(0, "=", &save); } if (parts.size() == 2){ - ft.ws().arg(parts[0].c_str())->setAttribute("NP",atoi(parts[1].c_str())); + ft.ws().arg(parts[0].c_str())->setAttribute("NewPhysics",atoi(parts[1].c_str())); } else throw string(Form("%s::create() ERROR: unknown token %s encountered, check input provided for %s",instName,subarg.c_str(), pargv[i].c_str())); } @@ -2077,11 +2078,17 @@ std::string RooFactoryWSTool::SpecialsIFace::create(RooFactoryWSTool& ft, const vector subargs = ft.splitFunctionArgs(pargv[i].c_str()) ; if (subargs.size()==1){ string expr = ft.processExpression(subargs[0].c_str()); - strlcat(pargsmorph, subargs[0].c_str(),BUFFER_SIZE); + for(auto const& param : funcArgs){ + if(pargv[i].find(param)!=string::npos) mapped_inputs[param]=subargs[0]; + } } else throw string(Form("Incorrect number of arguments in %s, have %d, expect 1",pargv[i].c_str(),(Int_t)subargs.size())) ; } } + for(auto const& param : funcArgs){ + if(strlen(pargsmorph) > 0) strlcat(pargsmorph, ",", BUFFER_SIZE); + strlcat(pargsmorph, mapped_inputs[param].c_str(),BUFFER_SIZE); + } ft.createArg("RooLagrangianMorphFunc",instName, pargsmorph); } else if (cl=="expr") { diff --git a/tutorials/roofit/input_histos_rf_lagrangianmorph.root b/tutorials/roofit/input_histos_rf_lagrangianmorph.root new file mode 100644 index 0000000000000000000000000000000000000000..7337e7afa92734fedb6038a04bfcaf255c644aa3 GIT binary patch literal 103902 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%0AkG92Sytd@QxKO|m$#V_10p}7_ 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 literal 0 HcmV?d00001 diff --git a/tutorials/roofit/rf512_wsfactory_oper.C b/tutorials/roofit/rf512_wsfactory_oper.C index 0664f996f28c3..3f67402592904 100644 --- a/tutorials/roofit/rf512_wsfactory_oper.C +++ b/tutorials/roofit/rf512_wsfactory_oper.C @@ -60,6 +60,15 @@ void rf512_wsfactory_oper() // Function addition is done with sum(func1,func2) w->factory("sum::uv2(u,v)"); + // Lagrangian morphing function for the example shown in rf711_lagrangianmorph + std::string infilename = std::string(gROOT->GetTutorialDir()) + "/roofit/input_histos_rf_lagrangianmorph.root"; + w->factory(("lagrangianmorph::morph($fileName('"+infilename+"'),$observableName('pTV'),$couplings({cHq3[0,1],SM[1]}),$NewPhysics(cHq3=1,SM=0),$folders({'SM_NPsq0','cHq3_NPsq1','cHq3_NPsq2'}))").c_str()); + + + // Taylor expansion is done with taylorexpand(func,{var1,var2,...},val,order) + w->factory("taylorexpand::te(expr::poly('x^4+5*x^3+2*x^2+x+1',x),{x},0.0,2)"); + + // I n t e r p r e t e d a n d c o m p i l e d e x p r e s s i o n b a s e d p . d . f . s . // --------------------------------------------------------------------------------------------------- diff --git a/tutorials/roofit/rf512_wsfactory_oper.py b/tutorials/roofit/rf512_wsfactory_oper.py index b0f3f44a49880..96990e5b17168 100644 --- a/tutorials/roofit/rf512_wsfactory_oper.py +++ b/tutorials/roofit/rf512_wsfactory_oper.py @@ -55,6 +55,14 @@ # Function addition is done with sum(func1,func2) w.factory("sum::uv2(u,v)") +# Lagrangian morphing function for the example shown in rf711_lagrangianmorph +infilename = ROOT.gROOT.GetTutorialDir().Data() + "/roofit/input_histos_rf_lagrangianmorph.root"; +w.factory("lagrangianmorph::morph($observableName('pTV'),$fileName('"+infilename+"'),$couplings({cHq3[0,1],SM[1]}),$NewPhysics(cHq3=1,SM=0),$folders({'SM_NPsq0','cHq3_NPsq1','cHq3_NPsq2'}))") + +# Taylor expansion is done with taylorexpand(func,{var1,var2,...},val,order) +w.factory("taylorexpand::te(expr::poly('x^4+5*x^3+2*x^2+x+1',x),{x},0.0,2)") + + # Interpreted and compiled expression based pdfs # --------------------------------------------------------------------------------------------------- diff --git a/tutorials/roofit/rf710_roopoly.C b/tutorials/roofit/rf710_roopoly.C index 6db247af7eb42..82055955cda61 100644 --- a/tutorials/roofit/rf710_roopoly.C +++ b/tutorials/roofit/rf710_roopoly.C @@ -63,5 +63,5 @@ void rf710_roopoly() legend->AddEntry("f", "Polynomial of fourth order", "L"); legend->Draw(); c->Draw(); - c->SaveAs("rf710_roopoly.pdf"); + c->SaveAs("rf710_roopoly.png"); } diff --git a/tutorials/roofit/rf710_roopoly.py b/tutorials/roofit/rf710_roopoly.py index ecc6f851872b4..42b5f20efdd67 100644 --- a/tutorials/roofit/rf710_roopoly.py +++ b/tutorials/roofit/rf710_roopoly.py @@ -1,5 +1,12 @@ -## \file## \ingroup tutorial_roofit## \notebook##Taylor expansion of RooFit functions using the taylorExpand function -#### \macro_code#### \date November 2021## \authors Rahul Balasubramanian +## \file +## \ingroup tutorial_roofit +## \notebook +## Taylor expansion of RooFit functions using the taylorExpand function +## +## \macro_code +## +## \date November 2021 +## \authors Rahul Balasubramanian import ROOT @@ -48,4 +55,4 @@ legend.Draw() c.Draw() -c.SaveAs("rf710_roopoly.pdf") +c.SaveAs("rf710_roopoly.png") diff --git a/tutorials/roofit/rf711_lagrangianmorph.C b/tutorials/roofit/rf711_lagrangianmorph.C new file mode 100644 index 0000000000000..a78d915ed0eba --- /dev/null +++ b/tutorials/roofit/rf711_lagrangianmorph.C @@ -0,0 +1,182 @@ +/// \file +/// \ingroup tutorial_roofit +/// \notebook -js +/// Morphing effective field theory distributions with RooLagrangianMorphFunc +/// A morphing function as a function of one coefficient is setup and can be used +/// to obtain the distribution for any value of the coefficient. +/// +/// \macro_image +/// \macro_output +/// \macro_code +/// +/// \date January 2022 +/// \author Rahul Balasubramanian + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace RooFit; + +void rf711_lagrangianmorph() +{ + // C r e a t e v a r i a b l e s f o r + // m o r p h i n g f u n c t i o n + // --------------------------------------------- + + std::string observablename = "pTV"; + + // Setup observable that is morphed + RooRealVar obsvar(observablename.c_str(), "observable of pTV", 10, 600); + + // Setup two couplings that enters the morphing function + // kSM -> SM coupling set to constant (1) + // cHq3 -> EFT parameter with NewPhysics attribute set to true + RooRealVar kSM("kSM", "sm modifier", 1.0); + RooRealVar cHq3("cHq3", "EFT modifier", 0.0, 1.0); + cHq3.setAttribute("NewPhysics", true); + + // I n p u t s n e e d e d f o r c o n f i g + // --------------------------------------------- + std::string infilename = std::string(gROOT->GetTutorialDir()) + "/roofit/input_histos_rf_lagrangianmorph.root"; + std::vector samplelist = {"SM_NPsq0", "cHq3_NPsq1", "cHq3_NPsq2"}; + + // S e t u p C o n f i g + // --------------------------------------------- + RooLagrangianMorphFunc::Config config; + config.fileName = infilename; + config.observableName = observablename; + config.folderNames = samplelist; + config.couplings.add(cHq3); + config.couplings.add(kSM); + + // C r e a t e m o r p h i n g f u n c t i o n + // --------------------------------------------- + RooLagrangianMorphFunc morphfunc("morphfunc", "morphed dist. of pTV", config); + + // G e t m o r p h e d d i s t r i b u t i o n f o r + // d i f f e r e n t c H q 3 + // --------------------------------------------- + morphfunc.setParameter("cHq3", 0.01); + auto morph_hist_0p01 = morphfunc.createTH1("morph_cHq3=0.01"); + morphfunc.setParameter("cHq3", 0.25); + auto morph_hist_0p25 = morphfunc.createTH1("morph_cHq3=0.25"); + morphfunc.setParameter("cHq3", 0.5); + auto morph_hist_0p5 = morphfunc.createTH1("morph_cHq3=0.5"); + RooDataHist morph_datahist_0p01("morph_dh_cHq3=0.01", "", RooArgList(obsvar), morph_hist_0p01); + RooDataHist morph_datahist_0p25("morph_dh_cHq3=0.25", "", RooArgList(obsvar), morph_hist_0p25); + RooDataHist morph_datahist_0p5("morph_dh_cHq3=0.5", "", RooArgList(obsvar), morph_hist_0p5); + + // E x t r a c t i n p u t t e m p l a t e s + // f o r p l o t t i n g + // --------------------------------------------- + TFile *file = new TFile(infilename.c_str(), "READ"); + TFolder *folder = 0; + file->GetObject(samplelist[0].c_str(), folder); + TH1 *input_hist0 = dynamic_cast(folder->FindObject(observablename.c_str())); + input_hist0->SetDirectory(NULL); + file->GetObject(samplelist[1].c_str(), folder); + TH1 *input_hist1 = dynamic_cast(folder->FindObject(observablename.c_str())); + input_hist1->SetDirectory(NULL); + file->GetObject(samplelist[2].c_str(), folder); + TH1 *input_hist2 = dynamic_cast(folder->FindObject(observablename.c_str())); + input_hist2->SetDirectory(NULL); + file->Close(); + + RooDataHist input_dh0(samplelist[0].c_str(), "", RooArgList(obsvar), input_hist0); + RooDataHist input_dh1(samplelist[1].c_str(), "", RooArgList(obsvar), input_hist1); + RooDataHist input_dh2(samplelist[2].c_str(), "", RooArgList(obsvar), input_hist2); + + auto frame0 = obsvar.frame(Title("Input templates for p_{T}^{V}")); + input_dh0.plotOn(frame0, Name(samplelist[0].c_str()), LineColor(kBlack), MarkerColor(kBlack), MarkerSize(1)); + input_dh1.plotOn(frame0, Name(samplelist[1].c_str()), LineColor(kRed), MarkerColor(kRed), MarkerSize(1)); + input_dh2.plotOn(frame0, Name(samplelist[2].c_str()), LineColor(kBlue), MarkerColor(kBlue), MarkerSize(1)); + + // P l o t m o r p h e d d i s t r i b u t i o n f o r + // d i f f e r e n t c H q 3 + // --------------------------------------------- + auto frame1 = obsvar.frame(Title("Morphed templates for selected values")); + morph_datahist_0p01.plotOn(frame1, Name("morph_dh_cHq3=0.01"), DrawOption("C"), LineColor(kGreen), + DataError(RooAbsData::None), XErrorSize(0)); + morph_datahist_0p25.plotOn(frame1, Name("morph_dh_cHq3=0.25"), DrawOption("C"), LineColor(kGreen + 1), + DataError(RooAbsData::None), XErrorSize(0)); + morph_datahist_0p5.plotOn(frame1, Name("morph_dh_cHq3=0.5"), DrawOption("C"), LineColor(kGreen + 2), + DataError(RooAbsData::None), XErrorSize(0)); + + // C r e a t e w r a p p e d p d f t o g e n e r a t e + // 2D d a t a s e t o f c H q 3 a s a f u n c t i o n o f + // o b s e r v a b l e + // --------------------------------------------- + + RooWrapperPdf model("wrap_pdf", "wrap_pdf", morphfunc); + RooDataSet *data = model.generate(RooArgSet(cHq3, obsvar), 1000000); + TH1 *hh_data = data->createHistogram("pTV vs cHq3", obsvar, Binning(20), YVar(cHq3, Binning(50))); + hh_data->SetTitle("Morphing prediction"); + + // D r a w p l o t s o n c a n v a s + // --------------------------------------------- + TCanvas *c1 = new TCanvas("fig3", "fig3", 1200, 400); + c1->Divide(3, 1); + + c1->cd(1); + gPad->SetLeftMargin(0.15); + gPad->SetRightMargin(0.05); + + frame0->Draw(); + frame0->GetXaxis()->SetTitle("c_{Hq^{(3)}}"); + TLegend *leg1 = new TLegend(0.55, 0.65, 0.94, 0.87); + leg1->SetTextSize(0.04); + leg1->SetFillColor(kWhite); + leg1->SetLineColor(kWhite); + leg1->AddEntry(frame0->findObject("SM_NPsq0"), "SM", "LP"); + leg1->AddEntry((TObject *)0, "", ""); + leg1->AddEntry(frame0->findObject("cHq3_NPsq1"), "c_{Hq^(3)}=1.0 at #Lambda^{-2}", "LP"); + leg1->AddEntry((TObject *)0, "", ""); + leg1->AddEntry(frame0->findObject("cHq3_NPsq2"), "c_{Hq^(3)}=1.0 at #Lambda^{-4}", "LP"); + leg1->Draw(); + + c1->cd(2); + gPad->SetLeftMargin(0.15); + gPad->SetRightMargin(0.05); + + frame1->Draw(); + frame1->GetXaxis()->SetTitle("p_{T}^{V}"); + + TLegend *leg2 = new TLegend(0.60, 0.65, 0.94, 0.87); + leg2->SetTextSize(0.04); + leg2->SetFillColor(kWhite); + leg2->SetLineColor(kWhite); + leg2->AddEntry(frame1->findObject("morph_dh_cHq3=0.01"), "c_{Hq^{(3)}}=0.01", "L"); + leg2->AddEntry((TObject *)0, "", ""); + leg2->AddEntry(frame1->findObject("morph_dh_cHq3=0.25"), "c_{Hq^{(3)}}=0.25", "L"); + leg2->AddEntry((TObject *)0, "", ""); + leg2->AddEntry(frame1->findObject("morph_dh_cHq3=0.5"), "c_{Hq^{(3)}}=0.5", "L"); + leg2->AddEntry((TObject *)0, "", ""); + leg2->Draw(); + + c1->cd(3); + gPad->SetLeftMargin(0.12); + gPad->SetRightMargin(0.18); + gStyle->SetNumberContours(255); + gStyle->SetPalette(kGreyScale); + gStyle->SetOptStat(0); + TColor::InvertPalette(); + gPad->SetLogz(); + hh_data->GetYaxis()->SetTitle("c_{Hq^{(3)}}"); + hh_data->GetYaxis()->SetRangeUser(0, 0.5); + hh_data->GetXaxis()->SetTitle("p_{T}^{V}"); + hh_data->GetZaxis()->SetTitleOffset(1.8); + hh_data->Draw("COLZ"); + c1->SaveAs("rf711_lagrangianmorph.png"); +} diff --git a/tutorials/roofit/rf711_lagrangianmorph.py b/tutorials/roofit/rf711_lagrangianmorph.py new file mode 100644 index 0000000000000..9333757e5eca9 --- /dev/null +++ b/tutorials/roofit/rf711_lagrangianmorph.py @@ -0,0 +1,176 @@ +## \file +## \ingroup tutorial_roofit +## \notebook -js +## Morphing effective field theory distributions with RooLagrangianMorphFunc. +## A morphing function as a function of one coefficient is setup and can be used +## to obtain the distribution for any value of the coefficient. +## +## \macro_code +## +## \date January 2022 +## \authors Rahul Balasubramanian + +import ROOT + +ROOT.gStyle.SetOptStat(0) +ROOT.PyConfig.IgnoreCommandLineOptions = True +ROOT.gROOT.SetBatch(True) + +# Create functions +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +observablename = "pTV" + +# Setup observable that is to be morphed +obsvar = ROOT.RooRealVar(observablename, "observable of pTV", 10, 600) + +# Setup two couplings that enters the morphing function +# kSM -> SM coupling set to constant (1) +# cHq3 -> EFT parameter with NewPhysics attribute set to true +kSM = ROOT.RooRealVar("kSM", "sm modifier", 1.0) +cHq3 = ROOT.RooRealVar("cHq3", "EFT modifier", 0.0, 1.0) +cHq3.setAttribute("NewPhysics", True) + +# Inputs to setup config +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +infilename = ROOT.gROOT.GetTutorialDir().Data() + "/roofit/input_histos_rf_lagrangianmorph.root"; +par = "cHq3" +samplelist = ["SM_NPsq0", "cHq3_NPsq1", "cHq3_NPsq2"] + +# Set Config +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +config = ROOT.RooLagrangianMorphFunc.Config() +config.fileName = infilename +config.observableName = observablename +config.folderNames = samplelist +config.couplings.add(cHq3) +config.couplings.add(kSM) + + +# Create morphing function +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +morphfunc = ROOT.RooLagrangianMorphFunc("morphfunc", "morphed dist. of pTV", config) + +# Get morphed distribution at cHq3 = 0.01, 0.5 +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +morphfunc.setParameter("cHq3", 0.01) +morph_hist_0p01 = morphfunc.createTH1("morph_cHq3=0.01") +morphfunc.setParameter("cHq3", 0.25) +morph_hist_0p25 = morphfunc.createTH1("morph_cHq3=0.25") +morphfunc.setParameter("cHq3", 0.5) +morph_hist_0p5 = morphfunc.createTH1("morph_cHq3=0.5") +morph_datahist_0p01 = ROOT.RooDataHist("morph_dh_cHq3=0.01", "", [obsvar], morph_hist_0p01) +morph_datahist_0p25 = ROOT.RooDataHist("morph_dh_cHq3=0.25", "", [obsvar], morph_hist_0p25) +morph_datahist_0p5 = ROOT.RooDataHist("morph_dh_cHq3=0.5", "", [obsvar], morph_hist_0p5) + +# Extract input templates for plotting +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +input_hists = {sample: ROOT.TFile.Open(infilename).Get(sample).FindObject(observablename) for sample in samplelist} +input_datahists = { + sample: ROOT.RooDataHist("dh_" + sample, "dh_" + sample, [obsvar], input_hists[sample]) + for sample in samplelist +} + +# Plot input templates +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +frame0 = obsvar.frame(Title="Input templates for p_{T}^{V}") +for sample, color in zip(samplelist, [ROOT.kBlack, ROOT.kRed, ROOT.kBlue]): + input_datahists[sample].plotOn(frame0, Name=sample, LineColor=color, MarkerColor=color, MarkerSize=1) + +# Plot morphed templates for cHq3=0.01,0.25,0.5 +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +frame1 = obsvar.frame(Title="Morphed templates for selected values") +morph_datahist_0p01.plotOn( + frame1, + Name="morph_dh_cHq3=0.01", + DrawOption="C", + LineColor=ROOT.kGreen, + DataError=getattr(ROOT.RooAbsData, "None"), + XErrorSize=0, +) +morph_datahist_0p25.plotOn( + frame1, + Name="morph_dh_cHq3=0.25", + DrawOption="C", + LineColor=ROOT.kGreen + 1, + DataError=getattr(ROOT.RooAbsData, "None"), + XErrorSize=0, +) +morph_datahist_0p5.plotOn( + frame1, + Name="morph_dh_cHq3=0.5", + DrawOption="C", + LineColor=ROOT.kGreen + 2, + DataError=getattr(ROOT.RooAbsData, "None"), + XErrorSize=0, +) + +# Create wrapped pdf to generate 2D dataset of cHq3 as a function of pTV +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +model = ROOT.RooWrapperPdf("wrap_pdf", "wrap_pdf", morphfunc) +data = model.generate({cHq3, obsvar}, 1000000) +hh_data = ROOT.RooAbsData.createHistogram(data, "x,y", obsvar, Binning=20, YVar=dict(var=cHq3, Binning=50)) +hh_data.SetTitle("Morphing prediction") + +# Draw plots on canvas +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +c1 = ROOT.TCanvas("fig3", "fig3", 1200, 400) +c1.Divide(3, 1) + +c1.cd(1) +ROOT.gPad.SetLeftMargin(0.15) +ROOT.gPad.SetRightMargin(0.05) + +frame0.Draw() +frame0.GetXaxis().SetTitle("c_{Hq^{(3)}}") +leg1 = ROOT.TLegend(0.55, 0.65, 0.94, 0.87) +leg1.SetTextSize(0.04) +leg1.SetFillColor(ROOT.kWhite) +leg1.SetLineColor(ROOT.kWhite) +leg1.AddEntry("SM_NPsq0", "SM", "LP") +leg1.AddEntry(0, "", "") +leg1.AddEntry("cHq3_NPsq1", "c_{Hq^{(3)}}=1.0 at #Lambda^{-2}", "LP") +leg1.AddEntry(0, "", "") +leg1.AddEntry("cHq3_NPsq2", "c_{Hq^{(3)}}=1.0 at #Lambda^{-4}", "LP") +leg1.Draw() + +c1.cd(2) +ROOT.gPad.SetLeftMargin(0.15) +ROOT.gPad.SetRightMargin(0.05) + +frame1.Draw() +frame1.GetXaxis().SetTitle("p_{T}^{V}") + +leg2 = ROOT.TLegend(0.62, 0.65, 0.94, 0.87) +leg2.SetTextSize(0.04) +leg2.SetFillColor(ROOT.kWhite) +leg2.SetLineColor(ROOT.kWhite) + +leg2.AddEntry("morph_dh_cHq3=0.01", "c_{Hq^{(3)}}=0.01", "L") +leg2.AddEntry(0, "", "") +leg2.AddEntry("morph_dh_cHq3=0.025", "c_{Hq^{(3)}}=0.025", "L") +leg2.AddEntry(0, "", "") +leg2.AddEntry("morph_dh_cHq3=0.5", "c_{Hq^{(3)}}=0.5", "L") +leg2.AddEntry(0, "", "") +leg2.Draw() + +c1.cd(3) +ROOT.gPad.SetLeftMargin(0.12) +ROOT.gPad.SetRightMargin(0.18) +ROOT.gStyle.SetNumberContours(255) +ROOT.gStyle.SetPalette(ROOT.kGreyScale) +ROOT.gStyle.SetOptStat(0) +ROOT.TColor.InvertPalette() +ROOT.gPad.SetLogz() +hh_data.GetYaxis().SetTitle("c_{Hq^{(3)}}") +hh_data.GetYaxis().SetRangeUser(0, 0.5) +hh_data.GetXaxis().SetTitle("p_{T}^{V}") +hh_data.GetZaxis().SetTitleOffset(1.8) +hh_data.Draw("COLZ") +c1.SaveAs("rf711_lagrangianmorph.png") diff --git a/tutorials/roofit/rf712_lagrangianmorphfit.C b/tutorials/roofit/rf712_lagrangianmorphfit.C new file mode 100644 index 0000000000000..f40535e341125 --- /dev/null +++ b/tutorials/roofit/rf712_lagrangianmorphfit.C @@ -0,0 +1,137 @@ +/// \file +/// \ingroup tutorial_roofit +/// \notebook -js +/// Performing a simple fit with RooLagrangianMorphFunc. +/// a morphing function is setup as a function of three variables and +/// a fit is performed on a pseudo-dataset. +/// +/// \macro_image +/// \macro_output +/// \macro_code +/// +/// \date January 2022 +/// \author Rahul Balasubramanian + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace RooFit; + +void rf712_lagrangianmorphfit() +{ + // C r e a t e v a r i a b l e s f o r + // m o r p h i n g f u n c t i o n + // --------------------------------------------- + + std::string observablename = "pTV"; + RooRealVar obsvar(observablename.c_str(), "observable of pTV", 10, 600); + RooRealVar kSM("kSM", "sm modifier", 1.0); + RooRealVar cHq3("cHq3", "EFT modifier", -10.0, 10.0); + cHq3.setAttribute("NewPhysics", true); + RooRealVar cHl3("cHl3", "EFT modifier", -10.0, 10.0); + cHl3.setAttribute("NewPhysics", true); + RooRealVar cHDD("cHDD", "EFT modifier", -10.0, 10.0); + cHDD.setAttribute("NewPhysics", true); + + // I n p u t s n e e d e d f o r c o n f i g + // --------------------------------------------- + std::string infilename = std::string(gROOT->GetTutorialDir()) + "/roofit/input_histos_rf_lagrangianmorph.root"; + std::vector samplelist = {"SM_NPsq0", "cHq3_NPsq1", "cHq3_NPsq2", "cHl3_NPsq1", + "cHl3_NPsq2", "cHDD_NPsq1", "cHDD_NPsq2", "cHl3_cHDD_NPsq2", + "cHq3_cHDD_NPsq2", "cHl3_cHq3_NPsq2"}; + + // S e t u p C o n f i g + // --------------------------------------------- + RooLagrangianMorphFunc::Config config; + config.fileName = infilename; + config.observableName = observablename; + config.folderNames = samplelist; + config.couplings.add(cHq3); + config.couplings.add(cHl3); + config.couplings.add(cHDD); + config.couplings.add(kSM); + + // C r e a t e m o r p h i n g f u n c t i o n + // --------------------------------------------- + RooLagrangianMorphFunc morphfunc("morphfunc", "morphed dist. of pTV", config); + + // C r e a t e p s e u d o d a t a h i s t o g r a m + // f o r f i t + // --------------------------------------------- + morphfunc.setParameter("cHq3", 0.01); + morphfunc.setParameter("cHl3", 1.0); + morphfunc.setParameter("cHDD", 0.2); + + auto pseudo_hist = morphfunc.createTH1("pseudo_hist"); + auto pseudo_dh = new RooDataHist("pseudo_dh", "pseudo_dh", RooArgList(obsvar), pseudo_hist); + + // reset parameters to zeros before fit + morphfunc.setParameter("cHq3", 0.0); + morphfunc.setParameter("cHl3", 0.0); + morphfunc.setParameter("cHDD", 0.0); + + // error set used as initial step size + cHq3.setError(0.1); + cHl3.setError(0.1); + cHDD.setError(0.1); + + // W r a p p d f o n m o r p h f u n c a n d + // f i t t o d a t a h i s t o g r a m + // --------------------------------------------- + // wrapper pdf to normalise morphing function to a morphing pdf + RooWrapperPdf model("wrap_pdf", "wrap_pdf", morphfunc); + auto fitres = model.fitTo(*pseudo_dh, SumW2Error(true), Optimize(false), Save()); + auto hcorr = fitres->correlationHist(); + + // E x t r a c t p o s t f i t d i s t r i b u t i o n + // a n d p l o t w i t h i n i t i a l + // h i s t o g r a m + // --------------------------------------------- + auto postfit_hist = morphfunc.createTH1("morphing_postfit_hist"); + RooDataHist postfit_dh("morphing_postfit_dh", "morphing_postfit_dh", RooArgList(obsvar), postfit_hist); + + auto frame0 = obsvar.frame(Title("Fitted histogram of p_{T}^{V}")); + postfit_dh.plotOn(frame0, Name("postfit_dist"), DrawOption("C"), LineColor(kBlue), DataError(RooAbsData::None), + XErrorSize(0)); + pseudo_dh->plotOn(frame0, Name("input")); + + // D r a w p l o t s o n c a n v a s + // --------------------------------------------- + TCanvas *c1 = new TCanvas("fig3", "fig3", 800, 400); + c1->Divide(2, 1); + + c1->cd(1); + gPad->SetLeftMargin(0.15); + gPad->SetRightMargin(0.05); + + model.paramOn(frame0, Layout(0.50, 0.75, 0.9), Parameters(config.couplings)); + frame0->GetXaxis()->SetTitle("p_{T}^{V}"); + frame0->Draw(); + + c1->cd(2); + gPad->SetLeftMargin(0.08); + gPad->SetRightMargin(0.15); + gStyle->SetPaintTextFormat("4.1f"); + gStyle->SetOptStat(0); + hcorr->SetMarkerSize(3.); + hcorr->SetTitle("correlation matrix"); + hcorr->GetYaxis()->SetTitleOffset(1.4); + hcorr->GetYaxis()->SetLabelSize(0.05); + hcorr->GetXaxis()->SetLabelSize(0.05); + hcorr->GetYaxis()->SetBinLabel(1, "c_{HDD}"); + hcorr->GetYaxis()->SetBinLabel(2, "c_{Hl^{(3)}}"); + hcorr->GetYaxis()->SetBinLabel(3, "c_{Hq^{(3)}}"); + hcorr->GetXaxis()->SetBinLabel(1, "c_{HDD}"); + hcorr->GetXaxis()->SetBinLabel(2, "c_{Hl^{(3)}}"); + hcorr->GetXaxis()->SetBinLabel(3, "c_{Hq^{(3)}}"); + hcorr->Draw("colz text"); + c1->SaveAs("rf712_lagrangianmorphfit.png"); +} diff --git a/tutorials/roofit/rf712_lagrangianmorphfit.py b/tutorials/roofit/rf712_lagrangianmorphfit.py new file mode 100644 index 0000000000000..bf6801df01054 --- /dev/null +++ b/tutorials/roofit/rf712_lagrangianmorphfit.py @@ -0,0 +1,147 @@ +## \file +## \ingroup tutorial_roofit +## \notebook -js +## Performing a simple fit with RooLagrangianMorphFunc +## +## \macro_image +## \macro_output +## \macro_code +## +## \date January 2022 +## \authors Rahul Balasubramanian + +import ROOT + +ROOT.gStyle.SetOptStat(0) +ROOT.PyConfig.IgnoreCommandLineOptions = True +ROOT.gROOT.SetBatch(True) + +# Create functions +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +observablename = "pTV" +obsvar = ROOT.RooRealVar(observablename, "observable of pTV", 10, 600) + +# Setup three EFT coefficent and constant SM modifier +kSM = ROOT.RooRealVar("kSM", "sm modifier", 1.0) +cHq3 = ROOT.RooRealVar("cHq3", "EFT modifier", -10.0, 10.0) +cHq3.setAttribute("NewPhysics", True) +cHl3 = ROOT.RooRealVar("cHl3", "EFT modifier", -10.0, 10.0) +cHl3.setAttribute("NewPhysics", True) +cHDD = ROOT.RooRealVar("cHDD", "EFT modifier", -10.0, 10.0) +cHDD.setAttribute("NewPhysics", True) + +# Inputs to setup config +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +infilename = ROOT.gROOT.GetTutorialDir().Data() + "/roofit/input_histos_rf_lagrangianmorph.root"; +par = "cHq3" +samplelist = [ + "SM_NPsq0", + "cHq3_NPsq1", + "cHq3_NPsq2", + "cHl3_NPsq1", + "cHl3_NPsq2", + "cHDD_NPsq1", + "cHDD_NPsq2", + "cHl3_cHDD_NPsq2", + "cHq3_cHDD_NPsq2", + "cHl3_cHq3_NPsq2", +] + +# Set Config +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +config = ROOT.RooLagrangianMorphFunc.Config() +config.fileName = infilename +config.observableName = observablename +config.folderNames = samplelist +config.couplings.add(cHq3) +config.couplings.add(cHDD) +config.couplings.add(cHl3) +config.couplings.add(kSM) + + +# Create morphing function +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +morphfunc = ROOT.RooLagrangianMorphFunc("morphfunc", "morphed dist. of pTV", config) + +# Create pseudo data histogram to fit at cHq3 = 0.01, cHl3 = 1.0, cHDD = 0.2 +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +morphfunc.setParameter("cHq3", 0.01) +morphfunc.setParameter("cHl3", 1.0) +morphfunc.setParameter("cHDD", 0.2) + +pseudo_hist = morphfunc.createTH1("pseudo_hist") +pseudo_dh = ROOT.RooDataHist("pseudo_dh", "pseudo_dh", [obsvar], pseudo_hist) + +# reset parameters to zeros before fit +morphfunc.setParameter("cHq3", 0.0) +morphfunc.setParameter("cHl3", 0.0) +morphfunc.setParameter("cHDD", 0.0) + +# set error to set initial step size in fit +cHq3.setError(0.1) +cHl3.setError(0.1) +cHDD.setError(0.1) + +# Wrap pdf on morphfunc and fit to data histogram +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +# wrapper pdf to normalise morphing function to a morphing pdf +model = ROOT.RooWrapperPdf("wrap_pdf", "wrap_pdf", morphfunc) +fitres = model.fitTo(pseudo_dh, SumW2Error=True, Optimize=False, Save=True) +# run the fit +# Get the correlation matrix +hcorr = fitres.correlationHist() + +# Extract postfit distribution and plot with initial histogram +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +postfit_hist = morphfunc.createTH1("morphing_postfit_hist") +postfit_dh = ROOT.RooDataHist("morphing_postfit_dh", "morphing_postfit_dh", [obsvar], postfit_hist) + +frame0 = obsvar.frame(Title="Input templates for p_{T}^{V}") +postfit_dh.plotOn( + frame0, + Name="postfit_dist", + DrawOption="C", + LineColor=ROOT.kBlue, + DataError=getattr(ROOT.RooAbsData, "None"), + XErrorSize=0, +) +pseudo_dh.plotOn(frame0, Name="input") + +# Draw plots on canvas +# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +c1 = ROOT.TCanvas("fig3", "fig3", 800, 400) +c1.Divide(2, 1) + +c1.cd(1) +ROOT.gPad.SetLeftMargin(0.15) +ROOT.gPad.SetRightMargin(0.05) + +model.paramOn(frame0, ROOT.RooFit.Layout(0.50, 0.75, 0.9)) +frame0.GetXaxis().SetTitle("p_{T}^{V}") +frame0.Draw() + +c1.cd(2) +ROOT.gPad.SetLeftMargin(0.08) +ROOT.gPad.SetRightMargin(0.15) +ROOT.gStyle.SetPaintTextFormat("4.1f") +ROOT.gStyle.SetOptStat(0) +hcorr.SetMarkerSize(3.0) +hcorr.SetTitle("correlation matrix") +hcorr.GetYaxis().SetTitleOffset(1.4) +hcorr.GetYaxis().SetLabelSize(0.05) +hcorr.GetXaxis().SetLabelSize(0.05) +hcorr.GetYaxis().SetBinLabel(1, "c_{HDD}") +hcorr.GetYaxis().SetBinLabel(2, "c_{Hl^{(3)}}") +hcorr.GetYaxis().SetBinLabel(3, "c_{Hq^{(3)}}") +hcorr.GetXaxis().SetBinLabel(1, "c_{HDD}") +hcorr.GetXaxis().SetBinLabel(2, "c_{Hl^{(3)}}") +hcorr.GetXaxis().SetBinLabel(3, "c_{Hq^{(3)}}") +hcorr.GetYaxis().SetTitleOffset(1.4) +hcorr.Draw("colz text") + +c1.SaveAs("rf712_lagrangianmorphfit.png") From ac0e3a8c63d5940128e4392f43733138df7715a1 Mon Sep 17 00:00:00 2001 From: Nicolas Morange Date: Wed, 26 Jan 2022 18:06:04 +0100 Subject: [PATCH 8/9] [RF] Remove safeDeleteList functionality of RooAbsCollection This is presumbaly a bit controversial. safeDeleteList remove elements in order in a RooAbsCollection, starting with the ones that only have clients and no servers. This is a slow process, and takes 25% of CPU time on large workspace manipulation workflows, as it takes place at each workspace::import call. It can also lead to slow ~RooWorkspace. The point is, I don't think this logic is needed at all. ~RooAbsArg takes care of properly breaking all the client-server links, both uplinks and downlinks, for every object. I couldn't find a logical case where a crash would occur if the safeDeleteList logic were to be removed. All RooFit tests pass after this patch. No problem for my heavy workspace manipulation worflows either. --- roofit/roofitcore/inc/RooAbsCollection.h | 2 +- roofit/roofitcore/src/RooAbsCollection.cxx | 45 ++++------------------ 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/roofit/roofitcore/inc/RooAbsCollection.h b/roofit/roofitcore/inc/RooAbsCollection.h index 055600ff04bbc..2020ff09dda43 100644 --- a/roofit/roofitcore/inc/RooAbsCollection.h +++ b/roofit/roofitcore/inc/RooAbsCollection.h @@ -356,7 +356,7 @@ class RooAbsCollection : public TObject, public RooPrintable { TString _name; // Our name. Bool_t _allRRV ; // All contents are RRV - void safeDeleteList() ; + void deleteList() ; // Support for snapshot method Bool_t addServerClonesToList(const RooAbsArg& var) ; diff --git a/roofit/roofitcore/src/RooAbsCollection.cxx b/roofit/roofitcore/src/RooAbsCollection.cxx index 9d44a89477a7a..f3eeb0f34d4e8 100644 --- a/roofit/roofitcore/src/RooAbsCollection.cxx +++ b/roofit/roofitcore/src/RooAbsCollection.cxx @@ -198,52 +198,21 @@ RooAbsCollection::~RooAbsCollection() { // Delete all variables in our list if we own them if(_ownCont){ - safeDeleteList() ; + deleteList() ; } } //////////////////////////////////////////////////////////////////////////////// -/// Examine client server dependencies in list and -/// delete contents in safe order: any client -/// is deleted before a server is deleted +/// Delete contents of the list. +/// The RooAbsArg destructor ensures clients and servers can be deleted in any +/// order. +/// Also cleans the hash-map for fast lookups if present. -void RooAbsCollection::safeDeleteList() +void RooAbsCollection::deleteList() { _hashAssistedFind = nullptr; - // Handle trivial case here - if (_list.size() > 1) { - std::vector tmp; - tmp.reserve(_list.size()); - do { - tmp.clear(); - for (auto arg : _list) { - // Check if arg depends on remainder of list - if (!arg->dependsOn(*this, arg)) tmp.push_back(arg); - } - - // sort and uniquify, in case some elements occur more than once - std::sort(tmp.begin(), tmp.end()); - - tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end()); - // okay, can remove and delete what's in tmp - auto newEnd = _list.end(); - for (auto item : tmp) { - newEnd = std::remove(_list.begin(), newEnd, item); - delete item; - } - _list.erase(newEnd, _list.end()); - } while (!tmp.empty() && _list.size() > 1); - - // Check if there are any remaining elements - if (_list.size() > 1) { - coutW(ObjectHandling) << "RooAbsCollection::safeDeleteList(" << GetName() - << ") WARNING: unable to delete following elements in client-server order " ; - Print("1") ; - } - } - // Built-in delete remaining elements for (auto item : _list) { delete item; @@ -757,7 +726,7 @@ void RooAbsCollection::removeAll() _hashAssistedFind = nullptr; if(_ownCont) { - safeDeleteList() ; + deleteList() ; _ownCont= kFALSE; } else { From ba6c7d3ef9465767b4ad907d75df3edf9a2ec125 Mon Sep 17 00:00:00 2001 From: Nicolas Morange Date: Wed, 26 Jan 2022 18:09:21 +0100 Subject: [PATCH 9/9] [RF] Include RooAbsData objects in the RooNameReg [RF] Include RooAbsData objects in the RooNameReg The logic from RooAbsArg is copied into RooAbsData. This allows to use the hash-map functionality of RooLinkedList for RooAbsData objects, as the namePtr mechanism allows to track renaming and therefore avoids false negatives that result in linear scans of the collection. In turn, this improves significantly the run-time of large workspace imports (x2 to x4), which were dominated by embeddedData() calls. This patch is based on the JSON tool use-case, but presumably will significantly also improve other heavy uses of workspace import, such as Higgs combination workspaces manipulation workflows. The cost of one additional pointer per RooAbsData object seems a low price to pay. --- roofit/roofitcore/inc/RooAbsArg.h | 2 +- roofit/roofitcore/inc/RooAbsData.h | 17 +++++++++++ roofit/roofitcore/inc/RooNameReg.h | 1 + roofit/roofitcore/src/RooAbsArg.cxx | 37 ++++++++--------------- roofit/roofitcore/src/RooAbsData.cxx | 39 +++++++++++++++++++++++-- roofit/roofitcore/src/RooLinkedList.cxx | 33 +++++++++++---------- 6 files changed, 85 insertions(+), 44 deletions(-) diff --git a/roofit/roofitcore/inc/RooAbsArg.h b/roofit/roofitcore/inc/RooAbsArg.h index c22f7242d1278..374d54d7dda62 100644 --- a/roofit/roofitcore/inc/RooAbsArg.h +++ b/roofit/roofitcore/inc/RooAbsArg.h @@ -726,7 +726,7 @@ class RooAbsArg : public TNamed, public RooPrintable { mutable RooExpensiveObjectCache* _eocache{nullptr}; // Pointer to global cache manager for any expensive components created by this object - mutable TNamed* _namePtr ; //! De-duplicated name pointer. This will be equal for all objects with the same name. + mutable const TNamed * _namePtr ; //! De-duplicated name pointer. This will be equal for all objects with the same name. Bool_t _isConstant ; //! Cached isConstant status mutable Bool_t _localNoInhibitDirty ; //! Prevent 'AlwaysDirty' mode for this node diff --git a/roofit/roofitcore/inc/RooAbsData.h b/roofit/roofitcore/inc/RooAbsData.h index 13441c912493a..6a672339a3afe 100644 --- a/roofit/roofitcore/inc/RooAbsData.h +++ b/roofit/roofitcore/inc/RooAbsData.h @@ -21,6 +21,7 @@ #include "RooArgSet.h" #include "RooArgList.h" #include "RooSpan.h" +#include "RooNameReg.h" #include "ROOT/RStringView.hxx" #include "TNamed.h" @@ -311,6 +312,20 @@ class RooAbsData : public TNamed, public RooPrintable { RooArgSet const* getGlobalObservables() const { return _globalObservables.get(); } void setGlobalObservables(RooArgSet const& globalObservables); + /// De-duplicated pointer to this object's name. + /// This can be used for fast name comparisons. + /// like `if (namePtr() == other.namePtr())`. + /// \note TNamed::GetName() will return a pointer that's + /// different for each object, but namePtr() always points + /// to a unique instance. + inline const TNamed* namePtr() const { + return _namePtr ; + } + + void SetName(const char* name) ; + void SetNameTitle(const char *name, const char *title) ; + + protected: static StorageType defaultStorageType ; @@ -360,6 +375,8 @@ class RooAbsData : public TNamed, public RooPrintable { std::unique_ptr _globalObservables; // Snapshot of global observables + mutable const TNamed * _namePtr ; //! De-duplicated name pointer. This will be equal for all objects with the same name. + private: void copyGlobalObservables(const RooAbsData& other); diff --git a/roofit/roofitcore/inc/RooNameReg.h b/roofit/roofitcore/inc/RooNameReg.h index 2b64da3ec321f..188a47f5e6cdd 100644 --- a/roofit/roofitcore/inc/RooNameReg.h +++ b/roofit/roofitcore/inc/RooNameReg.h @@ -44,6 +44,7 @@ class RooNameReg : public TNamed { RooNameReg(const RooNameReg& other) = delete; friend class RooAbsArg; + friend class RooAbsData; static void incrementRenameCounter() ; std::unordered_map> _map; diff --git a/roofit/roofitcore/src/RooAbsArg.cxx b/roofit/roofitcore/src/RooAbsArg.cxx index 5f0b7518c3826..713d1252ad0e4 100644 --- a/roofit/roofitcore/src/RooAbsArg.cxx +++ b/roofit/roofitcore/src/RooAbsArg.cxx @@ -127,7 +127,7 @@ RooAbsArg::RooAbsArg() _prohibitServerRedirect(kFALSE), _namePtr(0), _isConstant(kFALSE), _localNoInhibitDirty(kFALSE), _myws(0) { - _namePtr = (TNamed*) RooNameReg::instance().constPtr(GetName()) ; + _namePtr = RooNameReg::instance().constPtr(GetName()) ; } @@ -145,7 +145,7 @@ RooAbsArg::RooAbsArg(const char *name, const char *title) throw std::logic_error("Each RooFit object needs a name. " "Objects representing the same entity (e.g. an observable 'x') are identified using their name."); } - _namePtr = (TNamed*) RooNameReg::instance().constPtr(GetName()) ; + _namePtr = RooNameReg::instance().constPtr(GetName()) ; } //////////////////////////////////////////////////////////////////////////////// @@ -153,20 +153,13 @@ RooAbsArg::RooAbsArg(const char *name, const char *title) /// object. Transient properties and client-server links are not copied RooAbsArg::RooAbsArg(const RooAbsArg &other, const char *name) - : TNamed(other.GetName(), other.GetTitle()), RooPrintable(other), _boolAttrib(other._boolAttrib), + : TNamed(name ? name : other.GetName(), other.GetTitle()), RooPrintable(other), + _boolAttrib(other._boolAttrib), _stringAttrib(other._stringAttrib), _deleteWatch(other._deleteWatch), _operMode(Auto), _fast(kFALSE), - _ownedComponents(0), _prohibitServerRedirect(kFALSE), _namePtr(other._namePtr), + _ownedComponents(0), _prohibitServerRedirect(kFALSE), + _namePtr(name ? RooNameReg::instance().constPtr(name) : other._namePtr), _isConstant(other._isConstant), _localNoInhibitDirty(other._localNoInhibitDirty), _myws(0) { - // Use name in argument, if supplied - if (name) { - TNamed::SetName(name) ; - _namePtr = (TNamed*) RooNameReg::instance().constPtr(name) ; - } else { - // Same name, don't recalculate name pointer (expensive) - TNamed::SetName(other.GetName()) ; - _namePtr = other._namePtr ; - } // Copy server list by hand Bool_t valueProp, shapeProp ; @@ -2310,7 +2303,7 @@ RooAbsArg* RooAbsArg::cloneTree(const char* newname) const // Adjust name of head node if requested if (newname) { head->TNamed::SetName(newname) ; - head->_namePtr = (TNamed*) RooNameReg::instance().constPtr(newname) ; + head->_namePtr = RooNameReg::instance().constPtr(newname) ; } // Return the head @@ -2382,11 +2375,11 @@ void RooAbsArg::wireAllCaches() void RooAbsArg::SetName(const char* name) { TNamed::SetName(name) ; - TNamed* newPtr = (TNamed*) RooNameReg::instance().constPtr(GetName()) ; + auto newPtr = RooNameReg::instance().constPtr(GetName()) ; if (newPtr != _namePtr) { //cout << "Rename '" << _namePtr->GetName() << "' to '" << name << "' (set flag in new name)" << endl; _namePtr = newPtr; - _namePtr->SetBit(RooNameReg::kRenamedArg); + const_cast(_namePtr)->SetBit(RooNameReg::kRenamedArg); RooNameReg::incrementRenameCounter(); } } @@ -2398,14 +2391,8 @@ void RooAbsArg::SetName(const char* name) void RooAbsArg::SetNameTitle(const char *name, const char *title) { - TNamed::SetNameTitle(name,title) ; - TNamed* newPtr = (TNamed*) RooNameReg::instance().constPtr(GetName()) ; - if (newPtr != _namePtr) { - //cout << "Rename '" << _namePtr->GetName() << "' to '" << name << "' (set flag in new name)" << endl; - _namePtr = newPtr; - _namePtr->SetBit(RooNameReg::kRenamedArg); - RooNameReg::incrementRenameCounter(); - } + TNamed::SetTitle(title) ; + SetName(name); } @@ -2418,7 +2405,7 @@ void RooAbsArg::Streamer(TBuffer &R__b) _ioReadStack.push(this) ; R__b.ReadClassBuffer(RooAbsArg::Class(),this); _ioReadStack.pop() ; - _namePtr = (TNamed*) RooNameReg::instance().constPtr(GetName()) ; + _namePtr = RooNameReg::instance().constPtr(GetName()) ; _isConstant = getAttribute("Constant") ; } else { R__b.WriteClassBuffer(RooAbsArg::Class(),this); diff --git a/roofit/roofitcore/src/RooAbsData.cxx b/roofit/roofitcore/src/RooAbsData.cxx index c57888b1d89bf..23b2fa7d36e5c 100644 --- a/roofit/roofitcore/src/RooAbsData.cxx +++ b/roofit/roofitcore/src/RooAbsData.cxx @@ -187,7 +187,8 @@ RooAbsData::RooAbsData(std::string_view name, std::string_view title, const RooA TNamed(TString{name},TString{title}), _vars("Dataset Variables"), _cachedVars("Cached Variables"), - _dstore(dstore) + _dstore(dstore), + _namePtr(nullptr) { if (dynamic_cast(dstore)) { storageType = RooAbsData::Tree; @@ -216,6 +217,8 @@ RooAbsData::RooAbsData(std::string_view name, std::string_view title, const RooA var->attachArgs(_vars); } + _namePtr = RooNameReg::instance().constPtr(GetName()) ; + RooTrace::create(this); } @@ -223,9 +226,10 @@ RooAbsData::RooAbsData(std::string_view name, std::string_view title, const RooA /// Copy constructor RooAbsData::RooAbsData(const RooAbsData& other, const char* newname) : - TNamed(newname?newname:other.GetName(),other.GetTitle()), + TNamed(newname ? newname : other.GetName(),other.GetTitle()), RooPrintable(other), _vars(), - _cachedVars("Cached Variables") + _cachedVars("Cached Variables"), + _namePtr(newname ? RooNameReg::instance().constPtr(newname) : other._namePtr) { //cout << "created dataset " << this << endl ; claimVars(this) ; @@ -271,6 +275,7 @@ RooAbsData& RooAbsData::operator=(const RooAbsData& other) { claimVars(this); _vars.Clear(); _vars.addClone(other._vars); + _namePtr = other._namePtr; // reconnect any parameterized ranges to internal dataset observables for (const auto var : _vars) { @@ -2454,6 +2459,7 @@ void RooAbsData::Streamer(TBuffer &R__b) { if (R__b.IsReading()) { R__b.ReadClassBuffer(RooAbsData::Class(),this); + _namePtr = RooNameReg::instance().constPtr(GetName()) ; // Convert on the fly to vector storage if that the current working default if (defaultStorageType==RooAbsData::Vector) { @@ -2563,6 +2569,33 @@ void RooAbsData::setGlobalObservables(RooArgSet const& globalObservables) { } +//////////////////////////////////////////////////////////////////////////////// + +void RooAbsData::SetName(const char* name) +{ + TNamed::SetName(name) ; + auto newPtr = RooNameReg::instance().constPtr(GetName()) ; + if (newPtr != _namePtr) { + //cout << "Rename '" << _namePtr->GetName() << "' to '" << name << "' (set flag in new name)" << endl; + _namePtr = newPtr; + const_cast(_namePtr)->SetBit(RooNameReg::kRenamedArg); + RooNameReg::incrementRenameCounter(); + } +} + + + + +//////////////////////////////////////////////////////////////////////////////// + +void RooAbsData::SetNameTitle(const char *name, const char *title) +{ + TNamed::SetTitle(title) ; + SetName(name); +} + + + //////////////////////////////////////////////////////////////////////////////// /// Return sum of squared weights of this data. diff --git a/roofit/roofitcore/src/RooLinkedList.cxx b/roofit/roofitcore/src/RooLinkedList.cxx index 87beba877c8b3..576ce8f71d7b1 100644 --- a/roofit/roofitcore/src/RooLinkedList.cxx +++ b/roofit/roofitcore/src/RooLinkedList.cxx @@ -32,6 +32,7 @@ Use RooAbsCollection derived objects for public use #include "RooFit.h" #include "RooLinkedListIter.h" #include "RooAbsArg.h" +#include "RooAbsData.h" #include "RooMsgService.h" #include "Riostream.h" @@ -261,7 +262,7 @@ RooLinkedList::Pool* RooLinkedList::_pool = 0; //////////////////////////////////////////////////////////////////////////////// RooLinkedList::RooLinkedList(Int_t htsize) : - _hashThresh(htsize), _size(0), _first(0), _last(0), _htableName(nullptr), _htableLink(nullptr), _useNptr(kTRUE) + _hashThresh(htsize), _size(0), _first(0), _last(0), _htableName(nullptr), _htableLink(nullptr), _useNptr(true) { if (!_pool) _pool = new Pool; _pool->acquire(); @@ -404,7 +405,7 @@ void RooLinkedList::Add(TObject* arg, Int_t refCount) if (!arg) return ; // Only use RooAbsArg::namePtr() in lookup-by-name if all elements have it - if (!dynamic_cast(arg)) _useNptr = kFALSE; + if (!dynamic_cast(arg) && !dynamic_cast(arg)) _useNptr = false; // Add to hash table if (_htableName) { @@ -447,7 +448,7 @@ Bool_t RooLinkedList::Remove(TObject* arg) { // Find link element RooLinkedListElem* elem = findLink(arg) ; - if (!elem) return kFALSE ; + if (!elem) return false ; // Remove from hash table if (_htableName) { @@ -468,7 +469,7 @@ Bool_t RooLinkedList::Remove(TObject* arg) // Delete and shrink _size-- ; deleteElement(elem) ; - return kTRUE ; + return true ; } //////////////////////////////////////////////////////////////////////////////// @@ -502,13 +503,13 @@ TObject* RooLinkedList::At(Int_t index) const //////////////////////////////////////////////////////////////////////////////// /// Replace object 'oldArg' in collection with new object 'newArg'. -/// If 'oldArg' is not found in collection kFALSE is returned +/// If 'oldArg' is not found in collection false is returned Bool_t RooLinkedList::Replace(const TObject* oldArg, const TObject* newArg) { // Find existing element and replace arg RooLinkedListElem* elem = findLink(oldArg) ; - if (!elem) return kFALSE ; + if (!elem) return false ; if (_htableName) { _htableName->erase(oldArg->GetName()); @@ -521,7 +522,7 @@ Bool_t RooLinkedList::Replace(const TObject* oldArg, const TObject* newArg) } elem->_arg = (TObject*)newArg ; - return kTRUE ; + return true ; } //////////////////////////////////////////////////////////////////////////////// @@ -604,7 +605,7 @@ TObject* RooLinkedList::find(const char* name) const { if (_htableName) { - RooAbsArg* a = (RooAbsArg*) (*_htableName)[name] ; + TObject *a = const_cast((*_htableName)[name]) ; // RooHashTable::find could return false negative if element was renamed to 'name'. // The list search means it won't return false positive, so can return here. if (a) return a; @@ -615,13 +616,14 @@ TObject* RooLinkedList::find(const char* name) const if (nptr && nptr->TestBit(RooNameReg::kRenamedArg)) { RooLinkedListElem* ptr = _first ; while(ptr) { - if ((((RooAbsArg*)ptr->_arg)->namePtr() == nptr)) { + if ( (dynamic_cast(ptr->_arg) && static_cast(ptr->_arg)->namePtr() == nptr) || + (dynamic_cast(ptr->_arg) && static_cast(ptr->_arg)->namePtr() == nptr)) { return ptr->_arg ; } ptr = ptr->_next ; } } - return 0 ; + return nullptr ; } //cout << "RooLinkedList::find: possibly renamed '" << name << "'" << endl; } @@ -632,15 +634,16 @@ TObject* RooLinkedList::find(const char* name) const // when the size list is longer than ~7, but let's be a bit conservative. if (_useNptr && _size>9) { const TNamed* nptr= RooNameReg::known(name); - if (!nptr) return 0; + if (!nptr) return nullptr; while(ptr) { - if ((((RooAbsArg*)ptr->_arg)->namePtr() == nptr)) { - return ptr->_arg ; + if ( (dynamic_cast(ptr->_arg) && static_cast(ptr->_arg)->namePtr() == nptr) || + (dynamic_cast(ptr->_arg) && static_cast(ptr->_arg)->namePtr() == nptr)) { + return ptr->_arg ; } ptr = ptr->_next ; } - return 0 ; + return nullptr ; } while(ptr) { @@ -649,7 +652,7 @@ TObject* RooLinkedList::find(const char* name) const } ptr = ptr->_next ; } - return 0 ; + return nullptr ; } ////////////////////////////////////////////////////////////////////////////////