如何为使用LAPACK库的C函数编译Python的C扩展?

3

我为Python编写了一个C扩展,该模块已经成功编译成.so文件。但是当我尝试在Python端使用包装的C函数(调用包装的C函数的Python测试代码)时,我会得到以下ImportError错误。

ImportError: /home/username/newModule.cpython-36m-x86_64-linux-gnu.so: undefined symbol: dgetri_

我非常确定导入错误中的“undefined symbol: dgetri_”是因为生成的“.so”文件没有找到链接到“LAPACK”库。所以我的问题如下,
当包装的C函数依赖于LAPACK库以生成“.so”格式的模块时,我该如何编译Python的c扩展代码?
目前,我正在使用Python的“utils.core”模块编译C代码。我认为我需要从命令行编译C代码以链接“LAPACK”,但不知道要使用哪些适当的命令?
感谢任何帮助。
3个回答

2
您可能会对使用scipy.linalg.cython_lapack感兴趣。 它提供了访问LAPACK函数dgetri等的方法。而且,好消息是:

这使得第三方Cython模块可以从SciPy的BLAS和LAPACK中获得访问权限,而无需显式地链接库。这意味着像scikit-learn和statsmodels这样的项目不需要维护BLAS和LAPACK的单独构建依赖关系。

一个使用dger的示例可在Calling BLAS / LAPACK directly using the SciPy interface and Cython上找到。还可以查看Improving Cython Lapack performance with internal array definitions?。我在MPI python-Open-MPI的答案中详细介绍了如何使用cython_blas,因此以下是如何将其调整为dgetri:

  1. 代码的关键部分是用Cython编写的,位于专用文件myinverse.pyx中。

  2. 通过Cython将此文件转换为myinverse.c文件。

  3. 此c文件由您喜欢的C编译器gcc编译以构建共享库myinverse.so

  4. 优化的函数可以在导入myinverse之后在您的程序中使用。

这是一个Cython模块,应放置在.pyx文件中:

import numpy

cimport numpy
cimport scipy.linalg.cython_lapack
ctypedef numpy.float64_t DTYPE_t
cimport cython
from libc.stdlib cimport malloc, free

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def invert(numpy.ndarray[DTYPE_t, ndim=2] array):

    cdef  int rows = array.shape[0]
    cdef   int cols = array.shape[1]
    cdef  int info = 0
    if cols !=rows:
        return array,1,"not a square matrix"

    cdef int* ipiv = <int *> malloc(rows * sizeof(int))
    if not ipiv:
        raise MemoryError()

    scipy.linalg.cython_lapack.dgetrf(&cols,&rows,&array[0,0],&rows,ipiv,&info)
    if info !=0:
        free(ipiv)
        return array,info,"dgetrf failed, INFO="+str(info)
    #workspace query
    cdef double workl
    cdef int lwork=-1
    scipy.linalg.cython_lapack.dgetri(&cols,&array[0,0],&rows,ipiv,&workl,&lwork,&info)
    if info !=0:
        free(ipiv)
        return array,info,"dgetri failed, workspace query, INFO="+str(info)
    #allocation workspace
    lwork= int(workl)
    cdef double* work = <double *> malloc(lwork * sizeof(double))
    if not work:
        raise MemoryError()

    scipy.linalg.cython_lapack.dgetri(&cols,&array[0,0],&rows,ipiv,work,&lwork,&info)
    if info !=0:
        free(ipiv)
        free(work)
        return array,info,"dgetri failed, INFO="+str(info)

    free(ipiv)
    free(work)

    return array,info,""

为了将.pyx文件转换为Cython并编译,可以使用以下makefile(希望您正在使用Linux...)
all: myinverse myinverseb


myinverse: myinverse.pyx
    cython -a myinverse.pyx

myinverseb: myinverse.c
    gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing -I/usr/include/python2.7 -o myinverse.so myinverse.c

在主Python文件中调用新的Python myinverse函数,该函数使用LAPACK的dgetrf()和dgetri()进行链式操作:

import numpy as np

import myinverse
n=42

#A=np.zeros((n,n))
#for i in range(n):
#    A[i,i]=10
A=np.random.rand(n,n)
#A=np.zeros((n,n))
Am,info,string=myinverse.invert(A.copy())
if info==0:
    print np.linalg.norm(A.dot(Am)-np.identity(n), np.inf)
else :
    print "inversion failed, info=",info, string

1
作为额外的优势,scipy具有一些兼容不同版本的BLAS/LAPACK库的代码,例如,无论scipy是使用MKL、OpenBLAS或其他库构建的,这意味着当使用来自scipy的cython包装器时,调用代码不需要担心使用哪个线性代数库。 - Josef
谢谢您提供的所有细节,它们都是有趣的阅读材料。然而,在Linux编译时,我通过使用以下两行代码使我的代码工作正常。 - Geek

1

或者,在 Linux 编译时,我通过使用以下两行代码使我的代码正常工作:

$gcc -DNDEBUG -Wall -Wstrict-prototypes -fPIC -I/home/username/anaconda3/include/python3.6m -c stackDoc.cpp -o mydemo.o
$gcc -shared mydemo.o -o mydemo.so

我发现以下链接很有用, https://docs.python.org/2/extending/building.html

0

或者你可以使模块与lapack链接。

例如:

from distutils.core import setup
from distutils.extension import Extension

setup(
    name='MyExtension',
    version='0.1',
    ext_modules=[
        Extension('lib_name', ['lib_name.cpp'], extra_link_args=['-lopenblas']),
    ],
    scripts=['lib_name.cpp', '__init__.py'],
)

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