Using PyROOT code in a ROOT macro

Hello, I have a short python script that I would like to call from a regular ROOT macro. Here is the original script:

[code]import Get_Info
import time, numpy, sys

runnum = int(sys.argv[1])
info = Get_Info.Get_Info(runnum)
st,sd = info[‘Start time’],info[‘Start date’]
et,ed = info[‘end time’],info[‘end date’]

sdt = time.mktime(time.strptime(st+sd,"%H:%M:%S%m/%d/%Y"))
edt = time.mktime(time.strptime(et+ed,"%H:%M:%S%m/%d/%Y"))
delta = (edt-sdt)

nevents = int(info[‘No. Events’])
evt_times = numpy.arange(sdt,edt,delta/nevents)
[/code]

Now, I would like to call this script inside a ROOT macro such that I can get the array evt_times for a given integer as a command-line argument. I looked at the TPython documentation but couldn’t get anything to work. I tried creating a class with a member function that returns a TVectorD:

class Evt_Times(): def __init__(self): print 'Creating Evt_Times object...' def evt_times(self,runnum): info = Get_Info.Get_Info(runnum) ... nevents = int(info['No. Events']) evt_times = numpy.arange(sdt,edt,delta/nevents) return ROOT.TVectorD(len(evt_times),evt_times)

But then in the ROOT interpreter none of the commands that I tried seemed to work:

root [0] TPython::LoadMacro("dateinterpolate.py")
root [1] et = Evt_Times()
Creating Evt_Times object...
(class Evt_Times::Evt_Times)4355969680
root [2] et.evt_times(387)
Error: Can't call Evt_Times::evt_times(387) in current scope (tmpfile):1:
Possible candidates are...
(in Evt_Times)
public: TPyReturnEvt_Timesevt_times Evt_Times::evt_times();
*** Interpreter error recovered ***
root [4] et->evt_times(387)
Error: Can't call Evt_Times::evt_times(387) in current scope (tmpfile):1:
Possible candidates are...
(in Evt_Times)
public: TPyReturnEvt_Timesevt_times Evt_Times::evt_times();
*** Interpreter error recovered ***
root [5] et::evt_times(387)
Error: Function evt_times(387) is not defined in current scope  (tmpfile):1:
*** Interpreter error recovered ***
root [6] Evt_Times::evt_times(387)
Error: cannot call member function without object (tmpfile):1:
  (compiled)   0 TPyReturnEvt_Timesevt_times Evt_Times::evt_times();
Calling : Evt_Times::evt_times(int);
Match rank: file     line  signature
*** Interpreter error recovered ***
root [7] TVectorD tvd = (TVectorD*)et.evt_times(387)
Error: Can't call Evt_Times::evt_times(387) in current scope (tmpfile):1:
Possible candidates are...
(in Evt_Times)
public: TPyReturnEvt_Timesevt_times Evt_Times::evt_times();
*** Interpreter error recovered ***

Any ideas on where to go would be appreciated. I would like to have access to the array evt_times inside ROOT, ideally as a TVector or as a more basic type.

Thank you,
Jean-François

1 Like

Hi,

instead of: et = Evt_Times() use: Evt_Times et;And no, I don’t know why it makes a difference. Perhaps b/c the latter statement is valid C++, whereas the former isn’t, and the code then takes some different path through CINT.

Cheers,
Wim

I’m still not able to get the TVector in ROOT. Do I want to do

Evt_Times et;
TVectorD tvd = (TVectorD*) et.evt_times(387)

or something? I also tried (void*), not casting, putting et.evt_times(387) in a constructor…

Hi,

it’s two steps: one cast to (void*) to convince CINT to call the converter, then another (TVectorD*) cast to convince it of the class (for some reason, the assignment itself isn’t enough; note also that the assignment is to a TVectorD*, not TVectorD: you’d have to dereference for that to work). Note that I also think that the python script should either keep the TVectorD alive, or relinquish ownership if the object is returned like this.

Evt_Times et; TVectorD* tvd = (TVectorD*)(void*) et.evt_times(387)or:

Evt_Times et; TVectorD tvd = *(TVectorD*)(void*) et.evt_times(387)
For the copy (second statement), the lifetime of the TPyReturn should be sufficient to do the copy before the vector is deleted from the python side.

Cheers,
Wim

Thanks, now it works in my macro.

I am able to include this in a ROOT macro that I run without compilation, but if I ask for +compilation, ACLiC complains:

[quote]root [0]
Processing my_analysis.C+("…/rootfiles/Outputs/Run0464")…
Info in TUnixSystem::ACLiC: creating shared library /Users/jfcaron/Projects/TRIUMFBeamTest/standard_analysis/./my_analysis_C.so
In file included from /Users/jfcaron/Projects/TRIUMFBeamTest/standard_analysis/my_analysis_C_ACLiC_dict.cxx:17:
In file included from /Users/jfcaron/Projects/TRIUMFBeamTest/standard_analysis/my_analysis_C_ACLiC_dict.h:34:
/Users/jfcaron/Projects/TRIUMFBeamTest/standard_analysis/./my_analysis.C:540:3: error: use of undeclared identifier 'Evt_Times’
Evt_Times et;
^
/Users/jfcaron/Projects/TRIUMFBeamTest/standard_analysis/./my_analysis.C:541:39: error: use of undeclared identifier 'et’
TVectorD * tvd = (TVectorD*)(void*) et.evt_times(runnum);
^
2 errors generated.
clang: error: no such file or directory: '/Users/jfcaron/Projects/TRIUMFBeamTest/standard_analysis/my_analysis_C_ACLiC_dict.o’
Error in : Compilation failed!
Error: Function my_analysis("…/rootfiles/Outputs/Run0464") is not defined in current scope :0:
*** Interpreter error recovered ***
[/quote]
Is it possible to allow this to work with the macro compiled? I understand that the python part will not be compiled, but I thought it should be possible to have the compiled C++ code talk to the python side…at least I was able to compile and run a shorter example macro with TPython::Exec().

Hi,

not used this way: the LoadMacro() code makes use of the class generator feature of CINT to dynamically create the corresponding C++ classes in CINT. Naturally, the C++ compiler wants to see, for static compilation, a file containing the C++ class definition of Evt_Times, and there’s no such thing.

You could use the Python C-API to manipulate the Python objects, but they’d have to stay on the Python side then.

Note that TPython::Exec() is different: it takes a const char* and returns a Bool_t, so no unknown C++ classes there.

Cheers,
Wim

1 Like

What if I refactored the code to not use a class, but instead to just define a python function that takes an integer argument and returns a TVector. I could execute the code with a compiled macro with TPython::Exec, but would I be able to copy the vector into ROOT?

What if the python code was just a script that sent the contents of the vector to stdout, could my ROOT macro pick it up somehow?

Thanks for any ideas.

If the function returning a TVectorD lives in a module, something like this would do for both interpreted macro and complied function:[code]#include “TPython.h”
#include “TVectorD.h”

void mymacro() {
TPython::Exec(“import MyModule”);
TVectorD* tvd2 = (TVectorD*)(void*)TPython::Eval(“MyModule.myfunc()”);
}[/code]With again taking care that the lifetime of the TVectorD is properly handled.

Cheers,
Wim

1 Like