Guide on how to add JSON IO for you RooFit classes.
Contains facilities to serialize and deserialize RooWorkspaces to and from JSON and YML.
When using RooFit
, statistical models can be conveniently handled and stored as a RooWorkspace
. However, for the sake of interoperability with other statistical frameworks, and also ease of manipulation, it may be useful to store statistical models in text form. This library sets out to achieve exactly that, exporting to and importing from JSON and YML.
The default backend for this is the nlohmann
JSON implementation, which ships with ROOT as a builtin dependency and will import from and export to JSON. Alternatively, the RapidYAML (RYML
) implementation can be used to also import from and export to YML. This implementation can be selected at compile time with the cmake
flag roofit_hs3_ryml
.
The main class providing import from and export to JSON and YML is the RooJSONFactoryWSTool
. For basic usage, please refer to the class documentation.
One of the most challenging aspects of providing serialization and deserialization for RooFit
is the fact that RooFit
follows an "open-world" philosophy with respect to the functions and pdfs it can handle. Over the years, RooFit
has also accumulated a significant number of different pre-implemented functions and pdfs. What is more, you can easily create your own RooFit
function by inheriting from RooAbsReal
or your own RooFit
pdf by inheriting from RooAbsPdf
. This means that feature-complete serialization and deserialization to and from JSON and YML will probably never be fully achieved. However, this may not impede your usage of this library, as it was written in such a way as to allow users (that is, people like you) to easily add missing importers and exporters for existing RooFit
classes as well as custom implementations you might be using.
RooFitHS3
allows to different types of importers and exporters: Native implementations, and proxy-based ones. If for a certain class several implementations are provided, the native implementation(s) take precedence.
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 for an exporter, or the factory expressions for an importer.
This works in the following way: Every RooFit
class performs dependency tracking via proxies, which have names. This can be exploited to perform the mapping of proxy names to json
keys upon export. In the other direction, the RooWorkspace
has a factory interface that allows to call any constructor via a string interface. Hence:
RooFit
class has no other members aside from proxies, it can be exported using a set of export keys
.RooFit
class are passed as constructor arguments, it can be imported using a factory expression
.For the importer, an entry in the factory expressions needs to be added as follows:
Similarly, for the exporter, an entry in the export keys 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 &fname)
and RooFit::JSONIO::loadFactoryExpressions(const std::string &fname)
.
If either the importer or the exporter cannot be created with factory expressions and export keys, you can instead write a custom C++
class to perform the import and export for you.
In order to implement your own importer or exporter, you can inherit from the corresponding base classes RooFit::JSONIO::Importer
or RooFit::JSONIO::Exporter
, respectively. You can find simple examples as well as more complicated ones in ROOT
.
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.
Once your importer implementation exists, you need to register it with the tool using a line like the following:
As there can be several importers for the same json
key, the last (boolean) argument determines whether your new importer should be added at the top of the priority list (true
) or at the bottom (false
). If the import fails, other importers are attempted.
The implementation of an exporter works in a very similar fashion:
Also this needs to be registered with the tool
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.
If you encounter a missing importer or exporter, please consider filing a feature request via the issue tracker.
If you don't want to wait for one of the dev's to pick up your request an process it, you can use the above instructions to write your own. If you wrote an importer or exporter for a RooFit
class that is part of ROOT
, either via export keys
and factory expressions
, or via native C++
classes, please consider contributing your implementation to ROOT
such that it can help other users also missing this importer or exporter. You can fork ROOT
, commit your changes, and file a pull request to ROOT
for your work to be included in the next release!