[pypy-commit] pypy reflex-support: TF1 callbacks and associated tests
wlav
noreply at buildbot.pypy.org
Sat Sep 14 03:47:58 CEST 2013
Author: Wim Lavrijsen <WLavrijsen at lbl.gov>
Branch: reflex-support
Changeset: r66944:d2f110893a4b
Date: 2013-09-13 15:39 -0700
http://bitbucket.org/pypy/pypy/changeset/d2f110893a4b/
Log: TF1 callbacks and associated tests
diff --git a/pypy/module/cppyy/capi/cint_capi.py b/pypy/module/cppyy/capi/cint_capi.py
--- a/pypy/module/cppyy/capi/cint_capi.py
+++ b/pypy/module/cppyy/capi/cint_capi.py
@@ -99,6 +99,55 @@
return w_1
return obj.space.call_method(w_1, m2)
+### TF1 ----------------------------------------------------------------------
+tfn_pyfuncs = {}
+
+_tfn_install = rffi.llexternal(
+ "cppyy_tfn_install",
+ [rffi.CCHARP, rffi.INT], rffi.LONG,
+ threadsafe=False,
+ compilation_info=eci)
+
+ at unwrap_spec(args_w='args_w')
+def tf1_tf1(space, w_self, args_w):
+ """Pythonized version of TF1 constructor:
+ takes functions and callable objects, and allows a callback into them."""
+
+ from pypy.module.cppyy import interp_cppyy
+ tf1_class = interp_cppyy.scope_byname(space, "TF1")
+
+ # expected signature:
+ # 1. (char* name, pyfunc, double xmin, double xmax, int npar = 0)
+ argc = len(args_w)
+
+ try:
+ # Note: argcount is +1 for the class (== w_self)
+ if argc < 5 or 6 < argc:
+ raise TypeError("wrong number of arguments")
+
+ # second argument must be a name
+ funcname = space.str_w(args_w[1])
+
+ # last (optional) argument is number of parameters
+ npar = 0
+ if argc == 6: npar = space.int_w(args_w[5])
+
+ # third argument must be a callable python object
+ pyfunc = args_w[2]
+ if not space.is_true(space.callable(pyfunc)):
+ raise TypeError("2nd argument is not a valid python callable")
+
+ fid = _tfn_install(funcname, npar)
+ tfn_pyfuncs[fid] = pyfunc
+ newargs_w = (args_w[1], space.wrap(fid), args_w[3], args_w[4], space.wrap(npar))
+ except (OperationError, TypeError, IndexError):
+ newargs_w = args_w[1:] # drop class
+ pass
+
+ # return control back to the original, unpythonized overload
+ ol = tf1_class.get_overload("TF1")
+ return ol.call(None, newargs_w)
+
### TTree --------------------------------------------------------------------
_ttree_Branch = rffi.llexternal(
"cppyy_ttree_Branch",
@@ -293,6 +342,9 @@
allfuncs = [
+ ### TF1
+ tf1_tf1,
+
### TTree
ttree_Branch, ttree_iter, ttree_getattr,
]
@@ -311,6 +363,9 @@
_method_alias(space, w_pycppclass, "append", "Add")
_method_alias(space, w_pycppclass, "__len__", "GetSize")
+ elif name == "TF1":
+ space.setattr(w_pycppclass, space.wrap("__new__"), _pythonizations["tf1_tf1"])
+
elif name == "TFile":
_method_alias(space, w_pycppclass, "__getattr__", "Get")
@@ -347,3 +402,26 @@
if obj is not None:
memory_regulator.unregister(obj)
obj._rawobject = C_NULL_OBJECT
+
+# TFn callback (as above: needs better solution, but this is for CINT only)
+# TODO: it actually can fail ...
+ at cpython_api([rffi.LONG, rffi.INT, rffi.DOUBLEP, rffi.DOUBLEP], rffi.DOUBLE, error=CANNOT_FAIL)
+def cppyy_tfn_callback(space, idx, npar, a0, a1):
+ pyfunc = tfn_pyfuncs[idx]
+
+ from pypy.module._rawffi.interp_rawffi import unpack_simple_shape
+ from pypy.module._rawffi.array import W_Array, W_ArrayInstance
+ arr = space.interp_w(W_Array, unpack_simple_shape(space, space.wrap('d')))
+ address = rffi.cast(rffi.ULONG, a0)
+ arg0 = arr.fromaddress(space, address, 4)
+ try:
+ if npar != 0:
+ address = rffi.cast(rffi.ULONG, a1)
+ arg1 = arr.fromaddress(space, address, npar)
+ result = space.call_function(pyfunc, arg0, arg1)
+ else:
+ result = space.call_function(pyfunc, arg0)
+ except Exception:
+ # TODO: error handling here ..
+ return -1.
+ return space.float_w(result)
diff --git a/pypy/module/cppyy/converter.py b/pypy/module/cppyy/converter.py
--- a/pypy/module/cppyy/converter.py
+++ b/pypy/module/cppyy/converter.py
@@ -372,7 +372,12 @@
try:
obj = get_rawbuffer(space, w_obj)
except TypeError:
- obj = rffi.cast(rffi.VOIDP, get_rawobject(space, w_obj))
+ try:
+ # TODO: accept a 'capsule' rather than naked int
+ # (do accept int(0), though)
+ obj = rffi.cast(rffi.VOIDP, space.int_w(w_obj))
+ except Exception:
+ obj = rffi.cast(rffi.VOIDP, get_rawobject(space, w_obj))
return obj
def convert_argument(self, space, w_obj, address, call_local):
diff --git a/pypy/module/cppyy/include/cintcwrapper.h b/pypy/module/cppyy/include/cintcwrapper.h
--- a/pypy/module/cppyy/include/cintcwrapper.h
+++ b/pypy/module/cppyy/include/cintcwrapper.h
@@ -11,6 +11,8 @@
void* cppyy_load_dictionary(const char* lib_name);
/* pythonization helpers */
+ long cppyy_tfn_install(const char* funcname, int npar);
+
cppyy_object_t cppyy_ttree_Branch(
void* vtree, const char* branchname, const char* classname,
void* addobj, int bufsize, int splitlevel);
diff --git a/pypy/module/cppyy/src/cintcwrapper.cxx b/pypy/module/cppyy/src/cintcwrapper.cxx
--- a/pypy/module/cppyy/src/cintcwrapper.cxx
+++ b/pypy/module/cppyy/src/cintcwrapper.cxx
@@ -62,6 +62,9 @@
// memory regulation (cppyy_recursive_remove is generated a la cpyext capi calls)
extern "C" void cppyy_recursive_remove(void*);
+// TFN callback helper (generated a la cpyext capi calls)
+extern "C" double cppyy_tfn_callback(long, int, double*, double*);
+
class Cppyy_MemoryRegulator : public TObject {
public:
virtual void RecursiveRemove(TObject* object) {
@@ -987,6 +990,61 @@
/* pythonization helpers -------------------------------------------------- */
+static std::map<long, std::pair<long, int> > s_tagnum2fid;
+
+static int TFNPyCallback(G__value* res, G__CONST char*, struct G__param* libp, int hash) {
+ // This is a generic CINT-installable TFN (with N=1,2,3) callback (used to factor
+ // out some common code), to allow TFN to call back into python.
+
+ std::pair<long, int> fid_and_npar = s_tagnum2fid[G__value_get_tagnum(res)];
+
+ // callback (defined in cint_capi.py)
+ double d = cppyy_tfn_callback(fid_and_npar.first, fid_and_npar.second,
+ (double*)G__int(libp->para[0]), fid_and_npar.second ? (double*)G__int(libp->para[1]) : NULL);
+
+ // translate result (TODO: error checking)
+ G__letdouble( res, 100, d );
+ return ( 1 || hash || res || libp );
+}
+
+long cppyy_tfn_install(const char* funcname, int npar) {
+ // make a new function placeholder known to CINT
+ static Long_t s_fid = (Long_t)cppyy_tfn_install;
+ ++s_fid;
+
+ const char* signature = "D - - 0 - - D - - 0 - -";
+
+ // create a return type (typically masked/wrapped by a TPyReturn) for the method
+ G__linked_taginfo pti;
+ pti.tagnum = -1;
+ pti.tagtype = 'c';
+ std::string tagname("::py_"); // used as a buffer
+ tagname += funcname;
+ pti.tagname = tagname.c_str();
+ int tagnum = G__get_linked_tagnum(&pti); // creates entry for new names
+
+ // for free functions, add to global scope and add lookup through tp2f
+ // setup a connection between the pointer and the name
+ Long_t hash = 0, len = 0;
+ G__hash(funcname, hash, len);
+ G__lastifuncposition();
+ G__memfunc_setup(funcname, hash, (G__InterfaceMethod)&TFNPyCallback,
+ tagnum, tagnum, tagnum, 0, 2, 0, 1, 0, signature,
+ (char*)0, (void*)s_fid, 0);
+ G__resetifuncposition();
+
+ // setup a name in the global namespace (does not result in calls, so the signature
+ // does not matter; but it makes subsequent GetMethod() calls work)
+ G__MethodInfo meth = G__ClassInfo().AddMethod(
+ funcname, funcname, signature, 1, 0, (void*)&TFNPyCallback);
+
+ // store mapping so that the callback can find it
+ s_tagnum2fid[tagnum] = std::make_pair(s_fid, npar);
+
+ // hard to check result ... assume ok
+ return s_fid;
+}
+
cppyy_object_t cppyy_ttree_Branch(void* vtree, const char* branchname, const char* classname,
void* addobj, int bufsize, int splitlevel) {
// this little song-and-dance is to by-pass the handwritten Branch methods
diff --git a/pypy/module/cppyy/test/test_cint.py b/pypy/module/cppyy/test/test_cint.py
--- a/pypy/module/cppyy/test/test_cint.py
+++ b/pypy/module/cppyy/test/test_cint.py
@@ -430,6 +430,84 @@
hello.AddText( 'Hello, World!' )
+class AppTestCINTFUNCTION:
+ spaceconfig = dict(usemodules=['cppyy', '_rawffi', '_ffi', 'itertools'])
+
+ # test the function callbacks; this does not work with Reflex, as it can
+ # not generate functions on the fly (it might with cffi?)
+
+ def test01_global_function_callback(self):
+ """Test callback of a python global function"""
+
+ import cppyy
+ TF1 = cppyy.gbl.TF1
+
+ def identity(x):
+ return x[0]
+
+ f = TF1("pyf1", identity, -1., 1., 0)
+
+ assert f.Eval(0.5) == 0.5
+ assert f.Eval(-10.) == -10.
+ assert f.Eval(1.0) == 1.0
+
+ # check proper propagation of default value
+ f = TF1("pyf1d", identity, -1., 1.)
+
+ assert f.Eval(0.5) == 0.5
+
+ def test02_callable_object_callback(self):
+ """Test callback of a python callable object"""
+
+ import cppyy
+ TF1 = cppyy.gbl.TF1
+
+ class Linear:
+ def __call__(self, x, par):
+ return par[0] + x[0]*par[1]
+
+ f = TF1("pyf2", Linear(), -1., 1., 2)
+ f.SetParameters(5., 2.)
+
+ assert f.Eval(-0.1) == 4.8
+ assert f.Eval(1.3) == 7.6
+
+ def test03_fit_with_python_gaussian(self):
+ """Test fitting with a python global function"""
+
+ # note: this function is dread-fully slow when running testing un-translated
+
+ import cppyy, math
+ TF1, TH1F = cppyy.gbl.TF1, cppyy.gbl.TH1F
+
+ def pygaus(x, par):
+ arg1 = 0
+ scale1 =0
+ ddx = 0.01
+
+ if (par[2] != 0.0):
+ arg1 = (x[0]-par[1])/par[2]
+ scale1 = (ddx*0.39894228)/par[2]
+ h1 = par[0]/(1+par[3])
+
+ gauss = h1*scale1*math.exp(-0.5*arg1*arg1)
+ else:
+ gauss = 0.
+ return gauss
+
+ f = TF1("pygaus", pygaus, -4, 4, 4)
+ f.SetParameters(600, 0.43, 0.35, 600)
+
+ h = TH1F("h", "test", 100, -4, 4)
+ h.FillRandom("gaus", 200000)
+ h.Fit(f, "0Q")
+
+ assert f.GetNDF() == 96
+ result = f.GetParameters()
+ assert round(result[1] - 0., 1) == 0 # mean
+ assert round(result[2] - 1., 1) == 0 # s.d.
+
+
class AppTestSURPLUS:
spaceconfig = dict(usemodules=['cppyy', '_rawffi', '_ffi', 'itertools'])
More information about the pypy-commit
mailing list