English Français

Documentation / Manuel utilisateur en Python : PDF version

XIV.2. Macros Python HowTo

XIV.2. Macros Python HowTo

In this section we propose to explain how to handle some of the ROOT or C++-based object in python. The idea is just to breakdown the already provided pieces of code shown in Section I.3.5.

XIV.2.1. Macro "howtoConvertTDataServerArray.py"

XIV.2.1.1. Objective

The goal of this macro is only to show how to convert a TDataServer into a numpy.array and vice-versa.

XIV.2.1.2. Macro Uranie

"""
Example of TDataServer into numpy array converter
"""
import numpy as np
from rootlogon import DataServer

# import data into a dataserver
tds = DataServer.TDataServer("tds", "pouet")
tds.fileDataRead("myData.dat")
print("Dumping tds")
tds.scan()

# Define the list of variable to be read
VarList = "x:y"

# Create an array by defining the shape and the type (this array is a matrix)
TdsData = np.empty(shape=(tds.getNPatterns(), len(VarList.split(":"))),
                   dtype=np.float64)
# Dump the data into the created array (the last field empty unless a select is done on the sample)
tds.getTuple().extractData(TdsData, TdsData.size, VarList, tds.getCut().GetTitle())

# Check the content
print("Dumping the array")
print(TdsData)

# Create a new Dataserver and feed it
tds2 = DataServer.TDataServer("brandnew", "pouet")
# Add the attribute in the dataserver and create the tuple
for name in VarList.split(":"):
    index = VarList.split(":").index(name)
    VarData = np.ascontiguousarray(TdsData.transpose()[index])
    tds2.addAttributeUsingData(name, VarData, VarData.size)

print("Dumping new tds")
tds2.Scan("*")

The first block reads an input file myData.dat and store the data in the TDataServer as usually done. The new block is what we recommend: define the list of variable that one wants to store

VarList="x:y"

Once done, the np.array has to be constructed by defining its content and size. Thanks to the TDataServer object and the list of variable this can be done easily as

TdsData=np.empty( shape=(tds.getNPatterns(),len(VarList.split(":"))), dtype=npfloat64 )

Finally, the extractData method is called with the given created array and list of variables.

tds.getTuple().extractData(TdsData, TdsData.size, VarList)

The second part consists in creating a brand new TDataServer from a np.array, which basically will consist in our case in re-creatting the original TDataServer with a different name and title. To do so, one starts by creating the TDataServer

tds2=DataServer.TDataServer("brandnew","pouet")

Then, one loops over the list of variable and in the loop two things are done :

  • a new array is created by transposing the data, selecting the proper array of data and construct this as an memory-continuous array (for C++-consistency)

  • a new attribute is created using the method addAttributeUsingData that needs the name of the attribute, the buffer and the size of the array provded

for name in VarList.split(":"):
    VarData=np.ascontiguousarray(TdsData.transpose()[ VarList.split(":").index(name) ])
    tds2.addAttributeUsingData(name, VarData, VarData.size)
    pass

The output of this macro is shown in Section XIV.2.1.3.

XIV.2.1.3. Console

Processing howtoConvertTDataServerArray.py...

--- Uranie v0.0/0 --- Developed with ROOT (6.32.02)
                      Copyright (C) 2013-2024 CEA/DES 
                      Contact: support-uranie@cea.fr 
                      Date: Tue Jan 09, 2024

Dumping tds
************************************************
*    Row   * tds__n__i *       x.x *       y.y *
************************************************
*        0 *         1 *        -5 *        25 *
*        1 *         2 *        -4 *        16 *
*        2 *         3 *        -3 *         9 *
*        3 *         4 *        -2 *         4 *
*        4 *         5 *        -1 *         1 *
*        5 *         6 *         0 *         0 *
*        6 *         7 *         1 *         1 *
*        7 *         8 *         2 *         4 *
*        8 *         9 *         3 *         9 *
*        9 *        10 *         4 *        16 *
*       10 *        11 *         5 *        25 *
************************************************
Dumping the array
[[-5. 25.]
 [-4. 16.]
 [-3.  9.]
 [-2.  4.]
 [-1.  1.]
 [ 0.  0.]
 [ 1.  1.]
 [ 2.  4.]
 [ 3.  9.]
 [ 4. 16.]
 [ 5. 25.]]
