1 /* Copyright 2017-2021 PaGMO development team
2
3 This file is part of the PaGMO library.
4
5 The PaGMO library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7
8 * the GNU Lesser General Public License as published by the Free
9 Software Foundation; either version 3 of the License, or (at your
10 option) any later version.
11
12 or
13
14 * the GNU General Public License as published by the Free Software
15 Foundation; either version 3 of the License, or (at your option) any
16 later version.
17
18 or both in parallel, as here.
19
20 The PaGMO library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 for more details.
24
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the PaGMO library. If not,
27 see https://www.gnu.org/licenses/. */
28
29 #define BOOST_TEST_MODULE fork_island_test
30 #define BOOST_TEST_DYN_LINK
31 #include <boost/test/unit_test.hpp>
32
33 #include <chrono>
34 #include <csignal>
35 #include <exception>
36 #include <stdexcept>
37 #include <thread>
38 #include <utility>
39 #include <vector>
40
41 #include <sys/types.h>
42
43 #include <boost/algorithm/string/predicate.hpp>
44
45 #include <pagmo/algorithm.hpp>
46 #include <pagmo/algorithms/compass_search.hpp>
47 #include <pagmo/algorithms/de.hpp>
48 #include <pagmo/island.hpp>
49 #include <pagmo/islands/fork_island.hpp>
50 #include <pagmo/population.hpp>
51 #include <pagmo/problem.hpp>
52 #include <pagmo/problems/rosenbrock.hpp>
53 #include <pagmo/s11n.hpp>
54 #include <pagmo/types.hpp>
55
56 using namespace pagmo;
57
58 // A silly problem that, after max fitness evals, just waits.
59 struct godot1 {
godot1godot160 explicit godot1(unsigned max) : m_max(max), m_counter(0) {}
godot1godot161 godot1() : godot1(0) {}
fitnessgodot162 vector_double fitness(const vector_double &) const
63 {
64 if (m_max == m_counter++) {
65 std::this_thread::sleep_for(std::chrono::seconds(3600));
66 }
67 return {.5};
68 }
get_boundsgodot169 std::pair<vector_double, vector_double> get_bounds() const
70 {
71 return {{0.}, {1.}};
72 }
73 template <typename Archive>
serializegodot174 void serialize(Archive &ar, unsigned)
75 {
76 detail::archive(ar, m_max, m_counter);
77 }
78 unsigned m_max;
79 mutable unsigned m_counter;
80 };
81
82 PAGMO_S11N_PROBLEM_EXPORT(godot1)
83
BOOST_AUTO_TEST_CASE(fork_island_basic)84 BOOST_AUTO_TEST_CASE(fork_island_basic)
85 {
86 {
87 fork_island fi_0;
88 BOOST_CHECK(fi_0.get_child_pid() == pid_t(0));
89 fork_island fi_1(fi_0), fi_2(std::move(fi_0));
90 BOOST_CHECK(fi_1.get_child_pid() == pid_t(0));
91 BOOST_CHECK(fi_2.get_child_pid() == pid_t(0));
92 BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "No active child"));
93 BOOST_CHECK(boost::contains(fi_1.get_extra_info(), "No active child"));
94 BOOST_CHECK(boost::contains(fi_2.get_extra_info(), "No active child"));
95 BOOST_CHECK_EQUAL(fi_0.get_name(), "Fork island");
96 }
97 // NOTE: on recent OSX versions, the fork() behaviour changed and trying
98 // to do error handling via exceptions in the forked() process now does
99 // not seem to work any more. Let's disable the error handling tests
100 // for now, perhaps we can investigate this further in the future.
101 #if !defined(__APPLE__)
102 {
103 // Test: try to kill a running island.
104 island fi_0(fork_island{}, de{200}, godot1{20}, 20);
105 BOOST_CHECK(fi_0.extract<fork_island>() != nullptr);
106 BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "No active child"));
107 fi_0.evolve();
108 // Busy wait until the child is running.
109 pid_t child_pid;
110 while (!(child_pid = fi_0.extract<fork_island>()->get_child_pid())) {
111 }
112 BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "Child PID:"));
113 // """
114 // Kill the boy and let the man be born.
115 // """
116 kill(child_pid, SIGTERM);
117 // Check that killing the child raised an error in the parent process.
118 BOOST_CHECK_THROW(fi_0.wait_check(), std::exception);
119 BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "No active child"));
120 }
121 {
122 // Test: try to generate an error in the evolution.
123 // NOTE: de wants more than 1 individual in the pop.
124 island fi_0(fork_island{}, de{1}, rosenbrock{}, 1);
125 BOOST_CHECK(fi_0.extract<fork_island>() != nullptr);
126 BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "No active child"));
127 fi_0.evolve();
128 BOOST_CHECK_EXCEPTION(fi_0.wait_check(), std::runtime_error, [](const std::runtime_error &re) {
129 return boost::contains(re.what(), "needs at least 5 individuals in the population");
130 });
131 }
132 #endif
133 }
134
135 // Check that the population actually evolves.
BOOST_AUTO_TEST_CASE(fork_island_evolve)136 BOOST_AUTO_TEST_CASE(fork_island_evolve)
137 {
138 island fi_0(fork_island{}, compass_search{100}, rosenbrock{}, 1, 0);
139 const auto old_cf = fi_0.get_population().champion_f();
140 fi_0.evolve();
141 fi_0.wait_check();
142 const auto new_cf = fi_0.get_population().champion_f();
143 BOOST_CHECK(new_cf[0] < old_cf[0]);
144 }
145
146 // An algorithm that changes its state at every evolve() call.
147 struct stateful_algo {
evolvestateful_algo148 population evolve(const population &pop) const
149 {
150 ++n_evolve;
151 return pop;
152 }
153 template <typename Archive>
serializestateful_algo154 void serialize(Archive &ar, unsigned)
155 {
156 ar &n_evolve;
157 }
158 mutable int n_evolve = 0;
159 };
160
161 PAGMO_S11N_ALGORITHM_EXPORT(stateful_algo)
162
163 // Check that the state of the algorithm is preserved.
BOOST_AUTO_TEST_CASE(fork_island_stateful_algo)164 BOOST_AUTO_TEST_CASE(fork_island_stateful_algo)
165 {
166 island fi_0(fork_island{}, stateful_algo{}, rosenbrock{}, 1, 0);
167 BOOST_CHECK(fi_0.get_algorithm().extract<stateful_algo>()->n_evolve == 0);
168 fi_0.evolve();
169 fi_0.wait_check();
170 BOOST_CHECK(fi_0.get_algorithm().extract<stateful_algo>()->n_evolve == 1);
171 }
172
173 struct recursive_algo1 {
evolverecursive_algo1174 population evolve(const population &pop) const
175 {
176 island fi_0(fork_island{}, compass_search{100}, pop);
177 fi_0.evolve();
178 fi_0.wait_check();
179 return fi_0.get_population();
180 }
181 template <typename Archive>
serializerecursive_algo1182 void serialize(Archive &, unsigned)
183 {
184 }
185 };
186
187 struct recursive_algo2 {
evolverecursive_algo2188 population evolve(const population &pop) const
189 {
190 island fi_0(fork_island{}, de{1}, pop);
191 fi_0.evolve();
192 fi_0.wait_check();
193 return fi_0.get_population();
194 }
195 template <typename Archive>
serializerecursive_algo2196 void serialize(Archive &, unsigned)
197 {
198 }
199 };
200
201 PAGMO_S11N_ALGORITHM_EXPORT(recursive_algo1)
PAGMO_S11N_ALGORITHM_EXPORT(recursive_algo2)202 PAGMO_S11N_ALGORITHM_EXPORT(recursive_algo2)
203
204 // Try to call fork() inside fork().
205 BOOST_AUTO_TEST_CASE(fork_island_recurse)
206 {
207 {
208 island fi_0(fork_island{}, recursive_algo1{}, rosenbrock{}, 1, 0);
209 const auto old_cf = fi_0.get_population().champion_f();
210 fi_0.evolve();
211 fi_0.wait_check();
212 const auto new_cf = fi_0.get_population().champion_f();
213 BOOST_CHECK(new_cf[0] < old_cf[0]);
214 }
215 #if !defined(__APPLE__)
216 {
217 // Try also error transport.
218 island fi_0(fork_island{}, recursive_algo2{}, rosenbrock{}, 1, 0);
219 fi_0.evolve();
220 BOOST_CHECK_EXCEPTION(fi_0.wait_check(), std::runtime_error, [](const std::runtime_error &re) {
221 return boost::contains(re.what(), "needs at least 5 individuals in the population");
222 });
223 }
224 #endif
225 }
226
227 // Run a moderate amount of fork islands in parallel.
BOOST_AUTO_TEST_CASE(fork_island_torture)228 BOOST_AUTO_TEST_CASE(fork_island_torture)
229 {
230 std::vector<island> visl(100u, island(fork_island{}, compass_search{100}, rosenbrock{100}, 50, 0));
231 for (auto &isl : visl) {
232 isl.evolve();
233 }
234 for (auto &isl : visl) {
235 BOOST_CHECK_NO_THROW(isl.wait_check());
236 }
237 }
238