Author: Wim Lavrijsen <wlavrij...@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) + +@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 ... +@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']) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit