Skip to content

Commit a6e12d6

Browse files
committed
Convert vector of basic type to numpy array instead of list
-Port numpy_proxy class from nda
1 parent 06304b4 commit a6e12d6

File tree

4 files changed

+260
-27
lines changed

4 files changed

+260
-27
lines changed

c++/cpp2py/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
add_library(cpp2py signal_handler.cpp exceptions.cpp)
1+
add_library(cpp2py signal_handler.cpp exceptions.cpp numpy_proxy.cpp)
22
add_library(cpp2py::cpp2py ALIAS cpp2py)
33

44
target_compile_options(cpp2py PRIVATE -std=c++14 -fPIC)

c++/cpp2py/converters/vector.hpp

+83-26
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,106 @@
11
#pragma once
2-
//#include <vector>
3-
//#include <numeric>
2+
#include <vector>
3+
#include <numpy/arrayobject.h>
4+
5+
#include "../numpy_proxy.hpp"
46

57
namespace cpp2py {
68

9+
template <typename T>
10+
static void delete_pycapsule(PyObject *capsule) {
11+
auto *ptr = static_cast<std::unique_ptr<T[]> *>(PyCapsule_GetPointer(capsule, "guard"));
12+
delete ptr;
13+
}
14+
15+
// Convert vector to numpy_proxy, WARNING: Deep Copy
16+
template <typename T> numpy_proxy make_numpy_proxy_from_vector(std::vector<T> const &v) {
17+
18+
auto * data_ptr = new std::unique_ptr<T[]>{new T[v.size()]};
19+
std::copy(begin(v), end(v), data_ptr->get());
20+
auto capsule = PyCapsule_New(data_ptr, "guard", &delete_pycapsule<T>);
21+
22+
return {1, // rank
23+
npy_type<std::remove_const_t<T>>,
24+
(void *)data_ptr->get(),
25+
std::is_const_v<T>,
26+
v_t{static_cast<long>(v.size())}, // extents
27+
v_t{sizeof(T)}, // strides
28+
capsule};
29+
}
30+
31+
// Make a new vector from numpy view
32+
template <typename T>
33+
std::vector<T> make_vector_from_numpy_proxy(numpy_proxy const &p) {
34+
//EXPECTS(p.extents.size() == 1);
35+
//EXPECTS(p.strides == v_t{sizeof(T)});
36+
37+
T * data = static_cast<T *>(p.data);
38+
long size = p.extents[0];
39+
40+
std::vector<T> v(size);
41+
std::copy(data, data + size, begin(v));
42+
return v;
43+
}
44+
45+
// --------------------------------------
46+
747
template <typename T> struct py_converter<std::vector<T>> {
8-
9-
// --------------------------------------
10-
11-
static PyObject *c2py(std::vector<T> const &v) {
12-
PyObject *list = PyList_New(0);
13-
for (auto const &x : v) {
14-
pyref y = py_converter<T>::c2py(x);
15-
if (y.is_null() or (PyList_Append(list, y) == -1)) {
16-
Py_DECREF(list);
17-
return NULL;
18-
} // error
48+
49+
static PyObject *c2py(std::vector<T> const &v) {
50+
51+
if constexpr (has_npy_type<T>) {
52+
return make_numpy_proxy_from_vector(v).to_python();
53+
}
54+
else{ // Convert to Python List
55+
PyObject *list = PyList_New(0);
56+
for (auto const &x : v) {
57+
pyref y = py_converter<T>::c2py(x);
58+
if (y.is_null() or (PyList_Append(list, y) == -1)) {
59+
Py_DECREF(list);
60+
return NULL;
61+
} // error
62+
}
63+
return list;
1964
}
20-
return list;
2165
}
2266

23-
// --------------------------------------
67+
// --------------------------------------
2468

25-
static bool is_convertible(PyObject *ob, bool raise_exception) {
26-
if (!PySequence_Check(ob)) goto _false;
27-
{
69+
static bool is_convertible(PyObject *ob, bool raise_exception) {
70+
if (PySequence_Check(ob)) {
2871
pyref seq = PySequence_Fast(ob, "expected a sequence");
2972
int len = PySequence_Size(ob);
3073
for (int i = 0; i < len; i++)
3174
if (!py_converter<T>::is_convertible(PySequence_Fast_GET_ITEM((PyObject *)seq, i), raise_exception)) goto _false; //borrowed ref
3275
return true;
76+
} else if (PyArray_Check(ob)) {
77+
PyArrayObject *arr = (PyArrayObject *)(ob);
78+
if (PyArray_TYPE(arr) != npy_type<T>) goto _false;
79+
#ifdef PYTHON_NUMPY_VERSION_LT_17
80+
int rank = arr->nd;
81+
#else
82+
int rank = PyArray_NDIM(arr);
83+
#endif
84+
if (rank != 1) goto _false;
85+
return true;
3386
}
3487
_false:
3588
if (raise_exception) { PyErr_SetString(PyExc_TypeError, "Cannot convert to std::vector"); }
3689
return false;
3790
}
3891

39-
// --------------------------------------
40-
41-
static std::vector<T> py2c(PyObject *ob) {
42-
pyref seq = PySequence_Fast(ob, "expected a sequence");
43-
std::vector<T> res;
44-
int len = PySequence_Size(ob);
45-
for (int i = 0; i < len; i++) res.push_back(py_converter<T>::py2c(PySequence_Fast_GET_ITEM((PyObject *)seq, i))); //borrowed ref
46-
return res;
92+
// --------------------------------------
93+
94+
static std::vector<T> py2c(PyObject *ob) {
95+
if (PySequence_Check(ob)) {
96+
std::vector<T> res;
97+
pyref seq = PySequence_Fast(ob, "expected a sequence");
98+
int len = PySequence_Size(ob);
99+
for (int i = 0; i < len; i++) res.push_back(py_converter<T>::py2c(PySequence_Fast_GET_ITEM((PyObject *)seq, i))); //borrowed ref
100+
return res;
101+
}
102+
//ASSERT(PyArray_Check(ob));
103+
return make_vector_from_numpy_proxy<T>(make_numpy_proxy(ob));
47104
}
48105
};
49106

c++/cpp2py/numpy_proxy.cpp

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#include "numpy_proxy.hpp"
2+
3+
namespace cpp2py {
4+
5+
// Make a new view_info
6+
PyObject *numpy_proxy::to_python() {
7+
8+
// Apparently we can not get rid of this
9+
_import_array();
10+
11+
#ifdef PYTHON_NUMPY_VERSION_LT_17
12+
int flags = NPY_BEHAVED & ~NPY_OWNDATA;
13+
#else
14+
int flags = NPY_ARRAY_BEHAVED & ~NPY_ARRAY_OWNDATA;
15+
#endif
16+
// make the array read only
17+
if (is_const) flags &= ~NPY_ARRAY_WRITEABLE;
18+
PyObject *result =
19+
PyArray_NewFromDescr(&PyArray_Type, PyArray_DescrFromType(element_type), rank, extents.data(), strides.data(), data, flags, NULL);
20+
if (not result) return nullptr; // the Python error is set
21+
22+
if (!PyArray_Check(result)) {
23+
PyErr_SetString(PyExc_RuntimeError, "The python object is not a numpy array");
24+
return nullptr;
25+
}
26+
27+
PyArrayObject *arr = (PyArrayObject *)(result);
28+
#ifdef PYTHON_NUMPY_VERSION_LT_17
29+
arr->base = base;
30+
assert(arr->flags == (arr->flags & ~NPY_OWNDATA));
31+
#else
32+
int r = PyArray_SetBaseObject(arr, base);
33+
//EXPECTS(r == 0);
34+
//EXPECTS(PyArray_FLAGS(arr) == (PyArray_FLAGS(arr) & ~NPY_ARRAY_OWNDATA));
35+
#endif
36+
base = nullptr; // ref is stolen by the new object
37+
38+
return result;
39+
}
40+
41+
// ----------------------------------------------------------
42+
43+
// Extract a view_info from python
44+
numpy_proxy make_numpy_proxy(PyObject *obj) {
45+
46+
// Apparently we can not get rid of this
47+
_import_array();
48+
49+
if (obj == NULL) return {};
50+
if (not PyArray_Check(obj)) return {};
51+
52+
numpy_proxy result;
53+
54+
// extract strides and lengths
55+
PyArrayObject *arr = (PyArrayObject *)(obj);
56+
57+
#ifdef PYTHON_NUMPY_VERSION_LT_17
58+
result.rank = arr->nd;
59+
#else
60+
result.rank = PyArray_NDIM(arr);
61+
#endif
62+
63+
result.element_type = PyArray_TYPE(arr);
64+
result.extents.resize(result.rank);
65+
result.strides.resize(result.rank);
66+
result.data = PyArray_DATA(arr);
67+
// base is ignored, stays at nullptr
68+
69+
#ifdef PYTHON_NUMPY_VERSION_LT_17
70+
for (long i = 0; i < result.rank; ++i) {
71+
result.extents[i] = size_t(arr->dimensions[i]);
72+
result.strides[i] = std::ptrdiff_t(arr->strides[i]);
73+
}
74+
#else
75+
for (size_t i = 0; i < result.rank; ++i) {
76+
result.extents[i] = size_t(PyArray_DIMS(arr)[i]);
77+
result.strides[i] = std::ptrdiff_t(PyArray_STRIDES(arr)[i]);
78+
}
79+
#endif
80+
81+
//PRINT(result.rank);
82+
//PRINT(result.element_type);
83+
//PRINT(result.data);
84+
85+
return result;
86+
}
87+
88+
// ----------------------------------------------------------
89+
90+
PyObject *make_numpy_copy(PyObject *obj, int rank, long element_type) {
91+
92+
if (obj == nullptr) return nullptr;
93+
94+
// From obj, we ask the numpy library to make a numpy, and of the correct type.
95+
// This handles automatically the cases where :
96+
// - we have list, or list of list/tuple
97+
// - the numpy type is not the one we want.
98+
// - adjust the dimension if needed
99+
// If obj is an array :
100+
// - if Order is same, don't change it
101+
// - else impose it (may provoque a copy).
102+
// if obj is not array :
103+
// - Order = FortranOrder or SameOrder - > Fortran order otherwise C
104+
105+
int flags = 0; //(ForceCast ? NPY_FORCECAST : 0) ;// do NOT force a copy | (make_copy ? NPY_ENSURECOPY : 0);
106+
//if (!(PyArray_Check(obj) ))
107+
//flags |= ( IndexMapType::traversal_order == indexmaps::mem_layout::c_order(rank) ? NPY_C_CONTIGUOUS : NPY_F_CONTIGUOUS); //impose mem order
108+
#ifdef PYTHON_NUMPY_VERSION_LT_17
109+
flags |= (NPY_C_CONTIGUOUS); //impose mem order
110+
flags |= (NPY_ENSURECOPY);
111+
#else
112+
flags |= (NPY_ARRAY_C_CONTIGUOUS); // impose mem order
113+
flags |= (NPY_ARRAY_ENSURECOPY);
114+
#endif
115+
return PyArray_FromAny(obj, PyArray_DescrFromType(element_type), rank, rank, flags, NULL); // new ref
116+
}
117+
118+
} // namespace nda::python

c++/cpp2py/numpy_proxy.hpp

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#pragma once
2+
#include <Python.h>
3+
#include <numpy/arrayobject.h>
4+
5+
#include <vector>
6+
#include <complex>
7+
8+
namespace cpp2py {
9+
10+
using v_t = std::vector<long>;
11+
12+
// the basic information for a numpy array
13+
struct numpy_proxy {
14+
int rank = 0;
15+
long element_type = 0;
16+
void *data = nullptr;
17+
bool is_const = false;
18+
v_t extents, strides;
19+
PyObject *base = nullptr; // The ref. counting guard typically
20+
21+
// Returns a new ref (or NULL if failure) with a new numpy.
22+
// If failure, return null with the Python exception set
23+
PyObject *to_python();
24+
};
25+
26+
// From a numpy, extract the info. Better than a constructor, I want to use the aggregate constructor of the struct also.
27+
numpy_proxy make_numpy_proxy(PyObject *);
28+
29+
// Make a copy in Python with the given rank and element_type
30+
// If failure, return null with the Python exception set
31+
PyObject *make_numpy_copy(PyObject *obj, int rank, long elements_type);
32+
33+
//
34+
template <typename T> constexpr long npy_type = -1;
35+
template <typename T> constexpr bool has_npy_type = (npy_type<T> == -1);
36+
37+
#define NPY_CONVERT(C, P) template <> constexpr long npy_type<C> = P;
38+
NPY_CONVERT(bool, NPY_BOOL)
39+
NPY_CONVERT(char, NPY_STRING)
40+
NPY_CONVERT(signed char, NPY_BYTE)
41+
NPY_CONVERT(unsigned char, NPY_UBYTE)
42+
NPY_CONVERT(short, NPY_SHORT)
43+
NPY_CONVERT(unsigned short, NPY_USHORT)
44+
NPY_CONVERT(int, NPY_INT)
45+
NPY_CONVERT(unsigned int, NPY_UINT)
46+
NPY_CONVERT(long, NPY_LONG)
47+
NPY_CONVERT(unsigned long, NPY_ULONG)
48+
NPY_CONVERT(long long, NPY_LONGLONG)
49+
NPY_CONVERT(unsigned long long, NPY_ULONGLONG)
50+
NPY_CONVERT(float, NPY_FLOAT)
51+
NPY_CONVERT(double, NPY_DOUBLE)
52+
NPY_CONVERT(long double, NPY_LONGDOUBLE)
53+
NPY_CONVERT(std::complex<float>, NPY_CFLOAT)
54+
NPY_CONVERT(std::complex<double>, NPY_CDOUBLE)
55+
NPY_CONVERT(std::complex<long double>, NPY_CLONGDOUBLE)
56+
#undef NPY_CONVERT
57+
58+
} // namespace cpp2py

0 commit comments

Comments
 (0)