Dumping new tds
************************************************
*    Row   * brandnew_ *       x.x *       y.y *
************************************************
*        0 *         1 *        -5 *        25 *
*        1 *         2 *        -4 *        16 *
*        2 *         3 *        -3 *         9 *
*        3 *         4 *        -2 *         4 *
*        4 *         5 *        -1 *         1 *
*        5 *         6 *         0 *         0 *
*        6 *         7 *         1 *         1 *
*        7 *         8 *         2 *         4 *
*        8 *         9 *         3 *         9 *
*        9 *        10 *         4 *        16 *
*       10 *        11 *         5 *        25 *
************************************************

XIV.2.2. Macro "howtoConvertTMatrixDArray.py"

XIV.2.2.1. Objective

The goal of this macro is only to show how to convert a TMatrixD object into a numpy.array and vice-versa.

XIV.2.2.2. Macro Uranie

"""
Example of TMatrix into numpy array conversion
"""
import numpy as np
import ROOT

# define the number of rows and columns
nrow = 3
ncol = 5

# Initialise and fill a TMatrixD
InMat = ROOT.TMatrixD(nrow, ncol)
for i in range(nrow):
    for j in range(ncol):
        InMat[i][j] = ROOT.gRandom.Gaus(0, 1)

print("Original TMatrixD")
InMat.Print()

# Create the ndarray with the good shape
mat_version = np.frombuffer(InMat.GetMatrixArray(), dtype=np.float64,
                            count=nrow*ncol).reshape(nrow, ncol)
print("Numpy array transformation")
print(mat_version)

# Back to another TMatrixD
OutMat = ROOT.TMatrixD(nrow, ncol)
OutMat.SetMatrixArray(mat_version)
print("\nRecreated TMatrixD")
OutMat.Print()

The first block simply defines the size of the matrix to be created and creates along, from scratch, a TMatrixD with a random gaussian content. Once done, the conversion into a np.array is done in a single line manner below that would be breakdown to get the full picture:

mat_version=np.frombuffer( InMat.GetMatrixArray(), dtype=np.float64, count=nrow*ncol ).reshape(nrow,ncol)

The content of the original TMatrixD can be accessed by calling GetMatrixArray(). This returns a pointer to the first element's address, so it can be used by the lowest level array object of numpy, the np.frombuffer which also needs the type of data and the number of elements. This is important as the memory will be scanned given these two information. The last step is to call, on this newly created object the reshape method to create a properly organised np.array.

The second part consists in creating a brand new TMatrixD from the newly created np.array. The idea is simply to create the object empty and use the SetMatrixArray method to dump the array content into the matrix, as done below.

OutMat=ROOT.TMatrixD(nrow,ncol)
OutMat.SetMatrixArray(mat_version)

The output of this macro is shown in Section XIV.2.2.3.

XIV.2.2.3. Console

Processing howtoConvertTMatrixDArray.py...

--- Uranie v0.0/0 --- Developed with ROOT (6.32.02)
                      Copyright (C) 2013-2024 CEA/DES 
                      Contact: support-uranie@cea.fr 
                      Date: Tue Jan 09, 2024

Original TMatrixD

3x5 matrix is as follows

     |      0    |      1    |      2    |      3    |      4    |
----------------------------------------------------------------------
   0 |     0.9989     -0.4348      0.7818    -0.03005      0.8243 
   1 |   -0.05672     -0.9009     -0.0747    0.007912     -0.4108 
   2 |      1.391     -0.9851    -0.04894      -1.443      -1.061 

Numpy array transformation
[[ 0.99893272 -0.43476439  0.78179626 -0.03005277  0.82426369]
 [-0.05671733 -0.90087599 -0.07470447  0.00791221 -0.41076317]
 [ 1.39119397 -0.98506611 -0.04894054 -1.44333537 -1.06067041]]

Recreated TMatrixD

3x5 matrix is as follows

     |      0    |      1    |      2    |      3    |      4    |
----------------------------------------------------------------------
   0 |     0.9989     -0.4348      0.7818    -0.03005      0.8243 
   1 |   -0.05672     -0.9009     -0.0747    0.007912     -0.4108 
   2 |      1.391     -0.9851    -0.04894      -1.443      -1.061 

XIV.2.3. Macro "howtoLoadFunction.py"

