@@ -146,6 +146,38 @@ std::string parameterStepWidthsNode(std::string const &json)
146146 return json.substr (begin, end - begin + 1 );
147147}
148148
149+ std::string defaultDomainAxesNode (std::string const &json)
150+ {
151+ const std::string key = " \" domains\" :[" ;
152+ const auto domainsBegin = json.find (key);
153+ if (domainsBegin == std::string::npos) {
154+ return " " ;
155+ }
156+ const auto axesBegin = json.find (" \" axes\" :[" , domainsBegin);
157+ if (axesBegin == std::string::npos) {
158+ return " " ;
159+ }
160+ const auto axesEnd = json.find (" }]" , axesBegin);
161+ if (axesEnd == std::string::npos) {
162+ return " " ;
163+ }
164+ return json.substr (axesBegin, axesEnd - axesBegin + 2 );
165+ }
166+
167+ class ScopedNoDomainConstVarImportFlag {
168+ public:
169+ explicit ScopedNoDomainConstVarImportFlag (bool value)
170+ : _oldValue{RooJSONFactoryWSTool::importNoDomainParametersAsRooConstVars}
171+ {
172+ RooJSONFactoryWSTool::importNoDomainParametersAsRooConstVars = value;
173+ }
174+
175+ ~ScopedNoDomainConstVarImportFlag () { RooJSONFactoryWSTool::importNoDomainParametersAsRooConstVars = _oldValue; }
176+
177+ private:
178+ bool _oldValue;
179+ };
180+
149181} // namespace
150182
151183// Test that the IO of attributes and string attributes works.
@@ -294,6 +326,7 @@ TEST(RooFitHS3, ParameterStepWidthsImportAfterDefaultSnapshot)
294326 })" ;
295327
296328 RooWorkspace ws{" workspace" };
329+ ScopedNoDomainConstVarImportFlag flagGuard{false };
297330 ASSERT_TRUE (RooJSONFactoryWSTool{ws}.importJSONfromString (json));
298331
299332 ASSERT_NE (ws.var (" mu" ), nullptr );
@@ -395,6 +428,111 @@ TEST(RooFitHS3, RooGaussian)
395428 EXPECT_EQ (status, 0 );
396429}
397430
431+ TEST (RooFitHS3, RooGaussianConstVarSigmaExport)
432+ {
433+ ScopedNoDomainConstVarImportFlag flagGuard{true };
434+
435+ RooRealVar x{" x" , " x" , 0.0 , -10.0 , 10.0 };
436+ RooRealVar mean{" mean" , " mean" , 0.0 };
437+ mean.setConstant (true );
438+
439+ RooConstVar sigmaConst{" sigma_const" , " sigma_const" , 1.0 };
440+ RooGaussian gaussConst{" gauss_const" , " gauss_const" , x, mean, sigmaConst};
441+
442+ RooGaussian gaussLiteral{" gauss_literal" , " gauss_literal" , x, mean, RooFit::RooConst (2.0 )};
443+
444+ RooRealVar sigmaReal{" sigma_real" , " sigma_real" , 1.0 , 0.1 , 10.0 };
445+ sigmaReal.setConstant (true );
446+ RooGaussian gaussReal{" gauss_real" , " gauss_real" , x, mean, sigmaReal};
447+
448+ RooWorkspace ws;
449+ ws.import (gaussConst, RooFit::Silence ());
450+ ws.import (gaussLiteral, RooFit::RecycleConflictNodes (), RooFit::Silence ());
451+ ws.import (gaussReal, RooFit::RecycleConflictNodes (), RooFit::Silence ());
452+
453+ const std::string json = RooJSONFactoryWSTool{ws}.exportJSONtoString ();
454+ const std::string domainAxes = defaultDomainAxesNode (json);
455+ ASSERT_FALSE (domainAxes.empty ()) << json;
456+
457+ EXPECT_NE (json.find (" \" sigma\" :\" sigma_const\" " ), std::string::npos);
458+ EXPECT_NE (json.find (" \" name\" :\" sigma_const\" " ), std::string::npos);
459+ EXPECT_EQ (json.find (" is_const_var" ), std::string::npos);
460+ EXPECT_EQ (json.find (" \" sigma\" :1.0" ), std::string::npos);
461+ EXPECT_NE (json.find (" \" sigma\" :2.0" ), std::string::npos);
462+ EXPECT_EQ (domainAxes.find (" \" name\" :\" sigma_const\" " ), std::string::npos) << domainAxes;
463+
464+ EXPECT_NE (json.find (" \" sigma\" :\" sigma_real\" " ), std::string::npos);
465+ EXPECT_NE (json.find (" \" name\" :\" sigma_real\" " ), std::string::npos);
466+ EXPECT_NE (domainAxes.find (" \" name\" :\" sigma_real\" " ), std::string::npos) << domainAxes;
467+
468+ // The unbounded constant RooRealVar is still a RooRealVar, so it gets an
469+ // empty domain axis that distinguishes it from a RooConstVar.
470+ EXPECT_NE (domainAxes.find (" \" name\" :\" mean\" " ), std::string::npos) << domainAxes;
471+ EXPECT_EQ (domainAxes.find (" \" name\" :\" mean\" ,\" min\" " ), std::string::npos) << domainAxes;
472+ EXPECT_EQ (domainAxes.find (" \" name\" :\" mean\" ,\" max\" " ), std::string::npos) << domainAxes;
473+
474+ RooWorkspace imported;
475+ ASSERT_TRUE (RooJSONFactoryWSTool{imported}.importJSONfromString (json));
476+ EXPECT_NE (dynamic_cast <RooConstVar *>(imported.obj (" sigma_const" )), nullptr );
477+ EXPECT_EQ (imported.var (" sigma_const" ), nullptr );
478+ EXPECT_NE (dynamic_cast <RooRealVar *>(imported.obj (" sigma_real" )), nullptr );
479+ EXPECT_NE (dynamic_cast <RooRealVar *>(imported.obj (" mean" )), nullptr );
480+
481+ const std::string roundTripJson = RooJSONFactoryWSTool{imported}.exportJSONtoString ();
482+ const std::string roundTripDomainAxes = defaultDomainAxesNode (roundTripJson);
483+ EXPECT_NE (roundTripJson.find (" \" sigma\" :\" sigma_const\" " ), std::string::npos);
484+ EXPECT_EQ (roundTripJson.find (" is_const_var" ), std::string::npos);
485+ EXPECT_EQ (roundTripDomainAxes.find (" \" name\" :\" sigma_const\" " ), std::string::npos) << roundTripDomainAxes;
486+
487+ const std::string legacyJson = R"( {
488+ "metadata":{"hs3_version":"0.2"},
489+ "parameter_points":[{"name":"default_values","parameters":[
490+ {"name":"x","value":0.0},
491+ {"name":"mean","value":0.0},
492+ {"name":"sigma_const","value":1.0,"const":true}
493+ ]}],
494+ "distributions":[{"name":"gauss","type":"gaussian_dist","x":"x","mean":"mean","sigma":"sigma_const"}]
495+ })" ;
496+ RooWorkspace legacyImport;
497+ {
498+ ScopedNoDomainConstVarImportFlag legacyFlagGuard{false };
499+ ASSERT_TRUE (RooJSONFactoryWSTool{legacyImport}.importJSONfromString (legacyJson));
500+ }
501+ EXPECT_NE (dynamic_cast <RooRealVar *>(legacyImport.obj (" sigma_const" )), nullptr );
502+ EXPECT_EQ (dynamic_cast <RooConstVar *>(legacyImport.obj (" sigma_const" )), nullptr );
503+ }
504+
505+ TEST (RooFitHS3, RooConstVarCollectionProxyExport)
506+ {
507+ ScopedNoDomainConstVarImportFlag flagGuard{true };
508+
509+ RooRealVar x{" x" , " x" , 0.0 , -10.0 , 10.0 };
510+ RooRealVar mean1{" mean1" , " mean1" , -1.0 };
511+ RooRealVar mean2{" mean2" , " mean2" , 1.0 };
512+ RooRealVar sigma{" sigma" , " sigma" , 1.0 , 0.1 , 10.0 };
513+
514+ RooGaussian g1{" g1" , " g1" , x, mean1, sigma};
515+ RooGaussian g2{" g2" , " g2" , x, mean2, sigma};
516+ RooConstVar frac{" frac_const" , " frac_const" , 0.25 };
517+ RooAddPdf model{" model" , " model" , RooArgList{g1, g2}, RooArgList{frac}};
518+
519+ RooWorkspace ws;
520+ ws.import (model, RooFit::Silence ());
521+
522+ const std::string json = RooJSONFactoryWSTool{ws}.exportJSONtoString ();
523+ const std::string domainAxes = defaultDomainAxesNode (json);
524+ ASSERT_FALSE (domainAxes.empty ()) << json;
525+ EXPECT_NE (json.find (" \" coefficients\" :[\" frac_const\" ]" ), std::string::npos);
526+ EXPECT_NE (json.find (" \" name\" :\" frac_const\" " ), std::string::npos);
527+ EXPECT_EQ (json.find (" is_const_var" ), std::string::npos);
528+ EXPECT_EQ (domainAxes.find (" \" name\" :\" frac_const\" " ), std::string::npos) << domainAxes;
529+
530+ RooWorkspace imported;
531+ ASSERT_TRUE (RooJSONFactoryWSTool{imported}.importJSONfromString (json));
532+ EXPECT_NE (dynamic_cast <RooConstVar *>(imported.obj (" frac_const" )), nullptr );
533+ EXPECT_EQ (imported.var (" frac_const" ), nullptr );
534+ }
535+
398536TEST (RooFitHS3, RooBernstein)
399537{
400538 int status = validate ({" RooBernstein::bernstein(x[0, 10], { a[1], 3, b[5, 0, 20] })" });
0 commit comments