Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class RooJSONFactoryWSTool {
struct Config {
bool allowExportInvalidNames = true;
bool allowSanitizeNames = true;
bool importNoDomainParametersAsRooConstVars = true;
/// Identify constant factors of HistFactory samples that are used like the inverse bin width of HistFactory
/// before ROOT 6.26 by their structure, and don't write them, since `histfactory_dist` implies them. Turn this
/// off if a workspace has such factors that are not meant as bin widths: they are then written as normfactors.
Expand Down Expand Up @@ -239,6 +238,8 @@ class RooJSONFactoryWSTool {
void importAllNodes(const RooFit::Detail::JSONNode &n);

void importVariable(const RooFit::Detail::JSONNode &p);
void checkFixedValue(std::string const &name, const RooFit::Detail::JSONNode &p, std::string const &pointName);
void warnIfFreeWithoutDomain(RooRealVar const &var);
void importDependants(const RooFit::Detail::JSONNode &n);

void exportVariable(const RooAbsArg *v, RooFit::Detail::JSONNode &n, bool storeConstant, bool storeBins);
Expand Down
101 changes: 99 additions & 2 deletions roofit/hs3/src/Domains.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

#include <RooAbsBinning.h>
#include <RooBinning.h>
#include <RooConstVar.h>
#include <RooGlobalFunc.h>
#include <RooFitHS3/RooJSONFactoryWSTool.h>
#include <RooNumber.h>
#include <RooRealVar.h>
Expand Down Expand Up @@ -43,6 +45,10 @@ void writeBound(RooFit::Detail::JSONNode &node, const char *key, double value)

void Domains::populate(RooWorkspace &ws) const
{
// Parameters with a single value are RooConstVars, whichever domain gives the value.
for (const auto &domain : _map) {
domain.second.populateFixedValues(ws);
}
auto default_domain = _map.find(defaultDomainName);
if (default_domain != _map.end()) {
default_domain->second.populate(ws);
Expand Down Expand Up @@ -97,6 +103,46 @@ void Domains::readJSON(RooFit::Detail::JSONNode const &node)
auto &name = domain["name"];
_map[name.val()].readJSON(domain);
}
// A parameter with a single value has no other domain: every domain that lists it must give it the same value.
for (auto const &[domainName, domain] : _map) {
for (auto const &[varName, elem] : domain._map) {
if (!elem.hasFixedValue) {
continue;
}
for (auto const &[otherName, other] : _map) {
auto found = other._map.find(varName);
if (found == other._map.end()) {
continue;
}
if (!found->second.hasFixedValue) {
RooJSONFactoryWSTool::error("the parameter '" + varName + "' has the single value " +
std::to_string(elem.fixedValue) + " in the domain '" + domainName +
"', but a range in the domain '" + otherName + "'");
}
if (found->second.fixedValue != elem.fixedValue) {
RooJSONFactoryWSTool::error("the parameter '" + varName +
"' has different single values in the "
"domains '" +
domainName + "' and '" + otherName + "'");
}
}
}
}
}

void Domains::readFixedValue(const char *name, double value)
{
_map[defaultDomainName].readFixedValue(name, value);
}

double const *Domains::fixedValue(const char *name) const
{
for (auto const &domain : _map) {
if (double const *value = domain.second.fixedValue(name)) {
return value;
}
}
return nullptr;
}

void Domains::writeJSON(RooFit::Detail::JSONNode &node) const
Expand Down Expand Up @@ -184,7 +230,7 @@ void Domains::ProductDomain::writeBinning(RooFit::Detail::JSONNode &node, Produc
void Domains::ProductDomain::writeVariable(RooRealVar &var) const
{
auto found = _map.find(var.GetName());
if (found != _map.end()) {
if (found != _map.end() && !found->second.hasFixedValue) {
auto const &elem = found->second;
if (elem.hasMin) {
if (RooNumber::isInfinite(elem.min)) {
Expand All @@ -209,13 +255,48 @@ bool Domains::ProductDomain::hasVariable(const char *name) const
return _map.find(name) != _map.end();
}

double const *Domains::ProductDomain::fixedValue(const char *name) const
{
auto found = _map.find(name);
return found != _map.end() && found->second.hasFixedValue ? &found->second.fixedValue : nullptr;
}

void Domains::ProductDomain::readFixedValue(const char *name, double value)
{
auto &elem = _map[name];
elem = ProductDomainElement{};
elem.hasFixedValue = true;
elem.fixedValue = value;
}

void Domains::ProductDomain::readJSON(RooFit::Detail::JSONNode const &node)
{
if (!node.has_child("type") || node["type"].val() != "product_domain") {
RooJSONFactoryWSTool::error("only domains of type \"product_domain\" are currently supported!");
}
for (auto const &varNode : node["axes"].children()) {
auto &elem = _map[RooJSONFactoryWSTool::name(varNode)];
const std::string varName = RooJSONFactoryWSTool::name(varNode);
auto &elem = _map[varName];

if (varNode.has_child(fixedValueKey)) {
// The domain of the parameter is the set with this single value: no range, binning or categories.
for (const char *key : {"min", "max", "nbins", "edges", "categories"}) {
if (varNode.has_child(key)) {
RooJSONFactoryWSTool::error("the axis '" + varName + "' of the domain '" +
RooJSONFactoryWSTool::name(node) + "' has \"" + fixedValueKey + "\" and \"" +
key + "\", which exclude each other");
}
}
auto const &valueNode = varNode[fixedValueKey];
if (!valueNode.is_number()) {
RooJSONFactoryWSTool::error("the \"" + std::string(fixedValueKey) + "\" of the axis '" + varName +
"' must be a number");
}
elem = ProductDomainElement{};
elem.hasFixedValue = true;
elem.fixedValue = valueNode.val_double();
continue;
}

if (varNode.has_child("min")) {
elem.min = readBound(varNode, "min", -RooNumber::infinity());
Expand Down Expand Up @@ -259,6 +340,10 @@ void Domains::ProductDomain::writeJSON(RooFit::Detail::JSONNode &node) const
for (auto const &item : _map) {
auto const &elem = item.second;
RooFit::Detail::JSONNode &varnode = RooJSONFactoryWSTool::appendNamedChild(variablesNode, item.first);
if (elem.hasFixedValue) {
varnode[fixedValueKey] << elem.fixedValue;
continue;
}
const double min = elem.hasMin ? elem.min : -RooNumber::infinity();
const double max = elem.hasMax ? elem.max : RooNumber::infinity();
writeBound(varnode, "min", min);
Expand All @@ -273,6 +358,9 @@ void Domains::ProductDomain::populate(RooWorkspace &ws) const
{
for (auto const &item : _map) {
const auto &name = item.first;
if (item.second.hasFixedValue) {
continue; // see populateFixedValues()
}
if (!ws.arg(name)) {
const auto &elem = item.second;
const double vMin = elem.hasMin ? elem.min : -RooNumber::infinity();
Expand All @@ -283,6 +371,15 @@ void Domains::ProductDomain::populate(RooWorkspace &ws) const
}
}
}
void Domains::ProductDomain::populateFixedValues(RooWorkspace &ws) const
{
for (auto const &[name, elem] : _map) {
if (elem.hasFixedValue && !ws.arg(name)) {
ws.import(RooConstVar{name.c_str(), name.c_str(), elem.fixedValue}, RooFit::Silence());
}
}
}

void Domains::ProductDomain::registerBinnings(const char *name, RooWorkspace &ws) const
{
for (auto const &item : _map) {
Expand Down
12 changes: 12 additions & 0 deletions roofit/hs3/src/Domains.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class Domains {
public:
void readVariable(RooRealVar const &);
void writeVariable(RooRealVar &) const;
// A parameter that only takes the value `value` (a RooConstVar), written as an axis with a single value.
void readFixedValue(const char *name, double value);
// The single value of the parameter `name` if a domain has one for it, otherwise nullptr.
double const *fixedValue(const char *name) const;

void readJSON(RooFit::Detail::JSONNode const &);
void writeJSON(RooFit::Detail::JSONNode &) const;
Expand All @@ -45,6 +49,8 @@ class Domains {
void populate(RooWorkspace &ws) const;

class ProductDomain {
friend class Domains;

public:
void readVariable(const RooRealVar &);
void readVariable(const char *name, RooAbsBinning const &binning);
Expand All @@ -54,12 +60,18 @@ class Domains {
void writeJSON(RooFit::Detail::JSONNode &) const;

bool hasVariable(const char *name) const;
double const *fixedValue(const char *name) const;
void readFixedValue(const char *name, double value);

void populate(RooWorkspace &ws) const;
void populateFixedValues(RooWorkspace &ws) const;
void registerBinnings(const char *name, RooWorkspace &ws) const;

private:
struct ProductDomainElement {
// An axis with a single value: the parameter only takes this value (HS3 #131).
bool hasFixedValue = false;
double fixedValue = 0.0;
bool hasMin = false;
bool hasMax = false;
double min = 0.0;
Expand Down
4 changes: 4 additions & 0 deletions roofit/hs3/src/JSONIOUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ std::string removeSuffix(std::string_view str, std::string_view suffix);
std::unique_ptr<RooFit::Detail::JSONTree> varJSONString(const RooFit::Detail::JSONNode &treeRoot);
void writeAxisBinning(RooFit::Detail::JSONNode &node, const RooAbsBinning &binning);
double readBound(RooFit::Detail::JSONNode const &node, const char *key, double defaultValue);

// The key of a fixed domain axis (HS3 #131): the parameter only takes this value, and RooFit reads it as a
// RooConstVar.
inline constexpr const char *fixedValueKey = "value";
bool parseHS3Version(std::string const &version, int &major, int &minor);
bool isHS3VersionBefore(std::string const &version, int major, int minor);
std::vector<double> readEdges(const RooFit::Detail::JSONNode &node, std::string const &context);
Expand Down
63 changes: 48 additions & 15 deletions roofit/hs3/src/RooJSONFactoryWSTool.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1058,12 +1058,16 @@ void RooJSONFactoryWSTool::exportVariable(const RooAbsArg *v, JSONNode &node, bo
return;
}

// A named RooConstVar only takes its value: it is written as a domain axis with that single value, not in the
// parameter points.
if (cv) {
_domains->readFixedValue(cv->GetName(), cv->getVal());
return;
}

JSONNode &var = appendNamedChild(node, v->GetName());

if (cv) {
var["value"] << cv->getVal();
var["const"] << true;
} else if (rrv) {
if (rrv) {
var["value"] << rrv->getVal();
if (storeConstant && (rrv->isConstant() || rrv->getMin() >= rrv->getMax())) {
var["const"] << true;
Expand Down Expand Up @@ -1714,21 +1718,50 @@ void RooJSONFactoryWSTool::importVariable(const JSONNode &p)
std::string name(RooJSONFactoryWSTool::name(p));
RooJSONFactoryWSTool::testValidName(name, true);

if (_workspace.arg(name))
return;
if (!p.is_map()) {
oocoutE(nullptr, InputArguments) << "RooJSONFactoryWSTool() node '" << name << "' is not a map, skipping."
<< std::endl;
return;
}
if (config().importNoDomainParametersAsRooConstVars && !_domains->hasVariable(name.c_str())) {
if (!p.has_child("value")) {
RooJSONFactoryWSTool::error("cannot instantiate RooConstVar '" + name + "' without \"value\"!");
}
wsEmplace<RooConstVar>(name, p["value"].val_double());
checkFixedValue(name, p, "default_values");
if (_workspace.arg(name))
return;
if (!_domains->hasVariable(name.c_str())) {
// ROOT versions before HS3 0.3 marked named RooConstVars with an attribute.
JSONNode const *attrNode = _attributesNode ? _attributesNode->find(name) : nullptr;
if (attrNode && attrNode->has_child("is_const_var") && attrNode->find("is_const_var")->val_int() == 1) {
if (!p.has_child("value")) {
RooJSONFactoryWSTool::error("cannot instantiate RooConstVar '" + name + "' without \"value\"!");
}
wsEmplace<RooConstVar>(name, p["value"].val_double());
return;
}
}
RooRealVar &var = wsEmplace<RooRealVar>(name, 1.);
configureVariable(*_domains, p, var);
warnIfFreeWithoutDomain(var);
}

// A parameter with a single value in its domain only takes that value: a parameter point must not give it another.
void RooJSONFactoryWSTool::checkFixedValue(std::string const &name, JSONNode const &p, std::string const &pointName)
{
double const *fixed = _domains ? _domains->fixedValue(name.c_str()) : nullptr;
JSONNode const *valueNode = p.find("value");
if (fixed && valueNode && valueNode->is_number() && valueNode->val_double() != *fixed) {
RooJSONFactoryWSTool::error("the parameter point '" + pointName + "' gives the parameter '" + name +
"' the value " + valueNode->val() + ", but its domain only has the value " +
std::to_string(*fixed));
}
}

// HS3 requires every parameter to be listed in a domain or constant ("const": true) in the parameter point.
void RooJSONFactoryWSTool::warnIfFreeWithoutDomain(RooRealVar const &var)
{
if (!var.isConstant() && _domains && !_domains->hasVariable(var.GetName())) {
warning(std::string("RooFitHS3: the parameter '") + var.GetName() +
"' is not listed in any domain and is not constant: HS3 requires a domain, or \"const\": true. It "
"is read as a free parameter without range.");
}
configureVariable(*_domains, p, wsEmplace<RooRealVar>(name, 1.));
}

// Import all dependants (variables, functions and distributions) of node `n` into the workspace.
Expand Down Expand Up @@ -2178,15 +2211,15 @@ void RooJSONFactoryWSTool::importAllNodes(const JSONNode &n)
name + "', because there is no category with that name.");
continue;
}
checkFixedValue(varName, var, name);
RooRealVar *rrv = _workspace.var(varName);
if (!rrv && !_workspace.arg(varName)) {
// The parameter is not used by any other component: create it. It is constant if "const" is true or
// if no domain lists it, since there is no range to vary it in. "const": false is the same as no
// The parameter is not used by any other component: create it. "const": false is the same as no
// "const" at all.
testValidName(varName, true);
rrv = &wsEmplace<RooRealVar>(varName, 1.);
configureVariable(*_domains, var, *rrv);
rrv->setConstant(rrv->isConstant() || !_domains->hasVariable(varName.c_str()));
warnIfFreeWithoutDomain(*rrv);
} else if (rrv) {
configureVariable(*_domains, var, *rrv);
}
Expand Down
Loading
Loading