numpy_bind.hh 9.3 KB
Newer Older
1 2
// graph-tool -- a general graph modification and manipulation thingy
//
Tiago Peixoto's avatar
Tiago Peixoto committed
3
// Copyright (C) 2006-2019 Tiago de Paula Peixoto <tiago@skewed.de>
4 5 6 7 8 9 10 11 12 13 14 15 16 17
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

18 19 20
#ifndef NUMPY_BIND_HH
#define NUMPY_BIND_HH

21 22
#include <vector>
#include <boost/python.hpp>
23 24 25 26 27 28

// numpy unique symbol weirdness
#define PY_ARRAY_UNIQUE_SYMBOL graph_tool_numpy
#ifndef NUMPY_EXPORT
#define NO_IMPORT_ARRAY
#endif
29
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
30
#include "numpy/arrayobject.h"
31 32 33 34 35 36 37 38 39 40 41 42

#include <boost/array.hpp>
#define BOOST_DISABLE_ASSERTS
#include <boost/multi_array.hpp>

#include <boost/type_traits.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/map.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/mpl/for_each.hpp>

43 44
#include "demangle.hh"

Tiago Peixoto's avatar
Tiago Peixoto committed
45 46
typedef boost::mpl::map<
    boost::mpl::pair<bool, boost::mpl::int_<NPY_BOOL> >,
47
    boost::mpl::pair<char, boost::mpl::int_<NPY_STRING> >,
48 49
    boost::mpl::pair<int8_t, boost::mpl::int_<NPY_INT8> >,
    boost::mpl::pair<uint8_t, boost::mpl::int_<NPY_UINT8> >,
Tiago Peixoto's avatar
Tiago Peixoto committed
50
    boost::mpl::pair<int16_t, boost::mpl::int_<NPY_INT16> >,
51
    boost::mpl::pair<uint16_t, boost::mpl::int_<NPY_UINT16> >,
Tiago Peixoto's avatar
Tiago Peixoto committed
52
    boost::mpl::pair<int32_t, boost::mpl::int_<NPY_INT32> >,
53
    boost::mpl::pair<uint32_t, boost::mpl::int_<NPY_UINT32> >,
Tiago Peixoto's avatar
Tiago Peixoto committed
54 55
    boost::mpl::pair<int64_t, boost::mpl::int_<NPY_INT64> >,
    boost::mpl::pair<uint64_t, boost::mpl::int_<NPY_UINT64> >,
56
    boost::mpl::pair<unsigned long, boost::mpl::int_<NPY_ULONG> >,
57
    boost::mpl::pair<float, boost::mpl::int_<NPY_FLOAT> >,
Tiago Peixoto's avatar
Tiago Peixoto committed
58
    boost::mpl::pair<double, boost::mpl::int_<NPY_DOUBLE> >,
59 60 61 62
    boost::mpl::pair<long double, boost::mpl::int_<NPY_LONGDOUBLE> >,
    boost::mpl::pair<std::complex<float>, boost::mpl::int_<NPY_CFLOAT> >,
    boost::mpl::pair<std::complex<double>, boost::mpl::int_<NPY_CDOUBLE> >,
    boost::mpl::pair<std::complex<long double>, boost::mpl::int_<NPY_CLONGDOUBLE> > 
63 64 65
    > numpy_types;

template <class ValueType>
66
boost::python::object wrap_vector_owned(const std::vector<ValueType>& vec)
67
{
68
    size_t val_type = boost::mpl::at<numpy_types,ValueType>::type::value;
69 70
    npy_intp size[1];
    size[0] = vec.size();
71 72 73
    PyArrayObject* ndarray = (PyArrayObject*) PyArray_SimpleNew(1, size, val_type);
    if (!vec.empty())
        memcpy(PyArray_DATA(ndarray), vec.data(), vec.size() * sizeof(ValueType));
74 75
    PyArray_ENABLEFLAGS(ndarray, NPY_ARRAY_ALIGNED | NPY_ARRAY_C_CONTIGUOUS |
                        NPY_ARRAY_OWNDATA | NPY_ARRAY_WRITEABLE);
Tiago Peixoto's avatar
Tiago Peixoto committed
76 77
    boost::python::handle<> x((PyObject*) ndarray);
    boost::python::object o(x);
78 79 80 81
    return o;
}

