c++ - cómo devolver numpy.array desde boost:: python?
arrays boost-python (5)
Es un poco tarde, pero después de muchos intentos infructuosos encontré una forma de exponer matrices c ++ como matrices numpy directamente. Aquí hay un breve ejemplo de C ++ 11 usando boost::python
y 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());
});
}
El ejemplo describe un "captador" para la propiedad. Para el "setter", la forma más fácil es asignar los elementos de la matriz manualmente desde un objeto boost::python::object
utilizando un boost::python::stl_input_iterator<double>
.
Me gustaría devolver algunos datos del código c ++ como un objeto numpy.array
. Eché un vistazo a boost::python::numeric
, pero su documentación es muy escueta. ¿Puedo obtener un ejemplo de, por ejemplo, devolver un vector<double>
(no muy grande) vector<double>
a python? No me importa hacer copias de datos.
Hacerlo usando la API numpy directamente no es necesariamente difícil, pero utilizo boost :: multiarray regularmente para mis proyectos y encuentro conveniente transferir las formas de la matriz entre el límite C ++ / Python automáticamente. Entonces, aquí está mi receta. Utilice http://code.google.com/p/numpy-boost/ , o mejor aún, this versión del encabezado numpy_boost.hpp; que es una mejor opción para los proyectos boost :: python de archivos múltiples, aunque usa algo de C ++ 11. Luego, desde su código boost :: python, use algo como esto:
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;
}
Miré las respuestas disponibles y pensé, "esto será fácil". Procedí a pasar horas intentando lo que parecían ejemplos / adaptaciones triviales de las respuestas.
Luego implementé la respuesta de @maxim exactamente (tuve que instalar Eigen) y funcionó bien, pero todavía tuve problemas para adaptarlo. Mis problemas eran mayormente (por números) tontos, errores de sintaxis, pero adicionalmente estaba usando un puntero a los datos de una copia de std :: vector después de que el vector pareciera ser eliminado de la pila.
En este ejemplo, se devuelve un puntero al std :: vector, pero también se puede devolver el tamaño y el puntero de datos () o utilizar cualquier otra implementación que proporcione a su arreglo numpy acceso a los datos subyacentes de una manera estable (es decir, garantizada existe):
class_<test_wrap>("test_wrap")
.add_property("values", +[](test_wrap& self) -> object {
return wrap(self.pvalues()->data(),self.pvalues()->size());
})
;
Para test_wrap con std::vector<double>
(normalmente, pvalues () puede devolver el puntero sin rellenar el vector):
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;
}
};
El ejemplo completo está en Github, por lo que puede omitir los tediosos pasos de transcripción y preocuparse menos por la compilación, las librerías, etc. Debería poder hacer lo siguiente y obtener un ejemplo de funcionamiento (si tiene las funciones necesarias instaladas y la configuración de la ruta) ya):
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
Esto debería dar el resultado:
# 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]
* En mi caso, puse numpy en un virtualenv de la siguiente manera: esto debería ser innecesario si puedes ejecutar python -c "import numpy; print numpy.get_include()"
como lo sugiere @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"
¡Que te diviertas! :-)
Otra interfaz entre Boost.Python y NumPy se puede encontrar aquí:
https://github.com/ndarray/Boost.NumPy
Es una envoltura moderadamente completa de NumPy C-API en una interfaz Boost.Python, con la intención de eventualmente enviarla a Boost. No estoy seguro de que la documentación sea en general mejor que boost :: python :: numérica en este momento, pero hay muchos ejemplos de código y, al menos, está en desarrollo activo. Es bastante bajo nivel, y principalmente se enfoca en cómo abordar el problema más difícil de cómo pasar datos de C ++ hacia y desde NumPy sin copiar, pero así es como harías un retorno estándar de copiado con eso:
#include "boost/numpy.hpp"
namespace bp = boost::python;
namespace bn = boost::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);
}
Una solución que no requiere que descargue ninguna biblioteca especial de C ++ de terceros (pero necesita un 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.
}
Por supuesto, puede escribir una función de doble * y tamaño, que es genérica y luego invocarla desde el vector extrayendo esta información. También podría escribir una plantilla, pero necesitaría algún tipo de mapeo desde el tipo de datos hasta la enumeración NPY_TYPES
.