Подтвердить что ты не робот

Как вернуть numpy.array из boost:: python?

Я хотел бы вернуть некоторые данные из кода С++ в качестве объекта numpy.array. Я взглянул на boost::python::numeric, но его документация очень краткая. Могу ли я получить пример, например, возвращает (не очень большой) vector<double> в python? Я не против делать копии данных.

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ: библиотека, описанная в моем первоначальном ответе (https://github.com/ndarray/Boost.NumPy), была интегрирована непосредственно в Boost.Python с Boost 1.63, и, следовательно, автономная версия теперь устарела. Текст ниже теперь соответствует новой интегрированной версии (изменилось только пространство имен).

Boost.Python теперь включает в себя умеренно завершенную оболочку C-API NumPy в интерфейс Boost.Python. Он довольно низкоуровневый, и в основном сфокусирован на том, как решить более сложную проблему, состоящую в том, как передавать данные C++ в и из NumPy без копирования, но вот как вы должны сделать скопированный возврат std :: vector с этим:

#include "boost/python/numpy.hpp"

namespace bp = boost::python;
namespace bn = boost::python::numpy;

std::vector<double> myfunc(...);

bn::ndarray mywrapper(...) {
    std::vector<double> v = myfunc(...);
    Py_intptr_t shape[1] = { v.size() };
    bn::ndarray result = bn::zeros(1, shape, bn::dtype::get_builtin<double>());
    std::copy(v.begin(), v.end(), reinterpret_cast<double*>(result.get_data()));
    return result;
}

BOOST_PYTHON_MODULE(example) {
    bn::initialize();
    bp::def("myfunc", mywrapper);
}

Ответ 2

Решение, которое не требует загрузки какой-либо специальной сторонней библиотеки С++ (но вам нужно numpy).

#include <numpy/ndarrayobject.h> // ensure you include this header

boost::python::object stdVecToNumpyArray( std::vector<double> const& vec )
{
      npy_intp size = vec.size();

     /* const_cast is rather horrible but we need a writable pointer
        in C++11, vec.data() will do the trick
        but you will still need to const_cast
      */

      double * data = size ? const_cast<double *>(&vec[0]) 
        : static_cast<double *>(NULL); 

    // create a PyObject * from pointer and data 
      PyObject * pyObj = PyArray_SimpleNewFromData( 1, &size, NPY_DOUBLE, data );
      boost::python::handle<> handle( pyObj );
      boost::python::numeric::array arr( handle );

    /* The problem of returning arr is twofold: firstly the user can modify
      the data which will betray the const-correctness 
      Secondly the lifetime of the data is managed by the C++ API and not the 
      lifetime of the numpy array whatsoever. But we have a simple solution..
     */

       return arr.copy(); // copy the object. numpy owns the copy now.
  }

Конечно, вы можете написать функцию из double * и size, которая является общей, а затем вызывать ее из вектора, извлекая эту информацию. Вы также можете написать шаблон, но вам потребуется какое-то сопоставление от типа данных к перечислению NPY_TYPES.

Ответ 3

Немного поздно, но после многих неудачных попыток я нашел способ напрямую отображать массивы С++ в виде массивов numpy. Ниже приведен короткий пример С++ 11 с использованием boost::python и Eigen:

#include <numpy/ndarrayobject.h>
#include <boost/python.hpp>

#include <Eigen/Core>

// c++ type
struct my_type {
  Eigen::Vector3d position;
};


// wrap c++ array as numpy array
static boost::python::object wrap(double* data, npy_intp size) {
  using namespace boost::python;

  npy_intp shape[1] = { size }; // array size
  PyObject* obj = PyArray_New(&PyArray_Type, 1, shape, NPY_DOUBLE, // data type
                              NULL, data, // data pointer
                              0, NPY_ARRAY_CARRAY, // NPY_ARRAY_CARRAY_RO for readonly
                              NULL);
  handle<> array( obj );
  return object(array);
}



// module definition
BOOST_PYTHON_MODULE(test)
{
  // numpy requires this
  import_array();

  using namespace boost::python;

  // wrapper for my_type
  class_< my_type >("my_type")
    .add_property("position", +[](my_type& self) -> object {
        return wrap(self.position.data(), self.position.size());
      });

}

В этом примере описывается "getter" для свойства. Для "сеттера" самым простым способом является назначение элементов массива вручную из boost::python::object с помощью boost::python::stl_input_iterator<double>.

Ответ 4

Выполнение этого с помощью numpy api напрямую не обязательно сложно, но я регулярно использую boost:: multiarray для своих проектов и считаю удобным переносить формы массива между границами С++/Python автоматически. Итак, вот мой рецепт. Используйте http://code.google.com/p/numpy-boost/, или еще лучше, эту версию заголовок numpy_boost.hpp; который лучше подходит для проектов multi-file boost:: python, хотя он использует некоторые С++ 11. Затем из вашего кода boost:: python используйте что-то вроде этого:

PyObject* myfunc(/*....*/)
{
   // If your data is already in a boost::multiarray object:
   // numpy_boost< double, 1 > to_python( numpy_from_boost_array(result_cm) );
   // otherwise:
   numpy_boost< double, 1> to_python( boost::extents[n] );
   std::copy( my_vector.begin(), my_vector.end(), to_python.begin() );

   PyObject* result = to_python.py_ptr();
   Py_INCREF( result );

   return result;
}

Ответ 5

Я посмотрел на доступные ответы и подумал: "Это будет легко". Я продолжал тратить часы на то, что казалось тривиальным примером/адаптацией ответов.

Затем я внедрил @max ответ точно (пришлось установить Eigen), и он работал нормально, но мне все еще не удалось его адаптировать. Мои проблемы были в основном (по количеству) глупыми, синтаксическими ошибками, но, кроме того, я использовал указатель на скопированные данные std::vector после того, как вектор, казалось, был выпашен из стека.

В этом примере возвращается указатель на std::vector, но также вы можете вернуть указатель размера и данных() или использовать любую другую реализацию, которая дает ваш массив numpy доступ к базовым данным стабильным образом (т.е. гарантированно существует):

class_<test_wrap>("test_wrap")
    .add_property("values", +[](test_wrap& self) -> object {
            return wrap(self.pvalues()->data(),self.pvalues()->size());
        })
    ;

Для test_wrap с std::vector<double> (обычно pvalues ​​() может просто вернуть указатель без заполнения вектора):

class test_wrap {
public:
    std::vector<double> mValues;
    std::vector<double>* pvalues() {
        mValues.clear();
        for(double d_ = 0.0; d_ < 4; d_+=0.3)
        {
            mValues.push_back(d_);
        }
        return &mValues;
    }
};

Полный пример приведен в Github, поэтому вы можете пропустить утомительные шаги транскрипции и меньше беспокоиться о сборке, libs и т.д. Вы должны просто сделать следующее и получить пример функционирования (если у вас установлены необходимые функции и ваш путь уже установлен):

git clone https://github.com/ransage/boost_numpy_example.git
cd boost_numpy_example
# Install virtualenv, numpy if necessary; update path (see below*)
cd build && cmake .. && make && ./test_np.py

Это должно дать результат:

# cmake/make output
values has type <type 'numpy.ndarray'>
values has len 14
values is [ 0.   0.3  0.6  0.9  1.2  1.5  1.8  2.1  2.4  2.7  3.   3.3  3.6  3.9]

* В моем случае я помещаю numpy в virtualenv следующим образом - это не должно быть ненужным, если вы можете выполнить python -c "import numpy; print numpy.get_include()", как предложено @max:

# virtualenv, pip, path unnecessary if your Python has numpy
virtualenv venv
./venv/bin/pip install -r requirements.txt 
export PATH="$(pwd)/venv/bin:$PATH"

Удачи!: -)