代码之家  ›  专栏  ›  技术社区  ›  Dalek

将带有指针数组的不同“C”函数作为函数参数传递给类

  •  -2
  • Dalek  · 技术社区  · 8 年前

    normal 功能:

    from cpython cimport array
    import cython
    import ctypes
    cimport numpy as np
    cpdef void normal(np.ndarray[ndim=1, dtype=np.float64_t] u, 
                      np.ndarray[ndim=1, dtype=np.float64_t] yu, 
                      np.ndarray[ndim=1, dtype=np.float64_t] ypu):
    
          cdef int i
          cdef int n=len(u)
          for i in prange(n, nogil=True):
              yu[i]=-u[i]*u[i]*0.5                                                               
              ypu[i]=-u[i]                                                                    
          return                                                     
    cdef class _SampleFunc:
         cdef void (*func)(double *, double *, double *)
    
    cdef void sample(int* x, double* hx, double* hxx, void(*func)(double*, double*, double*), int n):
          def int i
          for i from 0 <= i < n:
              func[0](&x[i], &hx[i], &hxx[i])
          return 
    cdef class myClass:
         sample_wrapper = _SampleFunc() 
         sample_wrapper.func = Null
         def foo(np.ndarray[ndim=1, dtype=np.float64_t] x,
                 np.ndarray[ndim=1, dtype=np.float64_t] hx,
                 np.ndarray[ndim=1, dtype=np.float64_t] hxx,
                 _SampleFunc sample_func, 
                 int k):
             cdef np.ndarray[ndim=1, dtype=np.float64_t] sp
             cdef int num=len(x)
             func = sample_func.func
             assert func is not NULL, "function value is NULL"
             cdef int j
             for j from 0 <= j <k:
                 sample(&x[0],&hx[0], &hxx[0], func, num)
                 sp[j]=hx[0]
             return sp
    

    测验py公司

    import numpy as np
    from sample import *
    x = np.zeros(10, float)
    hx = np.zeros(10, float)
    hpx = np.zeros(10, float)
    
    x[0] = 0
    x[1] = 1.0
    x[2] = -1.0
    def pynormal(x):
        return -x*x*0.5,-x
    
    hx[0], hpx[0] = pynormal(x[0])
    hx[1], hpx[1] = pynormal(x[1])
    hx[2], hpx[2] = pynormal(x[2])
    num=20
    ars=myClass()
    s=ars.foo( x, hx, hpx, normal, num)
    

    test.py 代码我得到了这个错误:

    'ars._SampleFunc' object has no attribute 'func'
    

    我正在尝试为不同的 C 具有三个指针数组作为其参数的函数。到目前为止,我的结论是可以用一个类来完成,因为该类可以在python中访问。我想知道我怎样才能通过考试 以指针数组作为参数的函数 myClass

    cdef void normal(
                     int n,
                     double* u, 
                     double* yu, 
                     double* ypu
                    ):          
          cdef int i          
          for i in prange(n, nogil=True):
              yu[i]=-u[i]*u[i]*0.5                                                               
              ypu[i]=-u[i]                                                                    
          return 
    
    1 回复  |  直到 8 年前
        1
  •  2
  •   DavidW    8 年前

    首先要处理的是签名的函数 cdef void (*func)(double *, double *, double *) . 您不知道这些数组有多长,因此无法安全访问它们的元素。明智的做法是更改函数签名以传递长度:

    cdef void (*func)(double *, double *, double *, int)
    

    更令人困惑的是,您似乎在两个方向上迭代1D数组的同一个轴 normal sample


    I've recommended the ctypes standard library module as a solution to similar problems previously create a function pointer from a Python callable . 如果你只想打电话,有一个更简单但更有限的解决方案 cdef Cython函数。

    下面是一个演示如何实现该想法的简单示例:

    import numpy as np
    import ctypes
    
    ctypedef void (*func_t)(int, double *)
    
    cdef void sample(int n, double* x, func_t f):
        f(n,x)
    
    def call_sample(double[::1] x,
                    f):
    
        def func_wrapper(n, arg1):
            # x is a slightly opaque ctypes type
            # first cast it to a ctypes array of known size
            # and then create a numpy array from that
            arg1_as_ctypes_array = (ctypes.c_double*n).from_address(ctypes.addressof(arg1.contents))
            return f(np.asarray(arg1_as_ctypes_array))
    
    
        FTYPE = ctypes.CFUNCTYPE(None, # return type
                                 ctypes.c_int, # arguments
                                 ctypes.POINTER(ctypes.c_double))
        f_ctypes = FTYPE(func_wrapper) # convert Python callable to ctypes function pointer
    
        # a rather nasty line to convert to a C function pointer
        cdef func_t f_ptr = (<func_t*><size_t>ctypes.addressof(f_ctypes))[0]
    
        sample(x.shape[0], &x[0], f_ptr)
    
    
    def example_function(x):
        # expects a numpy array like object
        print(x)
    
    def test():
        a = np.random.rand(20)
        print(a)
        call_sample(a,example_function)
    

    我意识到ctypes和Cython之间有一些稍微混乱的转换-这是不可避免的。

    一点解释:我假设您希望保持Python接口的简单,因此 example_function 只需要取一个类似numpy数组的对象。ctypes传递的函数需要接受许多元素和指针,以匹配C接口。

    ctypes指针类型( LP_c_double arg1[5] )因此,它可以很好地用于简单用途,但它不在内部存储其长度。将其更改为numpy数组很有帮助(但不是必需的),这样您可以更广泛地使用它,因此我们创建了一个包装函数来实现这一点。我们做到了:

    arg1_as_ctypes_array = (ctypes.c_double*n).from_address(ctypes.addressof(arg1.contents))
    

    np.asarray(arg1_as_ctypes_array)
    

    将其转换为numpy数组。这将共享数据而不是复制数据,因此如果您更改它,则原始数据将更改。由于到numpy数组的转换遵循标准模式,因此很容易在中生成包装函数 call_sample .

    double ,不是 double* . 在这种情况下,你不必做任何事情,因为一个ctypes 双重的

    只有 功能

    如果您确定要传递的函数将始终是 然后,您可以避免使用ctypes,并提出一些更简单的方法。首先需要使函数签名与指针完全匹配:

    cdef void normal(int N, double *x): # other parameters as necessary
        cdef double[::1] x_as_mview = <double[:N:1]>x # cast to a memoryview
        # ... etc
    

    SampleFunc 与创建模块级对象几乎相同:

    # in Cython
    normal_samplefunc = SampleFunc()
    normal_samplefunc.func = &normal
    
    # in Python
    s=ars.foo( x, hx, hpx, normal_samplefunc, num)
    

    ars.foo 是你写的方式(不是 ctypes

    func = sample_func.func
    # ...
    sample(..., func,...)
    

    此代码将运行得更快,但您希望能够调用 典型的


    Python接口

    典型的 来自Python。Python函数和传递给C的函数可能需要不同的接口,因此我将为这两种用途定义一个单独的函数,但共享实现:

    def normal(double[::1] u, # ... other arguments
               ):
       # or cpdef, if you really want
       implementation goes here
    
    # then, depending on if you're using ctypes or not:
    def normal_ctypes(int n, u # other arguments ...
       ):
       u_as_ctypes_array = (ctypes.c_double*n).from_address(ctypes.addressof(x.contents))
       normal(u_as_ctypes_array, # other arguments
                    )
    
    # or
    cdef void normal_c(int n, double* u # ...
                  ):
       normal(<double[:N:1]>x # ...
              )
    
    推荐文章