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 }