Skip to content

Commit

Permalink
Convert std::vector and std::array of non-basic types to NPY arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
Wentzell committed Jul 16, 2020
1 parent 3e4667a commit c9932ee
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 75 deletions.
135 changes: 104 additions & 31 deletions c++/cpp2py/converters/std_array.hpp
Original file line number Diff line number Diff line change
@@ -1,54 +1,127 @@
#pragma once
#include <array>
#include <numpy/arrayobject.h>

#include "../numpy_proxy.hpp"
#include "../py_converter.hpp"
#include "./vector.hpp"

namespace cpp2py {

template <typename T, size_t R> struct py_converter<std::array<T, R>> {
// --------------------------------------
template <typename T, size_t R> numpy_proxy make_numpy_proxy_from_heap_array(std::array<T, R> *arr_heap) {

static PyObject *c2py(std::array<T, R> const &v) {
PyObject *list = PyList_New(0);
for (auto const &x : v) {
pyref y = py_converter<T>::c2py(x);
if (y.is_null() or (PyList_Append(list, y) == -1)) {
Py_DECREF(list);
return NULL;
} // error
}
return list;
auto delete_pycapsule = [](PyObject *capsule) {
auto *ptr = static_cast<std::array<T, R> *>(PyCapsule_GetPointer(capsule, "guard"));
delete ptr;
};
PyObject *capsule = PyCapsule_New(arr_heap, "guard", delete_pycapsule);

return numpy_proxy{1, // rank
npy_type<std::decay_t<T>>,
(void *)arr_heap->data(),
std::is_const_v<T>,
std::vector<long>{long(R)}, // extents
std::vector<long>{sizeof(T)}, // strides
capsule};
};

template <typename T, size_t R> numpy_proxy make_numpy_proxy_from_array(std::array<T, R> const &arr) {

if constexpr (has_npy_type<T>) {
auto *arr_heap = new std::array<T, R>{arr};
return make_numpy_proxy_from_heap_array(arr_heap);
} else {
auto *arr_heap = new std::array<pyref, R>{};
std::transform(begin(arr), end(arr), begin(*arr_heap), [](T const &x) { return py_converter<std::decay_t<T>>::c2py(x); });
return make_numpy_proxy_from_heap_array(arr_heap);
}
}

// Make a new array from numpy view
template <typename T, size_t R> std::array<T, R> make_array_from_numpy_proxy(numpy_proxy const &p) {
EXPECTS(p.extents.size() == 1);
EXPECTS(p.extents[0] == R);

std::array<T, R> arr;

if (p.element_type == npy_type<pyref>) {
auto *data = static_cast<pyref *>(p.data);
std::transform(data, data + R, begin(arr), [](PyObject *o) { return py_converter<std::decay_t<T>>::py2c(o); });
} else {
EXPECTS(p.strides == std::vector<long>{sizeof(T)});
T *data = static_cast<T *>(p.data);
std::copy(data, data + R, begin(arr));
}

return arr;
}

// --------------------------------------

template <typename T, size_t R> struct py_converter<std::array<T, R>> {

static PyObject *c2py(std::array<T, R> const &a) { return make_numpy_proxy_from_array(a).to_python(); }

// --------------------------------------

static bool is_convertible(PyObject *ob, bool raise_exception) {
if (!PySequence_Check(ob)) goto _false;
{
pyref seq = PySequence_Fast(ob, "expected a sequence");
int len = PySequence_Size(ob);
if (len != R) {
if (raise_exception) {
auto s = std::string{"Convertion to std::array<T, R> failed : the length of the sequence ( = "} + std::to_string(len)
+ " does not match R = " + std::to_string(R);
PyErr_SetString(PyExc_TypeError, s.c_str());
}
_import_array();

// Special case: 1-d ndarray of builtin type
if (PyArray_Check(ob)) {
PyArrayObject *arr = (PyArrayObject *)(ob);
#ifdef PYTHON_NUMPY_VERSION_LT_17
int rank = arr->nd;
#else
int rank = PyArray_NDIM(arr);
#endif
if (PyArray_TYPE(arr) == npy_type<T> and rank == 1) return true;
}

if (!PySequence_Check(ob)) {
if (raise_exception) { PyErr_SetString(PyExc_TypeError, "Cannot convert a non-sequence to std::array"); }
return false;
}

pyref seq = PySequence_Fast(ob, "expected a sequence");
int len = PySequence_Size(ob);
if (len != R) {
if (raise_exception) {
auto s = std::string{"Convertion to std::array<T, R> failed : the length of the sequence ( = "} + std::to_string(len)
+ " does not match R = " + std::to_string(R);
PyErr_SetString(PyExc_TypeError, s.c_str());
}
return false;
}
for (int i = 0; i < len; i++) {
if (!py_converter<std::decay_t<T>>::is_convertible(PySequence_Fast_GET_ITEM((PyObject *)seq, i), raise_exception)) {
if (PyErr_Occurred()) PyErr_Print();
return false;
}
for (int i = 0; i < len; i++)
if (!py_converter<T>::is_convertible(PySequence_Fast_GET_ITEM((PyObject *)seq, i), raise_exception)) goto _false; // borrowed ref

return true;
}
_false:
if (raise_exception) { PyErr_SetString(PyExc_TypeError, "Cannot convert to std::array"); }
return false;
return true;
}

// --------------------------------------

static std::array<T, R> py2c(PyObject *ob) {
pyref seq = PySequence_Fast(ob, "expected a sequence");
_import_array();

// Special case: 1-d ndarray of builtin type
if (PyArray_Check(ob)) {
PyArrayObject *arr = (PyArrayObject *)(ob);
#ifdef PYTHON_NUMPY_VERSION_LT_17
int rank = arr->nd;
#else
int rank = PyArray_NDIM(arr);
#endif
if (rank == 1) return make_array_from_numpy_proxy<T, R>(make_numpy_proxy(ob));
}

ASSERT(PySequence_Check(ob));
std::array<T, R> res;
for (int i = 0; i < R; i++) res[i] = py_converter<T>::py2c(PySequence_Fast_GET_ITEM((PyObject *)seq, i)); // borrowed ref
pyref seq = PySequence_Fast(ob, "expected a sequence");
for (int i = 0; i < R; i++) res[i] = py_converter<std::decay_t<T>>::py2c(PySequence_Fast_GET_ITEM((PyObject *)seq, i)); // borrowed ref
return res;
}
};
Expand Down
86 changes: 42 additions & 44 deletions c++/cpp2py/converters/vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "../traits.hpp"
#include "../macros.hpp"
#include "../numpy_proxy.hpp"
#include "../py_converter.hpp"

namespace cpp2py {

Expand All @@ -14,35 +15,54 @@ namespace cpp2py {
static_assert(is_instantiation_of_v<std::vector, std::decay_t<V>>, "Logic error");
using value_type = typename std::remove_reference_t<V>::value_type;

auto *vec_heap = new std::vector<value_type>{std::forward<V>(v)};
auto delete_pycapsule = [](PyObject *capsule) {
auto *ptr = static_cast<std::vector<value_type> *>(PyCapsule_GetPointer(capsule, "guard"));
delete ptr;
};
PyObject *capsule = PyCapsule_New(vec_heap, "guard", delete_pycapsule);

return {1, // rank
npy_type<value_type>,
(void *)vec_heap->data(),
std::is_const_v<value_type>,
std::vector<long>{long(vec_heap->size())}, // extents
std::vector<long>{sizeof(value_type)}, // strides
capsule};
if constexpr (has_npy_type<value_type>) {
auto *vec_heap = new std::vector<value_type>{std::forward<V>(v)};
auto delete_pycapsule = [](PyObject *capsule) {
auto *ptr = static_cast<std::vector<value_type> *>(PyCapsule_GetPointer(capsule, "guard"));
delete ptr;
};
PyObject *capsule = PyCapsule_New(vec_heap, "guard", delete_pycapsule);

return {1, // rank
npy_type<value_type>,
(void *)vec_heap->data(),
std::is_const_v<value_type>,
std::vector<long>{long(vec_heap->size())}, // extents
std::vector<long>{sizeof(value_type)}, // strides
capsule};
} else {
std::vector<pyref> vobj(v.size());
std::transform(begin(v), end(v), begin(vobj), [](auto &&x) {
if constexpr (std::is_reference_v<V>) {
return convert_to_python(x);
} else { // vector passed as rvalue
return convert_to_python(std::move(x));
}
});
return make_numpy_proxy_from_vector(std::move(vobj));
}
}

// Make a new vector from numpy view
template <typename T> std::vector<T> make_vector_from_numpy_proxy(numpy_proxy const &p) {
EXPECTS(p.extents.size() == 1);
EXPECTS(p.strides[0] % sizeof(T) == 0);

long size = p.extents[0];
long step = p.strides[0] / sizeof(T);

std::vector<T> v(size);

T *data = static_cast<T *>(p.data);
for(long i = 0; i < size; ++i)
v[i] = *(data + i * step);
if (p.element_type == npy_type<pyref>) {
long step = p.strides[0] / sizeof(pyref);
auto **data = static_cast<PyObject **>(p.data);
for(long i = 0; i < size; ++i)
v[i] = py_converter<std::decay_t<T>>::py2c(data[i * step]);
} else {
EXPECTS(p.strides[0] % sizeof(T) == 0);
long step = p.strides[0] / sizeof(T);
T *data = static_cast<T *>(p.data);
for(long i = 0; i < size; ++i)
v[i] = data[i * step];
}

return v;
}
Expand All @@ -53,26 +73,7 @@ namespace cpp2py {

template <typename V> static PyObject *c2py(V &&v) {
static_assert(is_instantiation_of_v<std::vector, std::decay_t<V>>, "Logic error");
using value_type = typename std::remove_reference_t<V>::value_type;

if constexpr (has_npy_type<value_type>) {
return make_numpy_proxy_from_vector(std::forward<V>(v)).to_python();
} else { // Convert to Python List
PyObject *list = PyList_New(0);
for (auto &x : v) {
pyref y;
if constexpr(std::is_reference_v<V>){
y = py_converter<value_type>::c2py(x);
} else { // Vector passed as rvalue
y = py_converter<value_type>::c2py(std::move(x));
}
if (y.is_null() or (PyList_Append(list, y) == -1)) {
Py_DECREF(list);
return NULL;
} // error
}
return list;
}
return make_numpy_proxy_from_vector(std::forward<V>(v)).to_python();
}

// --------------------------------------
Expand All @@ -99,11 +100,8 @@ namespace cpp2py {
pyref seq = PySequence_Fast(ob, "expected a sequence");
int len = PySequence_Size(ob);
for (int i = 0; i < len; i++) {
if (!py_converter<T>::is_convertible(PySequence_Fast_GET_ITEM((PyObject *)seq, i), false)) { // borrowed ref
if (raise_exception) {
auto err = std::string{"Cannot convert sequence to std::vector due to element at position "} + std::to_string(i);
PyErr_SetString(PyExc_TypeError, err.c_str());
}
if (!py_converter<std::decay_t<T>>::is_convertible(PySequence_Fast_GET_ITEM((PyObject *)seq, i), raise_exception)) { // borrowed ref
if (PyErr_Occurred()) PyErr_Print();
return false;
}
}
Expand Down

0 comments on commit c9932ee

Please sign in to comment.