使用数组成员包装C结构以便在python中访问:SWIG?用Cython? ctypes的?

时间:2011-05-29 00:52:55

标签: python c struct swig cython

我想从python访问一个C函数,该函数返回一个包含double数组的结构(其中这些数组的长度由结构的其他int成员给出)。宣言是

typedef struct {
  int dim;
  int vertices;
  int quadrature_degree;
  int polynomial_degree;
  int ngi;
  int quadrature_familiy;
  double *weight; /* 1D: ngi */
  double *l;      /* 2D: ngi * dim */
  double *n;      /* 2D: ngi * vertices */
  double *dn;     /* 3D: ngi * vertices * dim */
} element;

extern void get_element(int dim, int vertices, int quad_degree, int poly_degree, element* e);

重点是我希望能够以正确形状的NumPy数组访问所有double*成员(即dn应该可以作为3D数组访问)。

简单SWIG包装这给了我很好的结构,但所有double*成员都是<Swig Object of type 'double *' at 0x348c8a0>,这使得它们无用。我玩过NumPy SWIG接口文件,但无法使( DATA_TYPE* INPLACE_ARRAY1, int DIM1 )这样的任何类型图工作(我认为在这种情况下不可能让它们匹配但是我很高兴被证明是错误的)。

我的猜测是我必须将NumPy数组的代码初始化作为PyArrayObject用于这些成员,SWIG扩展我的结构以使它们可以在Python中访问?这看起来很多工作。任何人都可以看到使用SWIG更好的方式吗?如果这样做更容易,可以更改结构或返回它的方法。

或者我看了一下cython和ctypes。这些更适合我想要实现的目标吗?我没有使用过cython,所以无法判断它的包装能力。对于ctypes,我可以大致想象一下如何做,但这意味着手工编写我希望合理自动化的包装器可以为我做的事。

感激不尽的任何建议!

5 个答案:

答案 0 :(得分:8)

Cython规则:

cdef extern from "the header.h":

ctypedef struct element:
  int dim
  int vertices
  int quadrature_degree
  int polynomial_degree
  int ngi
  int quadrature_familiy
  double *weight
  double *l
  double *n
  double *dn

void get_element(int dim, int vertices, int quad_degree, int poly_degree, element* e)

然后你可以从python space

连接它

答案 1 :(得分:6)

使用SWIG需要整个结构的类型映射。只有指针成员的Tyepmaps是不够的,因为它们没有上下文来知道初始化NumPy数组的大小。我设法得到了我想要的以下类型图(基本上是从numpy.i复制和粘贴并适应我的需要,可能不是很强大):

%typemap (in,numinputs=0) element * (element temp) {
  $1 = &temp;
}

%typemap (argout) element * {
  /* weight */
  {
    npy_intp dims[1] = { $1->ngi };
    PyObject * array = PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, (void*)($1->weight));
    if (!array) SWIG_fail;
    $result = SWIG_Python_AppendOutput($result,array);
  }
  /* l */
  {
    npy_intp dims[2] = { $1->ngi, $1->dim };
    PyObject * array = PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, (void*)($1->l));
    if (!array) SWIG_fail;
    $result = SWIG_Python_AppendOutput($result,array);
  }
  /* n */
  {
    npy_intp dims[2] = { $1->ngi, $1->vertices };
    PyObject * array = PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, (void*)($1->n));
    if (!array) SWIG_fail;
    $result = SWIG_Python_AppendOutput($result,array);
  }
  /* dn */
  {
    npy_intp dims[3] = { $1->ngi, $1->vertices, $1->dim };
    PyObject * array = PyArray_SimpleNewFromData(3, dims, NPY_DOUBLE, (void*)($1->dn));
    if (!array) SWIG_fail;
    $result = SWIG_Python_AppendOutput($result,array);
  }
}

这与C函数的不同之处在于它返回带有我想要的数据的NumPy数组元组,这比以后从element对象中提取它更方便。第一个类型映射还消除了传递类型element的对象的需要。因此,我可以完全从python用户隐藏element结构。

python接口最终看起来像这样:

weight, l, n, dn = get_element(dim, vertices, quadrature_degree, polynomial_degree)

答案 2 :(得分:1)

查看SWIG的类型地图。它们允许您编写自己的代码来处理特定类型,特定实例(类型+名称)甚至参数组。我没有为结构做过,而是专门处理C函数采用数组及其大小的情况:

%typemap(in) (int argc, Descriptor* argv) {
    /* Check if is a list */
    if (PyList_Check($input)) {
        int size = PyList_Size($input);
        $1 = size;
        ...
        $2 = ...;
    }
}

这将采用一对参数int argc, Descriptor* argv(因为提供的名称也必须匹配)并传递给你使用的PyObject,并编写转换所需的任何C代码。你可以为double *dn做一个使用NumPy C API进行转换的类型图。

答案 3 :(得分:0)

您总是可以编写带有“element *”的辅助函数并返回您寻找的元素:

double element_get_weight(const element *elt, unsigned n) {
    assert(n < elt->ngi);  /* or similar */
    return elt->weight[n];
}

如果你需要修改和阅读,你当然需要单独的“getters”和“setter”。

SWIG应该能够轻松地包装所有这些并将它们暴露给Python。

性能可能不是很好,但可能并不比其他选择更差。

答案 4 :(得分:0)

使用ctypes的SWIG创建模块的等效内容如下所示:

from ctypes import *
from numpy import *

lib = cdll.LoadLibrary("_get_element.so")

class ELEMENT(Structure):
    _fields_ = [("dim", c_int),
                ("vertices", c_int),
                ("quadrature_degree", c_int),
                ("polynomial_degree", c_int),
                ("ngi", c_int),
                ("quadrature_familiy", c_int),
                ("weight", POINTER(c_double)),
                ("l", POINTER(c_double)),
                ("n", POINTER(c_double)),
                ("dn", POINTER(c_double))]

cget_element = lib.get_element
cget_element.argtypes = [c_int, c_int, c_int, c_int, POINTER(ELEMENT)]
cget_element.restype = None

def get_element(dim, vertices, quad_degree, poly_degree):
    e = ELEMENT()
    cget_element(dim, vertices, quad_degree, poly_degree, byref(e))
    weight = asarray([e.weight[i] for i in xrange(e.ngi)], dtype=float64)
    l = asarray([e.l[i] for i in xrange(e.ngi*e.dim)], dtype=float64).reshape((e.ngi,e.dim))
    n = asarray([e.n[i] for i in xrange(e.ngi*e.vertices)], dtype=float64).reshape((e.ngi,e.vertices))
    dn = asarray([e.dn[i] for i in xrange(e.ngi*e.vertices*e.dim)], dtype=float64).reshape((e.ngi,e.vertices,e.dim))
    return weight, l, n, dn