Skip to content

Commit ed429da

Browse files
authored
[RF][HS3] Allow registering JSONIO exporter by class name
When instantiating an Exporter like this: ``` STATIC_EXECUTE([]() { using namespace RooFit::JSONIO; registerImporter<MYCLASSFactory>("key", false); registerExporter<MYCLASSStreamer>(MYCLASS::Class(), false); }); ``` it is possible that this will segfault, as `MYCLASS` might not be sufficiently loaded yet to permit `MYCLASS::Class()` to be called. In order to circumvent this problem, a new method has been added: With `registerExporter<MYCLASSStreamer>("MYCLASS", false);`, it is now possible to add an exporter in a "delayed" fashion, where it will only be actually added to the exporter list when to RooJSONFactoryWSTool is invoked - before that, it will linger in a temprorary "Loader" helper object.
1 parent 224a485 commit ed429da

File tree

3 files changed

+172
-29
lines changed

3 files changed

+172
-29
lines changed

roofit/hs3/inc/RooFitHS3/JSONIO.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ using ExportMap = std::map<TClass const *, std::vector<std::unique_ptr<const Exp
7676
using ExportKeysMap = std::map<TClass const *, ExportKeys>;
7777
using ImportExpressionMap = std::map<const std::string, ImportExpression>;
7878

79-
void setupKeys();
8079
ImportMap &importers();
8180
ExportMap &exporters();
8281
ImportExpressionMap &importExpressions();
@@ -92,9 +91,15 @@ static bool registerExporter(const TClass *key, bool topPriority = true)
9291
{
9392
return registerExporter(key, std::make_unique<T>(), topPriority);
9493
}
94+
template <class T>
95+
static bool registerExporter(const std::string &key, bool topPriority = true)
96+
{
97+
return registerExporter(key, std::make_unique<T>(), topPriority);
98+
}
9599

96100
bool registerImporter(const std::string &key, std::unique_ptr<const Importer> f, bool topPriority = true);
97101
bool registerExporter(const TClass *key, std::unique_ptr<const Exporter> f, bool topPriority = true);
102+
bool registerExporter(const std::string &key, std::unique_ptr<const Exporter> f, bool topPriority = true);
98103
int removeImporters(const std::string &needle);
99104
int removeExporters(const std::string &needle);
100105
void printImporters();

roofit/hs3/src/JSONIO.cxx

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@
1818

1919
#include <TClass.h>
2020

21-
#include <iostream>
2221
#include <fstream>
22+
#include <iostream>
23+
#include <mutex>
2324
#include <sstream>
2425

2526
// Include raw strings with initial export and import keys in JSON
2627
#include "RooFitHS3_wsexportkeys.cxx"
2728
#include "RooFitHS3_wsfactoryexpressions.cxx"
2829

