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 mbh_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/mbh.hpp>
42 #include <pagmo/io.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/s11n.hpp>
49 #include <pagmo/threading.hpp>
50 #include <pagmo/types.hpp>
51 
52 using namespace pagmo;
53 
BOOST_AUTO_TEST_CASE(mbh_algorithm_construction)54 BOOST_AUTO_TEST_CASE(mbh_algorithm_construction)
55 {
56     compass_search inner_algo{100u, 0.1, 0.001, 0.7};
57     {
58         mbh user_algo{inner_algo, 5, 1e-3};
59         BOOST_CHECK((user_algo.get_perturb() == vector_double{1e-3}));
60         BOOST_CHECK(user_algo.get_verbosity() == 0u);
61         BOOST_CHECK((user_algo.get_log() == mbh::log_type{}));
62     }
63     {
64         mbh user_algo{inner_algo, 5, {1e-3, 1e-2, 1e-3, 1e-2}};
65         BOOST_CHECK((user_algo.get_perturb() == vector_double{1e-3, 1e-2, 1e-3, 1e-2}));
66         BOOST_CHECK(user_algo.get_verbosity() == 0u);
67         BOOST_CHECK((user_algo.get_log() == mbh::log_type{}));
68     }
69     BOOST_CHECK_THROW((mbh{inner_algo, 5u, -2.1}), std::invalid_argument);
70     BOOST_CHECK_THROW((mbh{inner_algo, 5u, 3.2}), std::invalid_argument);
71     BOOST_CHECK_THROW((mbh{inner_algo, 5u, std::nan("")}), std::invalid_argument);
72     BOOST_CHECK_THROW((mbh{inner_algo, 5u, {0.2, 0.1, 0.}}), std::invalid_argument);
73     BOOST_CHECK_THROW((mbh{inner_algo, 5u, {0.2, 0.1, -0.12}}), std::invalid_argument);
74     BOOST_CHECK_THROW((mbh{inner_algo, 5u, {0.2, 1.1, 0.12}}), std::invalid_argument);
75     BOOST_CHECK_THROW((mbh{inner_algo, 5u, {0.2, std::nan(""), 0.12}}), std::invalid_argument);
76     BOOST_CHECK_NO_THROW(mbh{});
77 }
78 
BOOST_AUTO_TEST_CASE(mbh_evolve_test)79 BOOST_AUTO_TEST_CASE(mbh_evolve_test)
80 {
81     // Here we only test that evolution is deterministic if the
82     // seed is controlled
83     {
84         problem prob{hock_schittkowsky_71{}};
85         prob.set_c_tol({1e-3, 1e-3});
86         population pop1{prob, 5u, 23u};
87         population pop2{prob, 5u, 23u};
88         population pop3{prob, 5u, 23u};
89 
90         mbh user_algo1{compass_search{100u, 0.1, 0.001, 0.7}, 5u, 0.1, 23u};
91         user_algo1.set_verbosity(1u);
92         pop1 = user_algo1.evolve(pop1);
93 
94         BOOST_CHECK(user_algo1.get_log().size() > 0u);
95 
96         mbh user_algo2{compass_search{100u, 0.1, 0.001, 0.7}, 5u, 0.1, 23u};
97         user_algo2.set_verbosity(1u);
98         pop2 = user_algo2.evolve(pop2);
99 
100         BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
101 
102         user_algo2.set_seed(23u);
103         pop3 = user_algo2.evolve(pop3);
104 
105         BOOST_CHECK(user_algo1.get_log() == user_algo2.get_log());
106     }
107     // We then check that the evolve throws if called on unsuitable problems
108     {
109         mbh user_algo{compass_search{100u, 0.1, 0.001, 0.7}, 5u, 0.1, 23u};
110         BOOST_CHECK_THROW(user_algo.evolve(population{problem{zdt{}}, 15u}), std::invalid_argument);
111     }
112     {
113         mbh user_algo{compass_search{100u, 0.1, 0.001, 0.7}, 5u, 0.1, 23u};
114         BOOST_CHECK_THROW(user_algo.evolve(population{problem{inventory{}}, 15u}), std::invalid_argument);
115     }
116     // And that it throws if called with a wrong dimension of the perturbation vector
117     {
118         mbh user_algo{compass_search{100u, 0.1, 0.001, 0.7}, 5u, {1e-3, 1e-2}, 23u};
119         BOOST_CHECK_THROW(user_algo.evolve(population{problem{hock_schittkowsky_71{}}, 15u}), std::invalid_argument);
120     }
121     // And a clean exit for 0 generations
122     problem prob{hock_schittkowsky_71{}};
123     population pop{prob, 10u};
124     BOOST_CHECK((mbh{compass_search{100u, 0.1, 0.001, 0.7}, 0u, {1e-3, 1e-2}, 23u}.evolve(pop).get_x()[0])
125                 == (pop.get_x()[0]));
126 }
127 
BOOST_AUTO_TEST_CASE(mbh_setters_getters_test)128 BOOST_AUTO_TEST_CASE(mbh_setters_getters_test)
129 {
130     mbh user_algo{compass_search{100u, 0.1, 0.001, 0.7}, 5u, {1e-3, 1e-2}, 23u};
131     user_algo.set_verbosity(23u);
132     BOOST_CHECK(user_algo.get_verbosity() == 23u);
133     user_algo.set_seed(23u);
134     BOOST_CHECK(user_algo.get_seed() == 23u);
135     user_algo.set_perturb({0.1, 0.2, 0.3, 0.4});
136     BOOST_CHECK((user_algo.get_perturb() == vector_double{0.1, 0.2, 0.3, 0.4}));
137     BOOST_CHECK_THROW(user_algo.set_perturb({0.1, std::nan(""), 0.3, 0.4}), std::invalid_argument);
138     BOOST_CHECK_THROW(user_algo.set_perturb({0.1, -0.2, 0.3, 0.4}), std::invalid_argument);
139     BOOST_CHECK_THROW(user_algo.set_perturb({0.1, 2.3, 0.3, 0.4}), std::invalid_argument);
140     BOOST_CHECK(user_algo.get_name().find("Monotonic Basin Hopping") != std::string::npos);
141     BOOST_CHECK(user_algo.get_extra_info().find("Inner algorithm extra info") != std::string::npos);
142     BOOST_CHECK_NO_THROW(user_algo.get_log());
143 }
144 
BOOST_AUTO_TEST_CASE(mbh_serialization_test)145 BOOST_AUTO_TEST_CASE(mbh_serialization_test)
146 {
147     // Make one evolution
148     problem prob{hock_schittkowsky_71{}};
149     population pop{prob, 10u, 23u};
150     algorithm algo{mbh{compass_search{100u, 0.1, 0.001, 0.7}, 5u, 1e-3, 23u}};
151     algo.set_verbosity(1u);
152     pop = algo.evolve(pop);
153 
154     // Store the string representation of p.
155     std::stringstream ss;
156     auto before_text = boost::lexical_cast<std::string>(algo);
157     auto before_log = algo.extract<mbh>()->get_log();
158     // Now serialize, deserialize and compare the result.
159     {
160         boost::archive::binary_oarchive oarchive(ss);
161         oarchive << algo;
162     }
163     // Change the content of p before deserializing.
164     algo = algorithm{};
165     {
166         boost::archive::binary_iarchive iarchive(ss);
167         iarchive >> algo;
168     }
169     auto after_text = boost::lexical_cast<std::string>(algo);
170     auto after_log = algo.extract<mbh>()->get_log();
171     BOOST_CHECK_EQUAL(before_text, after_text);
172     BOOST_CHECK(before_log == after_log);
173     // so we implement a close check
174     BOOST_CHECK(before_log.size() > 0u);
175     for (auto i = 0u; i < before_log.size(); ++i) {
176         BOOST_CHECK_EQUAL(std::get<0>(before_log[i]), std::get<0>(after_log[i]));
177         BOOST_CHECK_CLOSE(std::get<1>(before_log[i]), std::get<1>(after_log[i]), 1e-8);
178         BOOST_CHECK_EQUAL(std::get<2>(before_log[i]), std::get<2>(after_log[i]));
179         BOOST_CHECK_CLOSE(std::get<3>(before_log[i]), std::get<3>(after_log[i]), 1e-8);
180         BOOST_CHECK_EQUAL(std::get<4>(before_log[i]), std::get<4>(after_log[i]));
181     }
182 }
183 
184 struct ts1 {
evolvets1185     population evolve(population pop) const
186     {
187         return pop;
188     }
189 };
190 
191 struct ts2 {
evolvets2192     population evolve(population pop) const
193     {
194         return pop;
195     }
get_thread_safetyts2196     thread_safety get_thread_safety() const
197     {
198         return thread_safety::none;
199     }
200 };
201 
202 struct ts3 {
evolvets3203     population evolve(population pop) const
204     {
205         return pop;
206     }
get_thread_safetyts3207     thread_safety get_thread_safety()
208     {
209         return thread_safety::none;
210     }
211 };
212 
BOOST_AUTO_TEST_CASE(mbh_threading_test)213 BOOST_AUTO_TEST_CASE(mbh_threading_test)
214 {
215     BOOST_CHECK((algorithm{mbh{ts1{}, 5u, 1e-2, 23u}}.get_thread_safety() == thread_safety::basic));
216     BOOST_CHECK((algorithm{mbh{ts2{}, 5u, 1e-2, 23u}}.get_thread_safety() == thread_safety::none));
217     BOOST_CHECK((algorithm{mbh{ts3{}, 5u, 1e-2, 23u}}.get_thread_safety() == thread_safety::basic));
218 }
219 
220 struct ia1 {
evolveia1221     population evolve(const population &pop) const
222     {
223         return pop;
224     }
225     double m_data = 0.;
226 };
227 
BOOST_AUTO_TEST_CASE(mbh_inner_algo_get_test)228 BOOST_AUTO_TEST_CASE(mbh_inner_algo_get_test)
229 {
230     // We check that the correct overload is called according to (*this) being const or not
231     {
232         const mbh uda(ia1{}, 5u, 1e-2, 23u);
233         BOOST_CHECK(std::is_const<decltype(uda)>::value);
234         BOOST_CHECK(std::is_const<std::remove_reference<decltype(uda.get_inner_algorithm())>::type>::value);
235     }
236     {
237         mbh uda(ia1{}, 5u, 1e-2, 23u);
238         BOOST_CHECK(!std::is_const<decltype(uda)>::value);
239         BOOST_CHECK(!std::is_const<std::remove_reference<decltype(uda.get_inner_algorithm())>::type>::value);
240     }
241 }
242