ROOT Version 6.22 Release Notes

2020-08-17

Introduction

ROOT version 6.22/00 was released on June 14, 2020.

For more information, see:

http://root.cern

The following people have contributed to this version:

Guilherme Amadio, CERN/SFT,
Bertrand Bellenot, CERN/SFT,
Jakob Blomer, CERN/SFT,
Rene Brun, CERN/SFT,
Philippe Canal, FNAL,
Olivier Couet, CERN/SFT,
Surya Dwivedi, GSOC/SFT,
Massimiliano Galli, CERN/SFT,
Andrei Gheata, CERN/SFT,
Hadrien Grasland, IJCLab/LAL,
Enrico Guiraud, CERN/SFT,
Claire Guyot, CERN/SFT,
Stephan Hageboeck, CERN/SFT,
Sergey Linev, GSI,
Pere Mato, CERN/SFT,
Lorenzo Moneta, CERN/SFT,
Alja Mrak-Tadel, UCSD/CMS,
Jan Musinsky, SAS Kosice,
Axel Naumann, CERN/SFT,
Vincenzo Eduardo Padulano, Bicocca/SFT,
Danilo Piparo, CERN/SFT,
Timur Pocheptsoff, Qt Company,
Renato Quagliani, LPNHE, CNRS/IN2P3, Sorbonne Université,
Fons Rademakers, CERN/SFT,
Oksana Shadura, UNL/CMS,
Enric Tejedor Saavedra, CERN/SFT,
Matevz Tadel, UCSD/CMS,
Vassil Vassilev, Princeton/CMS,
Wouter Verkerke, NIKHEF/Atlas,
Stefan Wunsch, CERN/SFT

Deprecation and Removal

Core Libraries

TTree Libraries

    struct FloatInt {
       float f;
       int x;
    };

and

   int x = 1;
   FloatInt s{2.1, 3};
   TTree t("t", "t");
   t.Branch("i", &i); // Create a top level branch named "i" and a sub-branch name "x"
   t.Branch("x", &x);

the old version of t.GetBranch("x") was return the i.x sub-branch while the new version return the x top level branch.

chain->LoadTree(0);
chain->SetBranchAdress(branch_name, &address);

now TChain will also check the addresses in the case:

chain->SetBranchAdress(branch_name, &address);
chain->LoadTree(0);
// Not setting the top level branch address.
chain->SetBranchAdress(sub_branch_name, &address);
chain->GetEntry(0);

Resulted in the address set to be forgotten. Note that a work-around was:

// Not setting the top level branch address.
chain->GetEntry(0);
chain->SetBranchAdress(sub_branch_name, &address);

But also the address needed to (in most cases) also be set again after each new tree was loaded.

Note that, the following:

chain->SetBranchAdress(sub_branch_name, &address);
chain->SetBranchAdress(top_level_branch_name, &other_address);
chain->GetEntry(0);

will result (as one would expect) with the first SetBranchAddress being ignored/over-ridden.

RDataFrame

Histogram Libraries

Math Libraries

Minuit2

Matrix

RooFit Libraries

RooWorkspace::Import() for Python

RooWorkspace.import() cannot be used in Python, since it is a reserved keyword. Users therefore had to resort to

getattr(workspace, 'import')(...)

Now,

workspace.Import(...)

has been defined for the new PyROOT, which makes calling the function easier.

Modernised category classes

RooFit’s categories were modernised. Previously, the class RooCatType was used to store category states. It stores two members, an integer for the category index, and up to 256 characters for a category name. Now, such states are stored only using an integer, and category names can have arbitrary length. This will use 4 instead of 288 bytes per category entry in a dataset, and paves the way for faster computations that rely on category states.

The interface to define or manipulate category states was also updated. Since categories are mappings from state names to state index, this is now reflected in the interface. Among others, this is now possible:

ROOT 6.22 Before (still supported)
RooCategory cat("cat", "Lepton flavour");
cat["electron"] = 1;
cat["muon"] = 2;
RooCategory cat("cat", "Lepton flavour");
cat.defineType("electron", 1);
cat.defineType("muon", 2);

See also the Category reference guide or different RooFit tutorials, specifically rf404_categories.

Type-safe proxies for RooFit objects

RooFit’s proxy classes have been modernised. The class RooTemplateProxy allows for access to other RooFit objects similarly to a smart pointer. In older versions of RooFit, proxies would always hold pointers to very abstract classes, e.g. RooAbsReal, although one wanted to work with a PDF. Therefore, casting was frequently necessary. Further, one could provoke runtime errors by storing an incompatible object in a proxy. For example, one could store a variable RooRealVar in a proxy that was meant to store PDFs.

Now, RooFit classes that use proxies can be simplified as follows:

ROOT 6.22 Before (still supported)
Class definition
RooTemplateProxy<RooAbsPdf> pdf;
RooRealProxy realProxy;
Implementation
pdf->fitTo(...);
RooAbsArg* absArg = realProxy.absArg();
RooAbsPdf* pdf = dynamic_cast<RooAbsPdf*>(absArg);
// Should work, but maybe someone stored wrong
// object. Add some checking code.
if (pdf != nullptr) {
...
}
pdf->fitTo(...);

