Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap a C++ functor in Cython

I'm trying to wrap a C++ library in which the logic is implemented as templatized functors in .hpp files, and I'm struggling to find the right way to expose the C++ functors as Cython/Python functions. How are functors like the one below supposed to be wrapped in Cython?

I believe this should be possible, at least for template classes and functions, according to the Cython 0.20 docs.

Note: I think I've figured out how to wrap normal C++ functions—the problem occurs when I'm trying to wrap a templatized functor, i.e. a template struct that overloads the () operator (making it act like a function when a data type is fixed).

Disclaimer: I'm a total novice in C++ and very new to Cython so apologies if I'm making obvious mistakes here.


The functor I'm trying to wrap:

#include <vector>
#include "EMD_DEFS.hpp"
#include "flow_utils.hpp"

template<typename NUM_T, FLOW_TYPE_T FLOW_TYPE= NO_FLOW>
struct emd_hat_gd_metric {
    NUM_T operator()(const std::vector<NUM_T>& P, const std::vector<NUM_T>& Q,
                     const std::vector< std::vector<NUM_T> >& C,
                     NUM_T extra_mass_penalty= -1,
                     std::vector< std::vector<NUM_T> >* F= NULL);
};

My wrapper.pyx file:

# distutils: language = c++

from libcpp.vector cimport vector


cdef extern from "lib/emd_hat.hpp":
    # Apparently `cppclass` is necessary here even though 
    # `emd_hat_gd_metric` is not a class...?
    cdef cppclass emd_hat_gd_metric[NUM_T]:
        NUM_T operator()(vector[NUM_T]& P,
                         vector[NUM_T]& Q,
                         vector[vector[NUM_T]]& C) except +

cdef class EMD:

    cdef emd_hat_gd_metric *__thisptr

    def __cinit__(self):
        self.__thisptr = new emd_hat_gd_metric()

    def __dealloc__(self):
        del self.__thisptr

    def calculate(self, P, Q, C):
        # What goes here? How do I call the functor as a function?
        return self.__thisptr(P, Q, C)

The above just gives a Calling non-function type 'emd_hat_gd_metric[NUM_T]' error when I try to compile it with cython --cplus wrapper.pyx.

Here's the full library I'm trying to wrap.

End goal: to be able to call emd_hat_gd_metric as a Cython/Python function, with arguments being NumPy arrays.

like image 661
Will Avatar asked Nov 01 '22 04:11

Will


1 Answers

I couldn't find a real solution, but here's a workaround (that requires modifying the C++ code): just instantiate the template function with the data type you need in the C++ header, then declare that function normally in your .pyx file.

It's a little unwieldy if you need many different data types, but I only needed double. It would also be nicer if it wasn't necessary to modify the external library… but it works.


In the C++ some_library.hpp file:

Instantiate the functor with the data type you need (say, double):

template<typename T>
struct some_template_functor {
    T operator()(T x);
};

// Add this:
some_template_functor<double> some_template_functor_double;

In the Cython .pyx file:

Declare the function normally (no need for cppclass):

cdef extern from "path/to/some_library.hpp":
    cdef double some_template_functor_double(double x)

Then you can call some_template_functor_double from within Cython.

like image 66
Will Avatar answered Nov 15 '22 03:11

Will