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 cmaes_test
30 #define BOOST_TEST_DYN_LINK
31 #include <boost/test/unit_test.hpp>
32 
33 #include <initializer_list>
34 #include <iostream>
35 #include <limits> //  std::numeric_limits<double>::infinity();
36 #include <string>
37 
38 #include <boost/lexical_cast.hpp>
39 #include <boost/test/tools/floating_point_comparison.hpp>
40 
41 #include <pagmo/algorithm.hpp>
42 #include <pagmo/algorithms/cmaes.hpp>
43 #include <pagmo/population.hpp>
44 #include <pagmo/problems/hock_schittkowsky_71.hpp>
45 #include <pagmo/problems/inventory.hpp>
46 #include <pagmo/problems/rosenbrock.hpp>
47 #include <pagmo/problems/zdt.hpp>
48 #include <pagmo/rng.hpp>
49 #include <pagmo/s11n.hpp>
50 #include <pagmo/utils/generic.hpp>
51 
52 using namespace pagmo;
53 
BOOST_AUTO_TEST_CASE(cmaes_algorithm_construction)54 BOOST_AUTO_TEST_CASE(cmaes_algorithm_construction)
55 {
56     cmaes user_algo{10u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u};
57     BOOST_CHECK(user_algo.get_verbosity() == 0u);
58     BOOST_CHECK(user_algo.get_seed() == 23u);
59     BOOST_CHECK((user_algo.get_log() == cmaes::log_type{}));
60 
61     BOOST_CHECK_THROW((cmaes{10u, 1.2, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u}), std::invalid_argument);
62     BOOST_CHECK_THROW((cmaes{10u, -2.3, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u}), std::invalid_argument);
63     BOOST_CHECK_THROW((cmaes{10u, -1, 1.2, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u}), std::invalid_argument);
64     BOOST_CHECK_THROW((cmaes{10u, -1, -1.2, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u}), std::invalid_argument);
65     BOOST_CHECK_THROW((cmaes{10u, -1, -1, -1.2, -1, 0.5, 1e-6, 1e-6, false, false, 23u}), std::invalid_argument);
66     BOOST_CHECK_THROW((cmaes{10u, -1, -1, -1.2, -1, 0.5, 1e-6, 1e-6, false, false, 23u}), std::invalid_argument);
67     BOOST_CHECK_THROW((cmaes{10u, -1, -1, -1, -1.2, 0.5, 1e-6, 1e-6, false, false, 23u}), std::invalid_argument);
68     BOOST_CHECK_THROW((cmaes{10u, -1, -1, -1, -1.2, 0.5, 1e-6, 1e-6, false, false, 23u}), std::invalid_argument);
69 }
70 
71 struct unbounded_lb {
72     /// Fitness
fitnessunbounded_lb73     vector_double fitness(const vector_double &) const
74     {
75         return {0.};
76     }
77     /// Problem bounds
get_boundsunbounded_lb78     std::pair<vector_double, vector_double> get_bounds() const
79     {
80         return {{-std::numeric_limits<double>::infinity()}, {0.}};
81     }
82 };
83 
84 struct unbounded_ub {
85     /// Fitness
fitnessunbounded_ub86     vector_double fitness(const vector_double &) const
87     {
88         return {0.};
89     }
90     /// Problem bounds
get_boundsunbounded_ub91     std::pair<vector_double, vector_double> get_bounds() const
92     {
93         return {{0.}, {std::numeric_limits<double>::infinity()}};
94     }
95 };
96 
BOOST_AUTO_TEST_CASE(cmaes_evolve_test)97 BOOST_AUTO_TEST_CASE(cmaes_evolve_test)
98 {
99     {
100         // Here we only test that evolution is deterministic if the
101         // seed is controlled
102         problem prob{rosenbrock{25u}};
103         population pop1{prob, 5u, 23u};
104         population pop2{prob, 5u, 23u};
105         population pop3{prob, 5u, 23u};
106 
107         cmaes user_algo1{10u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u};
108         user_algo1.set_verbosity(1u);
109         pop1 = user_algo1.evolve(pop1);
110 
111         BOOST_CHECK(user_algo1.get_log().size() > 0u);
112 
113         cmaes user_algo2{10u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u};
114         user_algo2.set_verbosity(1u);
115         pop2 = user_algo2.evolve(pop2);
116 
117         BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
118 
119         user_algo2.set_seed(23u);
120         pop3 = user_algo2.evolve(pop3);
121 
122         BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
123     }
124 
125     {
126         // Here we only test that evolution is deterministic if the
127         // seed is controlled and force bounds is active
128         problem prob{rosenbrock{25u}};
129         population pop1{prob, 5u, 23u};
130         population pop2{prob, 5u, 23u};
131         population pop3{prob, 5u, 23u};
132 
133         cmaes user_algo1{10u, -1, -1, -1, -1, 1.0, 1e-6, 1e-6, false, true, 23u};
134         user_algo1.set_verbosity(1u);
135         pop1 = user_algo1.evolve(pop1);
136 
137         BOOST_CHECK(user_algo1.get_log().size() > 0u);
138 
139         cmaes user_algo2{10u, -1, -1, -1, -1, 1.0, 1e-6, 1e-6, false, true, 23u};
140         user_algo2.set_verbosity(1u);
141         pop2 = user_algo2.evolve(pop2);
142 
143         BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
144 
145         user_algo2.set_seed(23u);
146         pop3 = user_algo2.evolve(pop3);
147 
148         BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
149     }
150 
151     {
152         // Here we only test that evolution is deterministic if the
153         // seed is controlled and the problem is stochastic
154         problem prob{inventory{4u, 10u, 23u}};
155         population pop1{prob, 5u, 23u};
156         population pop2{prob, 5u, 23u};
157 
158         cmaes user_algo1{10u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u};
159         user_algo1.set_verbosity(1u);
160         pop1 = user_algo1.evolve(pop1);
161 
162         cmaes user_algo2{10u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u};
163         user_algo2.set_verbosity(1u);
164         pop2 = user_algo2.evolve(pop2);
165 
166         BOOST_CHECK(user_algo1.get_log().size() > 0u);
167         BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
168     }
169 
170     // Here we check that the exit condition of ftol and xtol actually provoke an exit within 5000 gen (rosenbrock{2} is
171     // used)
172     {
173         cmaes user_algo{5000u, -1, -1, -1, -1, 0.5, 1e-6, 1e-16, false, false, 23u};
174         user_algo.set_verbosity(1u);
175         problem prob{rosenbrock{2u}};
176         population pop{prob, 20u, 23u};
177         pop = user_algo.evolve(pop);
178         BOOST_CHECK(user_algo.get_log().size() < 5000u);
179     }
180     {
181         cmaes user_algo{5000u, -1, -1, -1, -1, 0.5, 1e-16, 1e-6, false, false, 23u};
182         user_algo.set_verbosity(1u);
183         problem prob{rosenbrock{2u}};
184         population pop{prob, 20u, 23u};
185         pop = user_algo.evolve(pop);
186         BOOST_CHECK(user_algo.get_log().size() < 5000u);
187     }
188 
189     // We then check that the evolve throws if called on unsuitable problems
190     BOOST_CHECK_THROW(cmaes{10u}.evolve(population{problem{rosenbrock{}}, 4u}), std::invalid_argument);
191     BOOST_CHECK_THROW(cmaes{10u}.evolve(population{problem{zdt{}}, 15u}), std::invalid_argument);
192     BOOST_CHECK_THROW(cmaes{10u}.evolve(population{problem{hock_schittkowsky_71{}}, 15u}), std::invalid_argument);
193 
194     detail::random_engine_type r_engine(32u);
195     population pop_lb{problem{unbounded_lb{}}};
196     population pop_ub{problem{unbounded_ub{}}};
197     for (auto i = 0u; i < 20u; ++i) {
198         pop_lb.push_back(vector_double{pagmo::uniform_real_from_range(0., 1., r_engine)});
199         pop_ub.push_back(vector_double{pagmo::uniform_real_from_range(0., 1., r_engine)});
200     }
201     BOOST_CHECK_THROW(cmaes{10u}.evolve(pop_lb), std::invalid_argument);
202     BOOST_CHECK_THROW(cmaes{10u}.evolve(pop_ub), std::invalid_argument);
203     // And a clean exit for 0 generations
204     population pop{rosenbrock{25u}, 10u};
205     BOOST_CHECK(cmaes{0u}.evolve(pop).get_x()[0] == pop.get_x()[0]);
206 
207     // and we call evolve on the stochastic problem
208     BOOST_CHECK_NO_THROW(cmaes{10u}.evolve(population{problem{inventory{}}, 15u}));
209 }
210 
BOOST_AUTO_TEST_CASE(cmaes_setters_getters_test)211 BOOST_AUTO_TEST_CASE(cmaes_setters_getters_test)
212 {
213     cmaes user_algo{10u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u};
214     cmaes user_algo2{10u, .5, .5, .5, .5, 0.5, 1e-6, 1e-6, false, false, 23u};
215     user_algo.set_verbosity(23u);
216     BOOST_CHECK(user_algo.get_verbosity() == 23u);
217     user_algo.set_seed(23u);
218     BOOST_CHECK(user_algo.get_seed() == 23u);
219     BOOST_CHECK(user_algo.get_name().find("CMA-ES") != std::string::npos);
220     BOOST_CHECK(user_algo.get_extra_info().find("cmu") != std::string::npos);
221     BOOST_CHECK(user_algo.get_extra_info().find("auto") != std::string::npos);
222     BOOST_CHECK(user_algo2.get_extra_info().find("auto") == std::string::npos);
223     BOOST_CHECK_NO_THROW(user_algo.get_log());
224 }
225 
BOOST_AUTO_TEST_CASE(cmaes_serialization_test)226 BOOST_AUTO_TEST_CASE(cmaes_serialization_test)
227 {
228     // Make one evolution
229     problem prob{rosenbrock{25u}};
230     population pop{prob, 10u, 23u};
231     algorithm algo{cmaes{10u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u}};
232     algo.set_verbosity(1u);
233     pop = algo.evolve(pop);
234 
235     // Store the string representation of p.
236     std::stringstream ss;
237     auto before_text = boost::lexical_cast<std::string>(algo);
238     auto before_log = algo.extract<cmaes>()->get_log();
239     // Now serialize, deserialize and compare the result.
240     {
241         boost::archive::binary_oarchive oarchive(ss);
242         oarchive << algo;
243     }
244     // Change the content of p before deserializing.
245     algo = algorithm{};
246     {
247         boost::archive::binary_iarchive iarchive(ss);
248         iarchive >> algo;
249     }
250     auto after_text = boost::lexical_cast<std::string>(algo);
251     auto after_log = algo.extract<cmaes>()->get_log();
252     BOOST_CHECK_EQUAL(before_text, after_text);
253     BOOST_CHECK(before_log == after_log);
254     // so we implement a close check
255     BOOST_CHECK(before_log.size() > 0u);
256     for (auto i = 0u; i < before_log.size(); ++i) {
257         BOOST_CHECK_EQUAL(std::get<0>(before_log[i]), std::get<0>(after_log[i]));
258         BOOST_CHECK_EQUAL(std::get<1>(before_log[i]), std::get<1>(after_log[i]));
259         BOOST_CHECK_CLOSE(std::get<2>(before_log[i]), std::get<2>(after_log[i]), 1e-8);
260         BOOST_CHECK_CLOSE(std::get<3>(before_log[i]), std::get<3>(after_log[i]), 1e-8);
261         BOOST_CHECK_CLOSE(std::get<4>(before_log[i]), std::get<4>(after_log[i]), 1e-8);
262         BOOST_CHECK_CLOSE(std::get<5>(before_log[i]), std::get<5>(after_log[i]), 1e-8);
263     }
264 }
265 
BOOST_AUTO_TEST_CASE(cmaes_memory_test)266 BOOST_AUTO_TEST_CASE(cmaes_memory_test)
267 {
268     // We check here that when memory is true calling evolve(pop) two times on 1 gen
269     // is the same as calling 1 time evolve with 2 gens
270     cmaes user_algo{1u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, true, false, 23u};
271     user_algo.set_verbosity(1u);
272     problem prob{rosenbrock{25u}};
273     population pop{prob, 10u, 23u};
274     pop = user_algo.evolve(pop);
275     pop = user_algo.evolve(pop);
276 
277     cmaes user_algo2{2u, -1, -1, -1, -1, 0.5, 1e-6, 1e-6, false, false, 23u};
278     user_algo2.set_verbosity(1u);
279     problem prob2{rosenbrock{25u}};
280     population pop2{prob2, 10u, 23u};
281     pop2 = user_algo2.evolve(pop2);
282 
283     auto log = user_algo.get_log();
284     auto log2 = user_algo2.get_log();
285     BOOST_CHECK_CLOSE(std::get<5>(log[0]), std::get<5>(log2[1]), 1e-8);
286     BOOST_CHECK_CLOSE(std::get<4>(log[0]), std::get<4>(log2[1]), 1e-8);
287     BOOST_CHECK_CLOSE(std::get<3>(log[0]), std::get<3>(log2[1]), 1e-8);
288     BOOST_CHECK_CLOSE(std::get<2>(log[0]), std::get<2>(log2[1]), 1e-8);
289     // the 1 and 0 will be different as fevals is reset at each evolve
290 }
291