Skip to content Skip to sidebar Skip to footer

Pass Cython Functions Via Python Interface

Can a cdef Cython function be passed to another (python def) cython function from a Python script? Minimal example: test_module.pyx cpdef min_arg(f, int N): cdef double x = 100

Solution 1:

The LowLevelCallable is a class of functions that must be accepted by the underlying Python module. This work has been done for a few modules, including the quadrature routine scipy.integrate.quad

If you wish to use the same wrapping method, you must either go through the SciPy routines that make use of it, such as scipy.ndimage.generic_filter1d or scipy.integrate.quad. The code sits in compiled extensions, however.

The alternative, if your problem is reasonably well defined for the callback, is to implement this yourself. I have done this in one of my codes, so I post the link for simplicity:

  1. In a .pxd file, I define the interface cyfunc_d_d: https://github.com/pdebuyl/skl1/blob/master/skl1/core.pxd
  2. I can re-use this interface in the "base" cython module https://github.com/pdebuyl/skl1/blob/master/skl1/euler.pyx and also in a "user-defined" module.

The final code makes plain "cython-cython" calls while allowing the passing of objects at the Cython level

I adapted the code to your problem:

  1. test_interface.pxd

    cdef classcyfunc:                                                                                                                         
        cpdef doublef(self, double x)                                                                                                         
    
    cdef classpyfunc(cyfunc):                                                                                                                 
        cdef object py_f                                                                                                                       
        cpdef doublef(self, double x)
  2. test_interface.pyx

    cdefclass cyfunc:
        cpdefdouble f(self, double x):
            return0def__cinit__(self):
            pass
    
    
    cdefclass pyfunc(cyfunc):
        cpdefdouble f(self, double x):
            return self.py_f(x)
        def__init__(self, f):
            self.py_f = f
    
  3. setup.py

    from setuptools import setup, ExtensionfromCython.Buildimport cythonize                                                                                                         
    
    setup(                                                                                                                                     
        ext_modules=cythonize((Extension('test_interface', ["test_interface.pyx"]),                                                            
                              Extension('test_module', ["test_module.pyx"]))                                                                   
                          )                                                                                                                    
    )                                                                                                                                          
    
  4. test_module.pyx

    from test_interface cimport cyfunc, pyfunc                                                                                                 
    
    cpdefmin_arg(f, int N):                                                                                                                   
        cdefdouble x = 100000.                                                                                                                
        cdefint best_i = -1                                                                                                                   
        cdefint i                                                                                                                             
        cdefdouble current_value                                                                                                              
    
        cdefcyfunc py_f                                                                                                                       
    
        ifisinstance(f, cyfunc):                                                                                                              
            py_f = f                                                                                                                           
            print('cyfunc')                                                                                                                    
        elifcallable(f):                                                                                                                      
            py_f = pyfunc(f)                                                                                                                   
            print('no cyfunc')                                                                                                                 
        else:                                                                                                                                  
            raise ValueError("f should be a callable or a cyfunc")                                                                             
    
        for i inrange(N):                                                                                                                     
            current_value = py_f.f(i)                                                                                                          
            if current_value < x:                                                                                                              
                x = current_value                                                                                                              
                best_i = i                                                                                                                     
        return best_i                                                                                                                          
    
    defpy_f(x):                                                                                                                               
        return (x-5)**2                                                                                                                        
    
    cdefclass cy_f(cyfunc):                                                                                                                   
        cpdefdouble f(self, double x):                                                                                                        
            return (x-5)**2

To use:

python3 setup.py build_ext --inplace
python3 -c 'import test_module ; print(test_module.min_arg(test_module.cy_f(), 10))'
python3 -c 'import test_module ; print(test_module.min_arg(test_module.py_f, 10))'

Post a Comment for "Pass Cython Functions Via Python Interface"