Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining a C++ struct return type in Cython

Tags:

c++

python

cython

I have written a small C++ library to simulate a system of ODEs, and have used Cython to wrap a function that initializes and solves the system. The C++ function takes in a handful of parameters, and returns a struct containing two matrices: a set of patterns used to construct the system, and the solved state of the system at every time step.

These matrices are implemented using the Eigen library, so I am using the Eigency interface to transfer the underlying contents of these data structures to NumPy arrays.

All is good if, in Python, I return a view to one of these matrices. However, I would like to return a tuple containing both of these matrices, which requires temporarily storing this struct, and then returning each of its matrix members:

## kernel.pyx

from eigency.core cimport *

cdef extern from "kernel_cpp.h":

  cdef cppclass network:
    MatrixXf state
    MatrixXf patterns

  cdef network _run "run" (int N, int K, int P, double g, double T, double tau, double dt)

def run(N=10000, K=100, P=30, g=1.5, T=0.5, tau=1e-3, dt=1e-3):
  return ndarray_view(_run(N,K,P,g,T,tau,dt).state) # <--- This works
  # This does not:
  # cdef network net = _run(N,K,P,g,T,tau,dt):
  # return (ndarray_view(net.state), ndarray_view(net.patterns))

The error that I get when building the commented code with python setup.py build_ext --inplace is

kernel.cpp:1552:11: error: no matching constructor for initialization of 'network'
  network __pyx_v_net;
          ^`

and this makes sense to me -- Cython is trying to call a constructor for network. But I want the construction to take place within "run." What is the correct type declaration here?

In case it helps, a stripped-down version of the code is here.

Thanks!

like image 711
Max Gillett Avatar asked Dec 04 '25 09:12

Max Gillett


1 Answers

It's worth knowing how Cython translates code which stack allocates C++ variables:

def run(N=10000, K=100, P=30, g=1.5, T=0.5, tau=1e-3, dt=1e-3):
   cdef network net = _run(N,K,P,g,T,tau,dt)

the inside of the function gets translated to:

{
    // start of function
    network net; // actually some mangled variable name (see your error message)

    // body of function
    net = _run(N,K,P,g,T,tau,dt);
}

i.e. it requires both a default constructor and a copy/move assignment operator. This doesn't match how C++ code tends to be written so occasionally causes problems. Cython usually assumes the existence of these (unless you tell it about other constructors) so you don't need to explicitly write these out in your cdef cppclass block.

I think your error message looks to occur when compiling the C++ code and it's not defining a default constructor. (The copy assignment later shouldn't be a problem since C++ will automatically generate this). You have two options:

  1. If you're happy modifying the C++ code then define a default constructor. This will allow the Cython code to work as written. If you're using >=C++11 then that could be as easy as:

    network() = default;
    

    It's possible that this won't work depending on whether the internal Eigen types can be default constructed though.

  2. If you don't want modify the C++ code, or if it turns out you can't easily define a default constructor, then you'll have to modify the Cython code to allocate the network with new. This also means you'll have to deal with deallocation yourself:

      # earlier
       cdef cppclass network:
         # need to tell Cython about copy constructor
         network(const network&)
         # everything else as before 
    
    def run(N=10000, K=100, P=30, g=1.5, T=0.5, tau=1e-3, dt=1e-3):
        cdef network* net
        try:
            net = new network(_run(N,K,P,g,T,tau,dt))
            return (ndarray_view(net.state), ndarray_view(net.patterns))
        finally:
            del net
    

    In this case no default constructor is needed, only a copy/move constructor (which you have to have anyway to be able to return a network from a function). Note use of finally to ensure than we free the memory.

like image 136
DavidW Avatar answered Dec 06 '25 23:12

DavidW



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!