[英]Extending Numpy with C function
我正在尝试加速我的Numpy代码,并决定我想实现一个特定的函数,我的代码大部分时间都在C中。
我实际上是C中的新手,但我设法编写了一个函数,它将矩阵中的每一行规范化为1。我可以编译它并用一些数据(在C中)测试它并且它做我想要的。 那时我为自己感到骄傲。
现在我试图从Python中调用我的光荣函数,它应该接受一个2d-Numpy数组。
我尝试过的各种事情都是
痛饮
SWIG + numpy.i
ctypes的
我的功能有原型
void normalize_logspace_matrix(size_t nrow, size_t ncol, double mat[nrow][ncol]);
因此它需要一个指向可变长度数组的指针并将其修改到位。
我尝试了以下纯SWIG接口文件:
%module c_utils
%{
extern void normalize_logspace_matrix(size_t, size_t, double mat[*][*]);
%}
extern void normalize_logspace_matrix(size_t, size_t, double** mat);
然后我会做(在Mac OS X 64bit上):
> swig -python c-utils.i
> gcc -fPIC c-utils_wrap.c -o c-utils_wrap.o \
-I/Library/Frameworks/Python.framework/Versions/6.2/include/python2.6/ \
-L/Library/Frameworks/Python.framework/Versions/6.2/lib/python2.6/ -c
c-utils_wrap.c: In function ‘_wrap_normalize_logspace_matrix’:
c-utils_wrap.c:2867: warning: passing argument 3 of ‘normalize_logspace_matrix’ from incompatible pointer type
> g++ -dynamiclib c-utils.o -o _c_utils.so
在Python中,我在导入模块时遇到以下错误:
>>> import c_utils
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: dynamic module does not define init function (initc_utils)
接下来我尝试使用SWIG + numpy.i
这种方法:
%module c_utils
%{
#define SWIG_FILE_WITH_INIT
#include "c-utils.h"
%}
%include "numpy.i"
%init %{
import_array();
%}
%apply ( int DIM1, int DIM2, DATA_TYPE* INPLACE_ARRAY2 )
{(size_t nrow, size_t ncol, double* mat)};
%include "c-utils.h"
但是,我没有比这更进一步:
> swig -python c-utils.i
c-utils.i:13: Warning 453: Can't apply (int DIM1,int DIM2,DATA_TYPE *INPLACE_ARRAY2). No typemaps are defined.
SWIG似乎没有找到numpy.i中定义的类型numpy.i
,但我不明白为什么,因为numpy.i
位于同一目录中,SWIG不会抱怨它无法找到它。
使用ctypes我没有走得太远,但很快就迷失在文档中,因为我无法弄清楚如何将它传递给2d阵列然后得到结果。
那么有人可以向我展示如何在Python / Numpy中使用我的函数的神奇技巧吗?
除非你有充分的理由不这样做,否则你应该使用cython来连接C和python。 (我们开始在numpy / scipy中使用cython而不是raw C)。
你可以在我的scikits talkbox中看到一个简单的例子(因为从那以后cython已经有了很大的改进,我想你今天可以写得更好)。
def cslfilter(c_np.ndarray b, c_np.ndarray a, c_np.ndarray x):
"""Fast version of slfilter for a set of frames and filter coefficients.
More precisely, given rank 2 arrays for coefficients and input, this
computes:
for i in range(x.shape[0]):
y[i] = lfilter(b[i], a[i], x[i])
This is mostly useful for processing on a set of windows with variable
filters, e.g. to compute LPC residual from a signal chopped into a set of
windows.
Parameters
----------
b: array
recursive coefficients
a: array
non-recursive coefficients
x: array
signal to filter
Note
----
This is a specialized function, and does not handle other types than
double, nor initial conditions."""
cdef int na, nb, nfr, i, nx
cdef double *raw_x, *raw_a, *raw_b, *raw_y
cdef c_np.ndarray[double, ndim=2] tb
cdef c_np.ndarray[double, ndim=2] ta
cdef c_np.ndarray[double, ndim=2] tx
cdef c_np.ndarray[double, ndim=2] ty
dt = np.common_type(a, b, x)
if not dt == np.float64:
raise ValueError("Only float64 supported for now")
if not x.ndim == 2:
raise ValueError("Only input of rank 2 support")
if not b.ndim == 2:
raise ValueError("Only b of rank 2 support")
if not a.ndim == 2:
raise ValueError("Only a of rank 2 support")
nfr = a.shape[0]
if not nfr == b.shape[0]:
raise ValueError("Number of filters should be the same")
if not nfr == x.shape[0]:
raise ValueError, \
"Number of filters and number of frames should be the same"
tx = np.ascontiguousarray(x, dtype=dt)
ty = np.ones((x.shape[0], x.shape[1]), dt)
na = a.shape[1]
nb = b.shape[1]
nx = x.shape[1]
ta = np.ascontiguousarray(np.copy(a), dtype=dt)
tb = np.ascontiguousarray(np.copy(b), dtype=dt)
raw_x = <double*>tx.data
raw_b = <double*>tb.data
raw_a = <double*>ta.data
raw_y = <double*>ty.data
for i in range(nfr):
filter_double(raw_b, nb, raw_a, na, raw_x, nx, raw_y)
raw_b += nb
raw_a += na
raw_x += nx
raw_y += nx
return ty
正如你所看到的,除了通常的参数检查你会在python中做什么,它几乎是一样的(filter_double是一个函数,如果你愿意,可以在一个单独的库中用纯C编写)。 当然,因为它是编译代码,所以未能检查你的参数会使你的解释器崩溃而不是引发异常(尽管最近的cython有几个级别的安全与速度权衡)。
回答真正的问题:SWIG没有告诉你它找不到任何类型地图。 它告诉你它不能应用typemap (int DIM1,int DIM2,DATA_TYPE *INPLACE_ARRAY2)
,这是因为没有为DATA_TYPE *
定义的typemap。 你需要告诉它你要将它应用于double*
:
%apply ( int DIM1, int DIM2, double* INPLACE_ARRAY2 )
{(size_t nrow, size_t ncol, double* mat)};
首先,你确定你正在编写最快的numpy代码吗? 如果通过规范化意味着将整行除以其总和,则可以编写快速矢量化代码,如下所示:
matrix /= matrix.sum(axis=0)
如果这不是你的想法,你仍然确定你需要一个快速的C扩展,我强烈建议你用cython而不是C编写它。这将节省你包装代码的所有开销和困难,并允许你可以编写一些看起来像python代码但在大多数情况下可以像C一样快速运行的东西。
我同意其他人认为Cython值得学习。 但是如果你必须编写C或C ++,请使用覆盖2d的1d数组,如下所示:
// sum1rows.cpp: 2d A as 1d A1
// Unfortunately
// void f( int m, int n, double a[m][n] ) { ... }
// is valid c but not c++ .
// See also
// http://stackoverflow.com/questions/3959457/high-performance-c-multi-dimensional-arrays
// http://stackoverflow.com/questions/tagged/multidimensional-array c++
#include <stdio.h>
void sum1( int n, double x[] ) // x /= sum(x)
{
float sum = 0;
for( int j = 0; j < n; j ++ )
sum += x[j];
for( int j = 0; j < n; j ++ )
x[j] /= sum;
}
void sum1rows( int nrow, int ncol, double A1[] ) // 1d A1 == 2d A[nrow][ncol]
{
for( int j = 0; j < nrow*ncol; j += ncol )
sum1( ncol, &A1[j] );
}
int main( int argc, char** argv )
{
int nrow = 100, ncol = 10;
double A[nrow][ncol];
for( int j = 0; j < nrow; j ++ )
for( int k = 0; k < ncol; k ++ )
A[j][k] = (j+1) * k;
double* A1 = &A[0][0]; // A as 1d array -- bad practice
sum1rows( nrow, ncol, A1 );
for( int j = 0; j < 2; j ++ ){
for( int k = 0; k < ncol; k ++ ){
printf( "%.2g ", A[j][k] );
}
printf( "\n" );
}
}
11月8日添加:你可能知道, numpy.reshape可以覆盖一个带有1d视图的numpy 2d数组传递给sum1rows
,如下所示:
import numpy as np
A = np.arange(10).reshape((2,5))
A1 = A.reshape(A.size) # a 1d view of A, not a copy
# sum1rows( 2, 5, A1 )
A[1,1] += 10
print "A:", A
print "A1:", A1
SciPy有一个扩展教程,包含数组的示例代码。 http://docs.scipy.org/doc/numpy/user/c-info.how-to-extend.html
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.