Python 定义更复杂的静态数组

Python 定义更复杂的静态数组,python,c,cython,Python,C,Cython,通常在数值方法中,有很多系数是静态的,因为它们对于特定的方法是固定的。我想知道Cython/C中设置这样的数组或变量的最佳方法是什么 在我的例子中,除了系数和级数外,龙格-库塔积分方法基本相同。现在我正在做一些类似(简化) #定义一些结构,使其可用于所有不同的Runge-Kutta方法 ctypedef结构RKHelper: 整数 双*系数 cdef: RK方法 #后来的第二种方法、第三种方法等。 firstRKMethod.numStages=3 firstRKMethod.coeffs=ma

通常在数值方法中,有很多系数是静态的,因为它们对于特定的方法是固定的。我想知道Cython/C中设置这样的数组或变量的最佳方法是什么

在我的例子中,除了系数和级数外,龙格-库塔积分方法基本相同。现在我正在做一些类似(简化)

#定义一些结构,使其可用于所有不同的Runge-Kutta方法
ctypedef结构RKHelper:
整数
双*系数
cdef:
RK方法
#后来的第二种方法、第三种方法等。
firstRKMethod.numStages=3
firstRKMethod.coeffs=malloc(firstRKMethod.numStages*sizeof(双精度))
#数组可以很大,并且大多数条目为零
对于范围内的ii(firstRKMethod.numStages):
firstRKMethod.coefs[ii]=0。
#设置非零元素
firstRKMethod.系数[2]=1.3
有几点:

  • 我知道malloc不适用于静态数组,但我不知道如何在Cython中将“numStages”或“RKHelper”声明为静态,所以我不能使用静态数组。。。或者我在RKHelper中执行类似于“double[4]”的操作,这不允许对所有RK方法使用相同的结构定义
  • 我想知道是否有比循环更好的方法。我不想手动设置整个数组(例如,数组=[0,0,0,0,0,1.3,…很多数字大多为零])
  • 就我所见,Cython中没有“真正的”静态变量,是吗
有没有更好的方法来做我想做的事


干杯

实现您想要实现的一种方法是将Runge-Kutta方案的系数设置为全局变量,这样您就可以使用静态数组。这会很快,但肯定很难看

丑陋的解决方案

cdef int numStages = 3
# Using the pointer notation you can set a static array
# as well as its elements in one go
cdef double* coeffs = [0.,0.,1.3]
# You can always change the coefficients further as you wish

def RungeKutta_StaticArrayGlobal():
    # Do stuff

    # Just to check
    return numStages
from libc.stdlib cimport calloc, free

ctypedef struct RKHelper:
    int numStages
    double* coeffs

def RungeKutta_DynamicArray():
    cdef:
        RKHelper firstRKMethod

    firstRKMethod.numStages = 3
    # Use calloc instead, it zero initialises the buffer, so you don't 
    # need to set the elements to zero within a loop
    firstRKMethod.coeffs = <double*> calloc(firstRKMethod.numStages,sizeof(double))

    # Set non-zero elements
    firstRKMethod.coeffs[2] = 1.3

    free(firstRKMethod.coeffs)

    # Just to check
    return firstRKMethod.numStages
更好的解决方案是定义一个
cython
类,其成员为Runge-Kutta系数

优雅的解决方案

cdef class RungeKutta_StaticArrayClass:
    cdef double* coeffs
    cdef int numStages
    def __cinit__(self):
        # Note that due to the static nature of self.coeffs, its elements
        # expire beyond the scope of this function    
        self.coeffs = [0.,0.,1.3]
        self.numStages = 3

    def GetnumStages(self):
        return self.numStages

    def Integrate(self):
        # Reset self.coeffs
        self.coeffs = [0.,0.,0.,0.,0.8,2.1]
        # Perform integration
关于设置元素的问题,让我们使用
calloc
而不是
malloc
使用动态分配的数组修改您自己的代码

动态分配的版本

cdef int numStages = 3
# Using the pointer notation you can set a static array
# as well as its elements in one go
cdef double* coeffs = [0.,0.,1.3]
# You can always change the coefficients further as you wish

def RungeKutta_StaticArrayGlobal():
    # Do stuff

    # Just to check
    return numStages
from libc.stdlib cimport calloc, free

ctypedef struct RKHelper:
    int numStages
    double* coeffs

def RungeKutta_DynamicArray():
    cdef:
        RKHelper firstRKMethod

    firstRKMethod.numStages = 3
    # Use calloc instead, it zero initialises the buffer, so you don't 
    # need to set the elements to zero within a loop
    firstRKMethod.coeffs = <double*> calloc(firstRKMethod.numStages,sizeof(double))

    # Set non-zero elements
    firstRKMethod.coeffs[2] = 1.3

    free(firstRKMethod.coeffs)

    # Just to check
    return firstRKMethod.numStages

RungeKutta_StaticArray
基本上具有接近
no op
的成本,这意味着对数组分配没有运行时惩罚。您可以选择在此函数中声明
coefs
,并且计时仍然相同。
RungeKutta_StaticArrayClass
尽管设置带有成员和构造函数的类的开销仍然比动态分配的版本快。

为什么不使用numpy数组呢?实际上,它不是静态的(请参见最后的注释),但您可以在全局范围内分配它,以便在模块启动时创建它。您还可以访问下面的原始C阵列,因此没有实际的效率成本

import numpy as np

# at module global scope
cdef double[::1] rk_coeffs = np.zeros((50,)) # avoid having to manually fill with 0s
# illustratively fill the non-zero elements
rk_coeffs[1] = 2.0
rk_coeffs[3] = 5.0

# if you need to convert to a C array
cdef double* rk_coeffs_ptr = &rk_coeffs[0]

注意,我对这个问题的理解是,您使用的“static”是指“编译到模块中”,而不是任何Python静态方法/类变量

谢谢你的回答。关于您的建议:如果许多条目相同(例如零),那么在代码中编写StaticArray()有点烦人。我不想在我的代码中有成千上万的带零的行。基本上,我希望有一种方法可以告诉Cython或编译器在编译时编写一个静态数组,即“Cython:在编译时使用此循环生成一个静态数组”。integrator类是我的第一个实现,但是考虑到GIL中cdef类的局限性,我试图在非常基本的块(比如integrator)中避开它。