comment retourner numpy.tableau de boost::python?
je voudrais retourner quelques données du code C++ comme un numpy.array
objet. J'ai eu un coup d'oeil à boost::python::numeric
, mais sa documentation est très laconique. Puis-je obtenir un exemple de retour d'un (pas très grand) vector<double>
pour python? Ça ne me dérange pas de faire des copies de données.
5 réponses
une autre interface entre Boost.Python and NumPy peut être trouvé ici:
https://github.com/ndarray/Boost.NumPy
c'est un enveloppement modérément complet de L'API-C dans un Boost.Python interface,avec l'intention de soumettre éventuellement à Boost. Je ne suis pas sûr que la documentation soit meilleure que boost::python::numeric à ce stade, mais il y a beaucoup d'exemples de code et au moins il est en développement actif. C'est assez de bas niveau, et surtout concentré sur la façon de résoudre le problème plus difficile de passer des données C++ à et à partir de NumPy sans copier, Mais voici comment faire un retour std::vector copié avec cela:
#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);
}
une solution qui ne vous oblige pas à télécharger une bibliothèque C++ tierce partie spéciale (mais vous avez besoin de 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.
}
bien sûr, vous pouvez écrire une fonction à partir de double * et size, qui est générique puis invoquer cela du vecteur en extrayant cette information. Vous pouvez aussi écrire un modèle, mais vous aurez besoin d'une sorte de mappage du type de données vers le NPY_TYPES
enum.
C'est un peu tard, mais après de nombreuses tentatives, j'ai trouvé un moyen d'exposer c++ tableaux numpy tableaux directement. Voici un exemple C++11 utilisant boost::python
et modes Propres:
#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());
});
}
L'exemple décrit un "getter" pour le bien. Pour le "setter", le plus simple est d'attribuer les éléments du tableau manuellement à partir d'un boost::python::object
à l'aide d'un boost::python::stl_input_iterator<double>
.
le faire directement en utilisant l'api numpy n'est pas nécessairement difficile, mais j'utilise boost::multiarray régulièrement pour mes projets et je trouve pratique de transférer les formes du tableau entre la frontière C++/Python automatiquement. Donc, voici ma recette. Utiliser http://code.google.com/p/numpy-boost/, ou mieux encore, version du numpy_boost.en-tête hpp; qui est mieux adapté pour boost multi-fichiers::Python projects, bien qu'il utilise du C++11. Puis, à partir de votre boost:: code python, utilisez quelque chose comme ceci:
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;
}
j'ai regardé les réponses disponibles et j'ai pensé, "ce sera facile". J'ai procédé à passer des heures à tenter ce qui semblait être un trivial exemples/adaptations des réponses.
puis j'ai implémenté exactement la réponse de @max (j'ai dû installer Eigen) et ça a bien marché, mais j'ai quand même eu du mal à l'adapter. Mes problèmes étaient la plupart du temps (par le nombre) stupide, des erreurs de syntaxe, mais en outre, j'ai utilisé un pointeur vers une std copiée::données du vecteur après le vecteur semble être tombé de la pile.
dans cet exemple, un pointeur vers le vecteur std::est retourné, mais vous pouvez aussi retourner le pointeur taille et données() ou utiliser toute autre implémentation qui donne à votre tableau numpy accès aux données sous-jacentes d'une manière stable (i.e. garanti d'exister):
class_<test_wrap>("test_wrap")
.add_property("values", +[](test_wrap& self) -> object {
return wrap(self.pvalues()->data(),self.pvalues()->size());
})
;
Pour test_wrap avec un std::vector<double>
(normalement pvalues() peut-il suffit de retourner le pointeur sans remplir le vecteur):
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;
}
};
L'exemple complet est sur Github, donc vous pouvez sauter le fastidieux les étapes de transcription et se soucient moins de construire, libs, etc. Vous devriez être capable de faire ce qui suit et d'obtenir un exemple de fonctionnement (Si vous avez déjà installé les fonctionnalités nécessaires et la configuration de votre chemin):
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
Cela devrait donner le résultat:
# 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]
*dans mon cas, j'ai mis num PY dans un virtualenv comme suit - cela devrait être inutile si vous pouvez exécuter python -c "import numpy; print numpy.get_include()"
comme suggéré par @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"
amusez-vous bien! : -)