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 #include <memory>
10 #include <string>
11 #include <typeindex>
12 #include <typeinfo>
13 
14 #include <pybind11/pybind11.h>
15 
16 #include <pagmo/algorithm.hpp>
17 #include <pagmo/island.hpp>
18 #include <pagmo/population.hpp>
19 
20 #include "common_utils.hpp"
21 #include "handle_thread_py_exception.hpp"
22 #include "island.hpp"
23 #include "s11n_wrappers.hpp"
24 
25 namespace pagmo
26 {
27 
28 namespace detail
29 {
30 
31 namespace py = pybind11;
32 
isl_inner(const py::object & o)33 isl_inner<py::object>::isl_inner(const py::object &o)
34 {
35     // NOTE: unlike pygmo.problem/algorithm, we don't have to enforce
36     // that o is not a pygmo.island, because pygmo.island does not satisfy
37     // the UDI interface and thus check_mandatory_method() below will fail
38     // if o is a pygmo.island.
39     //
40     // Check that o is an instance of a class, and not a type.
41     check_not_type(o, "island");
42     check_mandatory_method(o, "run_evolve", "island");
43     m_value = pygmo::deepcopy(o);
44 }
45 
clone() const46 std::unique_ptr<isl_inner_base> isl_inner<py::object>::clone() const
47 {
48     // This will make a deep copy using the ctor above.
49     return std::make_unique<isl_inner>(m_value);
50 }
51 
run_evolve(island & isl) const52 void isl_inner<py::object>::run_evolve(island &isl) const
53 {
54     // NOTE: run_evolve() is called from a separate thread in pagmo::island, need to construct a GTE before
55     // doing anything with the interpreter (including the throws in the checks below).
56     pygmo::gil_thread_ensurer gte;
57 
58     // NOTE: every time we call into the Python interpreter from a separate thread, we need to
59     // handle Python exceptions in a special way.
60     std::string isl_name;
61     try {
62         isl_name = get_name();
63     } catch (const py::error_already_set &eas) {
64         pygmo::handle_thread_py_exception("Could not fetch the name of a pythonic island. The error is:\n", eas);
65     }
66 
67     try {
68         auto ret = m_value.attr("run_evolve")(isl.get_algorithm(), isl.get_population());
69 
70         py::tuple ret_tup;
71         try {
72             ret_tup = py::cast<py::tuple>(ret);
73         } catch (const py::cast_error &) {
74             pygmo::py_throw(PyExc_TypeError, ("the 'run_evolve()' method of a user-defined island "
75                                               "must return a tuple, but it returned an object of type '"
76                                               + pygmo::str(pygmo::type(ret)) + "' instead")
77                                                  .c_str());
78         }
79         if (py::len(ret_tup) != 2) {
80             pygmo::py_throw(PyExc_ValueError,
81                             ("the tuple returned by the 'run_evolve()' method of a user-defined island "
82                              "must have 2 elements, but instead it has "
83                              + std::to_string(py::len(ret_tup)) + " element(s)")
84                                 .c_str());
85         }
86 
87         algorithm ret_algo;
88         try {
89             ret_algo = py::cast<algorithm>(ret_tup[0]);
90         } catch (const py::cast_error &) {
91             pygmo::py_throw(PyExc_TypeError,
92                             ("the first value returned by the 'run_evolve()' method of a user-defined island "
93                              "must be an algorithm, but an object of type '"
94                              + pygmo::str(pygmo::type(ret_tup[0])) + "' was returned instead")
95                                 .c_str());
96         }
97 
98         population ret_pop;
99         try {
100             ret_pop = py::cast<population>(ret_tup[1]);
101         } catch (const py::cast_error &) {
102             pygmo::py_throw(PyExc_TypeError,
103                             ("the second value returned by the 'run_evolve()' method of a user-defined island "
104                              "must be a population, but an object of type '"
105                              + pygmo::str(pygmo::type(ret_tup[1])) + "' was returned instead")
106                                 .c_str());
107         }
108 
109         isl.set_algorithm(ret_algo);
110         isl.set_population(ret_pop);
111     } catch (const py::error_already_set &eas) {
112         pygmo::handle_thread_py_exception(
113             "The asynchronous evolution of a pythonic island of type '" + isl_name + "' raised an error:\n", eas);
114     }
115 }
116 
get_name() const117 std::string isl_inner<py::object>::get_name() const
118 {
119     return getter_wrapper<std::string>(m_value, "get_name", pygmo::str(pygmo::type(m_value)));
120 }
121 
get_extra_info() const122 std::string isl_inner<py::object>::get_extra_info() const
123 {
124     return getter_wrapper<std::string>(m_value, "get_extra_info", std::string{});
125 }
126 
get_type_index() const127 std::type_index isl_inner<py::object>::get_type_index() const
128 {
129     return std::type_index(typeid(py::object));
130 }
131 
get_ptr() const132 const void *isl_inner<py::object>::get_ptr() const
133 {
134     return &m_value;
135 }
136 
get_ptr()137 void *isl_inner<py::object>::get_ptr()
138 {
139     return &m_value;
140 }
141 
142 template <typename Archive>
save(Archive & ar,unsigned) const143 void isl_inner<py::object>::save(Archive &ar, unsigned) const
144 {
145     pygmo::inner_class_save<isl_inner_base>(ar, *this);
146 }
147 
148 template <typename Archive>
load(Archive & ar,unsigned)149 void isl_inner<py::object>::load(Archive &ar, unsigned)
150 {
151     pygmo::inner_class_load<isl_inner_base>(ar, *this);
152 }
153 
154 } // namespace detail
155 
156 } // namespace pagmo
157 
158 PAGMO_S11N_ISLAND_IMPLEMENT(pybind11::object)
159