Check the doxygen reference guide for RooTemplateProxy for more information on how to modernise old code.

Documentation updates

Miscellaneous

HistFactory

Switch default statistical MC errors to Poisson

When defining HistFactory samples with statistical errors from C++, e.g.

  Sample background1( "background1", "background1", InputFile );
  background1.ActivateStatError();

statistical MC errors now default to Poisson instead of Gaussian constraints. This better reflects the uncertainty of the MC simulations, and now actually implements what is promised in the HistFactory paper. For large number of entries in a bin, this doesn’t make a difference, but for bins with low statistics, it better reflects the “counting” nature of bins. This can be reverted as follows:

  // C++:
  Channel chan("channel1");
  chan.SetStatErrorConfig( 0.05, "Gauss" );
  // Within a <Channel ... > XML:
  <StatErrorConfig RelErrorThreshold="0.05" ConstraintType="Gauss" />

Less verbose HistFactory

HistFactory was very verbose, writing to the terminal with lots of cout. Now, many HistFactory messages are going into RooFit’s message stream number 2. The verbosity can therefore be adjusted using

  RooMsgService::instance().getStream(2).minLevel = RooFit::PROGRESS;

hist2workspace is also much less verbose. The verbosity can be restored with hist2workspace -v for intermediate verbosity or -vv for what it printed previously.

TMVA

Deep Learning module

PyMVA

2D Graphics Libraries

Geometry Libraries

Geometry drawing in web browser

When ROOT compiled with -Droot7=ON flag, one can enable geometry drawing in web browser. Just apply –web option when starting root like: root --web tutorials/geom/rootgeom.C Not all features of TGeoPainter are supported - only plain drawing of selected TGeoVolume

Language Bindings

PyROOT

ROOT 6.22 makes the new (experimental) PyROOT its default. This new PyROOT is designed on top of the new cppyy, which provides more and better support for modern C++. The documentation for cppyy and the new features it provides can be found here.

For what concerns new additions to PyROOT, this is the list:

import ROOT
ROOT.PyConfig.ShutDown = False

On the other hand, there are some backwards-incompatible changes of the new PyROOT with respect to the new one, listed next:

> import ROOT

> ROOT.gInterpreter.Declare("""
template<typename T> T foo(T arg) { return arg; }
""")

> ROOT.foo['int']  # instantiation
 cppyy template proxy (internal)

> ROOT.foo('int')  # call (auto-instantiation from arg type)
'int'

Note that the above does not affect class templates, which can be instantiated either with parenthesis or square brackets:

> ROOT.std.vector['int'] # instantiation
<class cppyy.gbl.std.vector<int> at 0x5528378>

> ROOT.std.vector('int') # instantiation
<class cppyy.gbl.std.vector<int> at 0x5528378>
string (const char* s, size_t n)                           (1)
string (const string& str, size_t pos, size_t len = npos)  (2)

when invoking ROOT.std.string(s, len(s)), where s is a Python string, the new PyROOT will pick (2) whereas the old would pick (1).

> ROOT.gInterpreter.Declare("""
class A {};
void foo(A* a) {}
""")

> ROOT.foo(ROOT.nullptr)  # ok

> ROOT.foo(None)          # fails
TypeError: void ::foo(A* a) =>
TypeError: could not convert argument 1
> ROOT.gInterpreter.Declare("""
void foo(int& i) { ++i; }
void foo(double& d) { ++d; }
""")

> import ctypes

> i = ctypes.c_int(1)
> d = ctypes.c_double(1.)

> ROOT.foo(i); i
c_int(2)

> ROOT.foo(d); d
c_double(2.0)
> ROOT.gInterpreter.Declare('char MyWord[] = "Hello";')

> mw = ROOT.MyWord

> type(mw)
<class 'str'>

> mw  # '\x00' is not part of the string
'Hello'
> ROOT.gInterpreter.Declare("class CppBase {};")
 True
> class PyDerived(ROOT.CppBase): pass
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: CppBase not an acceptable base: no virtual destructor
Old PyROOT/cppyy New PyROOT/cppyy
cppyy.gbl.MakeNullPointer(klass) cppyy.bind_object(0, klass)
cppyy.gbl.BindObject / cppyy.bind_object cppyy.bind_object
cppyy.AsCObject libcppyy.as_cobject
cppyy.add_pythonization cppyy.py.add_pythonization
cppyy.compose_method cppyy.py.compose_method
cppyy.make_interface cppyy.py.pin_type
cppyy.gbl.nullptr cppyy.nullptr
cppyy.gbl.PyROOT.TPyException cppyy.gbl.CPyCppyy.TPyException
buffer.SetSize(N) buffer.reshape((N,))
obj.__cppname__ obj.__cpp_name__
obj._get_smart_ptr obj.__smartptr__
callable._creates callable.__creates__
callable._mempolicy callable.__mempolicy__
callable._threaded callable.__release_gil__
import ROOT
ROOT.PyConfig.IgnoreCommandLineOptions = False
> ROOT.gInterpreter.Declare("""
struct MyStruct {
  int a;
  float b;
};
""")

