Nektar++
Python/BasicUtils/SharedArray.hpp
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: SharedArray.cpp
4//
5// For more information, please see: http://www.nektar.info
6//
7// The MIT License
8//
9// Copyright (c) 2006 Scientific Computing and Imaging Institute,
10// University of Utah (USA) and Department of Aeronautics, Imperial
11// College London (UK).
12//
13// Permission is hereby granted, free of charge, to any person obtaining a
14// copy of this software and associated documentation files (the "Software"),
15// to deal in the Software without restriction, including without limitation
16// the rights to use, copy, modify, merge, publish, distribute, sublicense,
17// and/or sell copies of the Software, and to permit persons to whom the
18// Software is furnished to do so, subject to the following conditions:
19//
20// The above copyright notice and this permission notice shall be included
21// in all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29// DEALINGS IN THE SOFTWARE.
30//
31// Description: Python wrapper for ShareArray.
32//
33///////////////////////////////////////////////////////////////////////////////
34
35#ifndef NEKTAR_LIBUTILITIES_PYTHON_BASICUTILS_SHAREDARRAY_HPP
36#define NEKTAR_LIBUTILITIES_PYTHON_BASICUTILS_SHAREDARRAY_HPP
37
39
41
42#include <type_traits>
43
44using namespace Nektar;
45using namespace Nektar::LibUtilities;
46
47/// Template type to determine whether @tparam T is a std::shared_ptr.
48template <typename T> struct is_shared_ptr : std::false_type
49{
50};
51
52template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type
53{
54};
55
56template <typename T>
57struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type
58{
59};
60
61/// Template utility to determine whether @tparam T is a Nektar::Array<OneD, .>.
62template <typename T> struct is_nekarray_oned : std::false_type
63{
64};
65
66template <typename T> struct is_nekarray_oned<Array<OneD, T>> : std::true_type
67{
68};
69
70template <typename T>
71struct is_nekarray_oned<const Array<OneD, T>> : std::true_type
72{
73};
74
75#if PY_MAJOR_VERSION == 2
76template <typename T> void CapsuleDestructor(void *ptr)
77{
78 Array<OneD, T> *tmp = (Array<OneD, T> *)ptr;
79 delete tmp;
80}
81#else
82template <typename T> void CapsuleDestructor(PyObject *ptr)
83{
84 Array<OneD, T> *tmp = (Array<OneD, T> *)PyCapsule_GetPointer(ptr, nullptr);
85 delete tmp;
86}
87#endif
88
89/**
90 * @brief Convert for Array<OneD, T> to Python list of objects for numeric types
91 * T.
92 */
93template <typename T> struct OneDArrayToPython
94{
95 static PyObject *convert(Array<OneD, T> const &arr)
96 {
97 return convert_impl(arr);
98 }
99
100 template <typename U = T,
101 std::enable_if_t<std::is_arithmetic<U>::value> * = nullptr>
102 static PyObject *convert_impl(Array<OneD, U> const &arr)
103 {
104 // Create a Python capsule to hold a pointer that contains a lightweight
105 // copy of arr. Uhat way we guarantee Python will still have access to
106 // the memory allocated inside arr even if arr is deallocated in C++.
107#if PY_MAJOR_VERSION == 2
108 py::object capsule(py::handle<>(PyCObject_FromVoidPtr(
109 new Array<OneD, U>(arr), CapsuleDestructor<U>)));
110#else
111 py::object capsule(py::handle<>(
112 PyCapsule_New(new Array<OneD, U>(arr), nullptr,
113 (PyCapsule_Destructor)&CapsuleDestructor<U>)));
114#endif
115 PyObject *tmp =
116 py::incref(np::from_data(arr.data(), np::dtype::get_builtin<U>(),
117 py::make_tuple(arr.size()),
118 py::make_tuple(sizeof(U)), capsule)
119 .ptr());
120
121 return tmp;
122 }
123
124 /**
125 * @brief Converter function. This copies entries into the Python list and
126 * relies on internal boost converter being available for the shared_ptr
127 * type.
128 */
129 template <typename U = T,
130 std::enable_if_t<is_shared_ptr<U>::value ||
131 is_nekarray_oned<U>::value> * = nullptr>
132 static PyObject *convert_impl(Array<OneD, U> const &arr)
133 {
134 py::list tmp;
135 for (std::size_t i = 0; i < arr.size(); ++i)
136 {
137 tmp.append(arr[i]);
138 }
139
140 // Increment counter to avoid de-allocation of `tmp`.
141 return py::incref(tmp.ptr());
142 }
143};
144
145/**
146 * @brief Converter for Python to Nektar::Array<OneD, T>.
147 */
148template <typename T> struct PythonToOneDArray
149{
150 /// Default constructor.
152 {
153 py::converter::registry::push_back(&convertible, &construct,
154 py::type_id<Array<OneD, T>>());
155 }
156
157 /**
158 * @brief Determine whether the given @p objPtr is convertible to
159 * convertible to an Array type or not.
160 *
161 * This is a top-level function: different data types are handled separately
162 * in the ::try_convertible functions.
163 */
164 static void *convertible(PyObject *objPtr)
165 {
166 try
167 {
168 py::object obj((py::handle<>(py::borrowed(objPtr))));
169 return try_convertible(obj) ? objPtr : nullptr;
170 }
171 catch (boost::python::error_already_set &)
172 {
173 py::handle_exception();
174 PyErr_Clear();
175 return nullptr;
176 }
177 }
178
179 template <typename U = T,
180 std::enable_if_t<std::is_arithmetic<U>::value> * = nullptr>
181 static bool try_convertible(py::object &obj)
182 {
183 np::ndarray array = py::extract<np::ndarray>(obj);
184
185 // Check data types match
186 np::dtype dtype =
187 np::dtype::get_builtin<typename std::remove_const<U>::type>();
188 if (dtype != array.get_dtype())
189 {
190 return false;
191 }
192
193 // Check shape is 1D
194 if (array.get_nd() != 1)
195 {
196 return false;
197 }
198
199 return true;
200 }
201
202 template <typename U = T,
203 std::enable_if_t<is_shared_ptr<U>::value ||
204 is_nekarray_oned<U>::value> * = nullptr>
205 static bool try_convertible(py::object &obj)
206 {
207 py::extract<py::list> list_conv(obj);
208
209 if (!list_conv.check())
210 {
211 return false;
212 }
213
214 py::list l = list_conv();
215 const std::size_t nItems = py::len(l);
216
217 // We'll need to construct a temporary vector to hold each item in the
218 // list.
219 for (std::size_t i = 0; i < nItems; ++i)
220 {
221 py::extract<T> item_conv(l[i]);
222
223 if (!py::extract<T>(l[i]).check())
224 {
225 return false;
226 }
227 }
228
229 return true;
230 }
231
232 static void decrement(void *objPtr)
233 {
234 if (!Py_IsInitialized())
235 {
236 // In deinitialisation phase, reference counters are not terribly
237 // robust; decremementing counters here can lead to segfaults during
238 // program exit in some cases.
239 return;
240 }
241
242 // Otherwise decrement reference counter.
243 py::decref((PyObject *)objPtr);
244 }
245
246 static void construct(PyObject *objPtr,
247 py::converter::rvalue_from_python_stage1_data *data)
248 {
249 construct_impl(objPtr, data);
250 }
251
252 template <typename U = T,
253 std::enable_if_t<std::is_arithmetic<U>::value> * = nullptr>
254 static void construct_impl(
255 PyObject *objPtr, py::converter::rvalue_from_python_stage1_data *data)
256 {
257 // This has to be a _borrowed_ reference, otherwise at the end of this
258 // scope it seems memory gets deallocated
259 py::object obj(py::handle<>(py::borrowed(objPtr)));
260 np::ndarray array = py::extract<np::ndarray>(obj);
261
262 // If this array came from C++, extract the C++ array from PyCObject or
263 // PyCapsule and ensure that we set up the C++ array to have a reference
264 // to that object, so that it can be decremented as appropriate.
265 py::object base = array.get_base();
266 Array<OneD, T> *ptr = nullptr;
267
268#if PY_MAJOR_VERSION == 2
269 if (PyCObject_Check(base.ptr()))
270 {
271 ptr = reinterpret_cast<Array<OneD, T> *>(
272 PyCObject_AsVoidPtr(base.ptr()));
273 }
274#else
275 if (PyCapsule_CheckExact(base.ptr()))
276 {
277 ptr = reinterpret_cast<Array<OneD, T> *>(
278 PyCapsule_GetPointer(base.ptr(), nullptr));
279 }
280#endif
281
282 void *storage =
283 ((py::converter::rvalue_from_python_storage<Array<OneD, T>> *)data)
284 ->storage.bytes;
285 data->convertible = storage;
286
287 using nonconst_t = typename std::remove_const<T>::type;
288 new (storage)
289 Array<OneD, T>(array.shape(0), (nonconst_t *)array.get_data(),
290 (void *)objPtr, &decrement);
291 py::incref(objPtr);
292 }
293
294 template <typename U = T,
295 std::enable_if_t<is_shared_ptr<U>::value ||
296 is_nekarray_oned<U>::value> * = nullptr>
297 static void construct_impl(
298 PyObject *objPtr, py::converter::rvalue_from_python_stage1_data *data)
299 {
300 using nonconst_t = typename std::remove_const<T>::type;
301
302 py::object obj(py::handle<>(py::borrowed(objPtr)));
303 py::list l = py::extract<py::list>(obj);
304
305 const std::size_t nItems = py::len(l);
306
307 // Allocate some storage for the Array.
308 void *storage =
309 ((py::converter::rvalue_from_python_storage<Array<OneD, nonconst_t>>
310 *)data)
311 ->storage.bytes;
313 new (storage) Array<OneD, nonconst_t>(nItems);
314 data->convertible = storage;
315
316 // Fill the Array.
317 for (std::size_t i = 0; i < nItems; ++i)
318 {
319 (*tmp)[i] = py::extract<nonconst_t>(l[i]);
320 }
321 }
322};
323
324/**
325 * @brief Convenience function to export C++-to-Python and Python-to-C++
326 * converters for the requested type @tparam T.
327 */
328template <typename T> void export_SharedArray()
329{
330 py::to_python_converter<Array<OneD, T>, OneDArrayToPython<T>>();
332}
333
334#endif
void CapsuleDestructor(PyObject *ptr)
void export_SharedArray()
Convenience function to export C++-to-Python and Python-to-C++ converters for the requested type.
STL namespace.
Convert for Array<OneD, T> to Python list of objects for numeric types T.
static PyObject * convert_impl(Array< OneD, U > const &arr)
static PyObject * convert(Array< OneD, T > const &arr)
Converter for Python to Nektar::Array<OneD, T>.
PythonToOneDArray()
Default constructor.
static void construct_impl(PyObject *objPtr, py::converter::rvalue_from_python_stage1_data *data)
static bool try_convertible(py::object &obj)
static void construct(PyObject *objPtr, py::converter::rvalue_from_python_stage1_data *data)
static void decrement(void *objPtr)
static void * convertible(PyObject *objPtr)
Determine whether the given objPtr is convertible to convertible to an Array type or not.
Template utility to determine whether.
Template type to determine whether.