Обернуть структуру C членом массива для доступа в python: SWIG?Cython?ctypes? - PullRequest
12 голосов
/ 29 мая 2011

Я хочу получить доступ к функции C, которая возвращает структуру, содержащую двойные массивы (где длины этих массивов задаются другими членами int структуры) из python. Декларация

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 должен быть доступен в виде трехмерного массива).

Просто 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 ]

8 голосов
/ 01 июня 2011

Правила 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

6 голосов
/ 30 мая 2011

Использование 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.Следовательно, я могу полностью скрыть структуру element от пользователя python.

Интерфейс python, наконец, выглядит следующим образом:

weight, l, n, dn = get_element(dim, vertices, quadrature_degree, polynomial_degree)
1 голос
/ 29 мая 2011

Ознакомьтесь с картами типов 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, в которой для преобразования используется API NumPy C.

0 голосов
/ 30 мая 2011

Эквивалент модуля SWIG, созданного с использованием ctypes, выглядит следующим образом:

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
0 голосов
/ 29 мая 2011

Вы всегда можете написать вспомогательные функции, которые берут «элемент *» и возвращают искомый элемент:

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

Если вам нужно изменить, а также прочитать, вам, разумеется, потребуются отдельные «получатели» и «установщики».

SWIG должен иметь возможность легко обернуть все это и открыть их для Python.

Производительность может быть не очень хорошей, но, вероятно, не хуже, чем у альтернатив.

...