wlav avatar wlav committed d2f1108

TF1 callbacks and associated tests

Comments (0)

Files changed (5)

pypy/module/cppyy/capi/cint_capi.py

         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",
 
     allfuncs = [
 
+        ### TF1
+        tf1_tf1,
+
         ### TTree
         ttree_Branch, ttree_iter, ttree_getattr,
     ]
         _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")
 
     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)

pypy/module/cppyy/converter.py

         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):

pypy/module/cppyy/include/cintcwrapper.h

     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);

pypy/module/cppyy/src/cintcwrapper.cxx

 // 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) {
 
 
 /* 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

pypy/module/cppyy/test/test_cint.py

         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'])
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.