web-dev-qa-db-fra.com

comment renvoyer numpy.array de boost :: python?

Je voudrais renvoyer des données de code c ++ sous forme d'objet numpy.array. J'ai jeté un œil à boost::python::numeric, mais sa documentation est très succincte. Puis-je obtenir un exemple, par exemple retourner un (pas très grand) vector<double> en python? Cela ne me dérange pas de faire des copies de données.

27
eudoxos

UPDATE: la bibliothèque décrite dans ma réponse originale ( https://github.com/ndarray/Boost.NumPy ) a été intégrée directement dans Boost.Python à partir de Boost 1.63, et donc la version autonome est maintenant obsolète. Le texte ci-dessous correspond maintenant à la nouvelle version intégrée (seul l’espace de nom a changé).

Boost.Python inclut maintenant un wrapper moyennement complet de NumPy C-API dans une interface Boost.Python. Il s'agit d'un logiciel de bas niveau, principalement axé sur la résolution du problème plus complexe de la transmission de données C++ vers NumPy sans copie, mais voici comment vous feriez un retour std :: vector copié avec ceci:

#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);
}
20
jbosch

Une solution qui ne vous oblige pas à télécharger une bibliothèque C++ tierce 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 de double * et size, qui est générique, puis invoquer celle du vecteur en extrayant cette information. Vous pouvez également écrire un modèle, mais vous aurez besoin d’une sorte de mappage du type de données vers l’enum NPY_TYPES.

19
CashCow

Il est un peu tard, mais après de nombreuses tentatives infructueuses, j'ai trouvé un moyen d'exposer directement les tableaux c ++ en tant que tableaux numpy. Voici un exemple court de C++ 11 utilisant boost::python et 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());
      });

}

L'exemple décrit un "getter" pour la propriété. Pour le "setter", le moyen le plus simple consiste à affecter les éléments du tableau manuellement à partir d'un boost::python::object à l'aide d'un boost::python::stl_input_iterator<double>.

10
max

Le faire directement avec numpy api n’est pas forcément difficile, mais j’utilise boost :: multiarray régulièrement pour mes projets et trouve pratique de transférer automatiquement les formes du tableau entre les limites C++/Python. Alors, voici ma recette. Utilisez http://code.google.com/p/numpy-boost/ ou, mieux encore, this version de l’en-tête numpy_boost.hpp; ce qui convient mieux aux projets boost :: python multi-fichiers, bien qu'il utilise du C++ 11. Ensuite, depuis votre code boost :: 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
dsign

J'ai jeté un œil aux réponses disponibles et j'ai pensé: "ça va être facile". J'ai passé des heures à essayer ce qui semblait être un exemple trivial/une adaptation des réponses.

Ensuite, j'ai implémenté exactement la réponse de @ max (je devais installer Eigen) et cela a bien fonctionné, mais j'ai toujours eu du mal à l'adapter. Mes problèmes étaient pour la plupart (par nombre) d'erreurs de syntaxe stupides, mais en plus j'utilisais un pointeur sur les données copiées de std :: vector après que le vecteur semblait être tombé de la pile.

Dans cet exemple, un pointeur sur std :: vector est renvoyé, mais vous pouvez également renvoyer le pointeur size et data () ou utiliser toute autre implémentation qui donne à votre tableau numpy l'accès aux données sous-jacentes de manière stable 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 ​​() pourrait simplement renvoyer le pointeur sans renseigner 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 afin que vous puissiez ignorer les étapes fastidieuses de la transcription et vous soucier moins de la compilation, des bibliothèques, etc. Vous devriez pouvoir faire ce qui suit et obtenir un exemple qui fonctionne (si vous disposez des fonctionnalités nécessaires et de la déjà):

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 la sortie:

# 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, je mets numpy dans virtualenv comme suit - cela ne devrait pas être nécessaire 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"

S'amuser! :-)

1
sage