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 cstrs_test
30 #define BOOST_TEST_DYN_LINK
31 #include <boost/test/unit_test.hpp>
32
33 #include <boost/lexical_cast.hpp>
34 #include <boost/test/tools/floating_point_comparison.hpp>
35 #include <cmath>
36 #include <iostream>
37 #include <string>
38
39 #include <pagmo/algorithm.hpp>
40 #include <pagmo/algorithms/compass_search.hpp>
41 #include <pagmo/algorithms/cstrs_self_adaptive.hpp>
42 #include <pagmo/algorithms/de.hpp>
43 #include <pagmo/io.hpp>
44 #include <pagmo/population.hpp>
45 #include <pagmo/problems/cec2006.hpp>
46 #include <pagmo/problems/hock_schittkowsky_71.hpp>
47 #include <pagmo/problems/inventory.hpp>
48 #include <pagmo/problems/rosenbrock.hpp>
49 #include <pagmo/problems/zdt.hpp>
50 #include <pagmo/s11n.hpp>
51 #include <pagmo/threading.hpp>
52 #include <pagmo/types.hpp>
53
54 using namespace pagmo;
55
BOOST_AUTO_TEST_CASE(penalized_problem_construction)56 BOOST_AUTO_TEST_CASE(penalized_problem_construction)
57 {
58 using namespace detail;
59 auto NP = 20u;
60 problem udp{cec2006{1u}};
61 population pop{udp, NP};
62 penalized_udp udp_p{pop};
63 BOOST_CHECK(udp_p.m_pop_ptr == &pop);
64 BOOST_CHECK_EQUAL(udp_p.m_c_max.size(), udp.get_nc());
65 BOOST_CHECK_EQUAL(udp_p.m_f_hat_down.size(), udp.get_nf());
66 BOOST_CHECK_EQUAL(udp_p.m_f_hat_up.size(), udp.get_nf());
67 BOOST_CHECK_EQUAL(udp_p.m_f_hat_round.size(), udp.get_nf());
68 BOOST_CHECK_EQUAL(udp_p.m_fitness_map.size(), NP);
69 // We also test get bounds here
70 BOOST_CHECK(udp_p.get_bounds() == udp.get_bounds());
71 // And the debug stream operator
72 std::ostringstream text;
73 text << udp_p;
74 BOOST_CHECK(text.str().find("Best (hat down)") != std::string::npos);
75 }
76
BOOST_AUTO_TEST_CASE(penalized_problem_fitness_cache)77 BOOST_AUTO_TEST_CASE(penalized_problem_fitness_cache)
78 {
79 using namespace detail;
80 auto NP = 20u;
81 problem udp{cec2006{1u}};
82 population pop{udp, NP};
83 penalized_udp udp_p{pop};
84 BOOST_CHECK_EQUAL(udp_p.m_pop_ptr->get_problem().get_fevals(), NP);
85 population new_pop{udp_p};
86 // The following lines do not cause fevals increments as the cache is hit.
87 for (decltype(NP) i = 0u; i < NP; ++i) {
88 new_pop.push_back(pop.get_x()[i]);
89 }
90 // We check the cache was hit -> not increasing the fevals
91 BOOST_CHECK_EQUAL(udp_p.m_pop_ptr->get_problem().get_fevals(), NP);
92 new_pop.set_x(0, vector_double(13, 0.5));
93 // We check the cache was not hit -> increasing the fevals
94 BOOST_CHECK_EQUAL(udp_p.m_pop_ptr->get_problem().get_fevals(), NP + 1);
95 new_pop.set_x(1, vector_double(13, 0.5));
96 // We check the cache was hit -> not increasing the fevals
97 BOOST_CHECK_EQUAL(udp_p.m_pop_ptr->get_problem().get_fevals(), NP + 1);
98 }
99
BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_construction)100 BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_construction)
101 {
102 { // default constructor
103 cstrs_self_adaptive udp;
104 BOOST_CHECK(udp.get_inner_algorithm().extract<de>() != NULL);
105 BOOST_CHECK(udp.get_inner_algorithm().extract<compass_search>() == NULL);
106 }
107 { // constructor from iters
108 BOOST_CHECK_NO_THROW((cstrs_self_adaptive{1500u}));
109 BOOST_CHECK_NO_THROW((cstrs_self_adaptive{1500u, de{}}));
110 BOOST_CHECK_NO_THROW((cstrs_self_adaptive{1500u, de{}, 32u}));
111 }
112 // Here we only test that evolution is deterministic if the
113 // seed is controlled
114 {
115 problem prob{hock_schittkowsky_71{}};
116 prob.set_c_tol({1e-3, 1e-3});
117 population pop1{prob, 5u, 23u};
118 population pop2{prob, 5u, 23u};
119 population pop3{prob, 5u, 23u};
120
121 cstrs_self_adaptive user_algo1{150u, de{10u, 0.8, 0.9, 2u, 1e-6, 1e-6, 32u}, 32u};
122 user_algo1.set_verbosity(1u);
123 pop1 = user_algo1.evolve(pop1);
124 BOOST_CHECK(user_algo1.get_log().size() > 0u);
125
126 cstrs_self_adaptive user_algo2{150u, de{10u, 0.8, 0.9, 2u, 1e-6, 1e-6, 32u}, 32u};
127 user_algo2.set_verbosity(1u);
128 pop2 = user_algo2.evolve(pop2);
129
130 BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
131 user_algo2.set_seed(32u);
132 user_algo2.get_inner_algorithm().extract<de>()->set_seed(32u);
133 pop3 = user_algo2.evolve(pop3);
134
135 BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
136 }
137 // We then check that the evolve throws if called on unsuitable problems
138 {
139 cstrs_self_adaptive user_algo{150u, de{10u, 0.8, 0.9, 2u, 1e-6, 1e-6, 32u}, 32u};
140 BOOST_CHECK_THROW(user_algo.evolve(population{zdt{}, 15u}), std::invalid_argument);
141 }
142 {
143 cstrs_self_adaptive user_algo{150u, de{10u, 0.8, 0.9, 2u, 1e-6, 1e-6, 32u}, 32u};
144 BOOST_CHECK_THROW(user_algo.evolve(population{inventory{}, 15u}), std::invalid_argument);
145 }
146 {
147 cstrs_self_adaptive user_algo{150u, de{10u, 0.8, 0.9, 2u, 1e-6, 1e-6, 32u}, 32u};
148 BOOST_CHECK_THROW(user_algo.evolve(population{rosenbrock{}, 15u}), std::invalid_argument);
149 }
150 {
151 cstrs_self_adaptive user_algo{150u, de{10u, 0.8, 0.9, 2u, 1e-6, 1e-6, 32u}, 32u};
152 BOOST_CHECK_THROW(user_algo.evolve(population{hock_schittkowsky_71{}, 3u}), std::invalid_argument);
153 }
154 // And a clean exit for 0 iterations
155 problem prob{hock_schittkowsky_71{}};
156 population pop{prob, 10u};
157 BOOST_CHECK((cstrs_self_adaptive{0u, de{10u, 0.8, 0.9, 2u, 1e-6, 1e-6, 32u}, 32u}.evolve(pop).get_x()[0])
158 == (pop.get_x()[0]));
159 }
160
BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_serialization)161 BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_serialization)
162 {
163 // Make one evolution
164 problem prob{hock_schittkowsky_71{}};
165 population pop{prob, 10u, 23u};
166 algorithm algo{cstrs_self_adaptive{1500u, de{1u, 0.8, 0.9, 2u, 1e-6, 1e-6, 32u}, 32u}};
167 algo.set_verbosity(1u);
168 pop = algo.evolve(pop);
169
170 // Store the string representation of p.
171 std::stringstream ss;
172 auto before_text = boost::lexical_cast<std::string>(algo);
173 auto before_log = algo.extract<cstrs_self_adaptive>()->get_log();
174 // Now serialize, deserialize and compare the result.
175 {
176 boost::archive::binary_oarchive oarchive(ss);
177 oarchive << algo;
178 }
179 // Change the content of p before deserializing.
180 algo = algorithm{};
181 {
182 boost::archive::binary_iarchive iarchive(ss);
183 iarchive >> algo;
184 }
185 auto after_text = boost::lexical_cast<std::string>(algo);
186 auto after_log = algo.extract<cstrs_self_adaptive>()->get_log();
187 BOOST_CHECK_EQUAL(before_text, after_text);
188 BOOST_CHECK(before_log == after_log);
189 // so we implement a close check
190 BOOST_CHECK(before_log.size() > 0u);
191 for (auto i = 0u; i < before_log.size(); ++i) {
192 BOOST_CHECK_EQUAL(std::get<0>(before_log[i]), std::get<0>(after_log[i]));
193 BOOST_CHECK_EQUAL(std::get<1>(before_log[i]), std::get<1>(after_log[i]));
194 BOOST_CHECK_CLOSE(std::get<2>(before_log[i]), std::get<2>(after_log[i]), 1e-8);
195 BOOST_CHECK_CLOSE(std::get<3>(before_log[i]), std::get<3>(after_log[i]), 1e-8);
196 BOOST_CHECK_EQUAL(std::get<4>(before_log[i]), std::get<4>(after_log[i]));
197 BOOST_CHECK_CLOSE(std::get<5>(before_log[i]), std::get<5>(after_log[i]), 1e-8);
198 BOOST_CHECK_EQUAL(std::get<6>(before_log[i]), std::get<6>(after_log[i]));
199 }
200 }
201
202 struct ts1 {
evolvets1203 population evolve(population pop) const
204 {
205 return pop;
206 }
207 };
208
209 struct ts2 {
evolvets2210 population evolve(population pop) const
211 {
212 return pop;
213 }
get_thread_safetyts2214 thread_safety get_thread_safety() const
215 {
216 return thread_safety::none;
217 }
218 };
219
220 struct ts3 {
evolvets3221 population evolve(population pop) const
222 {
223 return pop;
224 }
get_thread_safetyts3225 thread_safety get_thread_safety()
226 {
227 return thread_safety::none;
228 }
229 };
230
BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_threading_test)231 BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_threading_test)
232 {
233 BOOST_CHECK((algorithm{cstrs_self_adaptive{1500u, ts1{}, 32u}}.get_thread_safety() == thread_safety::basic));
234 BOOST_CHECK((algorithm{cstrs_self_adaptive{1500u, ts2{}, 32u}}.get_thread_safety() == thread_safety::none));
235 BOOST_CHECK((algorithm{cstrs_self_adaptive{1500u, ts3{}, 32u}}.get_thread_safety() == thread_safety::basic));
236 }
237
238 struct ia1 {
evolveia1239 population evolve(const population &pop) const
240 {
241 return pop;
242 }
243 double m_data = 0.;
244 };
245
BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_inner_algo_get_test)246 BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_inner_algo_get_test)
247 {
248 // We check that the correct overload is called according to (*this) being const or not
249 {
250 const cstrs_self_adaptive uda(1500u, ia1{}, 32u);
251 BOOST_CHECK(std::is_const<decltype(uda)>::value);
252 BOOST_CHECK(std::is_const<std::remove_reference<decltype(uda.get_inner_algorithm())>::type>::value);
253 }
254 {
255 cstrs_self_adaptive uda(1500u, ia1{}, 32u);
256 BOOST_CHECK(!std::is_const<decltype(uda)>::value);
257 BOOST_CHECK(!std::is_const<std::remove_reference<decltype(uda.get_inner_algorithm())>::type>::value);
258 }
259 }
260
BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_all_feasible_test)261 BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_all_feasible_test)
262 {
263 // We check the behavior when all individuals are feasible
264
265 // We build an all feasible population
266 problem prob{hock_schittkowsky_71{}};
267 population pop{prob, 4u};
268 BOOST_CHECK_EQUAL(prob.get_nf(), 3u);
269 BOOST_CHECK_EQUAL(prob.get_nobj(), 1u);
270 BOOST_CHECK_EQUAL(prob.get_nec(), 1u);
271 BOOST_CHECK_EQUAL(prob.get_nic(), 1u);
272 pop.set_xf(0, vector_double{1., 2., 3., 4.}, vector_double{2, 0., -1});
273 pop.set_xf(1, vector_double{2., 2., 3., 4.}, vector_double{3, 0., -2});
274 pop.set_xf(2, vector_double{3., 2., 3., 4.}, vector_double{5, 0., -2});
275 pop.set_xf(3, vector_double{4., 2., 3., 4.}, vector_double{7, 0., -23});
276
277 // We define the penalized_udp problem. Upon construction the update method will be called
278 // and all penalties assigned. We test the correctness of their value
279 detail::penalized_udp udp_p{pop};
280 BOOST_CHECK_EQUAL(udp_p.m_scaling_factor, 0.);
281 BOOST_CHECK_EQUAL(udp_p.m_i_hat_up, 0.);
282 BOOST_CHECK_EQUAL(udp_p.m_i_hat_down, 0.);
283 BOOST_CHECK_EQUAL(udp_p.m_i_hat_round, 0.);
284 BOOST_CHECK_EQUAL(udp_p.m_apply_penalty_1, false);
285 BOOST_CHECK((udp_p.m_c_max == vector_double{0., 0.}));
286 BOOST_CHECK_EQUAL(udp_p.m_n_feasible, 4);
287 BOOST_CHECK((udp_p.m_f_hat_up == vector_double{2, 0., -1}));
288 BOOST_CHECK((udp_p.m_f_hat_round == vector_double{2, 0., -1}));
289 }
290
BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_infeasible_better_than_hat_down_test)291 BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_infeasible_better_than_hat_down_test)
292 {
293 // We check the behavior when all individuals are feasible
294
295 // We build an all feasible population
296 problem prob{hock_schittkowsky_71{}};
297 population pop{prob, 4u};
298 BOOST_CHECK_EQUAL(prob.get_nf(), 3u);
299 BOOST_CHECK_EQUAL(prob.get_nobj(), 1u);
300 BOOST_CHECK_EQUAL(prob.get_nec(), 1u);
301 BOOST_CHECK_EQUAL(prob.get_nic(), 1u);
302 pop.set_xf(0, vector_double{1., 2., 3., 4.}, vector_double{2, 0., -1.}); // feasible (hat_down)
303 pop.set_xf(1, vector_double{2., 2., 3., 4.}, vector_double{1, 0., 1.}); // infeasible (better than hat_down)
304 pop.set_xf(2, vector_double{2., 2., 3., 4.}, vector_double{0, 0., 1.}); // infeasible (better than hat_down)
305 pop.set_xf(3, vector_double{4., 2., 3., 4.}, vector_double{7, 3.4, 23});
306
307 // We define the penalized_udp problem. Upon construction the update method will be called
308 // and all penalties assigned. We test the correctness of their value
309 detail::penalized_udp udp_p{pop};
310 BOOST_CHECK((udp_p.m_f_hat_up == vector_double{0, 0., 1.}));
311 BOOST_CHECK((udp_p.m_f_hat_down == vector_double{2, 0., -1.}));
312 BOOST_CHECK((udp_p.m_f_hat_round == vector_double{7, 3.4, 23}));
313 BOOST_CHECK_EQUAL(udp_p.m_apply_penalty_1, true);
314 }
315
BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_infeasible_not_better_than_hat_down_test)316 BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_infeasible_not_better_than_hat_down_test)
317 {
318 // We check the behavior when all individuals are feasible
319
320 // We build an all feasible population
321 problem prob{hock_schittkowsky_71{}};
322 population pop{prob, 5u};
323 BOOST_CHECK_EQUAL(prob.get_nf(), 3u);
324 BOOST_CHECK_EQUAL(prob.get_nobj(), 1u);
325 BOOST_CHECK_EQUAL(prob.get_nec(), 1u);
326 BOOST_CHECK_EQUAL(prob.get_nic(), 1u);
327 pop.set_xf(0, vector_double{1., 2., 3., 4.}, vector_double{2, 0., -1.}); // feasible (hat_down)
328 pop.set_xf(1, vector_double{2., 2., 3., 4.}, vector_double{3, 21, 10.}); // infeasible (worse than hat_down)
329 pop.set_xf(2, vector_double{2., 2., 3., 4.}, vector_double{3, 22, 10.}); // infeasible (worse than hat_down)
330 pop.set_xf(3, vector_double{2., 2., 3., 4.}, vector_double{4, 22., 10.}); // infeasible (worse than hat_down)
331 pop.set_xf(4, vector_double{4., 2., 3., 4.}, vector_double{7, 3.4, 23});
332
333 // We define the penalized_udp problem. Upon construction the update method will be called
334 // and all penalties assigned. We test the correctness of their value
335 detail::penalized_udp udp_p{pop};
336 BOOST_CHECK((udp_p.m_f_hat_up == vector_double{4, 22., 10.}));
337 BOOST_CHECK((udp_p.m_f_hat_down == vector_double{2, 0., -1.}));
338 BOOST_CHECK((udp_p.m_f_hat_round == vector_double{7, 3.4, 23}));
339 BOOST_CHECK_EQUAL(udp_p.m_apply_penalty_1, false);
340 }
341
BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_all_infeasible_test)342 BOOST_AUTO_TEST_CASE(cstrs_self_adaptive_all_infeasible_test)
343 {
344 // We check the behavior when all individuals are feasible
345
346 // We build an all feasible population
347 problem prob{hock_schittkowsky_71{}};
348 population pop{prob, 4u};
349 BOOST_CHECK_EQUAL(prob.get_nf(), 3u);
350 BOOST_CHECK_EQUAL(prob.get_nobj(), 1u);
351 BOOST_CHECK_EQUAL(prob.get_nec(), 1u);
352 BOOST_CHECK_EQUAL(prob.get_nic(), 1u);
353 pop.set_xf(0, vector_double{1., 2., 3., 4.}, vector_double{2., 1., 1.});
354 pop.set_xf(1, vector_double{2., 2., 3., 4.}, vector_double{1., 1., 1.});
355 pop.set_xf(2, vector_double{2., 2., 3., 4.}, vector_double{4, 22., 10.});
356 pop.set_xf(3, vector_double{4., 2., 3., 4.}, vector_double{7, 22., 10.});
357
358 // We define the penalized_udp problem. Upon construction the update method will be called
359 // and all penalties assigned. We test the correctness of their value
360 detail::penalized_udp udp_p{pop};
361 BOOST_CHECK((udp_p.m_f_hat_up == vector_double{7, 22., 10.}));
362 BOOST_CHECK((udp_p.m_f_hat_down == vector_double{1., 1., 1.}));
363 BOOST_CHECK((udp_p.m_f_hat_round == vector_double{7, 22., 10.}));
364 BOOST_CHECK_EQUAL(udp_p.m_apply_penalty_1, true);
365 }