Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
870ece1
[RF][HS3] Validate the hs3_version of imported documents
cburgard Oct 25, 2026
dbf8654
[RF][HS3] Reject duplicate names of top-level HS3 objects
cburgard Oct 25, 2026
e9558fa
[RF][HS3] Make the "default_domain" optional on import
cburgard Oct 25, 2026
564e474
[RF][HS3] Import parameters that only appear in a parameter point
cburgard Oct 25, 2026
01f9531
[RF][HS3] Don't write binning on the axes of unbinned data
cburgard Oct 25, 2026
e2d46d0
[RF][HS3] Write and read the "init" parameter point of analyses
cburgard Oct 25, 2026
620afdb
[RF][HS3] Align type keys and fields with the HS3 catalog
cburgard Oct 25, 2026
0f33129
[RF][HS3] Export TFormula ternaries as the HS3 ifelse() conditional
cburgard Oct 25, 2026
896224f
[RF][HS3] Check the "extended" flag of imported mixture distributions
cburgard Oct 25, 2026
b36df0c
[RF][HS3] HistFactory: recognize "LogNormal", use "parameter" of norm…
cburgard Oct 25, 2026
449637d
[RF][HS3] HistFactory: normsys/histosys without constraint are uncons…
cburgard Oct 25, 2026
f9fc39f
[RF][HS3] HistFactory: read and write the staterror constraint on the…
cburgard Oct 25, 2026
c6e57b4
[RF][HS3] HistFactory: reject modifiers without interpolation in HS3 …
cburgard Oct 25, 2026
215ed6a
[RF][HS3] Validate the size of matrices, error arrays and bin edges
cburgard Oct 25, 2026
97c0e68
[RF][HS3] Take the range of unbinned observables from the data axes
cburgard Oct 25, 2026
ef7393a
[RF][HS3][docs] Update the RooFitHS3 developer documentation
cburgard Oct 25, 2026
5092e2b
[RF][HS3] Keep errors of zero in binned data before the first positiv…
cburgard Nov 5, 2026
88a0c43
[RF][HS3] Test that a constant RooRealVar stays distinct from a RooCo…
cburgard Nov 5, 2026
3eb77b6
[tutorials][RF][HS3] Format rf515_hfJSON.json with clang-format
cburgard Nov 5, 2026
0d0d4c4
[RF][HS3] Write transformed helper functions once
guitargeek Nov 5, 2026
812ab99
[RF][HS3] Name all analyses after their ModelConfig
guitargeek Nov 5, 2026
76dcca6
[RF][HS3] Reject duplicate names only from HS3 0.3 on
guitargeek Nov 5, 2026
18917bd
[RF][HS3] Read infinite bounds of unbinned axes
guitargeek Nov 5, 2026
6d5ec90
[RF][HS3] Accept a mixture of one extended summand
guitargeek Nov 5, 2026
9e3203c
[RF][HS3] Keep the constraint of formula normsys
guitargeek Nov 5, 2026
d02e8ff
[RF][HS3] Fix the factors of formula normsys
guitargeek Nov 5, 2026
661aa75
[RF][HS3] Get the "init" name from the ModelConfig
guitargeek Nov 5, 2026
d663f1d
[RF][HS3] Refuse normsys with a function as parameter
guitargeek Nov 5, 2026
b70f2e8
[RF][HS3] Imply Gaussian constraints before HS3 0.2
guitargeek Nov 5, 2026
45c8610
[RF][HS3] Warn about modifiers without constraint
guitargeek Nov 5, 2026
b8ad079
[RF][HS3] Reject log-normal constraints of gammas
guitargeek Nov 5, 2026
da1e3a9
[RF][HS3] Read "constraint_type" of staterror modifiers
guitargeek Nov 5, 2026
bcaec8b
[RF][HS3] Keep all-zero errors of binned data at zero
guitargeek Nov 5, 2026
14aecd7
[RF][HS3] Create categories before parameter points
guitargeek Nov 5, 2026
361e68a
[RF][HS3] Log warnings on the ObjectHandling topic
guitargeek Nov 5, 2026
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
69 changes: 51 additions & 18 deletions roofit/doc/developers/roofit_hs3.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,16 @@ implementation(s) take precedence.
### Writing your own importers and exporters: Proxy-based

