Чи є спосіб використовувати pythonappend з новою вбудованою функцією SWIG?


77

У мене є невеликий проект, який чудово працює з SWIG. Зокрема, деякі мої функції повертають std::vectors, які перекладаються на кортежі в Python. Зараз я роблю багато цифр, тому у мене просто SWIG перетворює їх у масиви numpy після їх повернення з коду c ++. Для цього я використовую щось на зразок наступного в SWIG.

%feature("pythonappend") My::Cool::Namespace::Data() const %{ if isinstance(val, tuple) : val = numpy.array(val) %}

(Насправді є кілька функцій з іменем Data, деякі з яких повертаються з плаваючою здатністю, тому я перевіряю, що valце насправді кортеж.) Це працює просто чудово.

Але я також хотів би використовувати -builtinпрапор, який зараз доступний. Дзвінки до цих функцій Data є рідкісними та в основному інтерактивними, тому їх повільність не є проблемою, але є й інші повільні цикли, які значно пришвидшуються завдяки вбудованій опції.

Проблема полягає в тому, що коли я використовую цей прапор, функція pythonappend мовчки ігнорується. Тепер Data просто повертає кортеж знову. Чи можна якось повернути масиви numpy? Я спробував скористатися друкарськими картами, але це перетворилося на гігантський безлад.

Редагувати:

Бореалід дуже красиво відповів на запитання. Тільки для повноти, я включаю пару пов’язаних, але тонко різних карт набору, які мені потрібні, тому що я повертаюся за посиланням const і використовую вектори векторів (не запускати!). Вони досить різні, і я не хотів би, щоб хтось ще спотикався, намагаючись з’ясувати незначні відмінності.

%typemap(out) std::vector<int>& {
  npy_intp result_size = $1->size();
  npy_intp dims[1] = { result_size };
  PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_INT);
  int* dat = (int*) PyArray_DATA(npy_arr);
  for (size_t i = 0; i < result_size; ++i) { dat[i] = (*$1)[i]; }
  $result = PyArray_Return(npy_arr);
}
%typemap(out) std::vector<std::vector<int> >& {
  npy_intp result_size = $1->size();
  npy_intp result_size2 = (result_size>0 ? (*$1)[0].size() : 0);
  npy_intp dims[2] = { result_size, result_size2 };
  PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_INT);
  int* dat = (int*) PyArray_DATA(npy_arr);
  for (size_t i = 0; i < result_size; ++i) { for (size_t j = 0; j < result_size2; ++j) { dat[i*result_size2+j] = (*$1)[i][j]; } }
  $result = PyArray_Return(npy_arr);
}

Редагувати 2:

Хоча не зовсім те, що я шукав, подібні проблеми також можуть бути вирішені за допомогою підходу @ MONK ( пояснення тут ).


4
Я не думаю, що ви можете зробити це, не написавши карту типу і не роблячи це на стороні С, саме тому, що -builtin видаляє код, де зазвичай розміщується pythonappend. Ви впевнені, що вбудований набагато швидше (тобто, профілювання змусило вас його використовувати?) У мене виникне спокуса використовувати два модулі, один із -builtin і один без нього.
Flexo

Я здивований, що немає жодного попередження, яке б -builtinігнорувало pythonappend. Я не відповідаю виклику набору тексту std::vectorв масиви numpy. Я зробив профіль, і це суттєво прискорило найприємніший цикл у моєму інтерфейсі (недостатньо довго, щоб зробити перерву; занадто довго, щоб часто чекати). Але я також зрозумів, що можу перенести цей цикл у свій код c ++ - хоча і дещо незграбно. Тож я так піду. Проте ваша пропозиція щодо "двох модулів" цікава і може бути корисною в інших випадках.
Mike

Ви зателефонували SWIG за допомогою -Wall? Я припускав, що в такому випадку це застереже.
Flexo

Жодного попередження, навіть з -Wall(хоча це досить велике ігнорування, яке, я думаю, не повинно навіть вимагати цього).
Mike

Спробуйте обернути Dataметоди Cython?
Пол Прайс

Відповіді:


8

Я згоден з вами, що використання typemapстає трохи брудним, але це правильний спосіб виконати це завдання. Ви також маєте рацію, що в документації SWIG прямо не сказано, що %pythonappendвона несумісна -builtin, але це категорично мається на увазі: %pythonappend додає до класу проксі-сервера Python , а клас-проксі-сервер Python взагалі не існує разом із -builtinпрапором.

Раніше, що ви робили, було перетворення SWIG std::vectorоб’єктів C ++ у кортежі Python, а потім передача цих кортежів назад туди numpy- де вони були перетворені знову.

Що ви дійсно хочете зробити, це перетворити їх один раз, на рівні C.

Ось код, який перетворить усі std::vector<int>об’єкти на цілочисельні масиви NumPy:

%{
#include "numpy/arrayobject.h"
%}

%init %{
    import_array();
%}

%typemap(out) std::vector<int> {
    npy_intp result_size = $1.size();

    npy_intp dims[1] = { result_size };

    PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_INT);
    int* dat = (int*) PyArray_DATA(npy_arr);

    for (size_t i = 0; i < result_size; ++i) {
        dat[i] = $1[i];
    }

    $result = PyArray_Return(npy_arr);
}

Це використовує функції numpy рівня C для побудови та повернення масиву. Для того, щоб:

  • Переконується, що arrayobject.hфайл NumPy включений у вихідний файл C ++
  • Причини, import_arrayщо викликаються під час завантаження модуля Python (інакше всі методи NumPy будуть за замовчуванням)
  • Будь-яке повернення std::vector<int>масивів NumPy відображає за допомогоюtypemap

Цей код повинен бути поміщений перед тим вам %importзаголовки , які містять функції , які повертають std::vector<int>. Окрім цього обмеження, воно цілком самодостатнє, тому воно не повинно додавати занадто багато суб’єктивного “безладу” до вашої кодової бази.

Якщо вам потрібні інші векторні типи, ви можете просто змінити NPY_INTі всі int*іint біти, в іншому випадку дублювання функції вище.


Чудово! У мене були всі елементи, які ви маєте для набору карт, але я не зовсім їх правильно склав. Хоча я ще офіційно не працюю з цим проектом, я провів досить ретельний тест, створивши простіший модуль. Дуже дякую!
Mike
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.