29-
namespace RooFit {
30-
namespace JSONIO {
30+
namespace RooFit::JSONIO {
3131

32-
void setupKeys()
32+
void setupExportKeys()
3333
{
3434
static bool isAlreadySetup = false;
3535
if (isAlreadySetup) {
@@ -38,16 +38,23 @@ void setupKeys()
3838

3939
isAlreadySetup = true;
4040

41-
{
42-
std::stringstream exportkeys;
43-
exportkeys << RooFitHS3_wsexportkeys;
44-
loadExportKeys(exportkeys);
45-
}
46-
{
47-
std::stringstream factoryexpressions;
48-
factoryexpressions << RooFitHS3_wsfactoryexpressions;
49-
loadFactoryExpressions(factoryexpressions);
41+
std::stringstream exportkeys;
42+
exportkeys << RooFitHS3_wsexportkeys;
43+
loadExportKeys(exportkeys);
44+
}
45+
46+
void setupFactoryExpressions()
47+
{
48+
static bool isAlreadySetup = false;
49+
if (isAlreadySetup) {
50+
return;
5051
}
52+
53+
isAlreadySetup = true;
54+
55+
std::stringstream factoryexpressions;
56+
factoryexpressions << RooFitHS3_wsfactoryexpressions;
57+
loadFactoryExpressions(factoryexpressions);
5158
}
5259

5360
ImportMap &importers()
@@ -56,22 +63,59 @@ ImportMap &importers()
5663
return _importers;
5764
}
5865

59-
ExportMap &exporters()
66+
namespace {
67+
68+
auto &exportersToAdd()
69+
{
70+
static std::map<const std::string, std::vector<std::unique_ptr<const Exporter>>> toAdd;
71+
return toAdd;
72+
}
73+
74+
ExportMap &exportersImpl()
6075
{
6176
static ExportMap _exporters;
6277
return _exporters;
6378
}
6479

80+
} // namespace
81+
82+
ExportMap &exporters()
83+
{
84+
// If there are exporters to be added, do this now.
85+
for (auto &item : exportersToAdd()) {
86+
TClass *klass = TClass::GetClass(item.first.c_str());
87+
auto &exporters = exportersImpl()[klass]; // registered exporters so far
88+
auto &toAdd = item.second; // exporters to add
89+
90+
// Find the nullptr separator
91+
auto nullIt = std::find(toAdd.begin(), toAdd.end(), nullptr);
92+
93+
if (nullIt == toAdd.end()) {
94+
throw std::runtime_error("toAdd does not contain nullptr separator");
95+
}
96+
97+
// Move elements after nullptr to the back
98+
exporters.insert(exporters.end(), std::make_move_iterator(nullIt + 1), std::make_move_iterator(toAdd.end()));
99+
100+
// Move elements before nullptr to the front
101+
exporters.insert(exporters.begin(), std::make_move_iterator(toAdd.begin()), std::make_move_iterator(nullIt));
102+
103+
toAdd.clear();
104+
}
105+
exportersToAdd().clear();
106+
return exportersImpl();
107+
}
108+
65109
ImportExpressionMap &importExpressions()
66110
{
67-
setupKeys();
111+
setupFactoryExpressions();
68112
static ImportExpressionMap _factoryExpressions;
69113
return _factoryExpressions;
70114
}
71115

72116
ExportKeysMap &exportKeys()
73117
{
74-
setupKeys();
118+
setupExportKeys();
75119
static ExportKeysMap _exportKeys;
76120
return _exportKeys;
77121
}
@@ -90,6 +134,19 @@ bool registerExporter(const TClass *key, std::unique_ptr<const Exporter> f, bool
90134
return true;
91135
}
92136

137+
bool registerExporter(const std::string &key, std::unique_ptr<const Exporter> f, bool topPriority)
138+
{
139+
auto &vec = exportersToAdd()[key];
140+
// The vector starts out with just a nullptr separator. Elements before it
141+
// will be added to the top of the exporter queue, and the elements after
142+
// get appended to the end.
143+
if (vec.empty()) {
144+
vec.emplace_back(nullptr);
145+
}
146+
vec.insert(topPriority ? vec.begin() : vec.end(), std::move(f));
147+
return true;
148+
}
149+
93150
int removeImporters(const std::string &needle)
94151
{
95152
int n = 0;
@@ -269,5 +326,4 @@ void printExportKeys()
269326
}
270327
}
271328

272-
} // namespace JSONIO
273-
} // namespace RooFit
329+
} // namespace RooFit::JSONIO