Proxy-based implementations can be added very easily and without
actually writing any `C++` code -- you only need to add a short item
to a list in a `JSON` file, namely the
[export keys](https://github.com/root-project/root/blob/master/etc/RooFitHS3_wsexportkeys.json)
actually writing any `C++` logic -- you only need to add a short item
to a `JSON` catalog, namely the
[export keys](https://github.com/root-project/root/blob/master/roofit/hs3/src/RooFitHS3_wsexportkeys.cxx)
for an exporter, or the
[factory expressions](https://github.com/root-project/root/blob/master/etc/RooFitHS3_wsfactoryexpressions.json)
for an importer.
[factory expressions](https://github.com/root-project/root/blob/master/roofit/hs3/src/RooFitHS3_wsfactoryexpressions.cxx)
for an importer. The built-in catalogs are `JSON` documents embedded as
raw string literals in these two source files. They are compiled into
the `RooFitHS3` library and loaded automatically when the library is
loaded, so there are no separate `JSON` files to install or find at
runtime.

This works in the following way: Every `RooFit` class performs
dependency tracking via proxies, which have names. This can be
Expand All @@ -67,7 +71,7 @@ interface. Hence:
expression`.

For the importer, an entry in the
[factory expressions](https://github.com/root-project/root/blob/master/etc/RooFitHS3_wsfactoryexpressions.json)
[factory expressions](https://github.com/root-project/root/blob/master/roofit/hs3/src/RooFitHS3_wsfactoryexpressions.cxx)
needs to be added as follows:

``` {.json}
Expand All @@ -82,7 +86,7 @@ needs to be added as follows:
```

Similarly, for the exporter, an entry in the
[export keys](https://github.com/root-project/root/blob/master/etc/RooFitHS3_wsexportkeys.json)
[export keys](https://github.com/root-project/root/blob/master/roofit/hs3/src/RooFitHS3_wsexportkeys.cxx)
needs to be added as follows:

``` {.json}
Expand All @@ -97,12 +101,18 @@ needs to be added as follows:
```


If you don't want to edit the central `json` files containing the
factory expressions or export keys, you can also put your custom
export keys or factory expressions into a different json file and load
that using `RooFit::JSONIO::loadExportKeys(const std::string
To contribute a catalog entry to `ROOT`, edit the embedded catalog in
`roofit/hs3/src/RooFitHS3_wsexportkeys.cxx` or
`roofit/hs3/src/RooFitHS3_wsfactoryexpressions.cxx` and rebuild `ROOT`.
If you don't want to change `ROOT` itself, you can put your custom
export keys or factory expressions into your own `JSON` file (with the
same format as the embedded catalogs) and add them to the built-in ones
at runtime using `RooFit::JSONIO::loadExportKeys(const std::string
&fname)` and `RooFit::JSONIO::loadFactoryExpressions(const
std::string &fname)`.
std::string &fname)`. Both functions also have an overload that reads
from a `std::istream`. You can inspect the currently known entries with
`RooFit::JSONIO::printExportKeys()` and
`RooFit::JSONIO::printFactoryExpressions()`.

If either the importer or the exporter cannot be created with factory
expressions and export keys, you can instead write a custom `C++`
Expand All @@ -121,9 +131,11 @@ in `ROOT`.
Any importer should take the following form:

``` {.cpp}
using RooFit::Detail::JSONNode;

class MyClassFactory : public RooFit::JSONIO::Importer {
public:
bool importFunction(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
bool importArg(RooJSONFactoryWSTool *tool, const JSONNode &p) const override
{
std::string name(RooJSONFactoryWSTool::name(p));

Expand All @@ -145,14 +157,24 @@ Any importer should take the following form:
};
```

If the class you are trying to import inherits from `RooAbsPdf` rather
than from `RooAbsReal`, you should define `importPdf` instead of
`importFunction`, with the same signature.
The same `importArg` function is used for functions (inheriting from
`RooAbsReal`) and pdfs (inheriting from `RooAbsPdf`). If the importer
can't handle a given `JSON` node, it should return `false` so that the
next importer is tried. The functions `importFunction` and `importPdf`
of older `ROOT` versions are still called for backwards compatibility,
so existing importers that override them keep working, but new code
should override `importArg`.

Once your importer implementation exists, you need to register it with the tool using a line like the following:

``` {.cpp}
RooFit::JSONIO::registerImporter("<json key>", new MyClassFactory(), true);
RooFit::JSONIO::registerImporter<MyClassFactory>("<json key>", true);
```

This is a shorthand for passing an instance owned by a `std::unique_ptr`:

``` {.cpp}
RooFit::JSONIO::registerImporter("<json key>", std::make_unique<MyClassFactory>(), true);
```

As there can be several importers for the same `json` key, the last
Expand Down Expand Up @@ -189,9 +211,20 @@ The implementation of an exporter works in a very similar fashion:
Also this needs to be registered with the tool

``` {.cpp}
RooFit::JSONIO::registerExporter(MyClass::Class(), new MyClassStreamer(), true);
RooFit::JSONIO::registerExporter<MyClassStreamer>(MyClass::Class(), true);
```

or equivalently

``` {.cpp}
RooFit::JSONIO::registerExporter(MyClass::Class(), std::make_unique<MyClassStreamer>(), true);
```

If the `TClass` of your class is not available where you register the
exporter, you can also pass the class name as a string instead of
`MyClass::Class()`. If `exportObject` returns `false`, the next
exporter is tried, and finally the export keys.

For more complicated cases where members are lists of elements, the
methods `is_seq()`, `set_seq()`, `is_map()`, `set_map()` and
ranged-based for-loops via `children()` might come in handy.
Expand Down
4 changes: 4 additions & 0 deletions roofit/hs3/inc/RooFitHS3/RooJSONFactoryWSTool.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ class RooJSONFactoryWSTool {
std::string getStringAttribute(const std::string &obj, const std::string &attrib);
void setStringAttribute(const std::string &obj, const std::string &attrib, const std::string &value);

/// The "hs3_version" of the document that is currently being imported, or an empty string outside of an import.
std::string const &hs3VersionInput() const { return _hs3VersionInput; }

private:
template <class T>
T *requestImpl(const std::string &objname);
Expand Down Expand Up @@ -251,6 +254,7 @@ class RooJSONFactoryWSTool {
const RooFit::Detail::JSONNode *_attributesNode = nullptr;
RooFit::Detail::JSONNode *_rootnodeOutput = nullptr;
RooFit::Detail::JSONNode *_varsNode = nullptr;
std::string _hs3VersionInput;
RooWorkspace &_workspace;

// objects to represent intermediate information
Expand Down
29 changes: 16 additions & 13 deletions roofit/hs3/src/Domains.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

#include "Domains.h"
#include "JSONIOUtils.h"

#include <RooAbsBinning.h>
#include <RooBinning.h>
Expand All @@ -29,15 +30,6 @@ constexpr static auto defaultDomainName = "default_domain";

namespace {

double readBound(RooFit::Detail::JSONNode const &node, const char *key, double defaultValue)
{
if (!node.has_child(key)) {
return defaultValue;
}
auto const &bound = node[key];
return bound.is_null() ? defaultValue : bound.val_double();
}

void writeBound(RooFit::Detail::JSONNode &node, double value)
{
if (RooNumber::isInfinite(value)) {
Expand All @@ -54,6 +46,12 @@ void Domains::populate(RooWorkspace &ws) const
auto default_domain = _map.find(defaultDomainName);
if (default_domain != _map.end()) {
default_domain->second.populate(ws);
} else {
// Without the RooFit-specific "default_domain", the variables are created with the range of the domain that
// mentions them.
for (const auto &domain : _map) {
domain.second.populate(ws);
}
}
for (const auto &domain : _map) {
if (domain.first == defaultDomainName)
Expand All @@ -78,15 +76,20 @@ void Domains::writeVariable(RooRealVar &var) const
auto default_domain = _map.find(defaultDomainName);
if (default_domain != _map.end()) {
default_domain->second.writeVariable(var);
return;
}
// Without the RooFit-specific "default_domain", use the first domain that mentions the variable.
for (const auto &domain : _map) {
if (domain.second.hasVariable(var.GetName())) {
domain.second.writeVariable(var);
return;
}
}
}

void Domains::readJSON(RooFit::Detail::JSONNode const &node)
{
auto default_domain_element = RooJSONFactoryWSTool::findNamedChild(node, defaultDomainName);
if (!default_domain_element) {
RooJSONFactoryWSTool::error("\"domains\" do not contain \"" + std::string{defaultDomainName} + "\"");
}
// The "default_domain" is written by RooFit, but it is not required by the HS3 standard.
for (auto &domain : node.children()) {
if (!domain.has_child("name")) {
RooJSONFactoryWSTool::error("encountered domain without \"name\"");
Expand Down
Loading
Loading