template <class ValueType>
82
boost::python::object wrap_vector_not_owned(std::vector<ValueType>& vec)
83
{
84
    PyArrayObject* ndarray;
85
    size_t val_type = boost::mpl::at<numpy_types,ValueType>::type::value;
86
    npy_intp size = vec.size();
87
    if (vec.empty())
88
        return wrap_vector_owned(vec); // return an _owned_ array of size one.
89 90
    ndarray = (PyArrayObject*) PyArray_SimpleNewFromData(1, &size, val_type,
                                                         vec.data());
91 92
    PyArray_ENABLEFLAGS(ndarray,NPY_ARRAY_ALIGNED | NPY_ARRAY_C_CONTIGUOUS |
                        NPY_ARRAY_WRITEABLE);
Tiago Peixoto's avatar
Tiago Peixoto committed
93 94
    boost::python::handle<> x((PyObject*) ndarray);
    boost::python::object o(x);
95 96 97
    return o;
}

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
template <class ValueType, size_t Dim>
boost::python::object wrap_vector_owned(const std::vector<std::array<ValueType, Dim>>& vec)
{
    size_t n = vec.size() * Dim;
    size_t val_type = boost::mpl::at<numpy_types,ValueType>::type::value;
    PyArrayObject* ndarray;
    if (n == 0)
    {
        npy_intp size[1];
        size[0] = vec.size();
        ndarray = (PyArrayObject*) PyArray_SimpleNew(1, size, val_type);
    }
    else
    {
        npy_intp shape[2] = {int(vec.size()), int(Dim)};
113 114 115
        ndarray = (PyArrayObject*) PyArray_SimpleNew(Dim, shape,
                                                     val_type);
        memcpy(PyArray_DATA(ndarray), vec.data(), n * sizeof(ValueType));
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    }
    PyArray_ENABLEFLAGS(ndarray, NPY_ARRAY_ALIGNED | NPY_ARRAY_F_CONTIGUOUS |
                        NPY_ARRAY_OWNDATA | NPY_ARRAY_WRITEABLE);
    boost::python::handle<> x((PyObject*) ndarray);
    boost::python::object o(x);
    return o;
}

template <class ValueType, size_t Dim>
boost::python::object wrap_vector_not_owned(const std::vector<std::array<ValueType, Dim>>& vec)
{
    size_t val_type = boost::mpl::at<numpy_types,ValueType>::type::value;
    if (vec.empty())
        return wrap_vector_owned(vec); // return an _owned_ array of size one.
    npy_intp shape[2] = {int(vec.size()), int(Dim)};
    PyArrayObject* ndarray =
        (PyArrayObject*) PyArray_SimpleNewFromData(Dim, shape, val_type,
                                                   vec.data());
    PyArray_ENABLEFLAGS(ndarray, NPY_ARRAY_ALIGNED | NPY_ARRAY_F_CONTIGUOUS |
                        NPY_ARRAY_WRITEABLE);
    boost::python::handle<> x((PyObject*) ndarray);
    boost::python::object o(x);
    return o;
}
140

141
template <class ValueType, size_t Dim>
142 143
boost::python::object
wrap_multi_array_owned(const boost::multi_array<ValueType,Dim>& array)
144
{
145
    size_t val_type = boost::mpl::at<numpy_types,ValueType>::type::value;
146
    npy_intp shape[Dim];
147
    for (size_t i = 0; i < Dim; ++i)
148 149
        shape[i] = array.shape()[i];
    PyArrayObject* ndarray =
150 151
        (PyArrayObject*) PyArray_SimpleNew(Dim, shape, val_type);
    memcpy(PyArray_DATA(ndarray), array.data(), array.num_elements() * sizeof(ValueType));
152 153
    PyArray_ENABLEFLAGS(ndarray, NPY_ARRAY_ALIGNED | NPY_ARRAY_C_CONTIGUOUS |
                        NPY_ARRAY_OWNDATA | NPY_ARRAY_WRITEABLE);
Tiago Peixoto's avatar
Tiago Peixoto committed
154 155
    boost::python::handle<> x((PyObject*) ndarray);
    boost::python::object o(x);
156 157 158
    return o;
}

