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 #include <algorithm>
30 #include <cmath>
31 #include <iomanip>
32 #include <sstream>
33 #include <stdexcept>
34 #include <string>
35
36 #include <pagmo/algorithms/compass_search.hpp>
37 #include <pagmo/algorithms/mbh.hpp>
38 #include <pagmo/exceptions.hpp>
39 #include <pagmo/io.hpp>
40 #include <pagmo/rng.hpp>
41 #include <pagmo/s11n.hpp>
42 #include <pagmo/threading.hpp>
43 #include <pagmo/types.hpp>
44 #include <pagmo/utils/constrained.hpp>
45 #include <pagmo/utils/generic.hpp>
46
47 // MINGW-specific warnings.
48 #if defined(__GNUC__) && defined(__MINGW32__)
49 #pragma GCC diagnostic push
50 #pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
51 #endif
52
53 namespace pagmo
54 {
55
56 /// Default constructor.
57 /**
58 * The default constructor will initialize the algorithm with the following parameters:
59 * - inner algorithm: pagmo::compass_search;
60 * - consecutive runs of the inner algorithm that need to result in no improvement for pagmo::mbh to stop: 5;
61 * - scalar perturbation: 1E-2;
62 * - seed: random.
63 *
64 * @throws unspecified any exception thrown by the constructor of pagmo::algorithm.
65 */
mbh()66 mbh::mbh() : m_algorithm(compass_search{}), m_stop(5u), m_perturb(1, 1e-2), m_verbosity(0u)
67 {
68 const auto rnd = pagmo::random_device::next();
69 m_seed = rnd;
70 m_e.seed(rnd);
71 }
72
scalar_ctor_impl(double perturb)73 void mbh::scalar_ctor_impl(double perturb)
74 {
75 if (std::isnan(perturb) || perturb > 1. || perturb <= 0.) {
76 pagmo_throw(std::invalid_argument, "The scalar perturbation must be in (0, 1], while a value of "
77 + std::to_string(perturb) + " was detected.");
78 }
79 }
80
vector_ctor_impl(const vector_double & perturb)81 void mbh::vector_ctor_impl(const vector_double &perturb)
82 {
83 if (!std::all_of(perturb.begin(), perturb.end(),
84 [](double item) { return (!std::isnan(item) && item > 0. && item <= 1.); })) {
85 pagmo_throw(std::invalid_argument,
86 "The perturbation must have all components in (0, 1], while that is not the case.");
87 }
88 }
89
90 /// Evolve method.
91 /**
92 * This method will evolve the input population up to when \p stop consecutve runs of the internal
93 * algorithm do not improve the solution.
94 *
95 * @param pop population to be evolved.
96 *
97 * @return evolved population.
98 *
99 * @throws std::invalid_argument if the problem is multi-objective or stochastic, or if the perturbation vector size
100 * does not equal the problem size.
101 */
evolve(population pop) const102 population mbh::evolve(population pop) const
103 {
104 // We store some useful variables
105 const auto &prob = pop.get_problem(); // This is a const reference, so using set_seed for example will not be
106 // allowed
107 auto dim = prob.get_nx(); // This getter does not return a const reference but a copy
108 auto nec = prob.get_nec(); // This getter does not return a const reference but a copy
109 const auto bounds = prob.get_bounds();
110 const auto &lb = bounds.first;
111 const auto &ub = bounds.second;
112 auto NP = pop.size();
113
114 auto fevals0 = prob.get_fevals(); // discount for the already made fevals
115 unsigned count = 1u; // regulates the screen output
116
117 // PREAMBLE-------------------------------------------------------------------------------------------------
118 if (prob.get_nobj() != 1u) {
119 pagmo_throw(std::invalid_argument, "Multiple objectives detected in " + prob.get_name() + " instance. "
120 + get_name() + " cannot deal with them");
121 }
122 if (prob.is_stochastic()) {
123 pagmo_throw(std::invalid_argument, "The input problem " + prob.get_name() + " appears to be stochastic, "
124 + get_name() + " cannot deal with it");
125 }
126 // Get out if there is nothing to do.
127 if (m_stop == 0u) {
128 return pop;
129 }
130 // Check if the perturbation vector has size 1, in which case the whole perturbation vector is filled with
131 // the same value equal to its first entry
132 if (m_perturb.size() == 1u) {
133 for (decltype(dim) i = 1u; i < dim; ++i) {
134 m_perturb.push_back(m_perturb[0]);
135 }
136 }
137 // Check that the perturbation vector size equals the size of the problem
138 if (m_perturb.size() != dim) {
139 pagmo_throw(std::invalid_argument, "The perturbation vector size is: " + std::to_string(m_perturb.size())
140 + ", while the problem dimension is: " + std::to_string(dim)
141 + ". They need to be equal for MBH to work.");
142 }
143 // ---------------------------------------------------------------------------------------------------------
144
145 // No throws, all valid: we clear the logs
146 m_log.clear();
147 // mbh main loop
148 unsigned i = 0u;
149 while (i < m_stop) {
150 // 1 - We make a copy of the current population
151 population pop_old(pop);
152 // 2 - We perturb the current population (NP funevals are made here)
153 for (decltype(NP) j = 0u; j < NP; ++j) {
154 vector_double tmp_x(dim);
155 for (decltype(dim) k = 0u; k < dim; ++k) {
156 tmp_x[k]
157 = uniform_real_from_range(std::max(pop.get_x()[j][k] - m_perturb[k] * (ub[k] - lb[k]), lb[k]),
158 std::min(pop.get_x()[j][k] + m_perturb[k] * (ub[k] - lb[k]), ub[k]), m_e);
159 }
160 pop.set_x(j, tmp_x); // fitness is evaluated here
161 }
162 // 3 - We evolve the current population with the selected algorithm
163 pop = m_algorithm.evolve(pop);
164 i++;
165 // 4 - We reset the counter if we have improved, otherwise we reset the population
166 if (compare_fc(pop.get_f()[pop.best_idx()], pop_old.get_f()[pop_old.best_idx()], nec, prob.get_c_tol())) {
167 i = 0u;
168 } else {
169 for (decltype(NP) j = 0u; j < NP; ++j) {
170 pop.set_xf(j, pop_old.get_x()[j], pop_old.get_f()[j]);
171 }
172 }
173 // 5 - We log to screen
174 if (m_verbosity > 0u) {
175 // Prints a log line after each call to the inner algorithm
176 // 1 - Every 50 lines print the column names
177 if (count % 50u == 1u) {
178 print("\n", std::setw(7), "Fevals:", std::setw(15), "Best:", std::setw(15), "Violated:", std::setw(15),
179 "Viol. Norm:", std::setw(15), "Trial:", '\n');
180 }
181 // 2 - Print
182 auto cur_best_f = pop.get_f()[pop.best_idx()];
183 auto c1eq = detail::test_eq_constraints(cur_best_f.data() + 1, cur_best_f.data() + 1 + nec,
184 prob.get_c_tol().data());
185 auto c1ineq = detail::test_ineq_constraints(
186 cur_best_f.data() + 1 + nec, cur_best_f.data() + cur_best_f.size(), prob.get_c_tol().data() + nec);
187 auto n = prob.get_nc() - c1eq.first - c1ineq.first;
188 auto l = c1eq.second + c1ineq.second;
189 print(std::setw(7), prob.get_fevals() - fevals0, std::setw(15), cur_best_f[0], std::setw(15), n,
190 std::setw(15), l, std::setw(15), i);
191 if (!prob.feasibility_f(pop.get_f()[pop.best_idx()])) {
192 std::cout << " i";
193 }
194 ++count;
195 std::cout << std::endl; // we flush here as we want the user to read in real time ...
196 // Logs
197 m_log.emplace_back(prob.get_fevals() - fevals0, cur_best_f[0], n, l, i);
198 }
199 }
200 // We extract chromosomes and fitnesses
201 return pop;
202 }
203
204 /// Set the seed.
205 /**
206 * @param seed the seed controlling the algorithm's stochastic behaviour.
207 */
set_seed(unsigned seed)208 void mbh::set_seed(unsigned seed)
209 {
210 m_e.seed(seed);
211 m_seed = seed;
212 }
213
214 /// Set the perturbation vector.
215 /**
216 * @param perturb the perturbation vector.
217 *
218 * @throws std::invalid_argument if not all the components of the perturbation vector are in the (0,1] range.
219 */
set_perturb(const vector_double & perturb)220 void mbh::set_perturb(const vector_double &perturb)
221 {
222 if (!std::all_of(perturb.begin(), perturb.end(),
223 [](double item) { return (!std::isnan(item) && item > 0. && item <= 1.); })) {
224 pagmo_throw(std::invalid_argument,
225 "The perturbation must have all components in (0, 1], while that is not the case.");
226 }
227 m_perturb = perturb;
228 }
229
230 /// Algorithm's thread safety level.
231 /**
232 * The thread safety of this meta-algorithm is the minimum between the thread safety
233 * of the internal pagmo::algorithm and the basic thread safety level. I.e., this algorithm
234 * never provides more than the basic thread safety level.
235 *
236 * @return the thread safety level of this algorithm.
237 */
get_thread_safety() const238 thread_safety mbh::get_thread_safety() const
239 {
240 return std::min(m_algorithm.get_thread_safety(), thread_safety::basic);
241 }
242
243 /// Extra info.
244 /**
245 * @return a string containing extra info on the algorithm.
246 */
get_extra_info() const247 std::string mbh::get_extra_info() const
248 {
249 std::ostringstream ss;
250 stream(ss, "\tStop: ", m_stop);
251 stream(ss, "\n\tPerturbation vector: ", m_perturb);
252 stream(ss, "\n\tSeed: ", m_seed);
253 stream(ss, "\n\tVerbosity: ", m_verbosity);
254 stream(ss, "\n\n\tInner algorithm: ", m_algorithm.get_name());
255 stream(ss, "\n\tInner algorithm extra info: ");
256 stream(ss, "\n", m_algorithm.get_extra_info());
257 return ss.str();
258 }
259
260 // Object serialization.
261 template <typename Archive>
serialize(Archive & ar,unsigned)262 void mbh::serialize(Archive &ar, unsigned)
263 {
264 detail::archive(ar, m_algorithm, m_stop, m_perturb, m_e, m_seed, m_verbosity, m_log);
265 }
266
267 } // namespace pagmo
268
269 PAGMO_S11N_ALGORITHM_IMPLEMENT(pagmo::mbh)
270