使用 python 函数作为参数调用 Cython(C) 函数

发布于 2025-01-12 02:10:20 字数 2268 浏览 1 评论 0原文

我想将本地定义的 python 函数与 gsl 库集成。 为此,我使用 Cython 实现了以下代码(以 Gauss-Legendre 求积为例):

pxd 文件:

cdef extern from "gsl/gsl_math.h":
ctypedef struct gsl_function:
double (* function) (double x, void * params) nogil
void * params

cdef extern from "gsl/gsl_integration.h":
gsl_integration_glfixed_table * gsl_integration_glfixed_table_alloc(size_t n) nogil 
double gsl_integration_glfixed(gsl_function *f, double a, double b, gsl_integration_glfixed_table * t) nogil
void  gsl_integration_glfixed_table_free(gsl_integration_glfixed_table *t) nogil 

cdef double int_gsl_GaussLegendre(double func(double, void *) nogil, void * p, double xmin, double xmax) nogil

和 pyx 文件:

cdef size_t size_GL=1000

cdef double int_gsl_GaussLegendre(double func(double, void *) nogil, void * p, double xmin, double xmax) nogil:
cdef double result, error;
cdef gsl_integration_glfixed_table * W 
cdef gsl_function F
W = gsl_integration_glfixed_table_alloc(size_GL)
F.function = func
F.params = p
result = gsl_integration_glfixed(&F, xmin, xmax, W)    
gsl_integration_glfixed_table_free(W)
return result

此代码适用于我的 Cython 代码中声明的任何 C 函数。当然,当我传递一个 python 函数作为参数时,这会失败。 我在 python 中的脚本:

def gsl_integral(py_func, xmin, xmax, args=()):
cdef size_t sizep = <int>(args.size)
cdef double[:] params = np.empty(sizep, dtype=np.double)
for i in range(0,sizep):
    params[i]=args[i]
cdef gsl_function F
F.function = py_func
F.params = params

返回:“无法将 Python 对象转换为'double (*)(double, void *) nogil'”

如果我改用:

def gsl_integral(py_func, xmin, xmax, args=()):
cdef size_t sizep = <int>(args.size)
cdef double[:] params = np.empty(sizep, dtype=np.double)
for i in range(0,sizep):
    params[i]=args[i]
cdef gsl_function F
F.function = <double *>py_func
F.params = params

返回:“Python 对象不能转换为原始类型的指针”

我有看到我可以将我的 cython 函数包装到一个类中(将 c 函数作为参数传递给 cython 中的 python 函数 ),但我不太确定在这种情况下该怎么做(加上这个例子对我不起作用。)作为一种解决方法,我一直传递给 Cython 两个数组 x 和 f(x),后者估计为我的本地 python f,为了定义一个 gsl 样条线,我可以稍后集成,但这一点也不优雅。 还有其他办法吗? 我想在没有 GIL 的情况下使用 GSL 集成

非常感谢, 罗曼

I would like to integrate locally defined python functions with the gsl libraries.
To do that, i have implemented the following code with Cython (example with Gauss-Legendre quadrature) :

pxd file :

cdef extern from "gsl/gsl_math.h":
ctypedef struct gsl_function:
double (* function) (double x, void * params) nogil
void * params

cdef extern from "gsl/gsl_integration.h":
gsl_integration_glfixed_table * gsl_integration_glfixed_table_alloc(size_t n) nogil 
double gsl_integration_glfixed(gsl_function *f, double a, double b, gsl_integration_glfixed_table * t) nogil
void  gsl_integration_glfixed_table_free(gsl_integration_glfixed_table *t) nogil 

cdef double int_gsl_GaussLegendre(double func(double, void *) nogil, void * p, double xmin, double xmax) nogil

and the pyx file :

cdef size_t size_GL=1000

cdef double int_gsl_GaussLegendre(double func(double, void *) nogil, void * p, double xmin, double xmax) nogil:
cdef double result, error;
cdef gsl_integration_glfixed_table * W 
cdef gsl_function F
W = gsl_integration_glfixed_table_alloc(size_GL)
F.function = func
F.params = p
result = gsl_integration_glfixed(&F, xmin, xmax, W)    
gsl_integration_glfixed_table_free(W)
return result

This code work for any C function declared within my Cython code. Of course, this will fail when i pass as argument a python function.
My script in python :

def gsl_integral(py_func, xmin, xmax, args=()):
cdef size_t sizep = <int>(args.size)
cdef double[:] params = np.empty(sizep, dtype=np.double)
for i in range(0,sizep):
    params[i]=args[i]
cdef gsl_function F
F.function = py_func
F.params = params

which return : "Cannot convert Python object to 'double (*)(double, void *) nogil'"

If i use instead :

def gsl_integral(py_func, xmin, xmax, args=()):
cdef size_t sizep = <int>(args.size)
cdef double[:] params = np.empty(sizep, dtype=np.double)
for i in range(0,sizep):
    params[i]=args[i]
cdef gsl_function F
F.function = <double *>py_func
F.params = params

which return: "Python objects cannot be cast to pointers of primitive types"

I have seen that i could wrap my cython function into a class (Pass c function as argument to python function in cython), but i'm not quite sure to understand how to do it in this situation (plus the example doesn't work me.) As a workaround, i have been passing to Cython two array, x and f(x), the latter estimated with my local python f, in order to define a gsl spline that i can latter integrate, but this not elegant at all.
Is there any other ways?
I would like to use GSL integration without the GIL

Many thanks,
Romain

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

清秋悲枫 2025-01-19 02:10:20

您的基本前提存在一些基本问题:

  • C 函数指针没有任何状态。 Python 可调用对象确实具有状态。因此,C 函数指针没有空间来存储调用 Python 函数所需的信息。
  • 如果您正在调用 Python 可调用对象,则无法释放 GIL,因为您必须拥有 GIL 才能执行该对象。

此答案概述了您的选项(C++ std::function 选项此处不适用) 。本质上,您可以

  • 使用 ctypes 从 Python 函数生成函数指针(它在运行时通过 hacky 代码生成来实现这一点 - 这在标准 C 中是不可能的),
  • 或者编写一个 cdef 函数具有适当的签名作为函数指针传递,并将 Python 可调用函数作为 void* 参数的一部分传递给该函数。

我推荐后者,但这都不是一个很好的选择。

There's a few fundamental issues with your basic premise:

  • C function pointers do not have any state. Python callable objects do have state. Therefore, C function pointers do not have space to store the information required to call a Python function.
  • If you're calling a Python callable object, you cannot release the GIL, since you must have the GIL to execute that object.

This answer outlines your options (the C++ std::function option doesn't apply here). Essentially you can either

  • use ctypes to generate a function pointer from the Python function (it does this with hacky code-generation at runtime - it isn't possible in standard C),
  • or write a cdef function with an appropriate signature to pass as the function pointer, and pass your Python callable to that as part of the void* params.

I recommend the latter, but neither is a great options.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文