roofit/hs3/test/testRooFitHS3.cxx

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ int validate(RooWorkspace &ws1, std::string const &argName, bool exact = true)
4444
{
4545
RooWorkspace ws2;
4646

47-
ws1.Print();
48-
4947
const std::string json1 = RooJSONFactoryWSTool{ws1}.exportJSONtoString();
5048

5149
if (writeJsonFiles) {
@@ -118,8 +116,7 @@ int validate(std::vector<std::string> const &expressions, bool exact = true)
118116
{
119117
RooWorkspace ws;
120118
for (std::size_t iExpr = 0; iExpr < expressions.size() - 1; ++iExpr) {
121-
std::cout << expressions[iExpr] << std::endl;
122-
ws.factory(expressions[iExpr])->Print();
119+
ws.factory(expressions[iExpr]);
123120
}
124121
const std::string argName = ws.factory(expressions.back())->GetName();
125122
return validate(ws, argName, exact);
@@ -433,8 +430,8 @@ TEST(RooFitHS3, SimultaneousGaussians)
433430
// https://github.com/root-project/root/issues/14637
434431
TEST(RooFitHS3, ScientificNotation)
435432
{
436-
RooRealVar v1("v1","v1",1.0);
437-
RooRealVar v2("v2","v2",1.0);
433+
RooRealVar v1("v1", "v1", 1.0);
434+
RooRealVar v2("v2", "v2", 1.0);
438435

439436
// make a formula that is some parameters times some numbers
440437
auto thestring = "@0*0.2e-6 + @1*0.1";
@@ -445,18 +442,19 @@ TEST(RooFitHS3, ScientificNotation)
445442
RooFormulaVar fvBad("fvBad", "fvBad", thestring, arglist);
446443

447444
// make gaussian with mean as that formula
448-
RooRealVar x("x","x",0.0,-5.0,5.0);
449-
RooGaussian g("g","g",x, fvBad, 1.0);
445+
RooRealVar x("x", "x", 0.0, -5.0, 5.0);
446+
RooGaussian g("g", "g", x, fvBad, 1.0);
450447

451448
RooWorkspace ws("ws");
452449
ws.import(g);
453-
//std::cout << (fvBad.expression()) << std::endl;
450+
// std::cout << (fvBad.expression()) << std::endl;
454451

455452
// export to json
456453
RooJSONFactoryWSTool t(ws);
457454
auto jsonStr = t.exportJSONtoString();
458455

459-
// try to import, before the fix, it threw RooJSONFactoryWSTool::DependencyMissingError because of problem reading the exponential char
456+
// try to import, before the fix, it threw RooJSONFactoryWSTool::DependencyMissingError because of problem reading
457+
// the exponential char
460458
RooWorkspace newws("newws");
461459
RooJSONFactoryWSTool t2(newws);
462460
ASSERT_TRUE(t2.importJSONfromString(jsonStr));
@@ -557,3 +555,87 @@ TEST(RooFitHS3, RooSpline)
557555
const int status = validate(ws, "spline", /*exact=*/true);
558556
EXPECT_EQ(status, 0);
559557
}
558+
559+
namespace {
560+
561+
class TestExporterA final : public RooFit::JSONIO::Exporter {
562+
public:
563+
std::string const &key() const override
564+
{
565+
static const std::string k{"unit_test_exporter_A"};
566+
return k;
567+
}
568+
bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *, RooFit::Detail::JSONNode &) const override
569+
{
570+
callCounter()++;
571+
return true; // do nothing, just for test
572+
}
573+
static int &callCounter()
574+
{
575+
static int counter = 0;
576+
return counter;
577+
}
578+
};
579+
580+
template <int N>
581+
class TestExporter final : public RooFit::JSONIO::Exporter {
582+
public:
583+
std::string const &key() const override
584+
{
585+
static const std::string k{"unit_test_exporter"};
586+
return k;
587+
}
588+
bool exportObject(RooJSONFactoryWSTool *, const RooAbsArg *, RooFit::Detail::JSONNode &) const override
589+
{
590+
callCounter()++;
591+
return true; // do nothing, just for test
592+
}
593+
static int &callCounter()
594+
{
595+
static int counter = 0;
596+
return counter;
597+
}
598+
};
599+
600+
} // namespace
601+
602+
// Test the custom exporter registration mechanism.
603+
TEST(RooFitHS3, RegisterExporterByClassName)
604+
{
605+
using RooFit::JSONIO::registerExporter;
606+
607+
constexpr const char *className = "RooGaussian";
608+
TClass *klass = TClass::GetClass(className);
609+
ASSERT_NE(klass, nullptr);
610+
611+
RooWorkspace ws{"ws"};
612+
ws.factory("RooGaussian::model(x[-10, 10], mu[-10, 10], sigma[2., 0.01, 10])");
613+
614+
// 1. Add new exporter by class pointer with top priority.
615+
// We expect this to get used.
616+
registerExporter<TestExporter<1>>(klass, /*topPriotiry=*/true);
617+
RooJSONFactoryWSTool{ws}.exportJSONtoString();
618+
EXPECT_EQ(TestExporter<1>::callCounter()--, 1);
619+
620+
// 2. Add new exporter by class pointer with bottom priority.
621+
// We expect the previous TestExporter<1> to still be used.
622+
registerExporter<TestExporter<2>>(klass, /*topPriotiry=*/false);
623+
RooJSONFactoryWSTool{ws}.exportJSONtoString();
624+
EXPECT_EQ(TestExporter<1>::callCounter()--, 1);
625+
626+
// 3. Add new exporter by name with top priority.
627+
// We expect this to get used.
628+
registerExporter<TestExporter<3>>(std::string{className}, /*topPriotiry=*/true);
629+
RooJSONFactoryWSTool{ws}.exportJSONtoString();
630+
EXPECT_EQ(TestExporter<3>::callCounter()--, 1);
631+
632+
// 4. Add new exporter by name with bottom priority.
633+
// We expect the previous TestExporter<3> to still be used.
634+
registerExporter<TestExporter<4>>(std::string{className}, /*topPriotiry=*/false);
635+
RooJSONFactoryWSTool{ws}.exportJSONtoString();
636+
EXPECT_EQ(TestExporter<3>::callCounter()--, 1);
637+
638+
// Cleanup for other tests, also making sure the expected number of
639+
// exporters is removed.
640+
EXPECT_EQ(RooFit::JSONIO::removeExporters("TestExporter"), 4);
641+
}

0 commit comments

Comments
 (0)