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 struct py_converter<nda::basic_array_view<T, R, Layout, Algebra>> {
26
27 using view_t = nda::basic_array_view<T, R, Layout, Algebra>;
28 using U = std::decay_t<T>;
29 static_assert(has_npy_type<U>, "Logical Error");
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 return true;
82 }
83
84 // --------- PY -> C --------
85
86 static view_t py2c(PyObject *obj) {
87 auto p = make_numpy_proxy(obj);
88 EXPECTS(p.extents.size() >= R);
89 EXPECTS(p.element_type == npy_type<T> or p.extents.size() > R);
90 EXPECTS((numpy_check_layout<R, Layout>(obj)));
91
92 std::array<long, R> extents, strides;
93 for (int u = 0; u < R; ++u) {
94 extents[u] = p.extents[u];
95 strides[u] = p.strides[u] / sizeof(T);
96 }
97 return view_t{{extents, strides}, static_cast<T *>(p.data)};
98 }
99 };
100
101 // -----------------------------------
102 // array
103 // -----------------------------------
104 template <typename T, int R, char Algebra>
105 struct py_converter<nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>> {
106
107 // T can be a npy type c2py::has_npy_type<T> == true or NOT (then we need to convert using c2py)
108 static_assert(not std::is_same_v<T, pyref>, "Not implemented");
109 static_assert(not std::is_same_v<T, PyObject *>, "Not implemented");
110
111 using array_t = nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>;
112 using view_t = nda::basic_array_view<T, R, nda::C_layout, Algebra>;
113 using converter_T = py_converter<std::decay_t<T>>;
114 using converter_view_T = py_converter<view_t>;
115 using converter_view_pyobject = py_converter<nda::basic_array_view<PyObject *, R, nda::C_layout, Algebra>>;
116
117 // ------------ tp_name ---------------
118
119 static std::string tp_name() {
120 std::ostringstream out;
121 out << "ndarray[" << python_typename<T>() << ", " << R << "]";
122 return out.str();
123 }
124
125 // --------- C -> PY --------
126
127 template <typename A>
128 static PyObject *c2py(A &&src) {
129 static_assert(std::is_same_v<std::decay_t<A>, nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>>,
130 "Logic Error in array c2py conversion");
131 auto p = nda::python::make_numpy_proxy_from_array_or_view(std::forward<A>(src));
132 return p.to_python();
133 }
134
135 // --------- PY -> C is possible --------
136
137 static PyObject *make_numpy(PyObject *obj) {
138 return PyArray_FromAny(obj, PyArray_DescrFromType(npy_type<T>), R, R, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ENSURECOPY, NULL); // new ref
139 }
140
141 static bool is_convertible(PyObject *obj, bool raise_python_exception) {
142 // if obj is not an numpy, we try to make a numpy with the proper type
143 if (not PyArray_Check(obj) or (PyArray_Check(obj) and has_npy_type<T> and (PyArray_TYPE((PyArrayObject *)(obj)) != npy_type<T>))) {
144 c2py::pyref numpy_obj = make_numpy(obj);
145 if (PyErr_Occurred()) {
146 if (!raise_python_exception) PyErr_Clear();
147 return false;
148 }
149 return is_convertible(numpy_obj, raise_python_exception);
150 }
151
152 if constexpr (has_npy_type<T>) {
153 // in this case, we convert to a view and then copy to the array, so the condition is the same
154 return converter_view_T::is_convertible(obj, raise_python_exception, false /*allow_lower_rank*/, false /*require_c_order*/);
155 } else {
156 // T is a type requiring conversion.
157 // 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...)
158 bool res = converter_view_pyobject::is_convertible(obj, raise_python_exception, true /*allow_lower_rank*/);
159 if (not res) return false;
160
161 // Check if all elements are convertible
162 // CAUTION: numpy array might be of higher rank!
163 // We Extract the first R extents from the numpy proxy
164 // and use __getitem__ to check convertibility of
165 // each such element
166 auto p = make_numpy_proxy(obj);
167 std::array<long, R> shape;
168 for (int i = 0; i < R; ++i) shape[i] = p.extents[i];
169 auto l = [obj](auto... i) -> bool {
170 pyref subobj = PyObject_GetItem(obj, pyref::make_tuple(PyLong_FromLong(i)...));
171 return converter_T::is_convertible(subobj, false);
172 };
173 res = sum(nda::array_adapter{shape, l});
174
175 if (!res and raise_python_exception) PyErr_SetString(PyExc_TypeError, "Cannot convert to array. One element can not be converted to C++.");
176 return res;
177 }
178 }
179
180 // --------- PY -> C --------
181
182 static array_t py2c(PyObject *obj) {
183
184 // if obj is not an numpy, we make a numpy and rerun
185 if (not PyArray_Check(obj) or (PyArray_Check(obj) and has_npy_type<T> and (PyArray_TYPE((PyArrayObject *)(obj)) != npy_type<T>))) {
186
187 c2py::pyref numpy_obj = make_numpy(obj);
188 EXPECTS(not PyErr_Occurred());
189 return py2c(numpy_obj);
190 }
191
192 if constexpr (has_npy_type<T>) {
193 if (not numpy_check_layout<R, nda::C_layout>(obj)) {
194 c2py::pyref obj_c_order = make_numpy(obj);
195 return array_t{converter_view_T::py2c(obj_c_order)};
196 }
197 return converter_view_T::py2c(obj);
198 } else {
199 auto p = make_numpy_proxy(obj);
200 std::array<long, R> shape;
201 for (int i = 0; i < R; ++i) shape[i] = p.extents[i];
202 auto l = [obj](auto... i) {
203 pyref subobj = PyObject_GetItem(obj, pyref::make_tuple(PyLong_FromLong(i)...));
204 return converter_T::py2c(subobj);
205 };
206 array_t res = nda::array_adapter{shape, l};
207 if (PyErr_Occurred()) PyErr_Print();
208 return res;
209 }
210 }
211 };
212
213 template <typename T, int R, char Algebra>
214 struct py_converter<nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>> const &> {
215 using array_t = nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>;
216 static PyObject *c2py(array_t const &a) { return cxx2py(nda::make_const_view(a)); }
217 };
218
219 template <typename T, int R, char Algebra>
220 struct py_converter<nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>> &> {
221 using array_t = nda::basic_array<T, R, nda::C_layout, Algebra, nda::heap<>>;
222 using view_t = nda::basic_array_view<T, R, nda::C_layout, Algebra>;
223 static PyObject *c2py(array_t &a) { return cxx2py(view_t(a)); }
224 };
225
226 template <typename E>
228 struct py_converter<E> {
229 static PyObject *c2py(E const &ex) { return cxx2py(nda::make_regular(ex)); }
230 };
231
232} // 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
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.