PyDoubleBuffer seems to have incorrect size

I seem to be misunderstanding the PyDoubleBuffer class. Specifically I am using it from a call to TGraph::GetX for a graph with five points, but the shape tuple responds with (2147483647,) and PyDoubleBuffer::tolist causes a segmentation fault. Am I abusing this class or is it not working in Python 3?

import numpy as np
import ROOT

x = y = np.linspace(0,5,num=5)
g = ROOT.TGraph(len(x), x, y)
print(g.GetX().shape)
print(g.GetX().tolist())

Output:

$ python --version
Python 3.6.4
$ python test.py 
(2147483647,)

 *** Break *** segmentation violation
[/usr/lib/system/libsystem_platform.dylib] _sigtramp (no debug info)
[<unknown binary>] (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] _PyCFunction_FastCallDict (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] call_function (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] _PyEval_EvalFrameDefault (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] _PyEval_EvalCodeWithName (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] PyEval_EvalCode (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] run_mod (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] PyRun_FileExFlags (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] PyRun_SimpleFileExFlags (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Python] Py_Main (no debug info)
[/opt/local/Library/Frameworks/Python.framework/Versions/3.6/Resources/Python.app/Contents/MacOS/Python] (no debug info)
[/usr/lib/system/libdyld.dylib] start (no debug info)
[<unknown binary>] (no debug info)

The return of GetX() is a naked pointer to double. It is pythonized by calling SetSize using GetN(). See this code snippet from ROOT.py:

   # python side pythonizations (should live in their own file, if we get many)
      def set_size(self, buf):
         buf.SetSize(self.GetN())
         return buf

    # TODO: add pythonization API to pypy-c
      if not PYPY_CPPYY_COMPATIBILITY_FIXME:
         cppyy.add_pythonization(
            cppyy.compose_method("^TGraph(2D)?$|^TGraph.*Errors$", "GetE?[XYZ]$", set_size))
         gRootDir = self.gRootDir
      else:
         gRootDir = _root.gRootDir

which is used internally by the PyDoubleBuffer (the SetSize() call is something I made up; it’s not part of any other interface), so that methods like len() work.

However, that code predates things like shape and tolist (new buffer interface, inherited from the python buffer type), and these are taken from the internal buffer info, which is clearly not properly filled/set. (‘shape’ is weird anyway for 1-dim arrays.)

Your best bet is probably to pass the result of GetX() to a numpy array, which should work b/c len() does, and then use that instead.

Note that all this stuff is completely rewritten in cppyy master …

Do any of the ROOT representatives want to comment on the time frame for updating cppyy within ROOT? Is there a simple way to use the master cppyy with ROOT?

I had already tried creating a numpy array from the object, but also get a segmentation fault.

import numpy as np
import ROOT

x = y = np.linspace(0,5,num=5)
g = ROOT.TGraph(len(x), x, y)
a = np.array(g.GetX()) #This cause segmentation fault
print(a)

I see … numpy happily uses shape internally. I tried a couple of different methods using reshape/resize w/o copying, but no luck. So don’t know of a workaround, unless you want to go nuclear and do:

a = np.array(tuple(g.GetX()))

Using cppyy master per se with ROOT is easy, except that you lose all the ROOT specific pythonizations, break TPython and a few other classes that use internals, and the callbacks on deletion of C++ objects are not there. I don’t know whether that matters.

With master, and stuffing in some pythonization by hand, I’d do something like this:

import numpy as np
import cppyy

# ROOT-specific pythonizations (currently in ROOT.py) --
def numpyify(self, buf):
   buf.reshape((self.GetN(),))
   return np.array(buf, copy=False)

cppyy.py.add_pythonization(
   cppyy.py.compose_method("^TGraph(2D)?$|^TGraph.*Errors$", "GetE?[XYZ]$", numpyify))
# -- end pythonizations

x = y = np.linspace(0,5,num=5)
g = cppyy.gbl.TGraph(len(x), x, y)
a = g.GetX()
print(a.shape)
print(a.tolist())
1 Like

tuple will work temporarily. Thanks for the help.

Listed as a bug here: https://sft.its.cern.ch/jira/projects/ROOT/issues/ROOT-9284

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.