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