在Cython中定义一个C++结构体的返回类型

3
我编写了一个小的C++库来模拟ODE系统,并使用Cython封装了一个初始化和解决系统的函数。C ++函数接受一些参数,并返回包含两个矩阵的结构:用于构造系统的模式集,以及每个时间步骤中系统的已解决状态
这些矩阵是使用Eigen库实现的,因此我正在使用Eigency接口将这些数据结构的底层内容传输到NumPy数组中。
如果在Python中返回对其中一个矩阵的视图,则一切正常。但是,我想返回一个包含这两个矩阵的元组,这需要临时存储此struct,然后返回其每个矩阵成员。
## 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))

当我使用python setup.py build_ext --inplace编译有注释的代码时,出现的错误是:
kernel.cpp:1552:11: error: no matching constructor for initialization of 'network'
  network __pyx_v_net;
          ^`

我觉得这很有道理——Cython试图调用network的构造函数。但是我希望构造在“run”内部进行。这里应该使用什么类型声明?

如果有帮助的话,代码的简化版本在这里

谢谢!

1个回答

3

了解Cython如何将代码转换为堆栈分配的C++变量是值得的:

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)

函数内部将被翻译为:

{
    // 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);
}

即需要默认构造函数和复制/移动赋值运算符。这与C++代码的编写方式不太匹配,因此有时会引起问题。Cython通常假定这些函数已经存在(除非您告诉它其他构造函数),因此您不需要在块中显式编写这些函数。
我认为您的错误消息似乎是在编译C++代码时出现的,因为它没有定义默认构造函数。(稍后的复制赋值应该不是问题,因为C++会自动生成这个)。您有两个选择:
  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.


第一个选项可行,谢谢!即使明确定义了复制/移动构造函数,我也无法让第二个选项工作。cdef network* net = new network(_run(N,K,P,g,T,tau,dt) 会产生这个错误:调用参数数量错误(期望0个,得到1个)。Cython编译器也不允许我在try块中使用cdef。 - Max Gillett
我在第二个选项中犯了两个错误 - 首先,您确实需要告诉Cython有关network复制构造函数(即使它只是默认的C ++构造函数)以执行new network(_run(...))。其次,您需要将cdef移出try块。为了确保正确性,我已更新答案。不过,我可能仍会选择第一个选项。 - DavidW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接