用数组成员包装 C 结构以便在 python 中访问:SWIG?赛通? c类型?

发布于 2024-11-10 03:59:41 字数 1190 浏览 2 评论 0 原文

我想从 python 访问一个 C 函数,该函数返回一个包含双精度数组的结构(其中这些数组的长度由该结构的其他 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);

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

简单地 SWIG 包装这给了我很好的结构,但是所有 double* 成员都是 的对象,这使得它们毫无用处。我尝试了 NumPy SWIG 接口文件,但无法获得任何类型映射,例如 ( DATA_TYPE*​​ INPLACE_ARRAY1, int DIM1 ) 来工作(我认为在这种情况下不可能让它们匹配)但我很高兴被证明是错误的)。

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

另外,我查看了 cython 和 ctypes。这些是否更适合我想要实现的目标?我没有使用过 cython 所以无法判断它的包装能力。对于 ctypes,我可以粗略地想象如何做到这一点,但这意味着手动编写我希望一个相当自动化的包装器可以为我做的事情。

如有任何建议,不胜感激!

I want to access a C function that returns a struct containing double arrays (where the lengths of these arrays is given by other int members of the struct) from python. The declaration is

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);

The important point is I want to be able to access all the double* members as NumPy arrays of the correct shape (i.e. dn should be a accessible as 3D array).

Simply SWIG-wrapping this gives me the struct just fine, but all the double* members are <Swig Object of type 'double *' at 0x348c8a0> which makes them useless. I played around with the NumPy SWIG interface file but couldn't get any of the typemaps like ( DATA_TYPE* INPLACE_ARRAY1, int DIM1 ) to work (I think it's not possible to get them to match in this case but I'd be happy to be proven wrong).

My guess is I'd have to hand code initialization of the NumPy arrays as PyArrayObject for these members and SWIG extend my struct to make them accessible in Python? That looks like a lot of work. Can anyone see a nicer way using SWIG? It would be possible to change the struct or the method returning it if that made things easier.

Alternatively I had a look at cython and ctypes. Would these be better suited for what I'm trying to achieve? I haven't used cython so can't judge it's wrapping capabilities. For ctypes I can roughly imagine how to do it, but it means writing by hand what I had hoped a reasonably automated wrapper could do for me.

Any suggestions gratefully received!

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

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

发布评论

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

评论(5

诗酒趁年少 2024-11-17 03:59:41

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

Cython rules:

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)

and then you can interface it, from python space

乖乖兔^ω^ 2024-11-17 03:59:41

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

Using SWIG requires a typemap for the entire struct. Tyepmaps for only the pointer members are not enough, since they don't have the context to know what size to initialize the NumPy arrays with. I managed to get what I wanted with the following typemaps (which was basically copy & paste from numpy.i and adapt to my needs, probably not very robust):

%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);
  }
}

This works different from the C function in that it returns a tuple of NumPy arrays with the data I want, which is more convenient than having to extract it from the element object later. The first typemap furthermore eliminates the need to pass in an object of type element. Hence I can hide the element struct entirely from the python user.

The python interface finally looks like this:

weight, l, n, dn = get_element(dim, vertices, quadrature_degree, polynomial_degree)
我不会写诗 2024-11-17 03:59:41

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

Check out SWIG's typemaps. They let you write your own code for handling specific types, specific instances (type+name) or even groups of arguments. I haven't done it for structures, but to specially handle a case where the C function takes an array and its size:

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

That will take the pair of arguments int argc, Descriptor* argv (since the names are provided they have to match as well) and pass you the PyObject used and you write whatever C code you need to do the conversion. You could do a typemap for double *dn that would use the NumPy C API to do the conversion.

傻比既视感 2024-11-17 03:59:41

您始终可以编写采用“元素 *”并返回您要查找的元素的辅助函数:

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

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

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

性能可能不是很好,但可能不比其他方案差。

You could always write helper functions that take an "element *" and return the element you seek:

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

If you need to modify as well as read, you will want separate "getters" and "setters", of course.

SWIG should be able to wrap all of these easily and expose them to Python.

Performance might not be great, but probably no worse than the alternatives.

情未る 2024-11-17 03:59:41

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

An equivalent to the SWIG created module using ctypes looks as follows:

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