Logo ROOT  
Reference Guide
Loading...
Searching...
No Matches
PyMethodBase.cxx
Go to the documentation of this file.
1// @(#)root/tmva/pymva $Id$
2// Authors: Omar Zapata, Lorenzo Moneta, Sergei Gleyzer 2015, Stefan Wunsch 2017
3
4/**********************************************************************************
5 * Project: TMVA - a Root-integrated toolkit for multivariate data analysis *
6 * Package: TMVA *
7 * Class : PyMethodBase *
8 * *
9 * Description: *
10 * Virtual base class for all MVA method based on python *
11 * *
12 **********************************************************************************/
13
14#include <Python.h> // Needs to be included first to avoid redefinition of _POSIX_C_SOURCE
15#include <TMVA/PyMethodBase.h>
16
17#include "TMVA/DataSet.h"
18#include "TMVA/DataSetInfo.h"
19#include "TMVA/MsgLogger.h"
20#include "TMVA/Results.h"
21#include "TMVA/Timer.h"
22#include "TMVA/Tools.h"
23
24#include "TSystem.h"
25
26#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
27#include <numpy/arrayobject.h>
28
29using namespace TMVA;
30
31namespace TMVA {
32namespace Internal {
33class PyGILRAII {
34 PyGILState_STATE m_GILState;
35
36public:
37 PyGILRAII() : m_GILState(PyGILState_Ensure()) {}
38 ~PyGILRAII() { PyGILState_Release(m_GILState); }
39};
40} // namespace Internal
41
42/// get current Python executable used by ROOT
44 TString python_version = gSystem->GetFromPipe("root-config --python-version");
45 if (python_version.IsNull()) {
46 TMVA::gTools().Log() << kFATAL << "Can't find a valid Python version used to build ROOT" << Endl;
47 return nullptr;
48 }
49#ifdef _MSC_VER
50 // on Windows there is a space before the version and the executable is python.exe
51 // for both versions of Python
52 python_version.ReplaceAll(" ", "");
53 if (python_version[0] == '2' || python_version[0] == '3')
54 return "python";
55#endif
56 if (python_version[0] == '2')
57 return "python";
58 else if (python_version[0] == '3')
59 return "python3";
60
61 TMVA::gTools().Log() << kFATAL << "Invalid Python version used to build ROOT : " << python_version << Endl;
62 return nullptr;
63}
64
65} // namespace TMVA
66
67
68// NOTE: Introduce here nothing that breaks if multiple instances
69// of the same method share these objects, e.g., the local namespace.
73
77
80
81///////////////////////////////////////////////////////////////////////////////
82
83PyMethodBase::PyMethodBase(const TString &jobName, Types::EMVA methodType, const TString &methodTitle, DataSetInfo &dsi,
84 const TString &theOption)
85 : MethodBase(jobName, methodType, methodTitle, dsi, theOption),
86 fClassifier(NULL)
87{
88 if (!PyIsInitialized()) {
90 }
91
92 // Set up private local namespace for each method instance
93 fLocalNS = PyDict_New();
94 if (!fLocalNS) {
95 Log() << kFATAL << "Can't init local namespace" << Endl;
96 }
97}
98
99///////////////////////////////////////////////////////////////////////////////
100
102 DataSetInfo &dsi,
103 const TString &weightFile): MethodBase(methodType, dsi, weightFile),
104 fClassifier(NULL)
105{
106 if (!PyIsInitialized()) {
107 PyInitialize();
108 }
109
110 // Set up private local namespace for each method instance
111 fLocalNS = PyDict_New();
112 if (!fLocalNS) {
113 Log() << kFATAL << "Can't init local namespace" << Endl;
114 }
115}
116
117///////////////////////////////////////////////////////////////////////////////
118
120{
121 // should we delete here fLocalNS ?
122 //PyFinalize();
123 if (fLocalNS) Py_DECREF(fLocalNS);
124}
125
126///////////////////////////////////////////////////////////////////////////////
127/// Evaluate Python code
128///
129/// \param[in] code Python code as string
130/// \return Python object from evaluation of code line
131///
132/// Take a Python code as input and evaluate it in the local namespace. Then,
133/// return the result as Python object.
134
136{
138 PyObject *pycode = Py_BuildValue("(sOO)", code.Data(), fGlobalNS, fLocalNS);
139 PyObject *result = PyObject_CallObject(fEval, pycode);
140 Py_DECREF(pycode);
141 return result;
142}
143
144///////////////////////////////////////////////////////////////////////////////
145/// Initialize Python interpreter
146///
147/// NOTE: We introduce a shared global namespace `fGlobalNS`, but using
148/// a private local namespace `fLocalNS`. This prohibits the interference
149/// of instances of the same method with the same factory, e.g., by overriding
150/// variables in the same local namespace.
151
153{
155
156 bool pyIsInitialized = PyIsInitialized();
157 if (!pyIsInitialized) {
158 Py_Initialize();
159 }
160
162 if (!pyIsInitialized) {
163 _import_array();
164 }
165
166 // note fMain is a borrowed reference
167 fMain = PyImport_AddModule("__main__");
168 if (!fMain) {
169 Log << kFATAL << "Can't import __main__" << Endl;
170 Log << Endl;
171 }
172 Py_INCREF(fMain);
173
174 fGlobalNS = PyModule_GetDict(fMain);
175 if (!fGlobalNS) {
176 Log << kFATAL << "Can't init global namespace" << Endl;
177 Log << Endl;
178 }
179 Py_INCREF(fGlobalNS);
180
181 #if PY_MAJOR_VERSION < 3
182 //preparing objects for eval
183 PyObject *bName = PyUnicode_FromString("__builtin__");
184 // Import the file as a Python module.
185 // returns a new reference
186 fModuleBuiltin = PyImport_Import(bName);
187 if (!fModuleBuiltin) {
188 Log << kFATAL << "Can't import __builtin__" << Endl;
189 Log << Endl;
190 }
191 #else
192 //preparing objects for eval
193 PyObject *bName = PyUnicode_FromString("builtins");
194 // Import the file as a Python module.
195 fModuleBuiltin = PyImport_Import(bName);
196 if (!fModuleBuiltin) {
197 Log << kFATAL << "Can't import builtins" << Endl;
198 Log << Endl;
199 }
200 #endif
201
202 // note mDict is a borrowed reference
203 PyObject *mDict = PyModule_GetDict(fModuleBuiltin);
204 fEval = PyDict_GetItemString(mDict, "eval");
205 fOpen = PyDict_GetItemString(mDict, "open");
206 // fEval and fOpen are borrowed referencers and we need to keep them alive
207 if (fEval) Py_INCREF(fEval);
208 if (fOpen) Py_INCREF(fOpen);
209
210 // bName is a new reference (from PyUnicode_FromString)
211 Py_DECREF(bName);
212
213 //preparing objects for pickle
214 PyObject *pName = PyUnicode_FromString("pickle");
215 // Import the file as a Python module.
216 // return object is a new reference !
217 fModulePickle = PyImport_Import(pName);
218 if (!fModulePickle) {
219 Log << kFATAL << "Can't import pickle" << Endl;
220 Log << Endl;
221 }
222 PyObject *pDict = PyModule_GetDict(fModulePickle);
223 // note the following return objects are borrowed references
224 fPickleDumps = PyDict_GetItemString(pDict, "dump");
225 fPickleLoads = PyDict_GetItemString(pDict, "load");
226 if (fPickleDumps) Py_INCREF(fPickleDumps);
227 if (fPickleLoads) Py_INCREF(fPickleLoads);
228
229 Py_DECREF(pName);
230}
231
232///////////////////////////////////////////////////////////////////////////////
233// Finalize Python interpreter
234
236{
237 if (fEval) Py_DECREF(fEval);
238 if (fOpen) Py_DECREF(fOpen);
239 if (fModuleBuiltin) Py_DECREF(fModuleBuiltin);
240 if (fPickleDumps) Py_DECREF(fPickleDumps);
241 if (fPickleLoads) Py_DECREF(fPickleLoads);
242 if(fMain) Py_DECREF(fMain);//objects fGlobalNS and fLocalNS will be free here
243 if (fGlobalNS) Py_DECREF(fGlobalNS);
244 Py_Finalize();
245}
246
247///////////////////////////////////////////////////////////////////////////////
248/// Check Python interpreter initialization status
249///
250/// \return Boolean whether interpreter is initialized
251
253{
254 if (!Py_IsInitialized()) return kFALSE;
255 if (!fEval) return kFALSE;
256 if (!fModuleBuiltin) return kFALSE;
257 if (!fPickleDumps) return kFALSE;
258 if (!fPickleLoads) return kFALSE;
259 return kTRUE;
260}
261
262///////////////////////////////////////////////////////////////////////////////
263/// Serialize Python object
264///
265/// \param[in] path Path where object is written to file
266/// \param[in] obj Python object
267///
268/// The input Python object is serialized and written to a file. The Python
269/// module `pickle` is used to do so.
270
272{
274
275 PyObject *file_arg = Py_BuildValue("(ss)", path.Data(),"wb");
276 PyObject *file = PyObject_CallObject(fOpen,file_arg);
277 PyObject *model_arg = Py_BuildValue("(OO)", obj,file);
278 PyObject *model_data = PyObject_CallObject(fPickleDumps , model_arg);
279
280 Py_DECREF(file_arg);
281 Py_DECREF(file);
282 Py_DECREF(model_arg);
283 Py_DECREF(model_data);
284}
285
286///////////////////////////////////////////////////////////////////////////////
287/// Unserialize Python object
288///
289/// \param[in] path Path to serialized Python object
290/// \param[in] obj Python object where the unserialized Python object is loaded
291/// \return Error code
292
294{
295 // Load file
296 PyObject *file_arg = Py_BuildValue("(ss)", path.Data(),"rb");
297 PyObject *file = PyObject_CallObject(fOpen,file_arg);
298 if(!file) return 1;
299
300 // Load object from file using pickle
301 PyObject *model_arg = Py_BuildValue("(O)", file);
302 *obj = PyObject_CallObject(fPickleLoads , model_arg);
303 if(!obj) return 2;
304
305 Py_DECREF(file_arg);
306 Py_DECREF(file);
307 Py_DECREF(model_arg);
308
309 return 0;
310}
311
312///////////////////////////////////////////////////////////////////////////////
313/// Execute Python code from string
314///
315/// \param[in] code Python code as string
316/// \param[in] errorMessage Error message which shall be shown if the execution fails
317/// \param[in] start Start symbol
318///
319/// Helper function to run python code from string in local namespace with
320/// error handling
321/// `start` defines the start symbol defined in PyRun_String (Py_eval_input,
322/// Py_single_input, Py_file_input)
323
324void PyMethodBase::PyRunString(TString code, TString errorMessage, int start) {
325 //std::cout << "Run: >> " << code << std::endl;
326 fPyReturn = PyRun_String(code, start, fGlobalNS, fLocalNS);
327 if (!fPyReturn) {
328 Log() << kWARNING << "Failed to run python code: " << code << Endl;
329 Log() << kWARNING << "Python error message:" << Endl;
330 PyErr_Print();
331 Log() << kFATAL << errorMessage << Endl;
332 }
333}
334
335///////////////////////////////////////////////////////////////////////////////
336/// Execute Python code from string
337///
338/// \param[in] code Python code as string
339/// \param[in] globalNS Global Namespace for Python Session
340/// \param[in] localNS Local Namespace for Python Session
341///
342/// Overloaded static Helper function to run python code
343/// from string and throw runtime error if the Python session
344/// is unable to execute the code
345
346void PyMethodBase::PyRunString(TString code, PyObject *globalNS, PyObject *localNS){
347 PyObject *fPyReturn = PyRun_String(code, Py_single_input, globalNS, localNS);
348 if (!fPyReturn) {
349 std::cout<<"\nPython error message:\n";
350 PyErr_Print();
351 throw std::runtime_error("\nFailed to run python code: "+code);
352 }
353}
354
355///////////////////////////////////////////////////////////////////////////////
356/// Returns `const char*` from Python string in PyObject
357///
358/// \param[in] string Python String object
359/// \return String representation in `const char*`
360
362 PyObject* encodedString = PyUnicode_AsUTF8String(string);
363 const char* cstring = PyBytes_AsString(encodedString);
364 return cstring;
365}
366
367//////////////////////////////////////////////////////////////////////////////////
368/// \brief Utility function which retrieves and returns the values of the Tuple
369/// object as a vector of size_t
370///
371/// \param[in] tupleObject Python Tuple object
372/// \return vector of tuple members
373
374std::vector<size_t> PyMethodBase::GetDataFromTuple(PyObject* tupleObject){
375 std::vector<size_t>tupleVec;
376 for(Py_ssize_t tupleIter=0;tupleIter<PyTuple_Size(tupleObject);++tupleIter){
377 auto itemObj = PyTuple_GetItem(tupleObject,tupleIter);
378 if (itemObj == Py_None)
379 tupleVec.push_back(0); // case shape is for example (None,2,3)
380 else
381 tupleVec.push_back((size_t)PyLong_AsLong(itemObj));
382 }
383 return tupleVec;
384}
385
386//////////////////////////////////////////////////////////////////////////////////
387/// \brief Utility function which retrieves and returns the values of the List
388/// object as a vector of size_t
389///
390/// \param[in] listObject Python List object
391/// \return vector of list members
392
393std::vector<size_t> PyMethodBase::GetDataFromList(PyObject* listObject){
394 std::vector<size_t>listVec;
395 for(Py_ssize_t listIter=0; listIter<PyList_Size(listObject);++listIter){
396 listVec.push_back((size_t)PyLong_AsLong(PyList_GetItem(listObject,listIter)));
397 }
398 return listVec;
399}
400
401//////////////////////////////////////////////////////////////////////////////////
402/// \brief Utility function which checks if a given key is present in a Python
403/// dictionary object and returns the associated value or throws runtime
404/// error.
405///
406/// \param[in] dict Python Dict object
407/// \param[in] key the key to search for in the dict
408/// \return Associated value PyObject
410{
411 return PyDict_GetItemWithError(dict, PyUnicode_FromString(key));
412}
int Py_ssize_t
Definition CPyCppyy.h:215
#define PyBytes_AsString
Definition CPyCppyy.h:64
_object PyObject
start
Definition Rotated.cxx:223
int Int_t
Signed integer 4 bytes (int).
Definition RtypesCore.h:59
constexpr Bool_t kFALSE
Definition RtypesCore.h:108
constexpr Bool_t kTRUE
Definition RtypesCore.h:107
externTSystem * gSystem
Definition TSystem.h:582
MsgLogger & Log() const
Class that contains all the data information.
Definition DataSetInfo.h:62
MethodBase(const TString &jobName, Types::EMVA methodType, const TString &methodTitle, DataSetInfo &dsi, const TString &theOption="")
standard constructor
ostringstream derivative to redirect and format output
Definition MsgLogger.h:57
static std::vector< size_t > GetDataFromTuple(PyObject *tupleObject)
Utility function which retrieves and returns the values of the Tuple object as a vector of size_t.
static int PyIsInitialized()
Check Python interpreter initialization status.
static std::vector< size_t > GetDataFromList(PyObject *listObject)
Utility function which retrieves and returns the values of the List object as a vector of size_t.
static PyObject * fOpen
static PyObject * fPickleDumps
PyObject * Eval(TString code)
Evaluate Python code.
static PyObject * fMain
static void PyInitialize()
Initialize Python interpreter.
static void Serialize(TString file, PyObject *classifier)
Serialize Python object.
static void PyFinalize()
static Int_t UnSerialize(TString file, PyObject **obj)
Unserialize Python object.
PyObject * fClassifier
static const char * PyStringAsString(PyObject *string)
Returns const char* from Python string in PyObject.
static PyObject * fPickleLoads
static PyObject * fGlobalNS
static PyObject * fModulePickle
static PyObject * fModuleBuiltin
PyMethodBase(const TString &jobName, Types::EMVA methodType, const TString &methodTitle, DataSetInfo &dsi, const TString &theOption="")
static PyObject * fEval
static PyObject * GetValueFromDict(PyObject *dict, const char *key)
Utility function which checks if a given key is present in a Python dictionary object and returns the...
void PyRunString(TString code, TString errorMessage="Failed to run python code", int start=256)
Execute Python code from string.
MsgLogger & Log() const
Definition Tools.h:228
Basic string class.
Definition TString.h:138
const char * Data() const
Definition TString.h:384
TString & ReplaceAll(const TString &s1, const TString &s2)
Definition TString.h:713
Bool_t IsNull() const
Definition TString.h:422
create variable transformations
TString Python_Executable()
Function to find current Python executable used by ROOT If "Python3" is installed,...
Tools & gTools()
MsgLogger & Endl(MsgLogger &ml)
Definition MsgLogger.h:148