TRIQS/nda 1.3.0
Multi-dimensional array library for C++
Loading...
Searching...
No Matches
converters.hpp
1// Copyright (c) 2024--present, The Simons Foundation
2// This file is part of TRIQS/nda and is licensed under the Apache License, Version 2.0.
3// SPDX-License-Identifier: Apache-2.0
4// See LICENSE in the root of this distribution for details.
5
6#pragma once
7
8#include <nda/nda.hpp>
9#include "./make_numpy_proxy_from_array.hpp"
10
11namespace c2py {
12
13 // -----------------------------------
14 // array_view
15 // -----------------------------------
16
17 template <int R, typename Layout>
18 bool numpy_check_layout(PyObject *obj) {
19 EXPECTS(PyArray_Check(obj));
20 PyArrayObject *arr = (PyArrayObject *)(obj);
21 return Layout::template mapping<R>::is_stride_order_valid(PyArray_DIMS(arr), PyArray_STRIDES(arr));
22 }
23
24 template <typename T, int R, typename Layout, char Algebra>
25 requires(has_npy_type<std::decay_t<T>>)
26 struct py_converter<nda::basic_array_view<T, R, Layout, Algebra>> {
27
28 using view_t = nda::basic_array_view<T, R, Layout, Algebra>;
29 using U = std::decay_t<T>;
30 static_assert(not std::is_same_v<U, pyref>, "Not implemented"); // would require to take care of the incref...
31 // However, it works for PyObject *
32
33 // ------------ tp_name ---------------
34
35 static std::string tp_name() {
36 std::ostringstream out;
37 out << "ndarray[" << python_typename<T>() << ", " << R << "]";
38 return out.str();
39 }
40
41 // --------- C -> PY --------
42
43 static PyObject *c2py(view_t v) {
44 auto p = nda::python::make_numpy_proxy_from_array_or_view(v);
45 return p.to_python();
46 }
47
48 // --------- PY -> C is possible --------
49
50 static bool is_convertible(PyObject *obj, bool raise_python_exception, bool allow_lower_rank = false, bool require_c_order = true) {
51 // has_npy_type<T> is true (static_assert at top)
52 // check the rank and type. First protects the rest
53 if (not PyArray_Check(obj)) {
54 if (raise_python_exception) PyErr_SetString(PyExc_TypeError, "Cannot convert to array_view : Python object is not a Numpy array");
55 return false;
56 }
57 PyArrayObject *arr = (PyArrayObject *)(obj);
58
59 auto r = PyArray_NDIM(arr);
60 if (allow_lower_rank ? r < R : r != R) {
61 if (raise_python_exception)
62 PyErr_SetString(
63 PyExc_TypeError,
64 ("Cannot convert to array_view : Rank is not correct. Expected " + std::to_string(R) + "\n Got " + std::to_string(PyArray_NDIM(arr)))
65 .c_str());
66 return false;
67 }
68
69 if (not allow_lower_rank && r == R) {
70 if (has_npy_type<U> && (PyArray_TYPE(arr) != npy_type<U>)) {
71 if (raise_python_exception) PyErr_SetString(PyExc_TypeError, "Cannot convert to array_view : Type mismatch");
72 return false;
73 }
74 }
75
76 if (require_c_order and not numpy_check_layout<R, Layout>(obj)) {
77 if (raise_python_exception) PyErr_SetString(PyExc_TypeError, "Cannot convert to array_view : Numpy array is not in C order");
78 return false;
79 }
80
81 if constexpr (not std::is_const_v<T>) {
82 if (not PyArray_ISWRITEABLE(arr)) {
83 if (raise_python_exception) PyErr_SetString(PyExc_TypeError, "Cannot convert to mutable array_view : Numpy array is read-only");
84 return false;
85 }
86 }
87
88 return true;
89 }
90
91 // --------- PY -> C --------
92
93 static view_t py2c(PyObject *obj) {
94 auto p = make_numpy_proxy(obj);
95 EXPECTS(p.extents.size() >= R);
96 EXPECTS(p.element_type == npy_type<T> or p.extents.size() > R);
97 EXPECTS((numpy_check_layout<R, Layout>(obj)));
98
99 std::array<long, R> extents, strides;
100 for (int u = 0; u < R; ++u) {
101 extents[u] = p.extents[u];
102 strides[u] = p.strides[u] / sizeof(T);
103 }
104 return view_t{{extents, strides}, static_cast<T *>(p.data)};
105 }
106 };
107
108 // -----------------------------------
109 // array
110 // -----------------------------------
111 template <typename T, int R, char Algebra>
112 struct py_converter<nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>> {
113
114 // T can be a npy type c2py::has_npy_type<T> == true or NOT (then we need to convert using c2py)
115 static_assert(not std::is_same_v<T, pyref>, "Not implemented");
116 static_assert(not std::is_same_v<T, PyObject *>, "Not implemented");
117
118 using array_t = nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>;
119 using view_t = nda::basic_array_view<T, R, nda::C_stride_layout, Algebra>;
120 using converter_T = py_converter<std::decay_t<T>>;
121 using converter_view_T = py_converter<view_t>;
122 using converter_view_pyobject = py_converter<nda::basic_array_view<PyObject *, R, nda::C_stride_layout, Algebra>>;
123
124 // ------------ tp_name ---------------
125
126 static std::string tp_name() {
127 std::ostringstream out;
128 out << "ndarray[" << python_typename<T>() << ", " << R << "]";
129 return out.str();
130 }
131
132 // --------- C -> PY --------
133
134 template <typename A>
135 static PyObject *c2py(A &&src) {
136 static_assert(std::is_same_v<std::decay_t<A>, nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>>,
137 "Logic Error in array c2py conversion");
138 auto p = nda::python::make_numpy_proxy_from_array_or_view(std::forward<A>(src));
139 return p.to_python();
140 }
141
142 // --------- PY -> C is possible --------
143
144 static PyObject *make_numpy(PyObject *obj) {
145 return PyArray_FromAny(obj, PyArray_DescrFromType(npy_type<T>), R, R, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ENSURECOPY, NULL); // new ref
146 }
147
148 static bool is_convertible(PyObject *obj, bool raise_python_exception) {
149 // if obj is not an numpy, we try to make a numpy with the proper type
150 if (not PyArray_Check(obj) or (PyArray_Check(obj) and has_npy_type<T> and (PyArray_TYPE((PyArrayObject *)(obj)) != npy_type<T>))) {
151 c2py::pyref numpy_obj = make_numpy(obj);
152 if (PyErr_Occurred()) {
153 if (!raise_python_exception) PyErr_Clear();
154 return false;
155 }
156 return is_convertible(numpy_obj, raise_python_exception);
157 }
158
159 if constexpr (has_npy_type<T>) {
160 // in this case, we convert to a view and then copy to the array, so the condition is the same
161 // use a const view converter to skip the writability check.
162 using const_view_t = nda::basic_array_view<const T, R, nda::C_stride_layout, Algebra>;
163 return py_converter<const_view_t>::is_convertible(obj, raise_python_exception, false /*allow_lower_rank*/, false /*require_c_order*/);
164 } else {
165 // T is a type requiring conversion.
166 // First I see whether I can convert it to a array of PyObject* ( i.e. it is an array of object and it has the proper rank...)
167 bool res = converter_view_pyobject::is_convertible(obj, raise_python_exception, true /*allow_lower_rank*/);
168 if (not res) return false;
169
170 // Check if all elements are convertible
171 // CAUTION: numpy array might be of higher rank!
172 // We Extract the first R extents from the numpy proxy
173 // and use __getitem__ to check convertibility of
174 // each such element
175 auto p = make_numpy_proxy(obj);
176 std::array<long, R> shape;
177 for (int i = 0; i < R; ++i) shape[i] = p.extents[i];
178 auto l = [obj](auto... i) -> bool {
179 pyref subobj = PyObject_GetItem(obj, pyref::make_tuple(PyLong_FromLong(i)...));
180 return converter_T::is_convertible(subobj, false);
181 };
182 long n_elements = 1;
183 for (int i = 0; i < R; ++i) n_elements *= shape[i];
184 long n_convertible = sum(nda::array_adapter{shape, l});
185
186 if (n_convertible != n_elements) {
187 if (raise_python_exception) PyErr_SetString(PyExc_TypeError, "Cannot convert to array. One element can not be converted to C++.");
188 return false;
189 }
190 return true;
191 }
192 }
193
194 // --------- PY -> C --------
195
196 static array_t py2c(PyObject *obj) {
197
198 // if obj is not an numpy, we make a numpy and rerun
199 if (not PyArray_Check(obj) or (PyArray_Check(obj) and has_npy_type<T> and (PyArray_TYPE((PyArrayObject *)(obj)) != npy_type<T>))) {
200
201 c2py::pyref numpy_obj = make_numpy(obj);
202 EXPECTS(not PyErr_Occurred());
203 return py2c(numpy_obj);
204 }
205
206 if constexpr (has_npy_type<T>) {
207 if (not numpy_check_layout<R, nda::C_layout>(obj) or not PyArray_ISWRITEABLE((PyArrayObject *)(obj))) {
208 c2py::pyref obj_copy = make_numpy(obj);
209 return array_t{converter_view_T::py2c(obj_copy)};
210 }
211 return converter_view_T::py2c(obj);
212 } else {
213 auto p = make_numpy_proxy(obj);
214 std::array<long, R> shape;
215 for (int i = 0; i < R; ++i) shape[i] = p.extents[i];
216 auto l = [obj](auto... i) {
217 pyref subobj = PyObject_GetItem(obj, pyref::make_tuple(PyLong_FromLong(i)...));
218 return converter_T::py2c(subobj);
219 };
220 array_t res = nda::array_adapter{shape, l};
221 if (PyErr_Occurred()) PyErr_Print();
222 return res;
223 }
224 }
225 };
226
227 template <typename T, int R, char Algebra>
228 requires(has_npy_type<std::decay_t<T>>)
229 struct py_converter<nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>> const &> {
230 using array_t = nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>;
231 static PyObject *c2py(array_t const &a, [[maybe_unused]] PyObject *guardian) {
232 return cxx2py(nda::make_const_view(a));
233 } // guardian is not used, as nda has its own shared handle which takes care of the ownership.
234 };
235
236 template <typename T, int R, char Algebra>
237 requires(has_npy_type<std::decay_t<T>>)
238 struct py_converter<nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>> &> {
239 using array_t = nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>;
240 using view_t = nda::basic_array_view<T, R, nda::C_layout, Algebra>;
241 static PyObject *c2py(array_t &a, [[maybe_unused]] PyObject *guardian) {
242 return cxx2py(view_t(a));
243 // guardian is not used, as nda has its own shared handle which takes care of the ownership.return arr;
244 }
245 };
246
247 template <typename E>
249 struct py_converter<E> {
250 static PyObject *c2py(E const &ex) { return cxx2py(nda::make_regular(ex)); }
251 };
252
253} // namespace c2py
auto sum(A const &a)
Sum all the elements of an nda::Array object.
decltype(auto) make_regular(A &&a)
Make a given object regular.
auto make_const_view(basic_array< T, R, LP, A, CP > const &a)
Make an nda::basic_array_view with a const value type from a given nda::basic_array.
constexpr bool is_expression
Constexpr variable that is true if type A is a lazy expression type.
Definition traits.hpp:135
heap_basic< mem::mallocator< AdrSp > > heap
Alias template of the nda::heap_basic policy using an nda::mem::mallocator.
Definition policies.hpp:52
std::string to_string(std::array< T, R > const &a)
Get a string representation of a std::array.
Definition array.hpp:52
Includes all relevant headers for the core nda library.