159
template <class ValueType, size_t Dim>
160 161
boost::python::object
wrap_multi_array_not_owned(boost::multi_array<ValueType,Dim>& array)
162
{
163
    size_t val_type = boost::mpl::at<numpy_types,ValueType>::type::value;
164 165 166
    PyArrayObject* ndarray =
        (PyArrayObject*) PyArray_SimpleNewFromData(Dim, array.shape(), val_type,
                                                   array.origin());
167 168
    PyArray_ENABLEFLAGS(ndarray, NPY_ARRAY_ALIGNED | NPY_ARRAY_C_CONTIGUOUS |
                        NPY_ARRAY_WRITEABLE);
Tiago Peixoto's avatar
Tiago Peixoto committed
169 170
    boost::python::handle<> x((PyObject*) ndarray);
    boost::python::object o(x);
171 172
    return o;
}
173

174 175
// get multi_array_ref from numpy ndarrays

176
template <class ValueType, size_t dim>
177 178
class numpy_multi_array:
    public boost::multi_array_ref<ValueType,dim>
179 180 181 182 183 184 185
{
    typedef boost::multi_array_ref<ValueType,dim> base_t;
public:
    template <class ExtentList, class StrideList>
    explicit numpy_multi_array(typename base_t::element* data,
                               const ExtentList& sizes,
                               const StrideList& strides)
186
        : base_t(data, sizes)
187
    {
188
        for (size_t i = 0; i < dim; ++i)
189 190 191 192
            base_t::stride_list_[i] = strides[i];
    }
};

193
struct InvalidNumpyConversion:
194 195
    public std::exception
{
196
    std::string _error;
197
public:
198
    InvalidNumpyConversion(const std::string& error) :_error(error) {}
199
    ~InvalidNumpyConversion() throw () {}
200 201 202 203
    const char * what () const throw () {return _error.c_str();}
};

template <class ValueType, size_t dim>
Tiago Peixoto's avatar
Tiago Peixoto committed
204
boost::multi_array_ref<ValueType,dim> get_array(boost::python::object points)
205 206 207
{
    PyArrayObject* pa = (PyArrayObject*) points.ptr();

208
    if (PyArray_NDIM(pa) != dim)
209
        throw InvalidNumpyConversion("invalid array dimension!");
210

Tiago Peixoto's avatar
Tiago Peixoto committed
211
    if (boost::mpl::at<numpy_types,ValueType>::type::value != PyArray_DESCR(pa)->type_num)
212
    {
213
        boost::python::handle<> x(boost::python::borrowed((PyObject*) PyArray_DESCR(pa)->typeobj));
Tiago Peixoto's avatar
Tiago Peixoto committed
214
        boost::python::object dtype(x);
215 216 217
        std::string type_name = boost::python::extract<std::string>(boost::python::str(dtype));
        std::string error = "invalid array value type: " + type_name;
        error += " (id: " + boost::lexical_cast<std::string>(PyArray_DESCR(pa)->type_num) + ")";
218
        error += ", wanted: " + name_demangle(typeid(ValueType).name());
219
        error += " (id: " + boost::lexical_cast<std::string>(boost::mpl::at<numpy_types,ValueType>::type::value) + ")";
220
        throw InvalidNumpyConversion(error);
221
    }
222

223
    std::vector<size_t> shape(dim);
224
    for (size_t i = 0; i < dim; ++i)
225
        shape[i] = PyArray_DIMS(pa)[i];
226

227
    std::vector<size_t> stride(dim);
228 229 230 231 232 233 234 235 236 237 238 239
    for (size_t i = 0; i < dim; ++i)
        stride[i] = PyArray_STRIDE(pa, i) / sizeof(ValueType);

    return numpy_multi_array<ValueType,dim>((ValueType *) PyArray_DATA(pa),
                                            shape, stride);

    // boost::storage_order_type store;
    // if ((PyArray_FLAGS(pa) ^ NPY_ARRAY_C_CONTIGUOUS) != 0)
    //     store = boost::c_storage_order();
    // if (PyArray_FLAGS(pa) ^ NPY_ARRAY_F_CONTIGUOUS) != 0)
    //     store = boost::fortran_storage_order();

240 241
}

242
#endif // NUMPY_BIND_HH