Search code examples
pythonc++numpyboost-pythonnumpy-ndarray

Passing boost::python::numpy::ndarray as (default or not) argument of a boost::python function?


Is it possible to pass boost::python::numpy::ndarray as (default or not) argument of a boost::python function ?

dummy has no ndarray. stupid as one ndarray argument but with no default value. silly has one ndarray as default value.

>> more dummy.cpp stupid.cpp silly.cpp 
::::::::::::::
dummy.cpp
::::::::::::::
#include <boost/python.hpp>
namespace bp = boost::python;

int f(double x, double y=1.0) {return (int)(x+y);};

BOOST_PYTHON_MODULE(dummy)
{
  bp::def("f", f, ( bp::arg("x"), bp::arg("y")=1.0 ) );
}
::::::::::::::
stupid.cpp
::::::::::::::
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
namespace bp = boost::python;
namespace np = boost::python::numpy;

int f(np::ndarray x, double y=1.0) {return (int)(x.shape(0)+y);};

BOOST_PYTHON_MODULE(stupid)
{
  bp::def("f", f, ( bp::arg("x"), bp::arg("y")=1.0 ) );
}
::::::::::::::
silly.cpp
::::::::::::::
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
namespace bp = boost::python;
namespace np = boost::python::numpy;

int f(np::ndarray x, np::ndarray * y=nullptr) {return (int)(y ? x.shape(0)+y->shape(0) : x.shape(0));};

BOOST_PYTHON_MODULE(silly)
{
  bp::def("f", f, ( bp::arg("x"), bp::arg("y")=nullptr ) );
}

>> make
g++ -I /usr/include/python2.7 -o dummy.so  -fPIC -shared dummy.cpp  -lboost_python -lboost_numpy -lpython2.7
g++ -I /usr/include/python2.7 -o stupid.so -fPIC -shared stupid.cpp -lboost_python -lboost_numpy -lpython2.7
g++ -I /usr/include/python2.7 -o silly.so  -fPIC -shared silly.cpp  -lboost_python -lboost_numpy -lpython2.7

>> python
Python 2.7.17 (default, Oct 19 2019, 23:36:22) 
[GCC 9.2.1 20191008] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dummy; dummy.f(1)
2
>>> import numpy; import stupid; stupid.f(numpy.array([1, 2, 3])) 
Segmentation fault

UPDATE

Tried to add Py_Initialize(); np::initialize(); in f without success

>> more stupid.cpp silly.cpp 
::::::::::::::
stupid.cpp
::::::::::::::
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
namespace bp = boost::python;
namespace np = boost::python::numpy;

int f(np::ndarray x, double y=1.0) {
  Py_Initialize();
  np::initialize();
  return (int)(x.shape(0)+y);
};

BOOST_PYTHON_MODULE(stupid)
{
  bp::def("f", f, ( bp::arg("x"), bp::arg("y")=1.0 ) );
}
::::::::::::::
silly.cpp
::::::::::::::
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
namespace bp = boost::python;
namespace np = boost::python::numpy;

int f(np::ndarray x, np::ndarray * y=nullptr) {
  Py_Initialize();
  np::initialize();
  return (int)(y ? x.shape(0)+y->shape(0) : x.shape(0));
};

BOOST_PYTHON_MODULE(silly)
{
  bp::def("f", f, ( bp::arg("x"), bp::arg("y")=nullptr ) );
}

>> make
g++ -I /usr/include/python2.7 -o stupid.so -fPIC -shared stupid.cpp -lboost_python -lboost_numpy -lpython2.7
g++ -I /usr/include/python2.7 -o silly.so  -fPIC -shared silly.cpp  -lboost_python -lboost_numpy -lpython2.7

>> python
Python 2.7.17 (default, Oct 19 2019, 23:36:22) 
[GCC 9.2.1 20191008] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy; import stupid; stupid.f(numpy.array([1, 2, 3]))
Segmentation fault

UPDATE

OK, got it to work with calls in BOOST_PYTHON_MODULE. Still KO with default argument (silly example).

>> more stupid.cpp silly.cpp 
::::::::::::::
stupid.cpp
::::::::::::::
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
namespace bp = boost::python;
namespace np = boost::python::numpy;

int f(np::ndarray x, double y=1.0) {
  return (int)(x.shape(0)+y);
};

BOOST_PYTHON_MODULE(stupid)
{
  Py_Initialize();
  np::initialize();
  bp::def("f", f, ( bp::arg("x"), bp::arg("y")=1.0 ) );
}
::::::::::::::
silly.cpp
::::::::::::::
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
namespace bp = boost::python;
namespace np = boost::python::numpy;

int f(np::ndarray x, np::ndarray * y=nullptr) {
  return (int)(y ? x.shape(0)+y->shape(0) : x.shape(0));
};

BOOST_PYTHON_MODULE(silly)
{
  Py_Initialize();
  np::initialize();
  bp::def("f", f, ( bp::arg("x"), bp::arg("y")=nullptr ) );
}

>> make
g++ -I /usr/include/python2.7 -o stupid.so -fPIC -shared stupid.cpp -lboost_python -lboost_numpy -lpython2.7
g++ -I /usr/include/python2.7 -o silly.so  -fPIC -shared silly.cpp  -lboost_python -lboost_numpy -lpython2.7

 >> python
Python 2.7.17 (default, Oct 19 2019, 23:36:22) 
[GCC 9.2.1 20191008] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy; import stupid; stupid.f(numpy.array([1, 2, 3]))
4
>>> import numpy; import silly; silly.f(numpy.array([1, 2, 3]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: No to_python (by-value) converter found for C++ type: decltype(nullptr)

WORKAROUND

>> more silly.cpp 
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
namespace bp = boost::python;
namespace np = boost::python::numpy;

int f(np::ndarray x, bp::object y) {
  np::ndarray yy = np::array(bp::list());
  if (!y.is_none()) yy = bp::extract<np::ndarray>(y);
  return (int)(x.shape(0)+yy.shape(0));
};

BOOST_PYTHON_MODULE(silly)
{
  Py_Initialize();
  np::initialize();
  bp::def("f", f, ( bp::arg("x"), bp::arg("y") ) );
}

>> make
g++ -I /usr/include/python2.7 -o silly.so  -fPIC -shared silly.cpp  -lboost_python -lboost_numpy -lpython2.7

>> python
Python 2.7.17 (default, Oct 19 2019, 23:36:22) 
[GCC 9.2.1 20191008] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy; import silly; silly.f(numpy.array([1, 2, 3]), numpy.array([1, 2]))
5
>>> import numpy; import silly; silly.f(numpy.array([1, 2, 3]), None)
3

Solution

  • To be able to use numpy first initialise the Python runtime and the numpy module:

    Py_Initialize();
    np::initialize();
    

    Failure to call these results in segmentation errors.

    =======================================================================

    For the silly.cpp the workaround is not needed. Here is the implementation:

    #include <boost/python.hpp>
    #include <boost/python/numpy.hpp>
    namespace bp = boost::python;
    namespace np = boost::python::numpy;
    
    int f(np::ndarray x, np::ndarray y = np::array(bp::list()) ) {
      return (int)(x.shape(0)+y.shape(0));
    };
    
    BOOST_PYTHON_MODULE(silly)
    {
      Py_Initialize();
      np::initialize();
      bp::def("f", f, ( bp::arg("x"), bp::arg("y") = np::array(bp::list()) ) );
    }
    

    And the test results:

    >>> import numpy, silly; silly.f(numpy.array([1, 2, 3]), numpy.array([1, 2]))
    5
    >>> import numpy, silly; silly.f(numpy.array([1, 2, 3]))
    3