> s = ROOT.MyStruct()

> ROOT.addressof(s, 'a')
94015521402096L

> ROOT.addressof(s, 'b')
94015521402100L

> ROOT.addressof(s)
94015521402096L

In old PyROOT, AddressOf could be used for that purpose too, but its behaviour was inconsistent. AddressOf(o) returned a buffer whose first position contained the address of object o, but Address(o, 'field') returned a buffer whose address was the address of the field, instead of such address being contained in the first position of the buffer. Note that, in the new PyROOT, AddressOf(o) can still be invoked, and it still returns a buffer whose first position contains the address of object o.

import ROOT

from array import array

class MyMultiGenFCN( ROOT.Math.IMultiGenFunction ):
    def NDim( self ):
        return 1

    def DoEval( self, x ):
        return (x[0] - 42) * (x[0] - 42)

    def Clone( self ):
        x = MyMultiGenFCN()
        ROOT.SetOwnership(x, False)
        return x

def main():
    fitter = ROOT.Fit.Fitter()
    myMultiGenFCN = MyMultiGenFCN()
    params = array('d', [1.])
    fitter.FitFCN(myMultiGenFCN, params)
    fitter.Result().Print(ROOT.std.cout, True)

if __name__ == '__main__':
    main()
> import cppyy

> cppyy.cppdef('std::vector<std::string> foo() { return std::vector<std::string>{"foo","bar"};}')

> v = cppyy.gbl.foo()

> type(v)
<class cppyy.gbl.std.vector<string> at 0x4ad8220>

> for s in v:
...   print(type(s))  # s is no longer a Python string, but an std::string
...
<class cppyy.gbl.std.string at 0x4cd41b0>
<class cppyy.gbl.std.string at 0x4cd41b0>

Tutorials

Class Reference Guide

Build, Configuration and Testing Infrastructure

Experimental address sanitizer build configuration

Added a build flag asan that switches on address sanitizer. It’s experimental, so expect problems. For example, when building with gcc, manipulations in global variables in llvm will abort the build. Such checks can be disabled using environment variables. Check the address sanitizer documentation or the link below for details. In clang, which allows to blacklist functions, the build will continue.

See core/sanitizer for information.

Optimization of ROOT header files

Many (but intentionally not all) unused includes were removed from ROOT header files. For instance, #include "TObjString.h" and #include "ThreadLocalStorage.h" were removed from TClass.h. Or #include "TDatime.h" was removed from TDirectory.h header file . Or #include "TDatime.h" was removed from TFile.h. This change may cause errors during compilation of ROOT-based code. To fix it, provide missing the includes where they are really required. This improves compile times and reduces code inter-dependency; see https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/WhyIWYU.md for a good overview of the motivation.

Even more includes will be “hidden” when ROOT configured with -Ddev=ON build option. In that case ROOT uses #ifdef R__LESS_INCLUDES to replace unused includes by class forward declarations. Such dev builds can be used to verify that ROOT-based code really includes all necessary ROOT headers.

Multi-Python PyROOT

In 6.22, the new (experimental) PyROOT is built by default. In order to build with the old PyROOT instead, the option -Dpyroot_legacy=ON can be used. This is a summary of the PyROOT options:

This new PyROOT also introduces the possibility of building its libraries for both Python2 and Python3 in a single ROOT build (CMake >= 3.14 is required). If no option is specified, PyROOT will be built for the most recent Python3 and Python2 versions that CMake can find. If only one version can be found, PyROOT will be built for only that version. Moreover, for a given Python installation to be considered, it must provide both the Python interpreter (binary) and the development package.

The user can also choose to build ROOT only for a given Python version, even if multiple installations exist in the system. For that purpose, the option -DPYTHON_EXECUTABLE=/path/to/python_exec can be used to point to the desired Python installation.

If the user wants to build PyROOT for both Python2 and Python3, but not necessarily with the highest versions that are available on the system, they can provide hints to CMake by using -DPython2_ROOT_DIR=python2_dir and/or -DPython3_ROOT_DIR=python3_dir to point to the root directory of some desired Python installation. Similarly, Python2_EXECUTABLE and/or Python3_EXECUTABLE can be used to point to particular Python executables.

When executing a Python script, the Python version used will determine which version of the PyROOT libraries will be loaded. Therefore, once the ROOT environment has been set (e.g. via source $ROOTSYS/bin/thisroot.sh), the user will be able to use PyROOT from any of the Python versions it has been built for.

Regarding TPython, its library is built only for the highest Python version that PyROOT is built with. Therefore, in a Python3-Python2 ROOT build, the Python code executed with TPython must be Python3-compliant.

Bugs and Issues fixed in this release

Release 6.22/02

Published on August 17, 2020

RDataFrame

Bugs and Issues fixed in this release

HEAD of the v6-22-00-patches branch

These changes will be part of a future 6.22/04.