【问题标题】:Wrap C struct with array member for access in python: SWIG? cython? ctypes?用数组成员包装 C 结构以在 python 中访问:SWIG?赛通?类型?
【发布时间】:2011-09-04 03:36:42
【问题描述】:

我想访问一个 C 函数,该函数从 python 中返回一个包含双精度数组(其中这些数组的长度由该结构的其他 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,我可以粗略地想象如何做到这一点,但这意味着手写我希望合理自动化的包装器可以为我做的事情。

任何建议都非常感谢!

【问题讨论】:

  • 如何运行这段代码?我试过get_element(1,2,3,6)得到错误ValueError:数组太大; arr.size * arr.dtype.itemsize 大于最大可能大小。

标签: python c struct swig cython


【解决方案1】:

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 空间接口它

【讨论】:

    【解决方案2】:

    使用 SWIG 需要整个结构的类型映射。仅指针成员的 Tyepmap 是不够的,因为它们没有上下文来知道初始化 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)
    

    【讨论】:

    • 你能在这里显示完整的接口文件吗?我没有得到预期的结果。
    • @xgdgsc 你可以找到它here
    • 谢谢!现在我在 python 中的程序在访问其中一个返回的结果时崩溃了,可能是什么原因?
    • @xgdgsc 您的程序可能崩溃的原因有很多,例如如果您的 C 函数返回的结构未正确初始化,但我认为这不是讨论它的地方。此外,我不再使用 SWIG 并转移到 Cython(请参阅 Fabrizio 的回答)。
    【解决方案3】:

    查看 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 进行转换。

    【讨论】:

    • double *dn 等的类型映射的问题是无法访问大小信息。不过,element * 的类型图可以解决问题,请参阅我的回答。
    【解决方案4】:

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

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

    如果您需要修改和读取,当然需要单独的“getter”和“setter”。

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

    性能可能不是很好,但可能不会比替代品差。

    【讨论】:

    • 这将是我最后的选择,因为添加这些 getter 和 setter 既麻烦又麻烦,从 python 中使用它们更是如此。性能并不是真正的问题。
    【解决方案5】:

    使用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
    

    【讨论】:

      猜你喜欢
      • 2021-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多