Logo ROOT  
Reference Guide
RTensorPyz.cxx
Go to the documentation of this file.
1// Author: Stefan Wunsch CERN 07/2019
2// Original PyROOT code by Wim Lavrijsen, LBL
3
4/*************************************************************************
5 * Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. *
6 * All rights reserved. *
7 * *
8 * For the licensing terms see $ROOTSYS/LICENSE. *
9 * For the list of contributors see $ROOTSYS/README/CREDITS. *
10 *************************************************************************/
11
12#include "CPyCppyy.h"
13#include "CPPInstance.h"
14#include "ProxyWrappers.h"
15#include "PyROOTPythonize.h"
16#include "TInterpreter.h"
17#include "PyzCppHelpers.hxx"
18
19#include <sstream>
20
21////////////////////////////////////////////////////////////////////////////
22/// \brief Adopt memory of a Python object with array interface using an RTensor
23/// \param[in] obj PyObject with array interface
24///
25/// This function returns an RTensor which adopts the memory of the given
26/// PyObject. The RTensor takes the data pointer and the shape from the array
27/// interface dictionary.
29{
30 if (!obj) {
31 PyErr_SetString(PyExc_RuntimeError, "Object not convertible: Invalid Python object.");
32 return NULL;
33 }
34
35 // Get array interface of object
36 auto pyinterface = GetArrayInterface(obj);
37 if (pyinterface == NULL)
38 return NULL;
39
40 // Get the data-pointer
41 const auto data = GetDataPointerFromArrayInterface(pyinterface);
42 if (data == 0)
43 return NULL;
44
45 // Get the size of the contiguous memory
46 auto pyshape = PyDict_GetItemString(pyinterface, "shape");
47 if (!pyshape) {
48 PyErr_SetString(PyExc_RuntimeError, "Object not convertible: __array_interface__['shape'] does not exist.");
49 return NULL;
50 }
51 std::vector<std::size_t> shape;
52 for (Py_ssize_t i = 0; i < PyTuple_Size(pyshape); i++) {
53 const auto s = PyLong_AsLong(PyTuple_GetItem(pyshape, i));
54 shape.push_back(s);
55 }
56
57 // Get the typestring and properties thereof
58 const auto typestr = GetTypestrFromArrayInterface(pyinterface);
59 if (typestr.compare("") == 0)
60 return NULL;
61 const auto dtypesize = GetDatatypeSizeFromTypestr(typestr);
62 if (!CheckEndianessFromTypestr(typestr))
63 return NULL;
64
65 const auto dtype = typestr.substr(1, typestr.size());
66 std::string cppdtype = GetCppTypeFromNumpyType(dtype);
67 if (cppdtype.compare("") == 0)
68 return NULL;
69
70 // Get strides
71 if (!PyObject_HasAttrString(obj, "strides")) {
72 PyErr_SetString(PyExc_RuntimeError, "Object not convertible: Object does not have method 'strides'.");
73 return NULL;
74 }
75 auto pystrides = PyObject_GetAttrString(obj, "strides");
76 std::vector<std::size_t> strides;
77 for (Py_ssize_t i = 0; i < PyTuple_Size(pystrides); i++) {
78 strides.push_back(PyInt_AsLong(PyTuple_GetItem(pystrides, i)) / dtypesize);
79 }
80 Py_DECREF(pystrides);
81
82 // Infer memory layout from strides
83 bool rowMajor = true;
84 if (strides.size() > 1) {
85 if (strides.front() < strides.back()) rowMajor = false;
86 }
87
88 // Construct an RTensor of the correct data-type
89 const std::string klassname = "TMVA::Experimental::RTensor<" + cppdtype + ",std::vector<" + cppdtype + ">>";
90 std::stringstream code;
91 code << "new " << klassname << "(reinterpret_cast<" << cppdtype << "*>(" << data << "),{";
92 for (auto s: shape) code << s << ",";
93 code << "},{";
94 for (auto s: strides) code << s << ",";
95 code << "},";
96 if (rowMajor) {
97 code << "TMVA::Experimental::MemoryLayout::RowMajor";
98 }
99 else {
100 code << "TMVA::Experimental::MemoryLayout::ColumnMajor";
101 }
102 code << ")";
103 const auto codestr = code.str();
104 auto address = (void*) gInterpreter->Calc(codestr.c_str());
105
106 // Bind the object to a Python-side proxy
107 auto klass = (Cppyy::TCppType_t)Cppyy::GetScope(klassname);
108 auto pyobj = CPyCppyy::BindCppObject(address, klass);
109
110 // Give Python the ownership of the underlying C++ object
111 ((CPyCppyy::CPPInstance*)pyobj)->PythonOwns();
112
113 // Bind pyobject holding adopted memory to the RTensor
114 if (PyObject_SetAttrString(pyobj, "__adopted__", obj)) {
115 PyErr_SetString(PyExc_RuntimeError, "Object not convertible: Failed to set Python object as attribute __adopted__.");
116 return NULL;
117 }
118
119 // Clean-up and return
120 Py_DECREF(pyinterface);
121 return pyobj;
122}
_object PyObject
Definition: PyMethodBase.h:41
unsigned long long GetDataPointerFromArrayInterface(PyObject *obj)
Get data pointer from Numpy array interface and perform error handling.
unsigned int GetDatatypeSizeFromTypestr(const std::string &typestr)
Get size of data type in bytes from Numpy type string.
std::string GetCppTypeFromNumpyType(const std::string &dtype)
Convert Numpy data-type string to the according C++ data-type string.
std::string GetTypestrFromArrayInterface(PyObject *obj)
Get type string from Numpy array interface and perform error handling.
bool CheckEndianessFromTypestr(const std::string &typestr)
Check whether endianess in type string matches the endianess of ROOT.
PyObject * GetArrayInterface(PyObject *obj)
Get Numpy array interface and perform error handling.
#define gInterpreter
Definition: TInterpreter.h:556
PyObject * BindCppObject(Cppyy::TCppObject_t object, Cppyy::TCppType_t klass, const unsigned flags=0)
TCppScope_t TCppType_t
Definition: cpp_cppyy.h:19
RPY_EXPORTED TCppScope_t GetScope(const std::string &scope_name)
PyObject * AsRTensor(PyObject *self, PyObject *obj)
Adopt memory of a Python object with array interface using an RTensor.
Definition: RTensorPyz.cxx:28
static constexpr double s