XIV.2.3.1. Objective

The goal of this macro is only to show how to load a C++ function in python as this could be of use for instance when producing a surrogate model (all of them can be exported in C++ and even though the recommended way to handle these is through the Uranie classes, it might happen that one wants to put one's hand on it).

Our example is taken from the UserFunction.C file provided with the Uranie sources (look for it in the the "$URANIESYS/share/uranie/macros" folder). The operation function only compute the product, ratio, sum and difference between two provided inputs (just checking for consistency that the denominator of the ratio is not equal to zero). The code is shown here for illustration purpose.

void operation(double *x, Double_t *y)
{
  Double_t x1 = x[0], x2 = x[1];
  // product
  y[0] = x1 * x2;
  // divide
  y[1] = ((x2!=0) ? x1 / x2 : -999);
  // sum
  y[2] = x1 + x2;
  // diff
  y[3] = x1 - x2;  
}

XIV.2.3.2. Macro Uranie

"""
Example of C++ function in python
"""
import numpy as np
import ROOT

# Loading a function
ROOT.gROOT.LoadMacro("UserFunctions.C")

# Preparing inputs and outputs
inp = np.array([1.2, 0.8])  # input values
out = np.zeros(4)  # output values initialise with 4 zeros

# Call the method, through the ROOT interface
ROOT.operation(inp, out)

# Print the result
print("Results are")
print(out)

The first block simply loads the input file UserFunctions.C through the ROOT's interface.

ROOT.gROOT.LoadMacro("UserFunctions.C")

Once done, the input and output np.array object are created, the former with provided values to be tested, the latter only filled with zeros. Calling the function is simply done by providing input and output arrays to the function operator that is now stored within ROOT by doing:

ROOT.operation( inp, out )

The output of this macro is shown in Section XIV.2.3.3.

XIV.2.3.3. Console

Processing howtoLoadFunction.py...

--- Uranie v0.0/0 --- Developed with ROOT (6.32.02)
                      Copyright (C) 2013-2024 CEA/DES 
                      Contact: support-uranie@cea.fr 
                      Date: Tue Jan 09, 2024

Results are
[0.96 1.5  2.   0.4 ]

XIV.2.4. Macro "howtoPassReference.py"

XIV.2.4.1. Objective

The goal of this macro is to show how to use function that would have been defined using the "by-reference" prototype, which is specific to C++. A simple exemple in Uranie is provided when considering the computeQuantile method: the second argument is a double in which the result will be stored.

XIV.2.4.2. Macro Uranie

"""
Example of C++style reference usage
"""
from ctypes import c_double  # for ROOT version greater or equal to 6.20
from rootlogon import DataServer

# create a dataserver and read data
tds = DataServer.TDataServer("pouet", "foo")
tds.fileDataRead("myData.dat")

# compute quantile (this method get the results 'by reference')
proba = 0.9  # ROOT.Double(0.9) for ROOT version lower than 6.20
quant = c_double(0.0)  # ROOT.Double(0.0) for ROOT version lower than 6.20

# Compute the quantile and dump the value returned by reference
tds.computeQuantile("x", proba, quant)
print("Result is ")
print(quant.value)

It starts by calling the ctypes module in order to get the c_double interface. This is only for ROOT version greater than 6.20. For older version, the solution relied on ROOT only (calling another interface discussed later-on).

from ctypes import c_double ## for ROOT version greater or equal to 6.20

Once done, a TDataServer object is created and filled from the myData.dat file. One then wants to estimate the 90% quantile, which can be done by calling the computeQuantile method, which requires two double as input, both passed "by-reference". To do so, one has to create two specific objects as below:

quant=c_double(0.9) ## ROOT.Double(0.95) for ROOT version lower than 6.20
value=c_double(0.00) ## ROOT.Double(0.0) for ROOT version lower than 6.20

Once done, the method is called and the result is displayed by getting the value of the c_double object

print(value.value)

The output of this macro is shown in Section XIV.2.4.3.

XIV.2.4.3. Console

Processing howtoPassReference.py...

--- Uranie v0.0/0 --- Developed with ROOT (6.32.02)
                      Copyright (C) 2013-2024 CEA/DES 
                      Contact: support-uranie@cea.fr 
                      Date: Tue Jan 09, 2024

Result is 
4.0
/language/en