简体   繁体   English

将 Cython 中的 numpy 数组传递给需要动态分配数组的 C 函数

[英]Passing numpy arrays in Cython to a C function that requires dynamically allocated arrays

I have some C code that has the following declaration:我有一些具有以下声明的 C 代码:

int myfunc(int m, int n, const double **a, double **b, double *c);

So a is a constant 2D array, b is a 2D array, and c is a 1D array, all dynamically allocated.所以a是一个常量二维数组, b是一个二维数组, c是一个一维数组,都是动态分配的。 b and c do not need to be anything specifically before they are passed to myfunc , and should be understood as output information. bc在传递给myfunc之前不需要是什么特别的东西,应该理解为输出信息。 For the purposes of this question, I'm not allowed to change the declaration of myfunc .出于这个问题的目的,我不允许更改myfunc的声明。

Question 1: How do I convert a given numpy array a_np into an array a with the format required by this C function, so that I can call this C function in Cython with a ?问题 1:如何将给定的 numpy 数组a_np转换为具有此 C 函数所需格式的数组a ,以便我可以在 Cython 中使用a调用此 C 函数?

Question 2: Are the declarations for b and c below correct, or do they need to be in some other format for the C function to understand them as a 2D and 1D array (respectively)?问题 2:下面bc的声明是否正确,或者它们是否需要采用其他格式以便 C 函数将它们理解为 2D 和 1D 数组(分别)?

My attempt:我的尝试:

myfile.pxd我的文件.pxd

cdef extern from "myfile.h":
    int myfunc(int p, int q, const double **a, double **b, double *c)

mytest.pyx mytest.pyx

cimport cython
cimport myfile
import numpy as np
cimport numpy as np

p = 3
q = 4
cdef:
    double** a = np.random.random([p,q])
    double** b
    double* c

myfile.myfunc(p, q, a, b, c)

Then in iPython I run然后在 iPython 中我运行

import pyximport; pyximport.install()
import mytest

The line with the definition of a gives me the error message Cannot convert Python object to 'double **' .带有a定义的行给了我错误消息Cannot convert Python object to 'double **' I don't get any error messages regarding b or c , but since I'm unable to run the C function at this time, I'm not sure the declarations of b and c are written correctly (that is, in a way that will enable the C function to output a 2D and a 1D array, respectively).我没有收到关于bc任何错误消息,但由于此时我无法运行 C 函数,我不确定bc的声明是否正确编写(也就是说,以某种方式将使 C 函数分别输出 2D 和 1D 数组)。

Other attempts: I've also tried following the solution here , but this doesn't work with the double-asterisk type of arrays I have in the myfunc declaration.其他尝试:我也尝试遵循此处的解决方案,但这不适用于myfunc声明中的双星号类型的数组。 The solution here does not apply to my task because I can't change the declaration of myfunc .这里的解决方案不适用于我的任务,因为我无法更改myfunc的声明。

Create a helper array in cython在 cython 中创建一个辅助数组

To get a double** from a numpy array, you can create a helper-array of pointers in your *.pyx file.要从 numpy 数组中获取double** ,您可以在 *.pyx 文件中创建一个辅助指针数组。 Further more, you have to make sure that the numpy array has the correct memory layout.此外,您必须确保 numpy 数组具有正确的内存布局。 (It might involve creating a copy) (这可能涉及创建副本)

Fortran order Fortran命令

If your C-function expects fortran order (all x-coordinates in one list, all y coordinates in another list, all z-coordinates in a third list, if your array a corresponds to a list of points in 3D space)如果您的 C 函数需要 fortran 顺序(一个列表中的所有 x 坐标,另一个列表中的所有 y 坐标,第三个列表中的所有 z 坐标,如果您的数组 a 对应于 3D 空间中的点列表)

N,M = a.shape
# Make sure the array a has the correct memory layout (here F-order)
cdef np.ndarray[double, ndim=2, mode="fortran"] a_cython =
                         np.asarray(a, dtype = float, order="F")
#Create our helper array
cdef double** point_to_a = <double **>malloc(M * sizeof(double*))
if not point_to_a: raise MemoryError
try:
    #Fillup the array with pointers
    for i in range(M): 
        point_to_a[i] = &a_cython[0, i]
    # Call the C function that expects a double**
    myfunc(... &point_to_a[0], ...)
finally:
    free(point_to_a)

C-order C-顺序

If your C-function expects C-order ([x1,y1,z1] is the first list, [x2,y2,z2] the second list for a list of 3D points):如果您的 C 函数需要 C 顺序([x1,y1,z1] 是第一个列表,[x2,y2,z2] 是 3D 点列表的第二个列表):

N,M = a.shape
# Make sure the array a has the correct memory layout (here C-order)
cdef np.ndarray[double, ndim=2, mode="c"] a_cython =
                         np.asarray(a, dtype = float, order="C")
#Create our helper array
cdef double** point_to_a = <double **>malloc(N * sizeof(double*))
if not point_to_a: raise MemoryError
try:
    for i in range(N): 
        point_to_a[i] = &a_cython[i, 0]
    # Call the C function that expects a double**
    myfunc(... &point_to_a[0], ...)
finally:
    free(point_to_a)

Reply 1: You can pass NumPy array via Cython to C using the location of the start of the array (see code below).回复1:可以通过Cython 将NumPy 数组传递给C 使用数组开头的位置(见下面代码)。

Reply 2: Your declarations seem correct but I don't use this approach of explicit memory management.回复2:你的声明看起来是对的,但是我没有使用这种显式内存管理的方法。 You can use NumPy to declare cdef -ed arrays.您可以使用 NumPy 来声明cdef -ed 数组。

Use

cdef double[:,::1] a = np.random.random([p, q])
cdef double[:,::1] b = np.empty([p, q])
cdef double[::1] b = np.empty(q)

Then pass &a[0] , the location of the start of the array, to your C function.然后将&a[0] (数组开头的位置)传递给您的 C 函数。 The ::1 is to ensure contiguousness. ::1是为了确保连续性。

A good reference for this is Jake Vanderplas' blog: https://jakevdp.github.io/blog/2012/08/08/memoryview-benchmarks/对此的一个很好的参考是 Jake Vanderplas 的博客: https ://jakevdp.github.io/blog/2012/08/08/memoryview-benchmarks/

Finally, typically one creates functions in Cython and calls them in Python, so your Python code would be:最后,通常在 Cython 中创建函数并在 Python 中调用它们,因此您的 Python 代码将是:

import pyximport; pyximport.install()
import mytest
mytest.mywrappedfunc()

where mywrappedfunc is a Python ( def and not cdef ) function defined in the module that can do the array declaration show above.其中mywrappedfunc是在模块中定义的 Python( def而不是cdef )函数,可以执行上面显示的数组声明。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM