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.

27
demandé sur Saullo G. P. Castro 2012-05-22 15:52:36

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);
}
19
répondu jbosch 2014-08-05 21:28:56

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.

19
répondu CashCow 2014-02-06 10:18:59

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>.

10
répondu max 2015-12-01 14:55:14

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;
}
2
répondu dsign 2012-05-22 13:41:24

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! : -)

1
répondu sage 2016-04-10 00:17:10