1 // Copyright 2020, 2021 PaGMO development team
2 //
3 // This file is part of the pygmo library.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla
6 // Public License v. 2.0. If a copy of the MPL was not distributed
7 // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
9 #ifndef PYGMO_COMMON_UTILS_HPP
10 #define PYGMO_COMMON_UTILS_HPP
11 
12 #include <initializer_list>
13 #include <sstream>
14 #include <string>
15 #include <vector>
16 
17 #include <boost/numeric/conversion/cast.hpp>
18 
19 #include <pybind11/numpy.h>
20 #include <pybind11/pybind11.h>
21 
22 #include <pagmo/topology.hpp>
23 #include <pagmo/types.hpp>
24 
25 namespace pygmo
26 {
27 
28 namespace py = pybind11;
29 
30 // Import and return the builtins module.
31 py::object builtins();
32 
33 // Throw a Python exception.
34 [[noreturn]] void py_throw(PyObject *, const char *);
35 
36 // This RAII struct ensures that the Python interpreter
37 // can be used from a thread created from outside Python
38 // (e.g., a pthread/std::thread/etc. created from C/C++).
39 // On creation, it will register the C/C++ thread with
40 // the Python interpreter and lock the GIL. On destruction,
41 // it will release any resource acquired on construction
42 // and unlock the GIL.
43 //
44 // See: https://docs.python.org/3/c-api/init.html
45 struct gil_thread_ensurer {
46     gil_thread_ensurer();
47     ~gil_thread_ensurer();
48 
49     // Make sure we don't accidentally try to copy/move it.
50     gil_thread_ensurer(const gil_thread_ensurer &) = delete;
51     gil_thread_ensurer(gil_thread_ensurer &&) = delete;
52     gil_thread_ensurer &operator=(const gil_thread_ensurer &) = delete;
53     gil_thread_ensurer &operator=(gil_thread_ensurer &&) = delete;
54 
55     PyGILState_STATE m_state;
56 };
57 
58 // This RAII struct will unlock the GIL on construction,
59 // and lock it again on destruction.
60 //
61 // See: https://docs.python.org/3/c-api/init.html
62 struct gil_releaser {
63     gil_releaser();
64     ~gil_releaser();
65 
66     // Make sure we don't accidentally try to copy/move it.
67     gil_releaser(const gil_releaser &) = delete;
68     gil_releaser(gil_releaser &&) = delete;
69     gil_releaser &operator=(const gil_releaser &) = delete;
70     gil_releaser &operator=(gil_releaser &&) = delete;
71 
72     PyThreadState *m_thread_state;
73 };
74 
75 // Check if 'o' has a callable attribute (i.e., a method) named 's'. If so, it will
76 // return the attribute, otherwise it will return None.
77 py::object callable_attribute(const py::object &, const char *);
78 
79 // Check if type is callable.
80 bool callable(const py::object &);
81 
82 // Get the string representation of an object.
83 std::string str(const py::object &);
84 
85 // Get the type of an object.
86 py::object type(const py::object &);
87 
88 // Perform a deep copy of input object o.
89 py::object deepcopy(const py::object &);
90 
91 // repr() via ostream.
92 template <typename T>
ostream_repr(const T & x)93 inline std::string ostream_repr(const T &x)
94 {
95     std::ostringstream oss;
96     oss << x;
97     return oss.str();
98 }
99 
100 // Generic copy wrappers.
101 template <typename T>
generic_copy_wrapper(const T & x)102 inline T generic_copy_wrapper(const T &x)
103 {
104     return x;
105 }
106 
107 template <typename T>
generic_deepcopy_wrapper(const T & x,py::dict)108 inline T generic_deepcopy_wrapper(const T &x, py::dict)
109 {
110     return x;
111 }
112 
113 // Convert an input 1D numpy array into a C++ vector.
114 template <typename Vector, typename T, int ExtraFlags>
ndarr_to_vector(const py::array_t<T,ExtraFlags> & a)115 inline Vector ndarr_to_vector(const py::array_t<T, ExtraFlags> &a)
116 {
117     // Get a one-dimensional view on the array.
118     // If the array is not 1D, this will throw.
119     auto r = a.template unchecked<1>();
120 
121     // Prepare the output vector with the
122     // correct size.
123     Vector retval(boost::numeric_cast<typename Vector::size_type>(r.shape(0)));
124 
125     // Copy the values from a into retval.
126     for (py::ssize_t i = 0; i < r.shape(0); ++i) {
127         retval[static_cast<typename Vector::size_type>(i)] = r(i);
128     }
129 
130     return retval;
131 }
132 
133 // Convert a vector of something into a 1D numpy array.
134 template <typename Array, typename T, typename Allocator>
vector_to_ndarr(const std::vector<T,Allocator> & v)135 inline Array vector_to_ndarr(const std::vector<T, Allocator> &v)
136 {
137     return Array(boost::numeric_cast<py::ssize_t>(v.size()), v.data());
138 }
139 
140 // Convert a numpy array into a sparsity pattern.
141 pagmo::sparsity_pattern ndarr_to_sp(const py::array_t<pagmo::vector_double::size_type> &);
142 
143 // Convert a sparsity pattern into a numpy array.
144 py::array_t<pagmo::vector_double::size_type> sp_to_ndarr(const pagmo::sparsity_pattern &);
145 
146 // Generic extract() wrappers.
147 template <typename C, typename T>
generic_cpp_extract(C & c,const T &)148 inline T *generic_cpp_extract(C &c, const T &)
149 {
150     return c.template extract<T>();
151 }
152 
153 template <typename C>
generic_py_extract(C & c,const py::object & t)154 inline py::object generic_py_extract(C &c, const py::object &t)
155 {
156     auto ptr = c.template extract<py::object>();
157     if (ptr && (t.equal(type(*ptr)) || t.equal(builtins().attr("object")))) {
158         // c contains a user-defined pythonic entity and either:
159         // - the type passed in by the user is the exact type of the user-defined
160         //   entity, or
161         // - the user supplied as t the builtin 'object' type (which we use as a
162         //   wildcard for any Python type).
163         // Let's return the extracted object.
164         return *ptr;
165     }
166 
167     // Either the user-defined entity is not pythonic, or the user specified the
168     // wrong type. Return None.
169     return py::none();
170 }
171 
172 // Convert a vector of vectors into a 2D numpy array.
173 template <typename Array, typename T, typename A1, typename A2>
vvector_to_ndarr(const std::vector<std::vector<T,A2>,A1> & v)174 inline Array vvector_to_ndarr(const std::vector<std::vector<T, A2>, A1> &v)
175 {
176     // The dimensions of the array to be created.
177     const auto nrows = v.size();
178     const auto ncols = nrows ? v[0].size() : 0u;
179 
180     // Create the output array.
181     Array retval({boost::numeric_cast<py::ssize_t>(nrows), boost::numeric_cast<py::ssize_t>(ncols)});
182 
183     // Get a mutable view into it and copy the data from sp.
184     auto r = retval.template mutable_unchecked<2>();
185     for (decltype(v.size()) i = 0; i < nrows; ++i) {
186         if (v[i].size() != ncols) {
187             py_throw(PyExc_ValueError, "cannot convert a vector of vectors to a NumPy 2D array "
188                                        "if the vector instances don't have all the same size");
189         }
190         for (decltype(v[i].size()) j = 0; j < ncols; ++j) {
191             r(static_cast<py::ssize_t>(i), static_cast<py::ssize_t>(j)) = v[i][j];
192         }
193     }
194 
195     return retval;
196 }
197 
198 // Convert an input 2D numpy array into a vector of vectors.
199 template <typename Vector, typename T, int ExtraFlags>
ndarr_to_vvector(const py::array_t<T,ExtraFlags> & a)200 inline Vector ndarr_to_vvector(const py::array_t<T, ExtraFlags> &a)
201 {
202     // Get a 2D view on the array.
203     // If the array is not 2D, this will throw.
204     auto r = a.template unchecked<2>();
205 
206     // Prepare the output vector with the
207     // correct size.
208     Vector retval(boost::numeric_cast<typename Vector::size_type>(r.shape(0)));
209 
210     // Copy the values from a into retval.
211     for (py::ssize_t i = 0; i < r.shape(0); ++i) {
212         retval[static_cast<decltype(retval.size())>(i)].resize(
213             boost::numeric_cast<decltype(retval[i].size())>(r.shape(1)));
214 
215         for (py::ssize_t j = 0; j < r.shape(1); ++j) {
216             retval[static_cast<decltype(retval.size())>(i)][static_cast<decltype(retval[i].size())>(j)] = r(i, j);
217         }
218     }
219 
220     return retval;
221 }
222 
223 // Convert an individuals_group_t into a Python tuple of:
224 // - 1D integral array of IDs,
225 // - 2D float array of dvs,
226 // - 2D float array of fvs.
227 py::tuple inds_to_tuple(const pagmo::individuals_group_t &);
228 
229 // Convert a Python iterable into an individuals_group_t.
230 pagmo::individuals_group_t iterable_to_inds(const py::iterable &);
231 
232 // Convert a Python iterable into a problem bounds.
233 std::pair<pagmo::vector_double, pagmo::vector_double> iterable_to_bounds(const py::iterable &o);
234 
235 // Conversion between BGL and NetworkX.
236 py::object bgl_graph_t_to_networkx(const pagmo::bgl_graph_t &);
237 pagmo::bgl_graph_t networkx_to_bgl_graph_t(const py::object &);
238 
239 } // namespace pygmo
240 
241 #endif
242