RcsPySim
A robot control and simulation library
type_casters.h
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright (c) 2020, Fabio Muratore, Honda Research Institute Europe GmbH, and
3  Technical University of Darmstadt.
4  All rights reserved.
5 
6  Redistribution and use in source and binary forms, with or without
7  modification, are permitted provided that the following conditions are met:
8  1. Redistributions of source code must retain the above copyright
9  notice, this list of conditions and the following disclaimer.
10  2. Redistributions in binary form must reproduce the above copyright
11  notice, this list of conditions and the following disclaimer in the
12  documentation and/or other materials provided with the distribution.
13  3. Neither the name of Fabio Muratore, Honda Research Institute Europe GmbH,
14  or Technical University of Darmstadt, nor the names of its contributors may
15  be used to endorse or promote products derived from this software without
16  specific prior written permission.
17 
18  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  DISCLAIMED. IN NO EVENT SHALL FABIO MURATORE, HONDA RESEARCH INSTITUTE EUROPE GMBH,
22  OR TECHNICAL UNIVERSITY OF DARMSTADT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
26  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  POSSIBILITY OF SUCH DAMAGE.
29 *******************************************************************************/
30 
31 #ifndef SRC_TYPE_CASTERS_H_
32 #define SRC_TYPE_CASTERS_H_
33 
34 #include <pybind11/cast.h>
35 #include <pybind11/numpy.h>
36 
37 #include <Rcs_MatNd.h>
38 
39 /*
40  * pybind11 type caster for RCS MatNd.
41  *
42  * A lot of this is adapted from the Eigen typecasters coming with pybind11.
43  */
44 namespace pybind11
45 {
46 namespace detail
47 {
48 
49 // pybind11 numpy array type with the same shape as the MatNd storage
50 typedef pybind11::array_t<double, pybind11::array::forcecast | pybind11::array::c_style> ArrayMatNd;
51 
52 /*
53  * Populate a MatNd so that it views the given numpy array's storage.
54  */
55 static MatNd MatNd_fromNumpy(ArrayMatNd& nparray)
56 {
57  // load dimensions
58  unsigned int m = 1;
59  unsigned int n = 1;
60  if (nparray.ndim() >= 1) {
61  m = nparray.shape(0);
62  }
63  if (nparray.ndim() == 2) {
64  n = nparray.shape(1);
65  }
66  return MatNd_fromPtr(m, n, const_cast<double*>(nparray.data()));
67 }
68 
69 /*
70  * Create a numpy array from the given MatNd.
71  *
72  * If parent is NULL (ie default initialized handle), the data will be copied
73  * If parent is None (python), an unguarded reference will be held
74  * If parent is any other object, it is assumed that that object own mat.
75  */
76 static handle MatNd_toNumpy(const MatNd* mat, handle parent = handle(), bool writeable = true)
77 {
78  // create with dimensions, optimizing if mat->n = 1
79  ArrayMatNd array;
80  if (mat->n == 1) {
81  // just a vector
82  array = ArrayMatNd(mat->m, mat->ele, parent);
83  }
84  else {
85  // a regular matrix
86  array = ArrayMatNd({mat->m, mat->n}, mat->ele, parent);
87  }
88 
89  // set read-only if needed
90  if (!writeable) {
91  array_proxy(array.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
92  }
93 
94  // release the handle to keep up the reference count!
95  return array.release();
96 }
97 
98 /*
99  * Create a numpy array holding a reference to the given MatNd
100  */
101 static handle MatNd_toNumpyRef(const MatNd* mat, handle parent = none())
102 {
103  return MatNd_toNumpy(mat, parent, false);
104 }
105 
106 /*
107  * Create a numpy array holding a reference to the given MatNd
108  */
109 static handle MatNd_toNumpyRef(MatNd* mat, handle parent = none())
110 {
111  return MatNd_toNumpy(mat, parent, true);
112 }
113 
114 /*
115  * Create a numpy array taking ownership of the given MatNd
116  */
117 static handle MatNd_toNumpyOwn(MatNd* mat)
118 {
119  capsule caps(mat, [](void* ref) { MatNd_destroy((MatNd*) ref); });
120  return MatNd_toNumpy(mat, caps);
121 }
122 
123 
124 template<>
125 struct type_caster<MatNd>
126 {
127 public:
128  /**
129  * Function signature for documentation
130  */
131 #if PYBIND11_VERSION_MINOR >= 3
132  static constexpr auto name = _("MatNd");
133 #else
134  static PYBIND11_DESCR name() { return type_descr(_("MatNd")); }
135 #endif
136 
137 
138 protected:
139  // local value
140  MatNd value;
141 
142  // a reference to the numpy array backing value
143  ArrayMatNd copyOrRef;
144 
145  // a double type caster used to process scalars as 1x1 matrices
146  make_caster<double> scalar_caster;
147 
148  // true if we're None a.k.a. NULL
149  bool none = false;
150 
151 public:
152  /**
153  * Access to the internal loaded value store.
154  */
155  operator MatNd*()
156  {
157  if (none) {
158  return NULL;
159  }
160  return &value;
161  }
162 
163  operator MatNd&()
164  {
165  if (none) {
166  throw value_error("Cannot convert None to a MatNd ref");
167  }
168 
169  return value;
170  }
171 
172  template<typename T_> using cast_op_type = pybind11::detail::cast_op_type<T_>;
173 
174  /**
175  * Conversion part 1 (Python->C++): convert a PyObject into a MatNd
176  * instance or return false upon failure. The second argument
177  * indicates whether implicit conversions should be applied.
178  */
179  bool load(handle src, bool convert)
180  {
181  if (!convert && !isinstance<ArrayMatNd>(src)) {
182  return false;
183  }
184  if (src.is_none()) {
185  // allow none and pass it as NULL
186  none = true;
187  return true;
188  }
189 
190  if (scalar_caster.load(src, convert)) {
191  // scalars should be handled as 1x1 mats. ArrayMatNd::ensure is capable of this,
192  // but would allocate memory which is unnecessary.
193  value.ele = cast_op<double*>(scalar_caster);
194  value.m = 1;
195  value.n = 1;
196  value.size = 1;
197  value.stackMem = true;
198  return true;
199  }
200 
201  // ensure and cast if needed. This copies only if needed
202  copyOrRef = ArrayMatNd::ensure(src);
203 
204  // we can only handle arrays of 2 or less dimensions
205  auto dims = copyOrRef.ndim();
206  if (dims > 2) {
207  return false;
208  }
209 
210  // fill value so that it uses the same memory as copyOrRef
211  value = MatNd_fromNumpy(copyOrRef);
212  return true;
213  }
214 
215  /**
216  * Conversion part 2 (C++ -> Python): convert a MatNd instance into
217  * a Python object. The second and third arguments are used to
218  * indicate the return value policy and parent object (for
219  * ``return_value_policy::reference_internal``).
220  */
221  static handle cast(MatNd* src, return_value_policy policy, handle parent)
222  {
223  switch (policy) {
224  case return_value_policy::take_ownership:
225  case return_value_policy::automatic:
226  // this is not entirely correct, since usually we wouldn't support this for pointers at all.
227  // however, when used in a tuple, we will always get this policy, so we need to deal with it.
228  // and after a fashion, moving a pointer is essentially the same as taking ownership
229  case return_value_policy::move:
230 
231  return MatNd_toNumpyOwn(src);
232  case return_value_policy::copy:
233  return MatNd_toNumpy(src);
234  case return_value_policy::reference:
235  case return_value_policy::automatic_reference:
236  return MatNd_toNumpyRef(src);
237  case return_value_policy::reference_internal:
238  return MatNd_toNumpyRef(src, parent);
239  default:
240  pybind11_fail("Invalid return_value_policy for Rcs MatNd type");
241  };
242  }
243 
244  static handle cast(const MatNd* src, return_value_policy policy, handle parent)
245  {
246  switch (policy) {
247  case return_value_policy::copy:
248  return MatNd_toNumpy(src);
249  case return_value_policy::reference:
250  case return_value_policy::automatic_reference:
251  return MatNd_toNumpyRef(src);
252  case return_value_policy::reference_internal:
253  return MatNd_toNumpyRef(src, parent);
254  default:
255  // can also not take ownership of a const matrix
256  pybind11_fail("Invalid return_value_policy for Rcs MatNd type");
257  };
258  }
259 };
260 
261 }
262 } // namespace pybind11::detail
263 
264 
265 #endif /* SRC_TYPE_CASTERS_H_ */